web安全中常见攻击及防御小总结

最近在看一本很经典的书《白帽子讲Web安全》(安利,道哥写得很有趣),再联想起平时工作中的一些Web安全的措施,决定记录、总结一下。

XSS

什么是XSS

XSS(Cross Site Scripting),全称跨站脚本攻击,即一种允许恶意web用户将代码植入到网站页面中的漏洞,是代码注入的一种。
它一般分为这几种类型:

  • 反射型XSS:被动的,非持久性的。黑客通常需要诱骗用户点击一个恶意链接,以达到攻击目的。url还可以通过短网址服务将之缩短,从而隐藏自己。
  • 存储型/持久型XSS:主动的,持久的。黑客通常是在自己的网站中写一个包含恶意代码的网页(比如博客)。所有访问这个网页的用户都会在他们的浏览器中执行这段代码,由此黑客达到攻击目的。
  • DOM Bases XSS:通过修改页面的DOM节点形成的XSS

防御措施

  1. httponly
    httponly是http返回头中set-cookie字段的一个属性。设置了 HttpOnly 属性的 cookie 不能使用 JavaScript 经由document.cookieXMLHttpRequest和Request APIs进行访问,以防范XSS后的Cookie挟持攻击。下面是一个例子:
    1
    2
    3
    <?php
    header('set-cookie: USER=123;PW=123456;HttpOnly');
    ?>
    返回头将包括:
    1
    Set-Cookie: USER=123;PW=123456;HttpOnly
  2. 输入检查
    对输入进行格式检查,即’XSS Filter’。php有一些内置过滤函数htmlentitieshtmlspecialcharsstrip_tags等。但这些函数不一定能过滤完整。这时候可以通过正则匹配(类似“白名单”的做法),检查请求中的参数。nodejs方面,网上有很多开源的检查格式的实现,如better-validate
  3. 输出检查
    对于在HTML中输出,在PHP中,可以使用htmlentitieshtmlspecialchars转换所有html标签或格式化特殊符号(&、’、”、<、>);对于在Javascript中输出,进行JavaScript编码(使用’\’对特殊字符进行转义,除数字字母之外,小于127的字符编码使用16进制’\xHH’的方式进行编码,大于用unicode);对于在CSS中输出,进行CSS编码(类似JavaScript编码);对于在地址中输出,在js中,可以使用encodeURI()函数。

CSRF

什么是CSRF

CSRF(Cross Site Request Forgery),跨站点请求伪造。举一个例子:攻击者诱使用户访问一个页面,以该用户身份在第三方站点里执行操作,比如删除某博客的文章。

CSRF和XSS的区别
XSS:用户过分信任网站,放任网站代码执行,使恶意脚本将代码植入到网站页面中(着重于脚本)
CSRF:网站过分信任用户,放任伪造的合法用户的请求执行网站的某些功能(着重于伪造)

CSRF进阶

  1. 浏览器的cookie策略:
    浏览器的cookie分为两种:一种是session cookie,没有指定expire,保存在浏览器进程的内存空间中,浏览器关闭后就会失效;另一种是third-party cookie(本地cookie),保存在本地,只有到了指定的expire时间后cookie才会失效。
    <img><frame><script><link>等标签中,session cookie会被发送,而third-party cookie则视浏览器策略而定。有些浏览器默认禁止了发送third-party cookie,而有些则不会拦截,容易被CSRF攻击者利用。不会拦截的浏览器有:Firefox3、Opera、Google Chrome、Android等。
  2. P3P头的副作用
    P3P Header(The Platform for Privacy Preferences)是W3C指定的一项标准。如果网站返回给浏览器的HTTP头中含有P3P头,则允许浏览器发送第三方cookie,即使是上面提及的禁止在<frame><script>等中发送第三方cookie的浏览器如IE等。

防御措施

  1. referer
    referer是http请求头的一个字段,用于告诉服务器请求是从哪个页面链接过来的。可以通过判断referer字段而防范XSS。
    1
    2
    3
    4
    5
    $refererUrl = parse_url($_SERVER['HTTP_REFERER']);
    $host = $refererUrl['host'];
    if(!isset($_SERVER['HTTP_REFERER']) || $referurl['host'] !== 'www.example.com') {
    exit('非本站请求!');
    }
    然而,referer的值是由浏览器提供的。某些浏览器,如IE6、FireFox2,目前有漏洞被黑客利用篡改referer值。可见,这种方法并非万无一失。而且,服务器并非什么时候都能拿到referer。用户可以限制referer的发送,浏览器在某些情况(https跳转到http)也不会发送referer。
    并且,我们知道,服务器端脚本可以轻易伪造referer,这种防范方法显然不够。我们可以通过验证码进行验证,也可以在客户端通过document.referer更准确地判断网页的真实来路。
  2. 在请求地址中添加token并验证
    在客户端,对于get请求,就把token作为url一个参数,如http://url?csrftoken=tokenvalue;对于post请求,则在页面表单中加入<input type=”hidden” name=”csrftoken” value=”tokenvalue”/>。在服务端,拦截验证这个token,拒绝没有token或者token不正确的请求。如token可以在用户登录后产生并放在session,每次请求时把请求中的token和session里的token比对。或者用加密算法生成token,再在服务端中验证其合法性。
    但这种方法需在每个请求中都加入token,比较麻烦,而且容易暴露。黑客可以从请求中(或者通过referer)获取token然后发动CSRF攻击。为了避免这一点,还需在添加token的时候增加一个判断:如果是链接到本站才加上token。
  3. 在http头中自定义属性并验证
    这种方法也是使用token进行验证,不同的是,这种方法通过XMLHttpRequest这个类把token放进http头中自定义的csrftoken这个属性里。这样通过XMLHttpRequest请求的地址就不会被记录到浏览器的地址栏,也不用担心token会透过referer泄漏到其他网站中去。这种方法的局限性是通常只用于Ajax方法。
    1
    2
    3
    4
    5
    let xhr = new XMLHttpRequest()
    xhr.open('POST', 'http://example.com')
    xhr.setRequestHeader('token', 'XXXXXXXXXX')
    // 其他设置
    xhr.send()

    自定义一些header属性进行跨域请求时,可能会遇到”not allowed by Access-Control-Allow-Headers in preflight response”,你可能需要在你的服务端设置”Access-Control-Allow-Headers”。

  4. 验证码
    验证码强制用户必须与应用进行交互,通常能很好地遏制CSRF攻击。如php的写法:前端页面将随机生成的验证码存在$_SESSION['authcode']中,后端对比$_SESSION['authcode']$_POST['authcode']。这种方法的局限性在于加重了用户操作的繁琐性。

参考