咱媽說別亂點鏈接是對的,有毒

平時經常聽到人們說別亂點鏈接,小心有病毒。還有長輩們轉發的“天吶~XXX的陰謀,全是病毒”、“XXX驚天大病毒,點了蘋果手機就要爆炸!”、“現在轉發熱門連接會亂扣費!千萬別點!”。

到底長輩們說的這些是對的還是錯的,是真的還是假的?下面我用通俗易懂的語言為大家剖析。

CSRF攻擊就是假裝你去行騙

首先我們說說CSRF(Cross-site request forgery),它的中文名稱是跨站請求偽造,也被稱為:one click attack/session riding,縮寫為:CSRF/XSRF。

簡單地說,CSRF就是利用了我們的登錄狀態或者授權狀態(請注意“利用”,并沒有竊取到),然后做一些損害我們自身利益的事情。

舉個例子,CSRF就是使用我們微信頭像和昵稱,然后去跟咱爸媽要錢。“爸,生活費不夠了。你打到XXX賬戶上”,爸爸看見頭像和昵稱以為是親生的就轉賬了。如此,行騙成功。

GET方式造成的CSRF攻擊

長輩們說得對,鏈接確實不能隨便點。我用簡單的代碼舉個例子。

信任的網站test.com

假設我們有一個銀行賬戶,其中有一個登錄頁面login.php和付款頁面paybill.php,這些頁面都屬于我們信任的網站test.com(test.com網站是虛擬的)。

在login.php中設置cookie進行登錄:

<?php
    setcookie('uid', 1, time()+86400);
    echo "your uid is {$_COOKIE['uid']}";

在paybill.php通過身份驗證后進行扣款,但是必須輸入收款人和扣款金額:

<?php
   //身份驗證
    if (!isset($_COOKIE['uid']) || $_COOKIE['uid']< 0) {  
        die('login error!');
    }
   //金額獲取   
    if (!isset($_GET['money'])) {
        die('no money');
    }   
   //收款人獲取
    if (!isset($_GET['to_who'])) {
        die('nobody');
    }   
    $uid = $_COOKIE['uid']; 
    $money = $_GET['money'];
    $to_who = $_GET['to_who'];
   //此處應該還有相關DB操作,省去一萬字
    echo "transfer {$money} yuan to {$to_who}!";

刷新一下login.php,進行登錄(實際的用戶登錄更為復雜,這里簡化了)

登錄信任的網站test.com

在瀏覽器訪問paybill.php頁面,轉錢1000元給媽媽。
http://test.com/csrf/paybill.php?money=1000&to_who=mama

轉賬1000元給媽媽

黑客網站

這時候黑客發現test.com網站沒有做任何防御措施,他立刻在自己的網站B上偽造了一個頁面,頁面上有這么一個鏈接,他的收款人to_who變成了hacker。

<html>
    <head>
        <meta charset="utf-8">
    </head>
    <body>
        <a  taget="_blank">震驚!!史上尺度最大的照片!!<a/>
    </body> 
</html>

假設因為好奇心點擊了該連接:


偽造的鏈接

這樣就轉了1000元給了hacker


為什么會出現這種情況,我們在別的網站點擊鏈接居然能扣自己賬戶的錢?點擊鏈接前,我們已經登錄了信任網站test.com,而這個test.com/csrf/paybill.php?money=1000&to_who=hacker這個連接是我們自己發送的,test.com會識別當前已經登錄,然后轉賬,test.com網站無法判斷到底是誰讓我們點擊的。

從上面這個實例可知,完成CSRF攻擊流程:
1、用戶登錄了信任的網站A,并且保存登錄狀態。
2、黑客找出網站A沒有防御的鏈接,通過社會工程學偽裝,誘導點擊。
3、只要登錄狀態保持,用戶主動訪問目標鏈接,則攻擊成功。

有人說那每次訪問其他網站,把之前的網站都注銷。是的,這個辦法可以,但這么做這現實嗎?我們需要注銷許多常用的網站,下次登錄又要輸入用戶名和密碼,極其反人類。這肯定不是最佳辦法,防御措施應該讓程序員考慮,用戶別亂點鏈接是最重要的。

CSRF的攻擊渠道不一定來自其他網站,也可以是廣告郵件、QQ空間、微信、facebook等社交媒體或軟件。試想一下,如果你的女朋友知道這個鏈接,她在QQ上發給你:

http://test.com/csrf/paybill.php?money=1000&to_who=girlfriend你點擊后,那就轉了1000元給女朋友,假設她將money改成10w,后果真的不敢想象,你居然存了這么私房錢,跪搓衣板吧,錢也都到了你女朋友賬戶上。

好了,小白用戶看見這里可以關閉,別亂點鏈接就對,該給女朋友的錢的還是一分不能少。

POST也能造成CSRF攻擊

上面的CSRF可以說相當危險,更新資源的操作不應該使用GET方式,GET方式只應該用于讀操作。更新操作一定要使用POST方式,特別涉及到錢的問題。

然而POST方式可以解決大部分的CSRF問題,還有剩下少部分的聰明的黑客,一樣能夠模擬POST請求,偽造身份進行攻擊。

假設paybill.php 我們修改為POST取:

<?php
    if (!isset($_COOKIE['uid'])) {
        die('login error!');
    }   
    if (!isset($_POST['money'])) {
        die('no money');
    }   
    if (!isset($_POST['to_who'])) {
        die('nobody');
    }   
    $uid = $_COOKIE['uid']; 
    $money = $_POST['money'];
    $to_who = $_POST['to_who'];
    if ($uid > 0) {
        echo "transfer {$money} yuan to {$to_who}!";
    }   

訪問http://test.com/csrf/paybill.php?money=1000&to_who=girlfriend,提示沒有money

但是道高一尺魔高一丈,聰明的黑客也改進了代碼,使用POST提交,并且money改為2000:

<html>
    <head>
        <meta charset="utf-8">
    </head>
    <body>
        <form action="http://test.com/csrf/paybill.php" method="post">
            <input type="hidden" name="money" value="2000">
            <input type="hidden" name="to_who" value="girlfriend">
            <input type="submit" value="點擊中大獎">
        </form>
    </body> 
</html>

點擊中大獎,如下(又轉了2000元給女朋友):

轉賬2000給女朋友

抓包結果如下,可以看到Cookie: uid=1和money=2000&to_who=girlfriend都已發送過去。

POST http://test.com/csrf/paybill.php HTTP/1.1
Host: test.com
Referer: http://hack.com/hack/welcome.php
Cookie: uid=1

money=2000&to_who=girlfriend

如此一來,不管哪種訪問方式都可能受到攻擊。所以,這并不是GET和POST誰更安全的問題,POST只是提高了攻擊門檻和成本(其實也就多幾行html和js)。

劃重點,那么CSRF能夠攻擊的根本原因是:服務器無法識別你的來源是否可靠。

防御CSRF的思想

那么防御的方法有很多:
1、比如加上驗證碼。但這么做很繁瑣,并且影響用戶體驗。
2、比如轉賬需要二次密碼驗證,現在很多銀行就這么搞的。
3、確認來源是否可靠(推薦)

不管防御方法1還是2,都是讓用戶自身再次確認授權。
這種安全防范的事兒,更應該由程序驗證。

根據驗證是否可靠性思路,可以有以下幾種方法:

  • 驗證HTTP Referer 字段

HTTP協議里面定義了一個訪問來源的字段,這個字段叫Referer。黑客偽造的鏈接或表單是在其他網站上,所以我們可以判斷Referer是否為自身網站,如果是,則允許訪問,如果不是,則拒絕訪問。

從我們的網站訪問paybill.php,抓包發現Referer是不存在的

"HTTP_REFERER"=>""

從黑客的網站訪問paybill.php,抓包發現Referer來自黑客網站

["HTTP_REFERER"]=> string(35) "http://hack.com/welcome.php"

然后代碼里判斷:

if  (HTTP_REFERER!="")  {
    die('可能是CSRF攻擊,拒絕訪問');
} else {
    die('允許訪問');
}

所以我們只需要攔截Referer就可以判斷是否為攻擊。
但是這種方法是有缺陷的,上面實驗嘗試過,如果對方在QQ上發送給你一個鏈接呢?點擊的時候屬于主動點擊,此時一樣沒有Referer。程序會把它歸屬為安全請求,那么就被繞過了。并且如果某些低版本的瀏覽器存在漏洞(比如IE6),Referer很有可能被篡改,所以這個方法并非十全十美。

  • 服務端驗證請求的token一致性

csrf攻擊的核心原理就是利用用戶驗證信息儲存cookie中,發送請求,使得服務器無法判斷真偽,而token之所以能夠攔截,就是因為它是csrf攻擊過程中幾乎不可能偽造的東西。

實現原理:在服務端生成一個隨機的token,加入到HTTP請求參數中,服務器攔截請求,查看發送的token和服務端的是否一致,若一致,則允許請求;若不一致,則拒絕請求。

新增form.php表單頁面,將token存入session(不要存在cookie中,你懂的):

<?php
session_start();
$csrf_token = md5(openssl_random_pseudo_bytes(32));//生成隨機token
$_SESSION['token']= $csrf_token;
?>
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>csrf</title>
</head>
<body>
    <h1>轉賬</h1>
    <form action="paybill.php" method="POST">
        money:<input type="text" name="money">
        to_who:<input type="text" name="to_who">
        <input type="hidden" name="token" value="<?php echo $csrf_token ?>">
        <input type="submit" value="ok">
    </form>
</body>
</html>

在paybill.php獲取token,與session中存儲的token判斷是否一致:

<?php
    session_start();
    if (! isset($_COOKIE['uid'])) {
        die('login error!');
    }   
    if (! isset($_POST['money'])) {
        die('no money');
    }   
    if (! isset($_POST['to_who'])) {
        die('nobody');
    }   
    if (! isset($_POST['token']) || $_POST['token'] !=  $_SESSION['token']) {
        die('forbidden');
    }   
    $uid = $_COOKIE['uid']; 
    $money = $_POST['money'];
    $to_who = $_POST['to_who'];

    if ($uid > 0) {
        echo "transfer {$money} yuan to {$to_who}!";
    }   

查看頁面form.php:

請求成功:


每次訪問表單頁面,都應該生成一個token:

<input type="hidden" name="token" value="a88f67a7effa917450cff12e179df35d">

我們再嘗試從黑客網站進行訪問,顯示"forbidden",證明在token驗證時被攔截:

這樣子就已經有效防御csrf攻擊。該方法可以用于a鏈接和表單等請求,屬于同一個原理。

注意:網上很多文章并沒有生成唯一的或者隨機性較大的token,都是同一個token,這是有問題的,如果黑客看到該token,一樣可以偽造請求,進行攻擊。

Ajax防御CSRF

實際上A jax防御的思想也可以利用上面的token驗證方式。

在IBM上看過一篇文章說Ajax防御時,在 HTTP 頭中自定義屬性并驗證token。

它是這么說的:

把 token 以參數的形式置于 HTTP 請求之中,而是把它放到 HTTP 頭中自定義的屬性里。通過 XMLHttpRequest 這個類,可以一次性給所有該類請求加上 csrftoken 這個 HTTP 頭屬性,并把 token 值放入其中。這樣解決了上種方法在請求中加入 token 的不便,同時,通過 XMLHttpRequest 請求的地址不會被記錄到瀏覽器的地址欄,也不用擔心 token 會透過 Referer 泄露到其他網站中去。

個人覺得不需要如此麻煩,易用性也不太好,直接對Aajx進行一次封裝,加入一個open_token的選項,true就把token也發送過去,否則不進行驗證,原理和上面是一樣的。

最好將token賦值給js的一個全局變量,整個網站都可以使用。

總結

OWASP 2017年的十大安全威脅已經公布了,我們可以看看2013年和2017年CSRF穩穩排在第八位。


OWASP

總之,CSRF是一種常見的Web安全威脅,它攻擊特點是利用用戶身份信息偽裝,發送請求,造成危害。這種攻擊成本極低,但網站和用戶不注意,很容易受到傷害。當然,更令人欣賞的是黑客利用社會工程學欺騙大眾,這才是最重要的。

如果大家對社會工程學感興趣,推薦一部電影——《我是誰:沒有絕對安全的系統》,非常精彩。

沒有絕對安全的系統

互聯網安全你攻我防,你槍我盾,沒有永遠靈驗的方法,只有學會攻擊,才能抵御攻擊。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容