web渗透测试(8):SQL注入

SQL注入是最常见的(web)漏洞之一。所有SQL注入练习,在这里找到,使用MySQL作为后端。当SQL查询中包含SQL注入时,缺少用户控制输入的编码/转义。

根据信息在查询中的添加方式,您需要不同的东西来破坏语法。

有三种不同的方法可以在SQL语句中回显信息:

  1. 使用引号:单引号或双引号。
  2. 使用反引号。
  3. 直接注入:将数据直接从数据库服务器发送到攻击者控制的计算机。

例如,如果你想使用信息作为一个字符串,你可以这样做:

SELECT * FROM user WHERE name="root";

或者

SELECT * FROM user WHERE name='root';

如果你想使用整数信息,你可以这样做:

SELECT * FROM user WHERE id=1;

最后,如果你想使用信息作为列名,你需要这样做:

SELECT * FROM user ORDER BY name;

或者

SELECT * FROM user ORDER BY `name`;

也可以使用整数作为字符串,但会更慢:

SELECT * FROM user WHERE id ='1';
 

回显信息的方式,甚至使用什么分隔符,将决定使用的检测技术。但是,您没有此信息,您需要尝试猜测它。您需要制定假设并尝试验证它们。这就是为什么花时间浏览liveCD上的例子非常重要的原因。

db.php文件代码:

<?php 
  $lnk = mysql_connect("localhost", "pentesterlab", "pentesterlab");
  $db = mysql_select_db('exercises', $lnk);
?>

Example 1

<?php

  require_once('../header.php');
  require_once('db.php');
    $sql = "SELECT * FROM users where name='";
    $sql .= $_GET["name"]."'";    
    $result = mysql_query($sql);
    if ($result) {
        ?>
        <table class='table table-striped'>
      <tr><th>id</th><th>name</th><th>age</th></tr>
        <?php
        while ($row = mysql_fetch_assoc($result)) {
            echo "<tr>";
                echo "<td>".$row['id']."</td>";
                echo "<td>".$row['name']."</td>";
                echo "<td>".$row['age']."</td>";
            echo "</tr>";
        }    
        echo "</table>";
    }
  require_once '../footer.php';
?>

在第一个例子中,我们可以看到参数是一个字符串,我们可以在表中看到一行。要理解服务器端代码,我们需要开始探索:

如果我们添加额外的字符,如“1234”,使用?name=root1234,表中不会显示任何记录。从这里,我们可以猜测请求在某种匹配中使用我们的值。

如果我们在请求中注入空格,则使用?name=root+++(编码后),显示记录。MySQL(默认情况下)在执行比较时将忽略字符串中的尾随空格。

如果我们使用双引号,?name=root”则表中不会显示任何记录。

如果我们使用注入单引号,?name=root’表格就会消失。我们可能打破了一些……

从第一部分开始,我们可以推断出整个必须如下:

SELECT * FROM users WHERE name='[INPUT]';

现在,让我们验证这个假设。

如果我们是对的,以下注入应该给出相同的结果。

  • ?name=root’ and ‘1’=’1:初始查询中的引用将在注入结束时关闭。
  • ?name=root’ and ‘1’=’1′ #(不要忘记编码#):初始查询中的引用将被注释掉。
  • ?name=root’ and 1=1 #(别忘了编码#):在初始查询引用将被注释掉我们不需要’1’=’1’。
  • ?name=root’ #(不要忘记编码#):初始查询中的引用将被注释掉,我们不需要1=1。

现在这些请求可能不会返回相同的内容:

  • ?name=root’ and ‘1’=’0:初始查询中的引用将在注入结束时关闭。页面不应返回任何结果(空表),因为选择标准始终返回false。
  • ?name=root’ and ‘1’=’1 #(不要忘记编码#):初始查询中的引用将被注释掉。我们应该与上面的查询具有相同的结果。
  • ?name=root’ or ‘1’=’1:初始查询中的引用将在注入结束时关闭。or将选择所有结果,第二部分始终为真。它可能会给出相同的结果,但不太可能,因为该值用作此示例的过滤器(而不是一次只显示一个结果的页面)。
  • ?name=root’ or ‘1’=’1′ #(不要忘记编码#):初始查询中的引用将被注释掉。我们应该与上面的查询具有相同的结果。

通过所有这些测试,我们可以确保我们有一个SQL注入。此培训仅侧重于检测。您可以查看其他PentesterLab高级课程,并了解如何利用此类问题。PentesterLab高级课程后面会讲到的。PentesterLab高级课程也是指web渗透测试平台:Web Pentester II。

Example 2

<?php
  require_once('../header.php');
  require_once('db.php');

    if (preg_match('/ /', $_GET["name"])) {
        die("ERROR NO SPACE");    
    }
    $sql = "SELECT * FROM users where name='";
    $sql .= $_GET["name"]."'";

    $result = mysql_query($sql);
    if ($result) {
        ?>
        <table class='table table-striped'>
      <tr><th>id</th><th>name</th><th>age</th></tr>
        <?php
        while ($row = mysql_fetch_assoc($result)) {
            echo "<tr>";
                echo "<td>".$row['id']."</td>";
                echo "<td>".$row['name']."</td>";
                echo "<td>".$row['age']."</td>";
            echo "</tr>";
        }    
        echo "</table>";
    }
  require '../footer.php';
?>

在此示例中,错误消息提供了开发人员创建的保护:ERROR NO SPACE。只要在请求中注入空格,就会显示此错误消息。它阻止我们使用该’ and ‘1’=’1方法或任何使用空格字符的指纹识别。但是,使用制表符(HT或\t)可轻松绕过此过滤。您将需要使用编码,以在HTTP请求中使用它。使用此简单绕过,您应该能够看到如何检测此漏洞。

Example 3

<?php
    require_once('../header.php');
  require_once('db.php');
    if (preg_match('/\s+/', $_GET["name"])) {
        die("ERROR NO SPACE");    
    }
    $sql = "SELECT * FROM users where name='";
    $sql .= $_GET["name"]."'";

    $result = mysql_query($sql);
    if ($result) {
        ?>
        <table class='table table-striped'>
      <tr><th>id</th><th>name</th><th>age</th></tr>
        <?php
        while ($row = mysql_fetch_assoc($result)) {
            echo "<tr>";
                echo "<td>".$row['id']."</td>";
                echo "<td>".$row['name']."</td>";
                echo "<td>".$row['age']."</td>";
            echo "</tr>";
        }    
        echo "</table>";
    }
    require '../footer.php';
?>

在此示例中,开发人员阻止空格和制表符。有一种方法可以绕过这个过滤器。您可以在关键字之间使用注释来构建有效请求,而无需任何空格或制表。可以使用以下SQL注释:/**/。通过使用此注释替换前面示例中的所有空格/列表,您应该能够测试此漏洞。

Example 4

<?php
  require_once('../header.php');
  require_once('db.php');
  $sql="SELECT * FROM users where id=";
    $sql.=mysql_real_escape_string($_GET["id"])." ";
    $result = mysql_query($sql);
    

    if ($result) {
        ?>
        <table class='table table-striped'>
      <tr><th>id</th><th>name</th><th>age</th></tr>

        <?php
        while ($row = mysql_fetch_assoc($result)) {
            echo "<tr>";
                echo "<td>".$row['id']."</td>";
                echo "<td>".$row['name']."</td>";
                echo "<td>".$row['age']."</td>";
            echo "</tr>";
        }    
        echo "</table>";
    }
    require '../footer.php';
?>

此示例表示对如何防止SQL注入的典型误解。在前面的3个示例中,使用该功能mysql_real_escape_string可以防止漏洞。在此示例中,开发人员使用相同的逻辑。但是,使用的值是整数,并且不会在单引号之间回显。由于该值直接放在查询中,因此使用mysql_real_escape_string不会阻止任何操作。在这里,您只需要能够添加空格和SQL关键字来打破语法。检测方法与用于基于字符串的SQL注入的方法非常类似。您只需在有效负载的开头不需要引号。例如:?id=2 or 1=1。

另一种检测方法是使用整数。最初的要求是?id=2。通过使用值2,我们可以检测到SQL注入:

  • ?id=2 #(#需要编码)应该返回相同的东西。
  • ?id=3-1应该返回相同的东西。数据库将自动执行减法,您将得到相同的结果。
  • ?id=2-0 应该返回相同的东西。
  • ?id=1+1(+需要编码)应该返回相同的东西。数据库将自动执行添加,您将获得相同的结果。
  • ?id=2.0 应该返回相同的东西。

并且以下内容不应返回相同的结果:

  • ?id=2+1。
  • ?id=3-0。

Example 5

<?php

  require_once('../header.php');
  require_once('db.php');
    if (!preg_match('/^[0-9]+/', $_GET["id"])) {
        die("ERROR INTEGER REQUIRED");    
    }
    $sql = "SELECT * FROM users where id=";
    $sql .= $_GET["id"] ;
    
    $result = mysql_query($sql);

    if ($result) {
        ?>
        <table class='table table-striped'>
      <tr><th>id</th><th>name</th><th>age</th></tr>
        <?php
        while ($row = mysql_fetch_assoc($result)) {
            echo "<tr>";
                echo "<td>".$row['id']."</td>";
                echo "<td>".$row['name']."</td>";
                echo "<td>".$row['age']."</td>";
            echo "</tr>";
        }    
        echo "</table>";
    }
    require '../footer.php';
?>

这个例子与以前的检测方式非常相似。如果查看代码,您将看到开发人员试图通过使用正则表达式来阻止SQL注入:

if (!preg_match('/^[0-9]+/', $_GET["id"])) {
    die("ERROR INTEGER REQUIRED");  
}

但是,使用的正则表达式是不正确的; 它只是确保参数id 开始以数字。先前使用的检测方法可用于检测此漏洞。例如:?id=2 or 1=1。

Example 6

<?php

   require_once('../header.php');
  require_once('db.php');
    if (!preg_match('/[0-9]+$/', $_GET["id"])) {
        die("ERROR INTEGER REQUIRED");    
    }
    $sql = "SELECT * FROM users where id=";
    $sql .= $_GET["id"] ;

    
    $result = mysql_query($sql);


if ($result) {
        ?>
        <table class='table table-striped'>
      <tr><th>id</th><th>name</th><th>age</th></tr>
        <?php
        while ($row = mysql_fetch_assoc($result)) {
            echo "<tr>";
                echo "<td>".$row['id']."</td>";
                echo "<td>".$row['name']."</td>";
                echo "<td>".$row['age']."</td>";
            echo "</tr>";
        }    
        echo "</table>";
    }
    require '../footer.php';
?>

这个例子是另一种方式。开发人员再次在正则表达式中犯了一个错误:

if (!preg_match('/[0-9]+$/', $_GET["id"])) {
    die("ERROR INTEGER REQUIRED");  
}

此正则表达式仅确保参数以数字id 结尾(由于$符号)。它不能确保参数的开头有效(缺失^)。您可以使用之前学到的方法。

您只需要在有效负载的末尾添加一个整数。这个数字可以是有效载荷的一部分,也可以放在SQL注释之后:?id=1 or 1=1 # 123。(#需要编码)!

Example 7

<?php

  require_once('../header.php');
  require_once('db.php');
    if (!preg_match('/^-?[0-9]+$/m', $_GET["id"])) {
        die("ERROR INTEGER REQUIRED");    
    }
    $sql = "SELECT * FROM users where id=";
    $sql .= $_GET["id"];
    
    $result = mysql_query($sql);

    if ($result) {
        ?>
        <table class='table table-striped'>
      <tr><th>id</th><th>name</th><th>age</th></tr>
        <?php
        while ($row = mysql_fetch_assoc($result)) {
            echo "<tr>";
                echo "<td>".$row['id']."</td>";
                echo "<td>".$row['name']."</td>";
                echo "<td>".$row['age']."</td>";
            echo "</tr>";
        }    
        echo "</table>";
    }
    require '../footer.php';
?>

另一个坏正则表达式的例子:

if (!preg_match('/^-?[0-9]+$/m', $_GET["id"])) {
  die("ERROR INTEGER REQUIRED");    
}
 

在这里我们可以看到字符串的开始(^)和结束($)被正确检查。但是,正则表达式包含修饰符PCRE_MULTILINE(/m)。多行修饰符仅验证其中一行仅包含整数,因此以下值有效(由于其中包含新行):

123\nPAYLOAD;

PAYLOAD\n123;

PAYLOAD\n123\nPAYLOAD。

在URL中使用时,需要对这些值进行编码,但是通过使用编码和先前看到的技术,您应该能够检测到此漏洞。例如:

http://192.168.1.11/sqli/example7.php?id=2%0A%20or%201=1

Example 8

<?php

  require_once('../header.php');
  require_once('db.php');
    $sql = "SELECT * FROM users ORDER BY `";
    $sql .= mysql_real_escape_string($_GET["order"])."`";
    $result = mysql_query($sql);
    
    if ($result) {
        ?>
        <table  class='table table-striped'>
        <tr>
            <th><a href="example8.php?order=id">id</th>
            <th><a href="example8.php?order=name">name</th>
            <th><a href="example8.php?order=age">age</th>
        </tr>
        <?php
        while ($row = mysql_fetch_assoc($result)) {
            echo "<tr>";
                echo "<td>".$row['id']."</td>";
                echo "<td>".$row['name']."</td>";
                echo "<td>".$row['age']."</td>";
            echo "</tr>";
        }    
        echo "</table>";
    }
    require '../footer.php';
?>

在此示例中,参数名称表示它将在SQL查询中回显的位置。如果查看MySQL文档,有两种方法可以在ORDER BY语句中提供值:

  • 直接:ORDER BY name;
  • 在反引号之间:ORDER BY `name`。

该ORDER BY声明不能与单引号或双引号内的值一起使用。如果使用它,则不会对任何内容进行排序,因为MySQL将这些视为常量。

要检测此类漏洞,我们可以尝试使用不同的有效负载获得相同的结果:

  • name` #(#需要编码)应该给出相同的结果。
  • name` ASC #(#需要编码)应该给出相同的结果。
  • name`, `name:初始查询中的反向标记将在注入结束时关闭。

以下有效负载应该给出不同的结果:

  • name` DESC #(#需要编码)。
  • name` 不应该给出任何结果,因为语法不正确。

Example 9

<?php
  require_once('../header.php');
  require_once('db.php');
    $sql = "SELECT * FROM users ORDER BY ";
  $sql .= mysql_real_escape_string($_GET["order"]);
    $result = mysql_query($sql);
    if ($result) {
        ?>
        <table class='table table-striped'>
        <tr>
            <th><a href="example9.php?order=id">id</th>
            <th><a href="example9.php?order=name">name</th>
            <th><a href="example9.php?order=age">age</th>
        </tr>
        <?php
        while ($row = mysql_fetch_assoc($result)) {
            echo "<tr>";
                echo "<td>".$row['id']."</td>";
                echo "<td>".$row['name']."</td>";
                echo "<td>".$row['age']."</td>";
            echo "</tr>";
        }    
        echo "</table>";
    }
  require '../footer.php';
?>

这个例子类似于前一个例子,但不是反向引号“`

在这种情况下可以使用其他方法,因为我们之前没有反向引号直接注入请求。我们可以使用MySQL IF语句生成更多有效负载:

  • IF(1, name,age) 应该给出相同的结果。
  • IF(0, name,age)应该给出不同的结果。您可以看到列按年龄排序,但sort函数将值比较为字符串,而不是整数(10小于2)。这是一个副作用IF,如果列中的一个包含字符串,则会将值排序为字符串。
微信公众号
手机浏览(小程序)
0
分享到:
没有账号? 忘记密码?