Web攻防--JNDI注入--Log4j漏洞--Fastjson反序列化漏洞

2023-09-11 18:01:22

JNDI注入

什麼是JNDI
JNDI全稱為 Java Naming and Directory Interface(Java命名和目錄介面),是一組應用程式介面,為開發人員查詢和存取各種資源提供了統一的通用介面,可以用來定義使用者、網路、機器、物件和服務等各種資源。

JNDI支援的服務主要有:DNS、LDAP、CORBA、RMI等。

簡單從安全形度來看待JNDI就是Java中的一組介面,在其所支援的服務中最常用的就是RMI和LDAP服務
RMI:遠端方法呼叫登入檔
LDAP:輕量級目錄存取協定
通過這兩種協定可以使目標伺服器載入遠端Class檔案,攻擊者通過構造Class檔案來達到RCE的效果
在jdk中提供JDNI服務的有如下幾個包

javax.naming:主要用於命名操作,它包含了命名服務的類和介面,該包定義了Context介面和InitialContext類;

javax.naming.directory:主要用於目錄操作,它定義了DirContext介面和InitialDir- Context類;

javax.naming.event:在命名目錄伺服器中請求事件通知;

javax.naming.ldap:提供LDAP支援;

javax.naming.spi:允許動態插入不同實現,為不同命名目錄服務供應商的開發人員提供開發和實現的途徑,以便應用程式通過JNDI可以存取相關服務。

通過InitialContext類中的lookup()方法才能使用RMI和LDAP協定進行遠端呼叫。
在其他元件中的包也存在參照lookup()方法的情況
比如

在RMI服務中呼叫了InitialContext.lookup()的類有:
org.springframework.transaction.jta.JtaTransactionManager.readObject()
com.sun.rowset.JdbcRowSetImpl.execute()
javax.management.remote.rmi.RMIConnector.connect()
org.hibernate.jmx.StatisticsService.setSessionFactoryJNDIName(String sfJNDIName)

在LDAP服務中呼叫了InitialContext.lookup()的類有:
InitialDirContext.lookup()
Spring LdapTemplate.lookup()
LdapTemplate.lookupContext()

簡單JNDIdemo程式碼範例

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);//獲取指定的遠端物件
    }
}

如果其中獲取的遠端物件可控的話,可以通過編寫惡意的Class檔案來使伺服器來載入檔案,達到命令執行的效果

import java.io.IOException;

public class Test {
    public Test() throws IOException {
        Runtime.getRuntime().exec("notepad");//呼叫計算器
    }
}

注入工具 JNDI-Injection-Exploit

詳解參考
安全技術系列之JNDI注入
Java安全之JNDI注入

想要成功利用JNDI注入,這就要觀察當前伺服器的JDK版本,在不同版本號中所限制的內容都不一樣

  • JDK 6u45、7u21之後:java.rmi.server.useCodebaseOnly的預設值被設定為true。當該值為true時,將禁用自動載入遠端類檔案,僅從CLASSPATH和當前JVM的java.rmi.server.codebase指定路徑載入類檔案。使用這個屬性來防止使用者端VM從其他Codebase地址上動態載入類,增加了RMI ClassLoader的安全性。
  • JDK 6u141、7u131、8u121之後:增加了com.sun.jndi.rmi.object.trustURLCodebase選項,預設為false,禁止RMI和CORBA協定使用遠端codebase的選項,因此RMI和CORBA在以上的JDK版本上已經無法觸發該漏洞,但依然可以通過指定URI為LDAP協定來進行JNDI注入攻擊。
  • JDK 6u211、7u201、8u191之後:增加了com.sun.jndi.ldap.object.trustURLCodebase選項,預設為false,禁止LDAP協定使用遠端codebase的選項,把LDAP協定的攻擊途徑也給禁了。
    如圖.

Log4j漏洞

什麼是Log4j

Apache的一個開源專案,通過使用Log4j,我們可以控制紀錄檔資訊輸送的目的地是控制檯、檔案、GUI元件,甚至是套介面伺服器、NT的事件記錄器、UNIX Syslog守護行程等;我們也可以控制每一條紀錄檔的輸出格式;通過定義每一條紀錄檔資訊的級別,我們能夠更加細緻地控制紀錄檔的生成過程。最令人感興趣的就是,這些可以通過一個組態檔來靈活地進行設定,而不需要修改應用的程式碼。

利用原理

這裡的漏洞原理是利用JNDI的服務機制從而進行遠端載入檔案,達到命令執行
首先log4j列印紀錄檔有四個級別:debug、info、warn、error,不管哪個方法列印紀錄檔,在正常的log處理過程中,對${這兩個緊鄰的字元做了檢測,一旦遇到類似表示式結構的字串就會觸發替換機制。
一旦在log字串中檢測到${},就會解析其中的字串嘗試使用lookup()查詢,因此只要能控制log引數內容,就有機會實現漏洞利用。
簡單demo段範例:

package com.example.log4jwebdemo;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


@WebServlet("/log4j")
public class Log4jServlet extends HttpServlet {
    //構造HTTP Web服務 使用帶漏洞Log4j版本 實現功能
    private static final Logger log= LogManager.getLogger(Log4jServlet.class);
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String code =req.getParameter("code");
        log.error("{}",code);

        //1、開發原始碼中參照漏洞元件如log4j
        //2、開發中使用元件的程式碼(觸發漏洞程式碼)
        //3、可控變數去傳遞Payload來實現攻擊
        //4、code接受payload要進行url編碼
    }
}

Fastjson反序列化漏洞

什麼是fastjson

在前後端資料傳輸互動中,經常會遇到字串(String)與json,XML等格式相互轉換與解析,其中json以跨語言,跨前後端的優點在開發中被頻繁使用,基本上是標準的資料交換格式。它的介面簡單易用,已經被廣泛使用在快取序列化,協定互動,Web輸出等各種應用場景中。FastJson是阿里巴巴的的開源庫,用於對JSON格式的資料進行解析和打包。

簡單demo段範例
首先定義User類

package com.Pengj;

//給fastjson資料轉換測試用的
public class User {
    private String name;
    private Integer age;

    public Integer getAge() {
        return age;
    }

    public String getName() {
        return name;
    }


    public void setAge(Integer age) {
        this.age = age;
        System.out.println(age);
    }

    public void setName(String name) {
        this.name = name;
        System.out.println(name);
    }
}

呼叫執行命令的檔案

package com.Pengj;

import java.io.IOException;

public class Run {
    public Run() throws IOException {
        Runtime.getRuntime().exec("calc");
    }
}

json資料序列化和反序列化

package com.Pengj;


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;

//使用fastjson去處理User類資料
public class FastjsonTest {
    public static void main(String[] args) {
        //將User進行範例化
        User u = new User();
        u.setAge(20);
        u.setName("Pengj");
        //json物件轉換json資料							轉換結果:{"age":20,"name":"Pengj"}
        String jsonString = JSONObject.toJSONString(u);
        System.out.println("這就是json格式:"+jsonString);

       //引入autotype功能後的物件轉為json資料			轉換結果:{"@type":"com.Pengj.User","age":20,"name":"Pengj"}	
        String jsonString1 = JSONObject.toJSONString(u, SerializerFeature.WriteClassName);
        System.out.println(jsonString1);
        


       
        //下面JSON -> 物件
        String test = "{\"@type\":\"com.Pengj.Run\",\"age\":20,\"name\":\"Pengj\"}";//修改包含類名資訊後的json資料
        //將json資料進行反序列化
        JSONObject jsonObject = JSON.parseObject(test);
        System.out.println(jsonObject);

    }


}

在上述程式碼段中,使用JSONObject類中的toJSONString方法,將物件轉換為json資料,並啟用SerializerFeature.WriteClassName特性,將帶有@type標記的類名資訊也輸出出來,完成物件轉換為json資料的過程。
在將json資料使用JSON.parseObject方法反序列化為物件的時候,如果將類名資訊修改為其他類,那麼在反序列化過程中程式會嘗試將@type標記的類資訊反序列化到物件,從而載入@type所標記的類,然而,@type的類有可能被惡意構造,只需要合理構造一個JSON,使用@type指定一個想要的攻擊類庫就可以實現攻擊。例如將@type指定為包含lookup()方法的庫中就可以實現JNDI注入。

ParserConfig.getGlobalInstance().setAutoTypeSupport(false);		//禁用autotype
ParserConfig.getGlobalInstance().setSafeMode(true);		//啟用autotype

以上內容僅作學習記錄,如有錯誤或瑕疵,歡迎批評指正,感謝閱讀。