有關Web 安全學習的片段記錄(不定時更新)

s1mba發表於2014-10-26

很多Web 安全漏洞的產生原因都繞不開兩條:

1.違背了“資料與程式碼分離“原則。它有兩個條件:一是使用者能夠控制資料的輸入;二是程式碼拼湊了使用者輸入的資料,把資料當作程式碼執行了。

2.違背了許可權管理的黃金法則:最小許可權原則。


一、有關html/css, js, php, cgi 的一些認識



 

selector 常見的就 type, class(.), id(#),注意的是 id 只能一個頁面一個。

e.g <div class=""  id=""></div>

在html 中引入css 一般有以下3種方式:

<p style="margin-left: 0.5in; margin-right:0.5in">   ---行內式

<style type="text/css">
div{margin: 0;padding: 0;border:1px red solid;}     ---嵌入式;集中寫在<head>和</head>之間
</style>   

<head>
<title>title of article</title>
<link rel=stylesheet href="http://www.dhtmlet.com/rainer.css" type="text/css"> ---鏈入外部樣式表檔案 (Linking to a Style Sheet)
</head>

Remember, HTML will define the content and structure of our web pages, while CSS will define the visual style and appearance of our web pages.

還有一些其他類似的檔案型別,如 shtml, phtml, jhtml,這類可以算是動態網頁,比如 shtml (ssi)可以 <!--#include ../../1.html --> 引入一個html,伺服器會將其解析並填充在返回的頁面中;phtml 即原始碼包含 <?php ?> 語句;jhtml 原始碼包含 jsp <% %> 語句。


       當我們瀏覽器訪問一個站點的靜態檔案,會把檔案內容都下載下來(一般壓縮),當然如果遇到外聯的css/js,會再發起請求得

到。如果我們右鍵檢視網頁原始碼,一片混亂沒法看,可以使用firefox + firebug,可以清晰看到html dom tree,右鍵inspect 

element 可以很快定位到tree node,由於是下載到本地,所以可以自己嘗試修改element 檢視效果,這並不影響伺服器上的原始

檔案。最後瀏覽器會開始渲染,包括執行js比如document.write() 之類,就呈現出現在我們所看到的網頁模樣,可以使用firefox F12 斷點除錯js。

所謂的 dom 樹操作就是一系列類似 getElementById 之類的函式。注意 js 是在客戶端執行的,可以動態地改變 dom 樹,通俗地說就是可以改變頁面

的html,人們從瀏覽器看見的頁面也就變化了。

html 的解析順序:html parser --> css parser -->javascript parser


CGI 的意思是啥?不是一種語言,也不是一種技術,而是一種模式。搜尋一下CGI的定義Common Gateway Interface,簡稱CGI。在物理上是一段程式,存放在伺服器上。只要是提供資料輸出的伺服器端程式都可以叫CGI,ASP/PHP/JSP這些都可以認為是,你用C/C++寫一個可以提供資料輸出的伺服器端bin檔案,也叫CGI,至於python/perl/shell 等指令碼當然也能寫cgi。


對一個 CGI 程式,做的工作其實只有:從環境變數(environment variables)和標準輸入(standard input)中讀取資料、處理資料、

向標準輸出(standard output)輸出資料。環境變數中儲存的叫 Request Meta-Variables,也就是諸如 QUERY_STRING、

PATH_INFO 之類的東西,這些是由 Web Server 通過環境變數傳遞給 CGI 程式的,CGI 程式也是從環境變數中讀取的。

標準輸入中存放的往往是使用者通過GET 或者 POST 提交的資料,這些資料也是由 Web Server 傳過來的(客戶端提交)。傳統的

get 即是以 url?key1=value1&key2=value2的 形式傳輸過去。而post 形式(http請求包體)就比較多了,可以是傳統的

key=value,也可以是json/xml 等形式,只是這些從標準輸入得到後還需要經過一個解析的過程才能得到想要的key=value 形式的呈現。

注意標準輸入的概念,如果在本地執行 php xx.php args  那些 xx.php 的標準輸入就是控制命令視窗,獲取輸入需要通過 $argv;如果是通過 uri 路徑訪問 xx.php 如 http://localhost/xx.php,那麼 xx.php 的標準輸入來自 webserver 給的資料,可以通過 php://input 獲取。


當然cgi 的body輸出也是多種形式了,可以是簡單的application/json、text/xml 形式,也可以是php echo 出一個text/plain or text/html,但要明確的是php 等指令碼務器端執行的,也就是說當客戶端訪問test.php 時,server 先執行php指令碼(php 會 讀取標準輸入,處理過程,向標準輸出輸出數據),形象地來說,就是“戳一次就動一次”,根據使用者輸入的不同而產生不同的輸出結果,即動態網頁的概念。注意,php, js css, 都可以和html 標籤寫在同個檔案中。


有時候訪問出現403 forbidden ,有種原因是 apache 設定的user,即執行httpd的user 是nobody(假設),對你想要訪問的目

錄/檔案 沒有讀或者執行的許可權,所以server 沒辦法讀取執行檔案,故 禁止訪問。還有種情況是配置檔案寫明 deny xxx,禁止某些來源ip 訪問,或者

禁止訪問某些目錄、某種字尾的檔案。


二.   各種編碼、轉義相關


從瀏覽器 url發出的請求,如果進行了 urlencode(比如chrome一般會編碼 "<>,firefox 一般會編碼 ' " `<>, 而低版本 ie 不會編碼任何字元),比

如將 " 成%22 發出去,在伺服器端的php 接收到的是原始的" 還是編碼後的%22 得看用$_GET["key"] 還是$_SERVER['QUERY_STRING'],還要看

php 腳本內有沒有做addslashes 或者 htmlspecialchars 等函式呼叫,這樣就能判斷解析指令碼 echo/print 出來的html 是怎樣的組織形式,當然客

端請求到的html 也就是這樣的形式了。那為什麼在chrome中對於< 等沒有alert 彈窗呢,只是因為某些瀏覽器有anti_xss 模組或者filter,在瀏覽

解析 html 時候 過濾掉這些危險的script 而沒有執行,比如 ie 可以關閉掉 xss 篩選器讓其彈框。

對於低版本 ie 而言,如果頁面 js 取location.href or #錨引數 or  &get引數 的值,則保持 位址列原有模樣。而高版本 ie 或者 其他瀏覽器 取到的都是

編碼後的樣子(取決於瀏覽器本身會編碼哪些字元發起請求)。這對於 domxss 來說是一個比較重要的區分點。需要注意的是 chrome:

http://www.foo.com/dom/loc.html?<script>alert(1)</script> -- 編碼

http://www.foo.com/dom/loc.html#<script>alert(1)</script> -- 不編碼


為了看引數是否Urlencode對返回結果是否有影響,可以用一些工具比如 fiddle 發出編碼和不編碼時的請求,對比觀察。

這種不編碼訪問才能觸發的xss 漏洞,最簡單的利用方式是寫一個html,裡面用 iframe src 引入完整不編碼 payload 連結,用 ie 訪問此 html。

注意如果此時彈 cookie 的話彈出的是 iframe 內 domain 域的 cookie,因為瀏覽器在請求第三方站點時也會把相關cookie傳送出去(沒有P3P 屬性

的 persistent cookie 有例外),如下:

<html lang="zh-cn"><body><iframe src="http://subao.dayuw.cn/web/index.php?c=user&a='};alert(document.cookie);aa={//"></body></html>

注意:由於同源策略的存在,本地html 是讀取不到第三方站點 cookie的,但這裡演示的是第三方站點自己存在漏洞,自己執行 js 彈cookie。


現在常見的引數格式除了最原始的 /path/aa.html?a=1&b=2;還有 restful 的 /page/1/id/2011 即 傳入的引數是 page=1&id=2011;

此外還有 rewrite,比如 /path/2015/a(a : 1~100)/b(b: 10~20) 對應後端的cgi 可能是 /path/c.cgi?p=a&q=b;一些 MVC 框架的 cgi 可能需要根

據某個引數的值選擇不同的邏輯分支,比如 a.cgi?_path=/gdata/6/data&id=5,對映到一個類;還有一種是引數直接跟在uri後面的,

http://aa.qq.com/m/index/nav/type=bus&cond=3,可以理解為 /m/{module}/{action}/{query_list},

module 和 action 可以不斷對請求進行路由,逐級分發到不同的模組,最終query_list是使用常規的webserver解析方式。


htmlspecialchars 會把 < 編碼成 &lt; 還有 >,",' ,& 等

addslashes() 函式在指定的預定義字元前新增反斜槓。這些預定義字元是:

單引號 (')

雙引號 (")

反斜槓 (\)

NULL


這樣就無法從url 傳遞帶引號的引數來閉合引號來達到xss的目的,但是在charset=gbk 的情況下,如果引數含大於127的值如%ae,後面再跟引號,

雖然引號變成\', 因為 %5c 在 gbk 低位元組範圍內,%ae\ 在gbk 看來也許是一個字元,當然我們看起來好像是一個亂碼,這樣也會造成引號可以

合,sql 注入也存在這樣的字符集解析問題。設定了某種字符集,瀏覽器會按這種編碼去解析html 來展示給使用者。(memchr) 

比如<script> var test="123%ae\";alert(1);//"</script>


雖然大部分瀏覽器不會解析 Content-type: application/plain 響應頭下的 body,但如 safari 以及某些版本的ie 都可能不按套路行事。在一些不需要html 解釋的頁面,可以通過設定Content-type:application/json來避免xss。還有些站點直接返回 如 baiduApp/json,瀏覽器識別不了就會直接下載成檔案到本地。ie 瀏覽器在確定檔案型別時不完全依賴Content-type,比如 foo.cgi?id=123&a.html,ie 可能會認為這是個html 檔案或忽略 Content-type。


反斜槓 \ 在script域內會起轉義作用,而在html 標籤內就是表示的字元含義,從下面alert()出來的字元可以得知。

<script> var test="a\""; alert(test); </script>
<script> var test='a\''; alert(test); </script>

<script> var test='a';   alert(test); </script>
<input type="text" value="a\" onmousemove=alert(/xss/) " />


三.  登入跳轉、登入驗證


一般網站登入前的驗證可能是這樣實現的:

<form action="processs.php", id="login" method="post" onsubmit="return validate();">

在使用者填完資訊後會先呼叫validate() 函式進行驗證,如果返回true 才會真正提交表單。

在validate() 裡類似 if(document.forms.login.agreement.value != checked) { return false;}  

// document.getElementById("login").agreement.value id 必須全域性唯一

在不想過載頁面,也就是不提交,可以 onsubmit="quote(); return false;" 在quote()裡面可以 xhr= new XMLHttpRequest(); 

即ajax的方式來做一些操作。

現在很多提交的實現不再使用 form 表單,比如只要監聽某 button 標籤事件,點選觸發時執行事件,裡面用 ajax 方式提交請求。

 JavaScript Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$(".submit").on('click'function ()
{
    var msg = "";

    if ($(".user_name").val().trim() && $(".mail").val().trim() && $(".phone").val().trim())
    {
        submitUserInfo();
    }
    else
    {
    }
});

一般的網站登入跳轉實現方式之一是:在login.php 對錶單post 過來的user&pwd&email 驗證(見下面),如果對則設定一個鍵值如 $_SESSION["auth"]=true,設定response 的Location Head : home.php,本程式exit。瀏覽器接收到rsp,看到Location 頭部,於是跳轉請求至home.php。home.php 可以對$_SESSION["auth"] 繼續判斷一次,若true 則顯示登入後的頁面。當然這一切的前提是login.php開啟了session_start(),這樣第二次訪問home.php 也會帶上Cookie:PHPSESSID=xxx ,這樣server 通過 $_COOKIE 獲取sessionId就知道是同個用戶的請求,通過sessionId 就可以知道 $_SESSION 結構體中原本存放的資料,比如auth=True 之類。

superglobals : $_COOKIE  $_ENV  $_FILES  $_GET  $_POST $_REQUEST $_SERVER $_SESSION 

如果可以通過get 引數控制 Location 地址跳轉,需要做好驗證,不然可能讓壞人利用造成釣魚。

還有如 dom 跳轉,即 瀏覽器在解析 js 時進行跳轉。


實際上帶登入態的漏洞掃描也是帶上cookie 實現的,需要注意cookie失效的問題。一般server對每個網頁請求會做登入驗證,常用兩種方式

1). 從cookie頭中獲取sessionId,進而從server 端儲存的Session資訊中獲取相關驗證資訊,如user&pwd&email之類,與post過來的資訊進行比對(可能需要根據post資料欄位查資料庫),驗證通過則顯示請求的網頁,否則跳轉到登入頁面。

Session 的優點是簡單易用,可以直接從Session中取出使用者登入資訊。Session 的缺點是伺服器需要在記憶體中維護一個對映表來儲存使用者登入資訊,如果有兩臺以上伺服器就需要對Session 做叢集,因此使用Session的Web App 很難擴充套件。


比如 web.py 框架中 session.py 有:

'cookie_name': 'webpy_session_id' // 定義

self.session_id = web.cookies().get(cookie_name) // server 獲取sessionId

self._setcookie(self.session_id) // server 設定Client 的cookie,Set-Cookie 頭,即呼叫下面的函式

web.setcookie(cookie_name, session_id, expires=expires, domain=cookie_domain, httponly=httponly, secure=secure, path=cookie_path)

self.store[self.session_id] = dict(self._data) // server 端儲存session_id 相關的使用者資料


2). 先直接根據post 過來的資料欄位查資料庫進行比對,但此時還需要驗證cookie 不是偽造的,實現防偽造cookie的關鍵是通過一個向演算法

(例如MD5),舉例如下:

當使用者輸入了正確的口令登入成功後,伺服器可以根據post資料從資料庫取到使用者的id,並按照如下方式計算出一個字串:

"使用者id" - "過期時間" - MD5("使用者id" + "使用者口令" + "過期時間" + "SecretKey")

並通過set-cookie 設定瀏覽器的cookie。

當瀏覽器傳送cookie 到伺服器端後,伺服器可以拿到的資訊包括:使用者id、過期時間、MD5值

如果未到過期時間,伺服器就根據使用者id 查詢使用者口令,並計算:
MD5("使用者id" + "使用者口令" + "過期時間" + "SecretKey")

並與瀏覽器cookie中的MD5 進行比較,如果相等則說明使用者已登入,否則cookie就是偽造的。
這個演算法的關鍵在於MD5是一種單向演算法,即可以通過原始字串計算出MD5,但無法通過MD5反推出原始字串。

但資料量巨大的彩虹表維護的 字串與 md5的對映也許可以反推出原始字串,對此可以在 md5 前再加 salt 一下。


四、session, token, cookie的區別與聯絡

1. 由於HTTP協議是無狀態的協議,所以服務端需要記錄使用者的狀態時,就需要用某種機制來識具體的使用者,這個機制就是Session.典型的場景比如購物車,當你點選下單按鈕時,由於HTTP協議無狀態,所以並不知道是哪個使用者操作的,所以服務端要為特定的使用者建立了特定的Session,用用於標識這個使用者,並且跟蹤使用者,這樣才知道購物車裡面有幾本書。這個Session是儲存在服務端的,有一個唯一標識sessionId。在服務端儲存Session的方法很多,記憶體、資料庫、檔案都有。叢集的時候也要考慮Session的轉移,在大型的網站,一般會有專門的Session伺服器叢集,用來儲存使用者會話,這個時候 Session 資訊都是放在記憶體的,使用一些快取服務比如Memcached之類的來放 Session。即使瀏覽器的 session cookie 在其關閉時被清除,但此時伺服器卻是不知道的,故伺服器可能會設定一個過期時間,當距離客戶端上一次使用session的時間超過這個失效時間時,伺服器就可以認為客戶端已經停止了活動,才會把這個session刪除以節省儲存空間。

2. Session cookie
A session cookie, also known as an in-memory cookie or transient cookie, exists only in temporary memory while the user navigates the website.When an expiry date or validity interval is not set at cookie creation time, a session cookie is created. Web browsers normally delete session cookies when the user closes the browser.
思考一下服務端如何識別特定的客戶?這個時候Cookie就登場了。每次HTTP請求的時候,客戶端都會傳送相應的Cookie資訊(如 Cookie: PHPSESSID=xxxxx;)到服務端。實際上大多數的應用都是用 Cookie 來實現Session跟蹤的,第一次建立Session的時候,服務端會在HTTP協議中告訴客戶端,需要在 Cookie 裡面記錄一個Session ID(Examples of the names that some programming languages use when naming their cookie include JSESSIONID (JEE), PHPSESSID (PHP), and ASPSESSIONID (Microsoft ASP).),以後每次請求把這個會話ID傳送到伺服器,我就知道你是誰了。有人問,如果客戶端的瀏覽器禁用了 Cookie 怎麼辦?一般這種情況下,會使用一種叫做URL重寫的技術來進行會話跟蹤,即每次HTTP互動,URL後面都會被附加上一個諸如 sid=xxxxx 這樣的引數,服務端據此來識別使用者。sid 出現在 url 中的情形有個安全隱患是,假設一個站點被引入了一個外部圖片,開啟這個站點會發起圖片的get 請求,而 referer 就是受害站點的 url,由此洩露了 sid。


會話cookie: 是一種臨時的cookie,它記錄了使用者訪問站點時的設定和偏好,關閉瀏覽器,會話cookie就被刪除了。

3.Persistent cookie
A persistent cookie outlasts user sessions. If a persistent cookie has its Max-Age set to one year (for example), then, during that year, the initial value set in that cookie would be sent back to the server every time the user visited the server. This could be used to record a vital piece of information such as how the user initially came to this website. For this reason, persistent cookies are also called tracking cookies.
 

持久型cookie 一般用來儲存一些少量資訊,如當初使用者是從哪個url 跳轉來的。

持久cookie: 儲存在硬碟上,(不管瀏覽器退出,或者電腦重啟,持久cookie都存在), 持久cookie有過期時間。


所以,總結一下:

Session是在服務端儲存的一個資料結構,用來跟蹤使用者的狀態,這個資料可以儲存在叢集、資料庫、檔案中;

Cookie是客戶端儲存使用者資訊的一種機制,用來記錄使用者的一些資訊,也是實現Session的一種方式。

client :  

Cookie: name=value; name2=value2               

server:

Set-Cookie: LSID=DQAAAK…Eaem_vYg; Path=/accounts; Expires=Wed, 13 Jan 2021 22:23:01 GMT; Secure; HttpOnly

Set-Cookie: HSID=AYQEVn….DKrdst; Domain=.foo.com; Path=/; Expires=Wed, 13 Jan 2021 22:23:01 GMT; HttpOnly

Set-Cookie: SSID=Ap4P….GTEq; Domain=foo.com; Path=/; Expires=Wed, 13 Jan 2021 22:23:01 GMT; Secure; HttpOnly

cookie 無法跨瀏覽器存在。

Domain 和 Path 決定瀏覽器在訪問此站點某目錄下的網頁時cookie 才會被髮送出去(domain 可以設定為父域,但不可設定為子域和外域)。

Expires 確定cookie的過期時間,沒有過期時間則為session cookie,有則是persistent cookie,過期時間是過去時間點則表示刪除cookie。Secure 表

示只有通過https 連線才會傳送cookie。HttpOnly 表示只有通過http 訪問才會傳送cookie,比如在客戶端執行js: document.cookie 是獲取不到cookie

的,如果只設定了 Secure 而未設定 httponly,那麼還是可以通過 客戶端 js 獲取到 cookie。

需要注意的是設定 path 不能防止重要的cookie 被盜取,假設在同域下的a路徑存在xss漏洞可以執行js,想盜取b 路徑的cookie,只需在 b 路徑用 

iframe 方式載入 a 路徑,獲取 a 路徑的cookie(iframe 載入的是同域頁面,故 b路徑的js 可以訪問 iframe document 的屬性),如下所示:


桌面應用程式也通過HTTP協議跟Web伺服器互動, 桌面應用程式一般不會使用cookie, 而是把 "使用者名稱+冒號+密碼"用BASE64 編碼的字串放在http request 中的header Authorization 中傳送給服務端, 這種方式叫HTTP基本認證(Basic Authentication)

在chrome瀏覽器裡可以用 http://username:password@url這種方式直接略過這個基本認證

這裡貼一下 401 與 403 的不同之處:

A clear explanation from Daniel Irvine:
401 Unauthorized, the HTTP status code for authentication errors. And that’s just it: it’s for authentication, not authorization. Receiving a 401 response is the server telling you, “you aren’t authenticated–either not authenticated at all or authenticated incorrectly–but please reauthenticate and try again.” To help you out, it will always include a WWW-Authenticate header that describes how to authenticate.
This is a response generally returned by your web server, not your web application.
It’s also something very temporary; the server is asking you to try again.
So, for authorization I use the 403 Forbidden response. It’s permanent, it’s tied to my application logic, and it’s a more concrete response than a 401.
Receiving a 403 response is the server telling you, “I’m sorry. I know who you are–I believe who you say you are–but you just don’t have permission to access this resource. Maybe if you ask the system administrator nicely, you’ll get permission. But please don’t bother me again until your predicament changes.”
In summary, a 401 Unauthorized response should be used for missing or bad authentication, and a 403 Forbidden response should be used afterwards, when the user is authenticated but isn’t authorized to perform the requested operation on the given resource.

Authentication是認證,Authorization 是授權。認證的目的是為了認出使用者是誰,而授權的目的是為了決定使用者能夠做什麼。

401 的限制一般可以在某個目錄下的 .htaccess 檔案(apache)寫上
AuthName "frank share web" 
AuthType Basic 
AuthUserFile /var/www/test/.htpasswd
require valid-user
且 httpd.conf 中對於此目錄中的設定中 加上  AllowOverride AuthConfig #表示進行身份驗證

安全業界對CSRF攻擊防禦的共識是使用Form Token(也有叫做Anti-CSRF Token的)。所謂Form Token即在輸出表單的地方增加一個隱藏域,值是一個隨機數,提交請求時會帶上這個數,Web應用程式在後臺校驗,如果是第三方站點的話是無法獲知這個數的。以著名開源PHP個人部落格程式WordPress為例,它的所有請求都有一個token欄位_wpnonce 。


五、瀏覽器特性和安全策略

1.同源策略
同源策略規定:不同域的客戶端指令碼在沒明確授權的情況下,不能讀寫對方的資源。
URL由協議、域名、埠和路徑組成,如果兩個URL的協議、域名和埠相同,則表示他們同源。
HTTP響應頭返回 Access-Control-Allow-Origin:http://www.test.com 表示授權

2.  沙盒框架(Sandboxed frame)
是對常規<iframe>表現行為的擴充套件,它能讓頂級頁面對其嵌入的子頁面及這些子頁面的子資源設定一些額外的限制
通過設定<iframe>的引數實現限制。
最原始的一些限制如下圖:


3. Flash安全沙箱
分為本地沙箱與遠端沙箱
類似於同源策略,在同一域內的資源會被放到一個安全組下,稱為安全沙箱
Web站點通過crossdomain.xml檔案配置可以提供允許的域跨域訪問本域上內容的許可權(放置於站點根目錄)

4. Cookie 的安全策略
如 四 所述。

5.  內容安全策略(Content Security Policy,CSP)
通過編碼在HTTP響應頭中的指令來實施策略
http 響應的擴充套件頭部都以 X- 打頭,用於區分標準的頭部欄位,比如
X-Frame-Options 用於防禦 ClickJacking
X-XSS-Protection  用於是否開啟xss filter
X-Content-Security-Policy(即CSP 策略),例如
X-Content-Security-Policy : default-src 'unsafe-inline' 'self'   即不允許任何外部的資源載入,且允許內嵌指令碼執行。



相關文章