在過去的幾年里,internet恐怖故事幾乎持續出現在新聞中,我們看到病毒以驚人的速度傳播,大量危及安全的計算機被當
作武器,從未結束的武裝與垃圾郵件作戰,以及許多從危及安全的網站鑒別盜竊的報導
作為好的web開發人員,打擊這些黑暗勢力是我們的責任,每個web開發人員需要把安全作為基本的web編程方面,不幸的是,
安全問題看起來很棘手--攻擊者只需要找到一個單獨的弱點,但是防御者必須保護每個單獨的方面
Django嘗試減輕這個難點,它設計來自動為你防護許多常見的新手(甚至有經驗的)web開發人員容易犯的安全錯誤,理解這些
問題是什么仍然很重要,Django怎樣保護你,以及--更重要的--你讓你的代碼更安全的步驟
但是,首先,一個重要的不承諾:我們決不是這個領域的專家,所以我們不會嘗試全面的解釋每個弱點,相反,我們將給出適
合Django的安全問題的一個簡短的大綱
web安全的主題
如果你只從本章學習到一件事情,則讓它為這個:
從不--在任何情況下--信任瀏覽器的數據
你從來不知道在HTTP連接的另一端是誰,它可能是一個你的用戶,但是它也可能很容易為一個尋找漏洞的攻擊者或小腳本
來自于瀏覽器的任何類型的數據需要被當作是偏執狂的一副健康良藥,它保護"in band"的數據--即從web表單提交的數據--
和"out of band"--即HTTP頭部,cookies,以及其他請求信息,欺騙通常瀏覽器自動添加的請求元數據是很微不足道的
本章討論的每個弱點都直接來自于信任來自于線上的數據然后在使用它之前清除數據失敗,你應該讓不斷的問"數據來自于何
處?"成為一般實踐
SQL注射
SQL注射是一個常見的開拓,攻擊者改變Web頁面參數(例如GET/POST數據或URLs)來插入天真的Web程序直接在它的數據庫執行
的任意的SQL片段,這可能是在瘋狂世界里最危險的--不幸的是它是最常見的--弱點
這個弱點最容易出現在當用戶手動輸入結構化SQL時,例如,設想寫一個方法來從一個聯系搜索頁面收集聯系信息列表,為了
防止在你的系統里讀取每個單獨的郵件時遇到垃圾郵件,我們將強迫用戶在我們提供他們的email地址前輸入某人的用戶名:
- def user_contacts(request):
- user = request.GET['username']
- sql = "SELECT * FROM user_contacts WHERE username = '%s';" % username
- # execute the SQL here...
注意,這個例子中,以及下面所有類似的"不要做這個"的例子中,我們故意保留了大部分用來讓方法真正工作的代碼,我們
不會讓這些代碼在某人偶然取走它們時工作
盡管起初這看起來不危險,但它真的是這樣
首先,保護我們的整個郵件列表的嘗試將以一個聰明的結構化查詢失敗,考慮如果一個攻擊者輸入"' OR 'a'='a"到查詢框里
這種情況下,字符串插補將構建的查詢將為:
- SELECT * FROM user_contacts WHERE username = '' OR 'a' = 'a';
由于我們在該字符串里允許不安全的SQL,攻擊者添加的OR子句確保每個單獨的行都返回
盡管如此,這是最小的引起驚慌的攻擊,設想一下如果攻擊者提交"'; DELETE FROM user_contacts WHERE 'a' = 'a"將發生
什么,我們將得到這個完整的查詢:
- SELECT * FROM user_contacts WHERE username = ''; DELETE FROM user_contacts WHERE 'a' = 'a';
呀!我們的聯系列表哪里去了?
解決方案
盡管這個問題很陰險并且有時很難發現,解決方案卻很簡單:從不信任用戶提交的數據,并且當傳遞給SQL時一直escape它
Django數據庫API為你做這個,它根據你使用的數據庫服務器(例如PostgreSQL,MySQL)的引號慣例自動escape所有特殊的SQL
參數,例如,在這個API調用中:
- foo.get_list(bar__exact="' OR 1=1")
Django將相應的escape輸入,結果是像這樣的語句:
- SELECT * FROM foos WHERE bar = '\' OR 1=1'
這是完全無害的
這適合所有的Django數據庫API,帶有一些額外情況:
1,extra()方法的where參數(參考附錄XXX),該參數設計時接受原始SQL
2,使用低級數據庫API來手動進行查詢
對于其中每種情況,很容易讓你自己受保護,每種情況下,避免字符串插補有利于傳遞"綁定參數",即,這部分我們開始的
例子應該被寫成:
- from django.db import connection
- def user_contacts(request):
- user = request.GET['username']
- sql = "SELECT * FROM user_contacts WHERE username = %s;"
- cursor = connection.cursor()
- cursor.execute(sql, [user])
- # ... do something with the results
低級execute方法使用SQL字符串和%s placeholders,并且自動escape和插入作為第二個參數傳遞的列表參數,你應該一直以
這種方式構建自定義的SQL
不幸的是,你不能在SQL的每個地方使用綁定參數,它們不允許作為標識符(即表名或者列名),這樣,如果你需要,如從一個
POST變量動態構建表的列表,你將需要在你的代碼里escape該名字,Django提供了一個方法django.db.backend.quote_name
它將根據當前數據庫的引號scheme來escape標識符
跨站點腳本(XSS)
很可能最常見的web弱點,跨站點腳本,或者XSS,在渲染到HTML之前失敗于正確的escape用戶提交的內容的web程序里發現
這允許攻擊者惡意的插入任意的通常是script標簽格式的HTML
攻擊者通常使用XSS攻擊來竊取cookie和session信息,或者騙取用戶提供私有信息給錯誤的人(也叫phishing)
這種類型的攻擊可以采用一些不同的形式,并且有幾乎無限的改變方式,所以我們將只看看一個典型的例子,讓我們看看一
個非常簡單的"hello world"視圖:
- def say_hello(request):
- name = request.GET.get('name', 'world')
- return render_to_response("hello.html", {"name" : name})
這個視圖簡單的從GET參數讀取一個名字并傳遞名字給hello.html模板,我們可能像這樣為該視圖寫一個模板:
- <h1>Hello, {{ name }}!</h1>
所以如果我們訪問http://example.com/hello/name=Jacob,渲染的頁面將包含:
- <h1>Hello, Jacob!</h1>
但是等等--如果我們訪問
- http://example.com/hello/name=<i>Jacob</i>
會發生什么?
則我們會得到:
- <h1>Hello, <i>Jacob</i>!</h1>
當然,攻擊者不會使用像i標簽的東西,他可以包含整個HTML集來用任意內容截取你的頁面,這種類型的攻擊被用來欺騙用戶
輸入數據到看起來像它們的銀行網站,但是事實上是把你的帳號信息發送給攻擊者的XSS-截取表單
如果你把存儲該數據在數據庫中并且后面在你的站點上顯示則會更糟
例如,在某點上MySpace被發現對于這種類型的XSS攻擊有弱點,用戶插入javascript到他的當你訪問他的profile頁面時自動
添加他為你的朋友的profile里,幾天之內他有了幾百萬的朋友
現在,這可能聽起來良好,但是記住該攻擊者讓他的代碼--而不是MySpace的--運行在你的電腦里,這違反了對于信任MySpa
ce上面的所有代碼都是事實上由MySpace所寫的假設
MySpace非常幸運這些惡意的代碼沒有自動刪除訪問者的帳號,更改他們的密碼,用垃圾郵件淹沒站點,或者其他任何該弱點
釋放的惡夢般的情形
的情形
解決方案
解決方案非常簡單:一直escape任何可能來自于用戶的內容,如果我們像這樣簡單的重寫我們的模板:
- <h1>Hello, {{ name|escape }}!</h1>
則我們不再易受攻擊了,你應該當在你的站點上顯示用戶提交的內容時一直使用escape標簽(或者一個相似物)
為什么Django不為你做這些?
修改Django來自動escape所有顯示在模板中的變量是一個頻繁出現在Django開發人員郵件列表中的討論主題
目前為止,Django的模板避免了這種行為,因為它敏銳而不可見的更改了應該很直接的行為(顯示變量),這是個狡猾的問題
和一個很難評價的平衡,添加隱藏的行為與Django的核心理念相悖(以及Python的,對于這種問題),但是安全同等重要
然而,也存在公平的機會使得Django在未來添加某種形式的自動escape(或者幾乎自動escape)行為,它將一直比本書更新
(特別是最終樹版本)
即使Django添加了這個特性,你應該仍然一直保有考慮"該數據從哪里來?"的習慣,沒有一直100%保護你的站點免受XSS攻擊
的自動解決方案
跨站點請求偽造(CSRF)
CSRF當惡意網站欺騙用戶未知的從一個他們已經認證的站點載入一個URL時發生--這樣,就可以使用他們的認證狀態
Django由內勁攻擊來防護這種類型的攻擊,攻擊本身和那些工具在第15章進行了詳述
Session偽造/截取
這是一個特殊的攻擊,而不是對用戶的session數據的一般類型的攻擊,它可以有一些不同的形式:
1,中間人攻擊,其中攻擊者當它在有線(或者無線)網絡上游走時竊聽session數據
2,Session偽造,其中攻擊者使用偽造的session ID(可能通過中間人攻擊獲得)來假裝為另外一個用戶
這前兩種的例子是在咖啡店的攻擊者使用無線網絡來獲取一個session cookie,然后他可以使用這個cookie來模仿原始用戶
3,cookie偽造攻擊,其中攻擊者覆蓋存儲在cookie中的假定只讀的數據,第12章詳細解釋了cookies怎樣工作,其中一個突
出點是對瀏覽器和惡意用戶在你不知情的情況下更改cookies是微不足道的
網站存儲類似于IsLoggedIn=1或者甚至LoggedInAsUser=jacob的cookie有很長的歷史,開拓這種類型的攻擊者太容易了
但是對于在更微妙的級別,信任任何存儲在cookie中的東西從不是個好主意,你從不知道誰正在翻找它們
4,Session定置,其中攻擊者欺騙用戶設置或者重設他們的session ID
例如,PHP允許session標識符在URL中傳遞(即http://example.com/?PHPSESSID=fa90197ca25f6ab40bb1374c510d7a32),欺騙
用戶點擊一個硬編碼了session ID的鏈接的攻擊者將導致用戶采用該session
這被用在phishing攻擊中來欺騙用戶輸入個人信息到攻擊者所有的帳號,它可以稍后登錄該帳號并得到那些數據
5,Session下毒,其中攻擊者注射潛在危險的數據到用戶的session中--通常通過一個用戶提交來設置session數據的web表單
一個規范的例子是站點在cookie中存儲簡單的用戶喜好(例如頁面背景顏色),攻擊者可以欺騙用戶點擊一個連接來提交一個
事實上包含XSS攻擊的"顏色",如果這個顏色沒有escape(參考上面的)用戶可能再次注射惡毒的代碼到用戶環境
解決方案
有一些可以防止遭受這些攻擊的一般原則:
1,從不允許session信息包含在URL中
Django的session框架(參考第12章)簡單的不允許session包含在URL中
2,不要在cookies中直接存儲數據,相反,存儲映射到存儲在后端的session數據的session ID
如果你使用Django內建的session框架(即request.session),它可以自動為你處理,session框架使用的唯一的cookie是一個
單獨的session ID,所有的session數據存儲在數據庫中
3,如果你在模板中顯示session數據記得escape它,參考上面的XSS部分,并且記得它適合任何用戶創建的內容,你應該把
session信息當作用戶創建的
4,預防任何可能的攻擊者竊取session IDs
盡管幾乎不可能檢測到某人在竊取session ID,Django確實有內建的強力的session攻擊的防護,Session IDs存儲為哈希(而
不是連續的數字),這防止了強力攻擊,并且如果用戶嘗試一個不存在的sessino ID時用戶將一直得到一個新的session ID,
這防止了session定置
注意這些原則和工具中沒有一個防止了中間人攻擊,這種類型的攻擊幾乎無法檢測,如果你的站點允許登錄用戶看到一些類
型的敏感數據,你應該一直通過HTTPS來服務站點,而且,如果你有一個允許SSL的站點,你應該設置SESSION_COOKIE_SECURE
設置為True,這將使Django只通過HTTPS發送session cookie
E-mail頭部注射
SQL注射的很少有人知道的姐妹e-mail頭部注射竊取email發送web表單并使用它們來發送垃圾郵件,任何從web表單數據構建
email頭部的形式都是這種類型的攻擊
讓我們看看規范的許多站點的聯系人表單,通常它email一個硬編碼的email地址,所以第一眼看來沒有垃圾郵件濫用的攻擊
盡管如此,大部分的這種表單也允許用戶輸入他自己的email主題(還有一個發送地址,有時候一些其他域),這個主題域被
用來構建email信息的主題頭部
如果當構建email信息時頭部沒有escape,攻擊者可以使用類似于"hello\ncc:spamvictim@example.com"(這里\n是換行字符)
這將使得構建的email頭部變成:
- To: hardcoded@example.com
- Subject: hello
- cc: spamvictim@example.com
和SQL注射一樣,如果我們信任用戶給定的主題行,我們將允許他后見一些惡意的頭部,則它們可以使用我們的聯系表單來
發送垃圾郵件
解決方案
我們可以用我們預防SQL注射同樣的方式來防止這種攻擊:一直escape或者驗證用戶提交的內容
Django內建的mail方法(位于django.core.mail)簡單的不允許用于構建頭部(發送和接受地址以及主題)的任何域中有換行
如果你嘗試使用django.core.mail.send_mail和一個包含換行的主題,Django將觸發BadHeaderError異常
如果你決定使用發送email的其他方法,你將需要確認頭部的換行導致出錯或者被清除,你可能想檢查django.core.mail中的
SafeMIMEText類來看看Django怎樣做這件事
目錄穿越
目錄穿越使另一個注射風格的攻擊,其中惡意的用戶欺騙文件系統代碼來讀和/或寫web服務器應該不允許訪問的文件
一個例子可能為一個從硬盤讀文件而不清除文件名的視圖:
- def dump_file(request):
- filename = request.GET["filename"]
- filename = os.path.join(BASE_PATH, filename)
- content = open(filename).read()
- # ...
盡管它看起來限制了文件訪問為訪問BASE_PATH(通過使用os.path.join)下面的文件,如果攻擊者傳遞一個包含..(這是兩個
句點,UNIX對"父目錄"的捷徑)的filename,他可以訪問BASE_PATH"之上"的文件,他發現正確數量的小數點來成功訪問只是
時間問題,比如../../../../../etc/passwd
讀取文件而不正確的escape的東西對于此問題是易受攻擊的,寫文件的視圖只是易受攻擊,但結果加倍可怕
另一個該問題的改變位于基于URL或者其他請求信息動態載入模塊的代碼中,一個宣揚良好的例子來自于Ruby on Rails世界
在2006中期之前,Rails使用類似于http://example.com/person/poke/1的URLs來直接載入模塊和調用方法,結果是細心組織
的URL可能自動載入任何的代碼,包括一個數據庫重置腳本!
解決方案
如果你的代碼需要基于用戶輸入讀寫文件,你需要非常小心的清除請求路徑來確保攻擊者不能從你限制訪問的基本目錄逃離
注意,不需要說,你應該從不寫可以讀取硬盤任何位置的代碼
怎樣做這個escape的好例子位于Django內建的靜態內容服務視圖(位于django.views.static),這里是相關的代碼:
- import os
- import posixpath
- # ...
- path = posixpath.normpath(urllib.unquote(path))
- newpath = ''
- for part in path.split('/'):
- if not part:
- # strip empty path components
- continue
- drive, part = os.path.splitdrive(part)
- head, part = os.path.split(part)
- if part in (os.curdir, os.pardir):
- # strip '.' amd '..' in path
- continue
- newpath = os.path.join(newpath, part).replace('\\', '/')
Django本身不讀文件(除非你使用static.serve方法,但是它被上面顯示的代碼保護),所以這個弱點不會影響核心代碼很多
另外,使用URL配置抽象意味著Django將從不載入你沒有顯示告訴它載入的代碼,沒有創建一個URL來導致Django載入沒有在
URL配置里提到的東西的方式
暴露出錯信息
在開發階段,可以在你的瀏覽器里看到堆棧和出錯信息是非常有用的,Django有特別讓調試容易的非常"漂亮"和豐富的調試
信息
盡管如此,一旦站點上線的話如果這些錯誤還顯示,它們有時候會無意的暴露幫助攻擊者的你的代碼或者配置的一些方面
而且,錯誤和堆棧信息對最終用戶根本沒有用處,如果你點代碼觸發了不可處理的異常,站點訪問者應該不能看到完整的
堆棧信息--或者任何代碼片段或者Python(面向程序員的)出錯信息,相反,訪問者應該看到友好的"該頁面不可得到"信息
當然,自然開發者需要看到堆棧信息來在他們的代碼中調試問題,所以框架應該從公眾隱藏所有的出錯信息,但是它應該
顯示他們給受信任的站點開發人員
解決方案
Django有一個簡單的標記來控制這些錯誤新的顯示,如果DEBUG設置被設為True,錯誤信息將顯示在瀏覽器中,否則Django
將渲染返回一個HTTP500("內部服務器錯誤")信息并渲染一個你提供的錯誤模板,這個錯誤模板被稱作500.html,并且應該
位于一個你的模板目錄的根目錄
既然開發人員仍然需要看到上線站點生成的錯誤信息,對于任何這種方式處理的錯誤將把完整的堆棧信息發送email給在
ADMINS設置中給定的任何地址
在Apache和mod_python下部署的用戶應該也確認他們在他們的Apache配置文件里設置了PythonDebug Off,這將確保任何在
Django有機會載入之前發生的錯誤都將不會顯示給公眾
最后一句話
希望所有這些關于安全問題的探討不會太有脅迫感,是這樣,web可以是一個瘋狂和野蠻的世界,但是通過一丁點的遠見,你
可以有一個難以置信的安全網站
記住web安全是一個不斷改變的領域,如果你在閱讀本書的最終樹版本,確保檢查更多更新的對于已發現的新弱點的安全資源
事實上,每個月或者每星期花費一些時間來研究和保持當前狀態的web程序安全一直是個好主意,這是很小的投資,但是你得
安徽新華電腦學校專業職業規劃師為你提供更多幫助【在線咨詢】