Tomcat 怎样防止跨站请求伪造(CSRF)
MittieAnnoi
8年前
<p>对于CSRF,可能一些朋友比较陌生。我们下面先简单介绍下。</p> <p>什么是CSRF呢,我们看下Wikipedia的说明:</p> <p>Cross-site request forgery,即跨站请求伪造,也称为 "One Click Attach" 或者"Session Riding",常缩写成CSRF。是 <strong>通过伪装来自受信任用户的请求来利用受信任的网站</strong> 。</p> <p>其中,说起CSRF,经常会举的一个例子,是这样的:</p> <p>用户A在访问网上银行,在银行网站里进行了一些操作后。</p> <p>之后点击了一个陌生的链接。而这个链接,是用户B提供的一个恶意网页W,网页W内也包含访问和用户A相同银行信息的链接,可能是转帐,也可能是修改密码。</p> <p>此时如果A的认证信息还未过期,就会被直接利用,成功帮助W进行了银行网站的对应操作,而这一切,都是在A不知情的情况下进行的。</p> <p>为了防范CSRF,常见的方式有:</p> <ul> <li> <p>请求中包含随机token信息</p> </li> <li> <p>Cookie中包含csrf token信息</p> </li> <li> <p>其他的验证请求头Refer等...</p> </li> </ul> <p>在Tomcat中,默认提供了一个防范CSRF的好工具: <strong>CSRF Prevention Filter</strong> 。</p> <p>Tomcat默认提供了各类的Filter,处理不同的场景和需求。像我们前面介绍过的处理编码的 <a href="http://mp.weixin.qq.com/s?__biz=MzI3MTEwODc5Ng==&mid=403224828&idx=1&sn=e93242685ed43aee33b9ee459ab02210&scene=21#wechat_redirect" rel="nofollow,noindex">Tomcat自带的设置编码Filter</a> , 还有进行跨域处理的 <a href="http://mp.weixin.qq.com/s?__biz=MzI3MTEwODc5Ng==&mid=403375476&idx=1&sn=4ea85119f4597605edad312abdf9eae7&scene=21#wechat_redirect" rel="nofollow,noindex">Tomcat与跨域问题</a> 等等。今天介绍的CSRF Prevention Filter也是其中的一个。</p> <p>整个Filter的工作流程可以概括成以下内容:</p> <p>该Filter为Web应用提供了基本的CSRF 保护。它的filter mapping对应到 <strong>/*</strong></p> <p>并且所有返回到页面上的链接,都通过调用 <strong>HttpServletResponse</strong> # encodeRedirectURL(String) 或者 <strong>HttpServletResponse</strong> # encodeURL(String) 进行编码。实现机制是<strong>生成一个token并且将其保存到session中,URL的encode也使用同样的token,当请求到达时,会比较请求中的token和session中的token是否一致,只有相同的才允许继续执行。</strong></p> <p>我们通过一个例子,深入源码,来了解下内部的实现细节。</p> <p>还是使用Tomcat自带的Manager应用来看下。</p> <p>在其 <strong>web.xml</strong> 中,有这样的配置:</p> <p><img src="https://simg.open-open.com/show/acf08fc74507726bf582c4c54f5cf9a5.jpg"></p> <p>下面的内容是CsrfPreventionFilter的 <strong>doFilter</strong> 方法,</p> <p><img src="https://simg.open-open.com/show/6155fb771bf2493b1a5e9def6d45afdf.jpg"></p> <p>我们注意到前面的配置里包含一个entryPoints,对照代码,马上就能明白,这项配置用来做类似于exclude的功能,在配置中的映射,可以跳过检查。</p> <p>而如果没有在entryPoints中,同时在session存在,但不包含对应的Nonce,就会直接返回 <strong>403</strong> (SC_FORBIDDEN)。</p> <p>如果session不存在,就会在doFilter中走到下面的内容:</p> <p><img src="https://simg.open-open.com/show/859ec915dfc94cb0268e123a45e4910a.jpg"></p> <p>做为初次请求,会在session中保存对应的Attribute,同时添加到一个LruCache中。这里的重点在于,使用了HttpServletResponseWrapper的子类 CsrfResponseWrapper 替换了它做为response传入后续的流程 。</p> <p>所以,后面所有调用encodeUrl的地方,其实实际调用到的是这个:</p> <p>public String encodeURL(String url) {</p> <p>return <strong>addNonce</strong> (super.encodeURL(url));</p> <p>}</p> <p>addNonce对应的,是在传入URL后面增加csrf token或者是nonce的标识,用于后续请求时的识别。</p> <p><img src="https://simg.open-open.com/show/43b58017966379a06a032f7131f449c2.jpg"></p> <p>页面具体的编码操作,则是对response的encodeURL的使用,也就是我们上面addNonce的使用:</p> <p>对应到Manager应用,它的页面是通过Servlet输出的,所以具体的逻辑在Java文件中,我们在页面上的连接观察到,此时获取应用列表的请求URL变成了这样:</p> <p>http://localhost:8080/manager/html/list? <strong>org.apache.catalina.filters.CSRF_NONCE</strong> =6BC061DD606D7BA1BDEF7F40657F0C47</p> <p>每个不在entryPoints中的请求,都会加上org.apache.catalina.filters.CSRF_NONCE=6BC061DD606D7BA1BDEF7F40657F0C47</p> <p>这种形式的URL输出,就是在页面上调用encodeURL的结果,对应的Manager中的代码是这个样子:</p> <p><img src="https://simg.open-open.com/show/81bd535b5ffa878da0f43dad3fcb581d.jpg"></p> <p>以上,就是CSRF Prevetion Filter实现的原理和细节。当然,上面返回403的地方,以及生成nonce的地方,都可以通过Filter提供的参数来进行配置,分别对应到denyStatus和randomClass。后者需要提供一个Random的实现。</p> <p> </p> <p> </p> <p>来自:http://mp.weixin.qq.com/s?__biz=MzI3MTEwODc5Ng==&mid=2650859236&idx=1&sn=426730453a1a0496594cc6808e497a8f&chksm=f1329937c64510212fa8034f7954583871b2fc8c32a71d0f068f2bccb0d0170e6cd22998ed7f</p> <p> </p>