前言
经典 Java 软件漏洞 struts 系列,准备一个个看过去。
环境搭建
Action 的 execute 方法加上一句(其他成员变量也可以):
ActionContext.getContext().getSession().put("username", this.username);
jsp 的 form 表单里面加上:
<s:token />
struts.xml 的 action 标签里面加上(两个拦截器得顺序可以翻过来,但是不加 default 那个拦截器就无法触发 setter):
<interceptor-ref name="defaultStack" />
<interceptor-ref name="token" />
<result name="invalid.token">/error.jsp</result>
测试访问 Action,看看会不会正常触发 token 校验。
漏洞利用
CSRF 漏洞,可以先正常流程传一个 username 触发 execute 中的 session 赋值,然后用这个 payload 绕过 token 校验:
username=Twings&struts.token.name=username&struts.token=Twings
漏洞分析
调试 TokenInterceptor 跟一遍就明白了,主要问题在于 token 校验函数 validToken 中:
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 的方式改变了:
Map session = ActionContext.getContext().getSession();
String tokenSessionName = buildTokenSessionAttributeName(tokenName);
String sessionToken = (String) session.get(tokenSessionName);
多了一个 buildTokenSessionAttributeName 方法:
public static String buildTokenSessionAttributeName( String tokenName ) {
return TOKEN_NAMESPACE + "." + tokenName;
}
现在存放 token 的键都会以:
struts.tokens.
开头了,所以一般就无法用其他键来伪装 token 了。
Orz