
0x01 等级:Low
<?php
// 判断表单是否提交
if( isset( $_REQUEST[ 'Submit' ] ) ) {
// 获取用户表单输入的id值
$id = $_REQUEST[ 'id' ];
// 根据用户传入的id值构造sql语句
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
/*
执行上面的sql语句。
mysqli_query() 执行某个针对数据库的查询语句。 参数1: 要使用的数据库连接,参数2:执行的语句
die() 输出一条信息,并退出脚本执行
以下语句解析:如果用户键入的id值查询为空则返回的结果转为布尔值则为false,下面语句使用or进行连接,or逻辑运算符有一个短路,就是如果or前面的语句为true,则不再执行后面的数据。也就是说,如果前面的语句查询结果不为空,则不再执行后面的语句。反之执行后面的语句。
*/
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
// 获取sql查询中的结果 mysqli_fetch_assoc()将查询结果作为一个关联数组
while( $row = mysqli_fetch_assoc( $result ) ) {
// 获取$row变量中的键值
$first = $row["first_name"];
$last = $row["last_name"];
// 对最终用户的反馈
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
// 关闭这个sql连接
mysqli_close($GLOBALS["___mysqli_ston"]);
}
?>
通过审计代码发现,我们可以传入id值,并且传入的id值并没有经过任何过滤。这就可能存在sql注入漏洞。
前端界面是通过from表单以GET请求接受我们传入的参数。执行之后会输出对应id的用户

其实这里存在一个union联合查询语句注入
0x01.2 判断注入点类型
我们传入id=2a,发现和id=2的查询结果一致。可以判断这里是一个字符型的注入点,如果是整型(数字型)的注入点,我们传入id=2a的时候要么是查询结果为空,要么直接报错。

那么为什么我们传入id=2a的结果和id=2的结果一致呢?其实PHP有一个弱类型转换。它会将2a转换为整数类型就是2,因此和id=2的结果一致。
看示例:

= 为PHP的赋值符号,表示将=右边的值赋值给=左边的变量,PHP中以$开头表示是一个变量。
== 在进行比较的时候,会先将值的类型转换为相同,再比较
=== 在进行比较的时候,会先判断两种值的类型是否相等,再比较,如果数据类型不相同,则直接返回false
可以看到字符串类型的'123a'与数字类型的123作==比较,他们的值是相等的,因为'123a'转为数字类型则为123。
因为在做类型转换的时候,字符串必须以数字开头才会被转换为数字类型。因此$b的结果为false,数字类型的123和'a123'做==运算是不相同的。
$c的结果之所以为false,是因为数字类型的123和字符串类型的'123'在做全等于(===)运算的时候,它不会将两者的数据类型,转为同一数据类型,而且它也会比较数据类型,如果数据类型不相同,则直接返回false。
但是这个类型转换,不再PHP中转换,而是在我们sql语句执行的时候。会发生类型转换,其原理和PHP类型转换一致。
因此我们传入的id=1a值会被转换为id=1,这也是两者查询结果一致的原因。

0x01.3 判断列的列数
由于是字符型注入,所以我们需要闭合后端语句中的单引号。
猜测我们后端是这么写的(即是有源码,我还是要猜测一下嘻嘻嘻嘻嘻)
select Firstname,Surname from users where id = $_GET['id']
假设我们传入的id值为id=1,那么我们的sql查询语句就为
select Firstname,Surname from users where id = '1'
在sql语句中实际上是这样查询的。

我们传入' order by 4 # 之后点击submit提交

之后会跳转到新页面提示未知的列。

ORDER BY 语句用于对结果集进行排序
order by 4 表示根据第4列进行排序。但是我们的slq查询中没有第4列,因此会报未知的第4列错误
以下在mysql中执行:

我们可以发现,当前选中了两个列即first_name和last_name列,当我们order by 2 的时候,回显是正常的,因为他们第2列,第2列就是last_name。
但是执行order by 3 的时候,列是没有第三列的。因此会报一个未知的列3。
我们可以使用order by 语句,可以判断列的列数。
为了方便,我们直接在url中注入,不再使用表单提交注入。
http://172.16.1.2/dvwa/vulnerabilities/sqli/?id=2' order by 3 --+ &Submit=Submit# 未知的列
http://172.16.1.2/dvwa/vulnerabilities/sqli/?id=2' order by 2 --+ &Submit=Submit# 回显正常
其中的#表示注释,在sql语句中的注释有:
#
--
/* 要注释的内容 */
注意:-- 后面是有一个空格的。
由上可以得知,sql查询中使用了两列。
0x01.4 union 语句
UNION 操作符用于合并两个或多个 SELECT 语句的结果集。
UNION语法:
SELECT column_name1 FROM table_name1 UNION SELECT column_name2 FROM table_name2
注释:默认地,UNION 操作符选取不同的值。如果允许重复的值,请使用 UNION ALL。
UNION ALL 语法:
SELECT column_name1 FROM table_name1 UNION ALL SELECT column_name2 FROM table_name2
UNION 结果集中的列名总是等于 UNION 中第一个 SELECT 语句中的列名

注意:UNION联合查询语句,需要UNION语句前的SELECT语句的列数和UNION后的SELECT的列数要一致。
所以这也是我们上步猜解列数的原因。

可以看到,UNION前的SELECT所选择的列为3列,分别为comment_id和comment以name,而后者SELECT则选择的为2列,分别是user_id和first_name。因此会报选择的列数不同错误。
0x01.4 union 获取数据
http://172.16.1.2/dvwa/vulnerabilities/sqli/?id=2' union select 1,2 --+ &Submit=Submit#
# 其中的+在url编码中表示空格,为了更直观我们使用--+ 当然--空格也是可以的

可以发现,页面存在返回了1和2 ,说明存在union联合查询注入。
union语句有个特性:如果SELECT指定的列名为数字且不存在,则会将列名作为结果输出到页面。如果SELECT指定的列都不存在,则会以第一个SELECT指定的列名作为列名,其值为指定的列名。
看示例:

如果SELECT指定的列名为数字且不存在,则会将列明作为值输出。

如果UNION前后的SELECT指定的列不为数字,则直接报未知的列错误。

我们将
http://172.16.1.2/dvwa/vulnerabilities/sqli/?id=-2' union select version(),user() -- &Submit=Submit#

本文作者为blog,转载请注明。