WEB中间件漏洞
Shiro
1.Shiro-550
对应CVE-2016-4437
参考:Shiro 550 漏洞学习(一) (yuque.com)
(1)利用版本
1.2.4
及以下
(2)漏洞原理
Apache Shiro
框架提供了记住密码功能,登录成功的话会将用户的登录信息进行经过AES
加密然后base64
编码,放在Cookie
的rememberMe
字段,大致如下
那么在服务端就会对该rememberMe
字段进行base64
解码,然后AES
解密,最后再反序列化,如果我们构造恶意的rememberMe
字段,这样就会导致反序列化的RCE
漏洞。同时,由于AES
加解密的KEY
是硬编码写在源码中的,我们可以直接获得。
(3)漏洞环境
简单搭建
源码构建Shiro
参考(48条消息) shiro debug 调试_scanner010的博客-CSDN博客
下载源码:apache/shiro: Apache Shiro (github.com)
然后换成
1.2.4
:git checkout shiro-root-1.2.4
jstl
版本切换打开
shiro/samples/web/pom.xml
,更换jstl
版本之后重新
reload
一下即可Tomcat
部署然后配置一下
Web
界面如下添加即可
之后运行或调试即可
(4)漏洞流程分析
rememberMe
解析流程
1 | AbstractRememberMeManager.getRememberedPrincipals |
大致图解如下
(5)漏洞代码分析
AbstractRememberMeManager.getRememberedPrincipals
当一个涉及到用户信息的请求来时,首先是进入
rememberMe
管理函数CookieRememberMeManager.getRememberedSerializedIdentity
在
AbstractRememberMeManager.getRememberedPrincipals
函数中调用getRememberedSerializedIdentity
尝试获取base64
解码后的rememberMe
字段AbstractRememberMeManager.convertBytesToPrincipals
随即调用
convertBytesToPrincipals
函数进行后续操作AbstractRememberMeManager.decrypt
convertBytesToPrincipals
函数中调用到decrypt
函数,通过getDecryptionCipherKey
获取密钥尝试对base64
解码后的Cookie
进行AES
解密一层一层撸下来,如下所示,可以在
AbstractRememberMeManager
中找到对应的硬编码的key
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public byte[] getDecryptionCipherKey() {
return decryptionCipherKey;
}
public void setDecryptionCipherKey(byte[] decryptionCipherKey) {
this.decryptionCipherKey = decryptionCipherKey;
}
public void setCipherKey(byte[] cipherKey) {
//Since this method should only be used in symmetric ciphers
//(where the enc and dec keys are the same), set it on both:
setEncryptionCipherKey(cipherKey);
setDecryptionCipherKey(cipherKey);
}
public AbstractRememberMeManager() {
this.serializer = new DefaultSerializer<PrincipalCollection>();
this.cipherService = new AesCipherService();
setCipherKey(DEFAULT_CIPHER_KEY_BYTES);
}
private static final byte[] DEFAULT_CIPHER_KEY_BYTES = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");AbstractRememberMeManager.deserialize
解密完成之后,回到
AbstractRememberMeManager.convertBytesToPrincipals
函数中即进行反序列化调用到了
DefaultSerializer.deserialize
来完成最后的反序列化过程
(6)漏洞利用
即将我们的恶意反序列化链进行相关编码加密,在登录时勾选rememberMe
,然后抓包设置为rememberMe
字段发送到服务器即可,常用的链条为CC1
,CC6
,CC10
,cc11
相关
POC
使用
ysoserial
生成序列化链条cc1
然后编码加密即可。
1 | import org.apache.shiro.crypto.AesCipherService; |
(7)漏洞检测
dnslog
key
检测key
正确则显示deleteMe
,反之则显示deleteMe
(8)漏洞修复
官方修复是key
的随机生成
2.Shiro-721
这个就先不复现了,比较没有实用价值
Tomcat
1.内存马
参考:Tomcat 内存马学习(一):Filter型 (yuque.com)
这个不能算漏洞,只能算一种写木马的方式吧
由于Tomcat
的机制问题,存在很多可以写内存马的地方,依据[wjlshare师傅博客](Tomcat 内存马学习(一):Filter型 – 天下大木头 (wjlshare.com))介绍主要有如下几种
servlet-api类
- filter型
- servlet型
spring类
- 拦截器
- controller型
Java Instrumentation类
- agent型
servlet-api类
先需要探讨一下该机制,大概流程如下
然后简单构建一下环境
- 创建项目
添加一下
Web
框架导入对应
Tomcat
的servlet-api
依赖然后就可以创建
Servlet-Filter
创建之后需要在
web/WEB-INF/web.xml
中加入创建的filter
这里设置了
url-pattern
为/*
,表示所有的url
都会经过该filter
然后配置一下
Tomcat
服务器即可相关代码
Servlet
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
public class Servlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.getWriter().write("MyServlet");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}MyFilter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20import javax.servlet.*;
import javax.servlet.annotation.*;
import java.io.IOException;
public class MyFilter implements Filter {
public void init(FilterConfig config) throws ServletException {
}
public void destroy() {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
response.getWriter().write("MyFilter");
chain.doFilter(request, response);
}
}
运行起来即可看到
任意存在的
URL
访问都会执行设置的MyFilter
,
当然一般是给对应的Servlet
来对应的filter
,修改一下web.xml
里的url-pattern
即可
①机制流程
TIPS
在
IDEA
中按两下shift
键可以快速搜索项目中的类