天天看點

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

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

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

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

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

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

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 會把 < 編碼成 < 還有 >,",' ,& 等

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 方式送出請求。

一般的網站登入跳轉實作方式之一是:在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 的屬性),如下所示:

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

桌面應用程式也通過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>的參數實作限制。

最原始的一些限制如下圖:

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

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'   即不允許任何外部的資源加載,且允許内嵌腳本執行。