前端防御XSS

Black_Hole 2016-03-02 16:37:00

0x00 前言


我不否认前端在处理XSS的时候没有后端那样方便快捷,但是很多人都在说过滤XSS的事就交给后端来做吧。前端做没什么用。我个人是非常反感这句话的。虽然说前端防御XSS比较麻烦,但是,不是一定不行。他只是写的代码比后端多了而已。而且前端防御XSS比后端防御XSS功能多,虽说后端也可以完成这些功能,但是代码量会比前端代码多很多很多。其实说了那么多,交给nginx||apache||nodeJs||Python会更好处理。但是我不会C,也就没办法写nginx模块了。而且也不在本文章的范围内,等我什么时候学会C再说把。

0x01 后端数据反馈过滤


现在大部分的网站都是在后端过滤一下后,就交给数据库,然后前端输出,整个流程只有后端做了防护,一般这个防护被绕过或者某个参数的防护没有做,那么网站就会被沦陷了(请别以为XSS只能获取cookie,熟练的程度取决于你的思想和编程) 现在我们来假设一下网站的一个URL参数没有做好过滤,直接导入数据库了,然后在前端反馈结果。代码如下:

把用户输入的内容导入到数据库里defenderXssTest_GetData.php:

<?php
if(empty($_GET['xss'])){        //判断当前URL是否存在XSS参数
    exit(); 
}
$xssString = $_GET['xss'];
/*数据库基础配置*/
$mysql_name ='localhost';
$mysql_username ='root';
$mysql_password ='123456';
$mysql_database ='xsstest'; 
$conn = mysql_connect($mysql_name,$mysql_username,$mysql_password);
mysql_query("set names 'utf8'");
mysql_select_db($mysql_database);

$sql = "insert into XSSTest (xss) values ('$xssString')";
mysql_query($sql);
mysql_close();

返回数据库中最后一条数据内容(即最新的内容)defenderXssTest_QueryData.php:

<?php
/*数据库基础配置*/
$mysql_name ='localhost';
$mysql_username ='root';
$mysql_password ='123456';
$mysql_database ='xsstest'; 
$conn = mysql_connect($mysql_name,$mysql_username,$mysql_password);
mysql_query("set names 'utf8'");
mysql_select_db($mysql_database);

$sql ="select * from XSSTest where id = (select max(id) from XSSTest)"; //返回数据库中最后一条数据
$xssText = mysql_query($sql);
while($row = mysql_fetch_array($xssText)){  //显示从数据库中返回的数据
    echo $row['xss'];
}
mysql_close();

前端输入及反馈defenderXssTest.html:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>前端防御XSS#Demo1</title>
</head>
<body>
    <input type="text" name="xss">
    <input type="submit" value="提交" id="xssGet">
</body>
<!--测试请记得更换jQuery路径!-->
<script type="text/javascript" src="/Public/js/library/jquery.js"></script>
<script>
    $("#xssGet").click(function(){
        $.ajax({
            url: '/defenderXssTest_GetData.php',
            type: 'get',
            dataType: 'text',
            data: "xss="+$('input:first').val(),
            cache:false, 
            async:false,
        })
        .done(function() {
            $.ajax({
                url: '/defenderXssTest_QueryData.php',
                type: 'post',
                dataType: 'text',
                cache:false, 
                async:false,
            })
            .done(function(data) {
                $("body").append(data);
            })
        })
    });
</script>
</html>

一共三个文件,因为测试用,我就没把数据库基础配置分离出来放在其他文件里了。

现在我们在浏览器里打开defenderXssTest.html文件:

现在我们再看下数据库:

已经导入到数据库里了。

OK,以上就是最普通的储蓄型XSS案例。为什么会出现这个问题呢,是因为PHP没有做好过滤。同时前端也没有做好过滤,这里会有人说前端做没用的,攻击者可以使用burp抓到此数据包,然后改包就可以绕过了。对,确实是这样。但是大伙从一开始就已经被误导了。想知道哪里被误导么,往下看。

这里我画个前端、Nginx、后端都做了过滤的图:

思维导图URL:https://www.processon.com/view/link/56c486cde4b0e2317a8b6681

这里我们可以看到防火墙的第一道门是前端过滤XSS机制。也是目前被大家所熟知的过滤结构。而本章要说的是:为什么不把前端过滤copy或者move到后端过滤机制下呢?

这里是新型的过滤机制的图:

思维导图URL:https://www.processon.com/view/link/56c4882ce4b0e5041c35ab53

这里我们在后端过滤机制的后面加上了前端过滤。为什么要这样做呢?

大家都知道前端过滤XSS是可以被抓包软件给修改的,所以是可以绕过,没有什么用。而Nginx过滤我相信大家都知道,很少有人愿意去用它,因为如果是做安全文章一类的话,是会被Nginx给抛弃当前的数据包的,也就是你发布的文章不会被存到数据库里,而且Nginx防御XSS模块并没有前端、后端那样简单方便,需要配置的东西很多。也导致了很多管理员不在Nginx安全上下功夫,即使管理员配置了Nginx过滤XSS模块,也可以绕过。

利用Nginx的一处逻辑缺陷(详情请移步到:http://www.freebuf.com/articles/web/61268.html文章里的0x03小节:利用Nginx&Apache环境bug来实现攻击),至于后端过滤机制肯定会有不严谨的时候,不然也而不会导致那么多XSS漏洞了。所以当攻击者输入的XSS字符串绕过了前端、Nginx、后端的话,那么就会直接导入到数据库中。那么这个时候后端传来的数据就不可信了。而如果我们在前端显示后端传来的数据时加了过滤会怎么样呢,答案是very good。当然了,这里有个前提,是前端显示后端传来数据的时候使用的是AJAX方法,而不是类似ThinkPHP这样在模板里调用。确切的说:此方法只针对于API接口

现在我们来做一个测试,之前的代码就是使用了AJAX方法,而

defenderXssTest_GetData.php和defenderXssTest_QueryData.php就类似于后端的API接口。我们现在在原有的基础上添加一些代码:

下面是前端过滤XSS的代码,取自于百度FEX前端团队的Ueditor在线编辑器:

function xssCheck(str,reg){
    return str ? str.replace(reg || /[&<">'](?:(amp|lt|quot|gt|#39|nbsp|#\d+);)?/g, function (a, b) {
        if(b){
            return a;
        }else{
            return {
                '<':'&lt;',
                '&':'&amp;',
                '"':'&quot;',
                '>':'&gt;',
                "'":'&#39;',
            }[a]
        }
    }) : '';
}

然后我们在原有代码的基础上添加xssCheck()函数就行了。如下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>前端防御XSS#Demo1</title>
</head>
<body>
    <input type="text" name="xss">
    <input type="submit" value="提交" id="xssGet">
</body>
<script type="text/javascript" src="/Public/js/library/jquery.js"></script>
<script>
    $("#xssGet").click(function(){
        $.ajax({
            url: '/defenderXssTest_GetData.php',
            type: 'get',
            dataType: 'text',
            data: "xss="+$('input:first').val(),
            cache:false, 
            async:false,
        })
        .done(function() {
            $.ajax({
                url: '/defenderXssTest_QueryData.php',
                type: 'post',
                dataType: 'text',
                cache:false, 
                async:false,
            })
            .done(function(data) {
                $("body").append(xssCheck(data));
            })
        })
    });
    function xssCheck(str,reg){
        return str ? str.replace(reg || /[&<">'](?:(amp|lt|quot|gt|#39|nbsp|#\d+);)?/g, function (a, b) {
            if(b){
                return a;
            }else{
                return {
                    '<':'&lt;',
                    '&':'&amp;',
                    '"':'&quot;',
                    '>':'&gt;',
                    "'":'&#39;',
                }[a]
            }
        }) : '';
    }
</script>
</html>

现在我们来输入XSS字符串看看:

变成了这个样子。我们再去数据库里看下:

的确是完整的XSS字符串,但是前端过滤了,导致此XSS没有用武之地。

所以前端开发人员只需要在网站的base.js代码里把过滤XSS的函数写进去,再把每一个ajax传过来的数据加上函数就可以了。

0x02 前端报警机制


这里的报警机制不能说特别的完整,是可以绕过的。那这个报警机制到底有何用处呢?就是在攻击者测试的时候发现及报警。

我们都知道测试XSS的时候和装逼的时候,攻击者会输入alert()函数,而之前的过滤方式,都是使用正则匹配,从而导致正则过长,匹配不易,运行过慢等问题。而现在我们完全可以重写alert函数来让攻击者在测试的时候,使用的是我们已经重写后的函数,这样做的好处是:当当前的参数不存在XSS的时候,这些函数是不会被触发的。而当当前参数存在XSS的时候,攻击者会依次输入:woaini->查看是否在源码里输出->woaini<>->查看<>有没有被过滤->输入<script>alret(1)</script>或者<img src="test" onerror="alert(1)" />->使用了我们重写的函数->触发报警机制。这样说可能有些人看不懂,下面是我画的图:

思维导图:https://www.processon.com/view/link/56c55805e4b0e5041c39261f

让我们来看下具体的代码吧:

var backAlert = alert;  //alert赋值给backAlert ,当后面重写alert时,避免照成死循环,照成溢出错误。
window.alert = function(str){       //重写alert函数
    backAlert(str);
    console.log("已触发报警,将数据发送到后台");
}

再把console.log换成ajax把数据发送给后台应用。后台接受的时候记得做过滤。前端代码记得加密,防止攻击者看出意图从而导致绕过,不触发报警。因为可能有些公司、个人网站已经有了自己的攻击报警系统、智能日志检索系统,我也就不再写了。把ajax发送的数据过滤后存到数据库里,再显示就行了。可以根据自己现有的框架进行开发,思路上面已经了,不难理解,代码也不难写。如果你不会或者说是不想写,可以等到我下一篇的文章。到时候里面会有全部的源代码。

下一章也是有关XSS防御的,在“前端报警机制”的基础上做的完善,有可能会用到后端,目前思路已经有了,但是没时间写。看三月底之前能不能写出来吧。

0x03 结语


之前EtherDream已经说了前端防火墙了,只是他做的是防御,而我是不防御直接报警。然后人工修复代码。因为虽然你防御住了,但是后端漏洞还在那,而触发报警机制后就可以进行人工修复。不是说EtherDream写的不好,反之非常好,在他的基础上也可以修改成前端报警机制,不过我还是喜欢让攻击者高兴几十分钟后,就懵逼的样子。在EtherDream的代码中有一个很棒的代码片段,他使用了内联事件监听了onclick等on事件,可以近一步的监听到黑客的操作。因为版权问题,我不方便把代码贴到本文中,毕竟是别人的思想结晶。想了解的话可以去查看:

http://fex.baidu.com/blog/2014/06/xss-frontend-firewall-1/

评论

莫里 2016-03-02 17:05:09

后端几行代码的事情
干嘛前端这么麻烦,维护都蛋疼事情
大多数前端都是弱智的好吗,哪里懂这玩意

雪碧0xroot 2016-03-02 17:11:28

XSS漏洞没出来,MySQL弱口令已经暴露了...

B

Black_Hole 2016-03-02 17:32:30

@莫里 这个文章的前提就是后端没有做好过滤

路人甲 2016-03-02 17:50:39

这个小哥写的东西还都挺多人喷的哈。。。

隐形人真忙 2016-03-02 18:56:07

思路很陈旧了 建议看看护心镜源码 仔细读读ED牛的全部blog

S

suolong 2016-03-02 20:02:05

如果是接口后端很有优势吧,如果考虑多终端的话,有个场景就是 接口 输出多个端,你a业务前端处理了,b引用前端未处理那么是不是就会触发xss 。如果后端处理完前端又处理一次,某些大业物上这里处理会影响效率吧,只是表达下想法。 赞文章!

W

Wooyun第一大屌 2016-03-03 09:15:06

小哥留下QQ

我是360安全忍者小号r00t4dm 2016-03-03 13:42:04

先赞一下文章
我认为在后端过滤比前端过滤要好一点,毕竟几句代码的事情

路人甲 2016-03-03 14:49:52

楼主说的东西ALI貌似有一个产品在线上,可以抓包看看源码。

爱梅小礼 2016-03-03 19:35:13

如何在一个已有的cms上方便地加上这个过滤功能呢

路人甲 2016-03-04 12:09:04

@隐形人真忙 大哥 给个blog的链接啊

路人甲 2016-03-05 14:03:15

只看了前面一部分就看不下去了。
过滤不应该在写入数据库之前做,而是应该在显示时做。
写入数据库前做发现过滤有问题怎么修改?

B

Black_Hole 2016-03-05 15:58:57

@看不下去了 客户端可以绕过:)

V

vc1 2016-03-06 02:47:52

画着原型切着图,突然飞来一口锅。。。
只谈0x01,
-
$("body").append(xssCheck(data))
-
jQueyr API : .append(content [, content] )
content
类型: String, Element, jQuery
DOM 元素,DOM 元素数组,HTML 字符串,或者 jQuery 对象,用来插在每个匹配元素里面的末尾。.append()函数将特定内容插入到每个匹配元素里面的最后面,作为它的最后一个【子元素】
-
append实际上插入的是html元素,参数可以是字符串形式。
xss就是把数据当成代码执行了
jquery中有操作文本的函数
$('<p>').text(data).appendTo($('body'))
-
escapeHtml这种函数,一般用在标签与数据混合在一起的情况
比如从模版或者字符串拼接生成dom
$('<div user-name="' + escapeHtml(data) + '"></div>').attr('user-name', 'data')
原生js也有setAttribute接口
-
近几年的MVC、更新的MVVC框架已经很好的解决了M、V分离的问题,而且很大的改善了有一处数据改一处代码的繁琐修复工作。
-
另外,有些网站的接口是跨平台使用,后端统一转义html,app开发还不干呢,在前端转义也是很普遍的做法了
-
崔更等下篇-。-

V

vc1 2016-03-06 02:48:38

[email protected]/* <![CDATA[ */!function(t,e,r,n,c,a,p){try{t=document.currentScript||function(){for(t=document.getElementsByTagName('script'),e=t.length;e--;)if(t[e].getAttribute('data-cfhash'))return t[e]}();if(t&&(c=t.previousSibling)){p=t.parentNode;if(a=c.getAttribute('data-cfemail')){for(e='',r='0x'+a.substr(0,2)|0,n=2;a.length-n;n+=2)e+='%'+('0'+('0x'+a.substr(n,2)^r).toString(16)).slice(-2);p.replaceChild(document.createTextNode(decodeURIComponent(e)),c)}p.removeChild(t)}}catch(u){}}()/* ]]> */

路人甲 2016-03-07 14:02:37

虽然说前端防御XSS比较麻烦,但是,不是一定不行。他只是写的代码比后端多了而已。而且前端防御XSS比后端防御XSS功能多,虽说后端也可以完成这些功能,但是代码量会比前端代码多很多很多。

路人甲 2016-03-09 10:45:16

只要是过滤,不是转义,就永远解决不了问题。

W

wangy3e 2016-03-14 13:54:46

不错的思路~~~

V

virtual 2016-03-19 22:19:33

alret是什么鬼

路人甲 2016-03-25 13:08:10

@SPRITEKING 角度刁钻

B

Black_Hole

只开发不攻击的安全汪

twitter weibo github wechat

随机分类

渗透测试 文章:154 篇
IoT安全 文章:29 篇
区块链 文章:2 篇
神器分享 文章:71 篇
Ruby安全 文章:2 篇

扫码关注公众号

WeChat Offical Account QRCode

最新评论

Yukong

🐮皮

H

HHHeey

好的,谢谢师傅的解答

Article_kelp

a类中的变量secret_class_var = "secret"是在merge

H

HHHeey

secret_var = 1 def test(): pass

H

hgsmonkey

tql!!!

目录