XSS攻擊
XSS(Cross-Site Scripting,跨站腳本)攻擊歷史悠久,是危害范圍非常廣的攻擊方式。
Cross-Site Stripting的縮寫本應該是CSS,但是為了避免和Cascading style sheets(層疊樣式表)的縮寫混淆,所以將Cross(即交叉)使用交叉形狀的X表示。
攻擊原理
XSS是注入攻擊的一種,攻擊者通過將代碼注入被攻擊者的網站中,用戶一旦訪問訪問網頁便會執行被注入的惡意腳本。XSS攻擊主要分為反射性XSS攻擊(Reflected XSS attack)和存儲型XSS攻擊(Stored XSS Attack)兩類。
攻擊示例
反射型XSS(非持久型)
反射性XSS有稱為非持久型XSS(Non-Persistent XSS)。當某個站點存在XSS漏洞時,這種攻擊會通過URL注入攻擊腳本,只有當用戶訪問這個URL是才會執行攻擊腳本。
例如:
@app.route('/hello1')
def hello1():
name = request.args.get('name')
response = '<h1>Hello, %s!</h1>' %name
這個視圖函數接收用戶通過查詢字符串(URL后邊的參數)傳入的數據,未做任何處理就把它直接插入到返回的響應主題中,返回給客戶端。如果某個用戶輸入了一段JavaScript代碼作為查詢參數name的值:
http://127.0.0.1:5000/hello4?name=<script>alert('Bingo!')</script>
那么客戶端收到的響應將變成下面的代碼:
<h1>Hello,<script>alert(‘Gingo!’)</script>!</h1>
當客戶端收到響應后,瀏覽器解析這行代碼就會打開一個彈窗,下圖是firefox瀏覽器(55.0版本)上的響應。
在chrome 72.0版本上的響應:
下面在IE11上的響應。
可見針對跨站攻擊,某些瀏覽器已經做了安全防范。
不要覺得一個小彈窗不會造成什么危害,能夠執行alert()函數就意味着通過這種方式可以執行任務JS代碼。即攻擊者通過javaScript幾乎可以做任何事情:竊取用戶的cookie和其他敏感數據,重定向到釣魚網站,發送其他請求,執行注入轉賬、發布廣告信息、在社交網站關注某個用戶等。
即使不插入js代碼,通過HTML和XSS注入也可以影響頁面正常的輸出,篡改頁面樣式,插入圖片等。
如果網站A存在XSS漏洞,攻擊者將包含攻擊代碼的鏈接發送給網站A的用戶Foo,當Foo訪問這個鏈接時就會執行攻擊代碼,從而受到攻擊。
存儲型XSS(持久型)
存儲型XSS也被稱為持久型XSS(persistent XSS),這種類型的XSS攻擊更常見,危害也更大。它和反射型XSS類似,不過會把攻擊代碼存儲到數據庫中,任何用戶訪問包含攻擊代碼的頁面都會被殃及。
比如,某個網站通過表單接收用戶的留言,如果服務器接收數據后未經處理就存儲到數據庫中,那么用戶可以在留言中出入任意javaScript代碼。比如攻擊者在留言中加入一行重定向代碼:
<script>window.location.href=”http://attacker.com”;<script>
其他任意用戶一旦訪問關於這條留言的頁面,包含這條留言的數據就會被瀏覽器解析,就會執行其中的javaScript腳本。那么這個用戶所在頁面就會被重定向到攻擊者寫入的站點。
防范措施
HTML轉義
防范XSS攻擊最主要的方法是對用戶輸入的內容進行HTML轉義,轉義后可以確保用戶輸入的內容在瀏覽器中作為文本顯示,而不是作為代碼解析。
這里的轉義和python的概念相同,即消除代碼執行時的歧義,也就是把變量標記的內容標記文本,而不是HTML代碼。具體來說,這會把變量中與HTML相關的符號轉換為安全字符,以避免變量中包含影響頁面輸出的HTML標簽或惡意的javaScript代碼。
比如,在flask中我們可以使用JinJa2提供的escape()函數對用戶傳入的數據進行轉義:
from jinja2 import escape
@app.route('/hello')
def hello():
name = request.args.get('name')
response = '<h1>Hello, %s!</h1>' %escape(name)
return response
在Jinja2中,HTML轉義相關的功能通過flask的依賴包MarkupSafe實現。
調用escape()並傳入用戶輸入的數據,可以獲得轉義后的內容,前面的示例中,用戶輸入的javaScript代碼將被轉義為:
<script>alert('Bingo!')</script>
轉義后,文本中的特殊字符(比如“>”和“<”)都將被轉義為HTML實體(character entity),這行文本最終在瀏覽器中會被顯示文文本形式的Hello, <script>alert('Bingo!')</script>!
如圖:
在python中,如果你想在單引號標記的字符串中顯示一個單引號,那么你需要在單引號前添加一個反斜線來轉義它,也就是把它標記為普通文本,而不是作為特殊字符解釋。在HTML中,也存在許多保留的特殊字符,比如大於小於號。如果你想以文本顯示這些字符,也需要對其進行轉義,即使用HTML字符實體表示這些字符。HTML實體就是一些用來表示保留符號的特殊文本,比如<表示小於號,"表示雙引號。
一般我們不會再視圖函數中直接構造返回的HTML響應,而是會使用Jinja2來渲染包含變量的模板,
驗證用戶輸入
XSS攻擊可以在任何用戶可定制內容的地方進行,例如圖片引用、自定義鏈接。僅僅轉義HTML中的特殊字符並不能完全規避XSS攻擊,因為在某些HTML屬性中,使用普通的字符也可以插入javaScript代碼。除了轉義用戶輸入外,我們還需要對用戶的輸入數據進行類型驗證。在所有接收到用戶輸入的地方做好驗證工作。
以某個程序的用戶資料頁面為例,我們來演示一下轉義無法完全避免的 XSS攻擊。程序允許用戶輸入個人資料中的個人網站地址,通過下面的方式顯示在資料頁面中:
<a href=”{{url}}”>Website</a>
其中{{url}}部分表示會被替換為用戶輸入的url變量值。如果不對URL進行驗證,那么用戶就可以寫入javaScript代碼,比如”javascript:alert(‘Bingo!’);”。因為這個值並不包含會被轉義的<和>。最終頁面上的連接代碼會變為:
<a href=”javascript:alert(‘Bingo!’);”>Website</a>
當用戶單擊這個鏈接時,瀏覽器就會執行被href屬性中設置的攻擊代碼。
另外,程序還允許用戶設置頭像圖片的URL。這個圖片通過下面的方式顯示:
<img src=”{{url}}”>
類似的,{{url}}部分表示會被替換為用戶輸入的url變量值。如果不對輸入的URL進行驗證,那么用戶可以將url設為”xxx”onerror=”alert(‘Bingo!’)”,最終的<img>標簽就會變為:
<img src=”xxx”onerror=”alert(‘Bingo!’)”>
在這里因為src中傳入了一個錯誤的URL,瀏覽器變回執行onerror屬性中設置的javaScript代碼。
如果你想允許部分HTML標簽,比如<b>和<i>,可以使用HTML過濾工具對用戶輸入的數據進行過濾,僅保留少量允許使用的HTML標簽,同時還要注意過濾HTML標簽的屬性。