SQL Injection
实验拓扑如图6.0.1。
图6.0.1 实验拓扑
6.1 Low级别
首先连接此站点,网站样子如图6.1.1所示。
图6.1.1 SQL Injection初始界面
测试一下,如图6.1.2。
图6.1.2 判断注入点
随后判断表中有多少字段,使用命令
1′ order by 5#
来判断。当猜测的值为5时系统报错,说明字段值应该小于5,如图6.1.3所示。
图6.1.3 当猜测字段值为5时报错
经过试验,得到正确的字段值为2。
接下来判断版本和数据库。输入
1′ and 1=2 union select user(),database() #
得到图6.1.4的结果,说明用户是root,数据库是dvwa。继续测试,输入
1′ and 1=2 union select version(),database() #
得到图6.1.5的结果,说明版本是MySql 5.0。
图6.1.4 判断用户和数据库
图6.1.5 判断版本和数据库
接下来判断表。输入
1′ and 1=2 union select table_name,2 from information_schema.tables where
table_schema=’dvwa’ #
判断存在的表,返回结果如图6.1.6,可以发现存在guestbook和users表。
图6.1.6 判断表名
接下来判断表中有哪些字段,使用users表,输入命令
1′ and 1=2 union select column_name,2 from information_schema.columns where
table_name=’users’ #
得到图6.1.7的结果,所有字段均显示出来。
图6.1.7 爆user表中的字段
我们可以发现关键字段user和password,直接使用
1′ and 1=2 union select user,password from users#
命令获取所有用户名和密码,如图6.1.8。
图6.1.8 爆用户名和密码
图6.1.9 爆guestbook表的字段
测试另一张表,发现并没有什么有用的信息,如图6.1.9,故不再深测。
6.2 Medium级别
这个级别里,网站只允许从下拉列表中选取数据,且使用POST方法提交,杜绝了直接在地址栏和TextBox中写入注入语句的可能。鉴于此,我们使用BurpSuite来修改POST提交的数据,如图6.2.1所示。
图6.2.1 在BurpSuite中修改POST提交的数据
图6.2.2 服务器对“’”进行了转义
在提交数据之后得到图6.2.2的结果,由此结果可知,服务端对提交的数据进行了过滤,其中“’”符号被转义为“\’”。将“’”符号去掉,直接使用1 and 1=1语句时,能得到正常结果,如图6.2.3所示,当使用1 and 1=2时无结果。由此可以判断出只要我们不使用“’”符号,仍然存在成功注入的可能。于是在Low级别的基础上,直接使用1 and 1=2 union select users,password
from users#便得到了结果,如图6.2.4所示。
图6.2.3 去掉单引号进行查询
图6.2.4 得到结果
6.3 High级别
对于高级别的难度,咋一看挺高大上,如图6.3.1,还需要弹窗输入ID才能查看信息。
图6.3.1 高级别难度界面
小试一下,发现仍能进行注入,如图6.3.2。
图6.3.2 高等级界面仍能进行注入
所以直接上大招,试了一下Level Medium里最后的语句,得到图6.3.3。然而这并不能实现图6.2.4的效果。这时注意到图6.3.2使用了“’”符号,因此可以判断出这次需要“’”,故使用Level Low里的最后的语句,成功获取全部数据,如图6.3.4。由于中间过程和Low级别类似,故不再累述,一些与Low级别出入的地方,在6.4节进行分析。
图6.3.3 使用Medium级别的语句测试
图6.3.4 获取全部数据
6.4 分析
因为High级别的过程过于简单,和想想中的高级别有一定的区别,因此我们来分析一下各级别的代码。
6.4.1 Low级别
Low级别的代码如代码段6.4.1所示。
代码段6.4.1 Low级别代码
1 |
<?php |
|
2 |
||
3 |
if( isset( $_REQUEST[ ‘Submit’ ] ) ) { |
|
4 |
// Get input |
|
5 |
$id = $_REQUEST[ ‘id’ ]; |
|
6 |
||
7 |
// Check database |
|
8 |
$query = “SELECT first_name, last_name FROM users WHERE user_id = ‘$id’;”; |
|
9 |
$result = mysql_query( $query ) or die( ‘<pre>’ . mysql_error() . ‘</pre>’ ); |
|
10 |
||
11 |
// Get results |
|
12 |
$num = mysql_numrows( $result ); |
|
13 |
$i = 0; |
|
14 |
while( $i < $num ) { |
|
15 |
// Get values |
|
16 |
$first = mysql_result( $result, $i, “first_name” ); |
|
17 |
$last = mysql_result( $result, $i, “last_name” ); |
|
18 |
||
19 |
// Feedback for end user |
|
20 |
echo “<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>”; |
|
21 |
||
22 |
// Increase loop count |
|
23 |
$i++; |
|
24 |
} |
|
25 |
||
26 |
mysql_close(); |
|
27 |
} |
|
28 |
||
29 |
?> |
由此代码可知,这里对输入的内容没有进行任何过滤等操作,直接对输入的值进行构造SQL语句,并使用之进行查询(第5行到第9行)。例如,当我们执行1′ and exists(select * from admin)#时,id的值即为1′ and exists(select * from admin)#,由此构造的SQL语句query为SELECT first_name, last_name FROM
users WHERE user_id = ‘1’ and exists(select * from admin)#’;。这时我们可以看到,原本的获取id的语句变成了获取id并判断是否存在admin表,中间是and连接,即只有两个都为真才返回真。而最后的“#”符号又对剩下的语句进行了注释,确保只执行我们想要的部分。这一点在接下来的操作里非常重要。所以后来我们就可以根据这个原理进行语句构造,猜测表结构,获取我们想要的各种信息。
6.4.2 Medium级别
Medium级别的代码如代码段6.4.2。
代码段6.4.2 Medium级别代码
1 |
|
<?php |
2 |
|
|
3 |
|
if( isset( $_POST[ ‘Submit’ ] ) ) { |
4 |
|
// Get input |
5 |
|
$id = $_POST[ ‘id’ ]; |
6 |
|
$id = mysql_real_escape_string( $id ); |
7 |
|
|
8 |
|
// Check database |
9 |
|
$query = “SELECT first_name, last_name FROM users WHERE user_id = $id;”; |
10 |
|
$result = mysql_query( $query ) or die( ‘<pre>’ . mysql_error() . ‘</pre>’ ); |
11 |
|
|
12 |
|
// Get results |
13 |
|
$num = mysql_numrows( $result ); |
14 |
|
$i = 0; |
15 |
|
while( $i < $num ) { |
16 |
|
// Display values |
17 |
|
$first = mysql_result( $result, $i, “first_name” ); |
18 |
|
$last = mysql_result( $result, $i, “last_name” ); |
19 |
|
|
20 |
|
// Feedback for end user |
21 |
|
echo “<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>”; |
22 |
|
|
23 |
|
// Increase loop count |
24 |
|
$i++; |
25 |
|
} |
26 |
|
|
27 |
|
//mysql_close(); |
28 |
|
} |
29 |
|
|
30 |
|
?> |
由此代码第6行可知,这里使用了 mysql_real_escape_string() 函数对输入的字符串进行简单的过滤,将一些字符进行转义,如“’”、“””等,并返回转义后的字符串。然而由第9行的代码可知,这里在进行查询时并没有对转义后的结果加上单引号,因此我们可以不使用引号进行注入。由此便得到了6.2节里的内容。
如果不在Low级别的基础上进行操作,则基本过程也和Low级别一样,只不过变成了在BurpSuite中修改POST的数据,再进行转发。
6.4.3 High级别
再来看High级别的内容,代码如代码段6.4.3所示。
代码段6.4.3 High级别代码
1 |
|
<?php |
2 |
|
|
3 |
|
if( isset( $_SESSION [ ‘id’ ] ) ) { |
4 |
|
// Get input |
5 |
|
$id = $_SESSION[ ‘id’ ]; |
6 |
|
|
7 |
|
// Check database |
8 |
|
$query = “SELECT first_name, last_name FROM users WHERE user_id = ‘$id’ LIMIT 1;”; |
9 |
|
$result = mysql_query( $query ) or die( ‘<pre>Something went wrong.</pre>’ ); |
10 |
|
|
11 |
|
// Get results |
12 |
|
$num = mysql_numrows( $result ); |
13 |
|
$i = 0; |
14 |
|
while( $i < $num ) { |
15 |
|
// Get values |
16 |
|
$first = mysql_result( $result, $i, “first_name” ); |
17 |
|
$last = mysql_result( $result, $i, “last_name” ); |
18 |
|
|
19 |
|
// Feedback for end user |
20 |
|
echo “<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>”; |
21 |
|
|
22 |
|
// Increase loop count |
23 |
|
$i++; |
24 |
|
} |
25 |
|
|
26 |
|
mysql_close(); |
27 |
|
} |
28 |
|
|
29 |
|
?> |
由第3行、第5行可知,这里使用SESSION方法来传递id的值,在第9行使用mysql_query()和die()函数,使得当查询不成功时整个页面直接down掉,如图6.4.1,退出重新登录也没用,除非退出并把cookie清空,再刷新登录。
图6.4.1 die掉的页面
观察query语句构造代码(第8行)可知,这里的查询语句和以往的不同,这里添加了LIMIT 1的限制,只返回第一列的内容。然而我们使用“#”直接把这句注释掉了,所以这个限制并不是很大,我们依然可以拿到想要的数据。
这里由于三个等级在一起实验,所以借鉴了前两个等级的代码,而如果真正要从0开始的话肯定要有一些查询失败的地方,这时就会不断出现图6.4.1的现象,需要不停地清除cookie才能成功。但基本思路和原理类似。
原创文章,91tfboys.com版权所有,转载请注明出处。