struts2系列漏洞 S2-010

前言

经典 Java 软件漏洞 struts 系列,准备一个个看过去。


环境搭建

Action 的 execute 方法加上一句(其他成员变量也可以):

1
ActionContext.getContext().getSession().put("username", this.username);

jsp 的 form 表单里面加上:

1
<s:token />

struts.xml 的 action 标签里面加上(两个拦截器得顺序可以翻过来,但是不加 default 那个拦截器就无法触发 setter):

1
2
3
<interceptor-ref name="defaultStack" />
<interceptor-ref name="token" />
<result name="invalid.token">/error.jsp</result>

测试访问 Action,看看会不会正常触发 token 校验。

漏洞利用

CSRF 漏洞,可以先正常流程传一个 username 触发 execute 中的 session 赋值,然后用这个 payload 绕过 token 校验:

1
username=Twings&struts.token.name=username&struts.token=Twings

漏洞分析

调试 TokenInterceptor 跟一遍就明白了,主要问题在于 token 校验函数 validToken 中:

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
27
28
29
public static boolean validToken() {
String tokenName = getTokenName();

if (tokenName == null) {
...
return false;
}

String token = getToken(tokenName);

if (token == null) {
...
return false;
}

Map session = ActionContext.getContext().getSession();
String sessionToken = (String) session.get(tokenName);

if (!token.equals(sessionToken)) {
...

return false;
}

// remove the token so it won't be used again
session.remove(tokenName);

return true;
}

struts2 将 token 存放在 session 中,但是存放用的键可以由用户自行设定,所以用户就可以通过将键设置为一个已知值的键(比如 username),从而绕过 token 验证。

漏洞修复

2.3.4.1 版本的修复,validToken 方法里面从 session 取 token 的方式改变了:

1
2
3
Map session = ActionContext.getContext().getSession();
String tokenSessionName = buildTokenSessionAttributeName(tokenName);
String sessionToken = (String) session.get(tokenSessionName);

多了一个 buildTokenSessionAttributeName 方法:

1
2
3
public static String buildTokenSessionAttributeName( String tokenName ) {
return TOKEN_NAMESPACE + "." + tokenName;
}

现在存放 token 的键都会以:

1
struts.tokens.

开头了,所以一般就无法用其他键来伪装 token 了。


Orz


struts2系列漏洞 S2-010
http://yoursite.com/2020/07/21/struts2系列漏洞-S2-010/
作者
Aluvion
发布于
2020年7月21日
许可协议