# 05|CSRF:为什么用户的操作他自己不承认? 你好,我是王昊天。 想象你是个青春阳光的精神小伙,和女神小美青梅竹马,培养了十几年的感情。眼看着就要抱得美人归时,半路杀出了个男二号,成了你的竞争对手。有一天你们恰好在一起聚会,男二号趁你上厕所,用你的手机给小美发了微信。 “小美,你闺蜜真好看,可以介绍给我吗?” 你回来时,小美大骂了你一通,然后生气地摔门而去。 在这个故事里,男二就通过他的行为完成了一次CSRF。 ## CSRF CSRF的全名是Cross-Site Request Forgery,中文名称是跨站点请求伪造,简单来说,**就是让Web应用程序不能有效地分辨一个外部的请求,是否真正来自发起请求的用户**,虽然这个请求可能是构造完整、并且输入合法的。 和前几节课程中学习过的漏洞相比,CSRF有自己的漏洞名称,明显是一个更为细分的漏洞类型,而非一个漏洞类别。它作为一个独立的细分漏洞类型,值得我们单独进行探讨,说明影响力是足够大的。 扩展开讲一讲,当一个Web应用在设计过程中没有充分考虑来自客户端请求的验证机制时,就可能会遇到CSRF问题。站在攻击者的视角来看,他可以通过一个URL、图片加载或者XMLHttpRequest等方式,让用户触发一个自动化请求发送行为,这个请求在Web Server接受时会被认为是合法的。 **接下来我们看一个典型的攻击场景。** 如下HTML目的是让用户更新自己的信息: ```plain

``` 其中的profile.php包含如下代码: ```php // initial the seesion in order to validate sessions session_start(); // if the session is registered a valid user the allow update if ( !session_is_registered("username") ) { echo "invalid session detected!"; // Redirect user to login page ... exit; } // The user session is valid, so process the request // and update the information update_profile(); ``` 这里的PHP代码中是包含了一些保护措施的,结合我们前面几节课程学到的内容来看,它包含了用户身份的有效性认证,阻止了越权访问。**但是上述代码并不能够有效地防止CSRF攻击,**如果攻击者可以构建下面这段代码,并且将它托管到某个站点,那么当用户保持登录状态并且访问攻击代码页面时,就会触发攻击代码: ```javascript ``` **其中主要参数是cmd字段,其含义是:** 1. 创建一个命名管道378; 2. Webmin作为客户端使用nc连接黑客控制的服务端,接收命令,并通过匿名管道将命令重定向到bash; 3. bash执行服务端发过来的命令,将输出重定向到命名管道378,并通过命名管道378将输出重定向到nc发送给服务端。 这一条命令实际包含了两条管道,一条匿名管道,一条命名管道,并使其各司其职。它先后将html文件中的your\_mituan\_app\_address、your\_ip、your\_port替换为谜团启动的应用URL、你的服务器地址、你的服务器IP,即可开始CSRF攻击。 让我们看看这次攻击经历了哪些流程:首先我们以管理员身份登录Webmin界面,在自己的服务器上启动nc进行监听:nc -l -p 1337,然后使用浏览器直接打开我们创建的HTML页面,到这里我们的攻击就已完成,服务器上的nc已经接入Webmin服务器的bash,可以执行任意命令。 #### 漏洞分析 既然已经成功利用了该漏洞,接下来我们就要分析这一类漏洞该如何修复。**最简单的方式就是校验这次访问的来源。**事实上,Webmin已经做了这种防御。你肯定会有疑问,为什么做了防御仍然会出现CVE-2021-31760漏洞呢?其实这是由于一个配置项引起的,在构建Webmin平台的过程中,我们对config文件进行了修改: ```plain /etc/webmin/config -> referers_none=0 ``` 在官方的说明中,该项就是在判断不同来源的request能否生效,你可以通过如下命令修改配置并重启Webmin服务: ```plain // 将referers_none=0修改为referers_none=1 vim /etc/webmin/config // 重启webmin服务 service webmin restart ``` 再次尝试就会发现该漏洞已经消失了,这也是我在追踪这个漏洞时惊讶的点。也许正是这个原因,截至写稿时,Webmin已经在存在漏洞的版本发布了至少5次更新,但是却并没有修复该漏洞。 到这里你肯定更好奇了,既然Webmin有相关的保护措施,那CVE-2021-31760这个漏洞是否真实存在呢? 这是个好问题,我们继续来深挖一下: 首先,**该配置项是如何生效的?** 通过对源码的追踪分析,我们可以发现存在如下函数调用链: ```bash # run.cgi # line 5 require './proc-lib.pl'; &ReadParse(); $access{'run'} || &error($text{'run_ecannot'}); ... # proc-lib.pl # line 9 &init_config(); ... # web-lib-funcs.pl # line 5142 if (!$gconfig{'referers_none'}) { # Known referers are allowed $trust = 1; } elsif ($trustvar == 2) { # Module wants to trust unknown referers $trust = 1; } else { $trust = 0; } } ... # webmin/web-lib-funcs.pl # line 5205 # function init_config ... if (!$trust) { # Looks like a link from elsewhere .. show an error $current_theme = undef; &header($text{'referer_title'}, "", undef, 0, 1, 1); $prot = lc($ENV{'HTTPS'}) eq 'on' ? "https" : "http"; my $url = "".&html_escape("$prot://$ENV{'HTTP_HOST'}$ENV{'REQUEST_URI'}").""; if ($referer_site) { # From a known host print &text('referer_warn', "".&html_escape($r)."", $url); print "

\n"; print &text('referer_fix1', &html_escape($http_host)),"

\n"; print &text('referer_fix2', &html_escape($http_host)),"

\n"; } else { # No referer info given print &text('referer_warn_unknown', $url),"

\n"; print &text('referer_fix3u'),"

\n"; print &text('referer_fix2u'),"

\n"; } print "

\n"; &footer(); exit; } ... ``` 至此,我们发现referers\_none配置项的启用,可以影响到run.cgi的工作流程,使其对于包含不同referers的http request继续提供支持。 是否该项配置项就足够了呢?其实答案是否定的,因为CSRF漏洞一般发生在跨域场景,但是这句话并不绝对,对于同域场景发生的CSRF攻击,上述配置项是难以抵御的。虽然同域场景对攻击者的能力有更高的要求,但是一旦问题发生,我们可以看到root权限级别的RCE仍然是非常恐怖的。 那么如何从开源代码中学习漏洞挖掘以及安全开发呢?授人以鱼不如授人以渔,**这个漏洞的学习除了本身的知识点,更重要的是如何通过对一个CVE漏洞的分析,去掌握漏洞分析和修复的规律。** 在分析一个漏洞时,一定要分析清楚函数调用关系,清晰地了解输入是经过怎样的过程最终影响到输出的。然后一个有质量的漏洞,产品团队一般会在漏洞公布的第一时间进行修复,我们可以使用GitHub的版本比对功能,拿漏洞出现的版本与修复后版本进行源码比对,通过这样的方式可以帮助我们了解优秀的项目是如何解决同类安全问题的。 通过这种方式,我们可以学习到很多优秀宝贵的经验,快速提升我们的开发水平。 ## 防御及检测 根据CSRF的攻击特点,我们可以采用以下几种方式进行防御: **1\. 同源策略** 该防御策略的产生主要为了针对CSRF攻击的第一个特征——跨域场景,它的设计思路主要是禁止外域(或者不受信任的域名)对Web Server发起请求。在HTTP协议中,有两个Header字段可以用来帮助我们判断来源域:Origin Header 和 Referer Header。这两个字段在浏览器发送请求时会自动携带,并且**不能由前端修改**。 你可能会有疑问:这两个字段很明显是依赖于浏览器实现的,现在浏览器种类那么多,如果浏览器不支持怎么办?必须承认,这是个很好的问题,HTTP协议标准本身在动态更新,很多比较旧版本的浏览器可能不支持这个Policy,如果出现这种情况最好的策略就是阻止这次请求。 **2\. Token** 回顾我们在总结CSRF特点时提到的特征,CSRF一般发生在跨域场景下,但是并不绝对。如果攻击者是在本域发起的CSRF攻击,那么同源策略就会失效,因此我们需要一种更严格的防护策略——CSRF Token。 那么CSRF Token如何实现呢?为每一个form表单生成唯一的token,并且在form提交时验证token,就是CSRF Token的实现思路,但是token需要保证不可预测。在代码实现上主要有2种思路。 第一种是在用户访问页面时,由服务器生成Token,将生成的Token存放于Session中,一般Token生成时会通过加密算法实现,输入一般包括随机字符串、时间戳等,要注意Token也会有有效期。 第二种是每次加载页面时通过JS遍历DOM树结构插入Token: ```plain GET: http://example.com?csrf_token=value POST: ``` 了解了客户端实现之后,你肯定自然地想到了后面的问题——服务端收到HTTP请求后怎么验证token的正确性呢? 要注意,对于分布式Web应用,使用Session存储Token会非常不方便,所以一般采用中间件存储或者动态计算的方式来优化。中间件存储方案是将Token存储在Redis中间件上,这样可以保证不同服务器取得的token值一致;动态计算方案是Token的原始输入不再采用随机数,而是采用UID等用户信息,同时加密算法采用对称加密算法,这样可以保证任何一台分布式服务器取到Token后都可以执行解密操作并进行数据正确性比对。 **3\. 接口设计** 对于同源策略的实现,是有一些特殊的场景需要被作为例外处理的。按照我们之前的设计,用户来自搜索引擎链接的跳转会被无差异判定为CSRF攻击,这时我们就要判断特定情况并进行放行处理,一般情况下我们都会放行GET请求。但此时如果Web应用实现上允许用户通过GET请求发送敏感操作,就会出现安全问题。这提醒我们,不要在GET请求中允许用户执行敏感操作。 这里我们可以引入一个更形象的、非技术手段的抵制CSRF的案例——人工形态的CSRF\_Token,在许多重要的支付环节,都需要在最后一步发送手机验证码、邮件验证码或者进行人脸识别,其实这就是通过应用流程设计的角度实现的一种CSRF\_Token变种验证操作。 现在的防御方案,主要考虑的是如何防止跨域的CSRF。因为攻击者无法获取到Token,所以大家会普遍认为,本域发生的CSRF暂时是安全的。但是,如果XSS和CSRF问题同时在本域发生,由于XSS可以让攻击者获取Token,CSRF的防御就宣告失效。因此我们需要在Web应用设计和开发过程中,严格过滤用户的输入,确保用户不能够输入我们不希望出现的内容,这样可以同时规避掉XSS和CSRF安全风险。 **4\. 双重Cookie** 在Web应用开发中新增CSRF\_Token机制还是稍有些麻烦,那么我们该如何通过现有的组件,来实现CSRF防御方案呢?答案是双重Cookie。 当用户访问Web网站时,Web应用为用户随机生成一个新的Cookie值,当Web应用每次执行表单提交操作时都需要携带这个Cookie值;由于同源策略的保护,攻击者无法获取或者修改这个Cookie项,因此实现了CSRF的保护。 但要注意的是这项技术需要用到JavaScript,因此在一些JavaScript Disabled的浏览器中是无法工作的。 除此以外,双重Cookie也面临一些风险。比如本域Web应用存在XSS漏洞,该防御将失效。以及为了确保Cookie传输安全,需要采用整站HTTPS,否则Cookie泄露也会导致该防御失效。 ## 总结 这节课我们探讨了一类主流的安全风险——CSRF,首先我们列出了CSRF风险的常见特征:首先,由于外域更容易被攻击者控制,攻击一般发生在跨域场景下;其次,CSRF在攻击过程中并没有获取到用户的登录凭据,只是借用户之手发送了恶意的请求;最后,攻击者可以采用图片URL、超链接、表单提交等许多方式实现攻击。 然后我们以2021年上半年的一个CSRF RCE漏洞为例,对它进行了实例分析,这个过程中我们首先完成了对CVE-2021-31760漏洞的复现,并针对该漏洞修复方案进行评估,然后又通过这个漏洞,学习了漏洞挖掘、漏洞分析以及漏洞修复方法。 最后我们给出了一些业内普遍认可的,新颖的解决方案,供你在工作中使用,他们分别是:同源策略、CSRF Token、接口设计层保护、双重Cookie和Samesite Cookie 以上,就是关于CSRF我们一起学习探讨的内容,欢迎大家在评论区留言讨论。什么?你说Samesite Cookie没讲?那就作为课后作业吧! ## 思考题 为了防御CSRF,除了上述安全方案,业内提出了一种新的解决方案——Samesite Cookie,你可以通过自己的研究,讲讲它和双重Cookie的区别吗? 欢迎在评论区留下你的思考,我们下节课再见!