前言
经典 Java 软件漏洞 struts 系列,准备一个个看过去。
环境搭建
Action 的 execute 方法加上一句(其他成员变量也可以):
1
| ActionContext.getContext().getSession().put("username", this.username);
|
jsp 的 form 表单里面加上:
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; }
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 的键都会以:
开头了,所以一般就无法用其他键来伪装 token 了。
Orz