JS原型链污染一个题

题目来源:LitCTF-2025-多重宇宙日记

题目提示打原型链

image-20250526203601111

image-20250526205511513

先注册一个账户登录,会来到/api/profile

image-20250526205455059

查看源码

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
<script>
// 更新表单的JS提交
document.getElementById('profileUpdateForm').addEventListener('submit', async function(event) {
event.preventDefault();
const statusEl = document.getElementById('updateStatus');
const currentSettingsEl = document.getElementById('currentSettings');
statusEl.textContent = '正在更新...';

const formData = new FormData(event.target);
const settingsPayload = {};
// 构建 settings 对象,只包含有值的字段
if (formData.get('theme')) settingsPayload.theme = formData.get('theme');
if (formData.get('language')) settingsPayload.language = formData.get('language');
// ...可以添加其他字段

try {
const response = await fetch('/api/profile/update', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ settings: settingsPayload }) // 包装在 "settings"键下
});
const result = await response.json();
if (response.ok) {
statusEl.textContent = '成功: ' + result.message;
currentSettingsEl.textContent = JSON.stringify(result.settings, null, 2);
// 刷新页面以更新导航栏(如果isAdmin状态改变)
setTimeout(() => window.location.reload(), 1000);
} else {
statusEl.textContent = '错误: ' + result.message;
}
} catch (error) {
statusEl.textContent = '请求失败: ' + error.toString();
}
});

// 发送原始JSON的函数
async function sendRawJson() {
const rawJson = document.getElementById('rawJsonSettings').value;
const statusEl = document.getElementById('rawJsonStatus');
const currentSettingsEl = document.getElementById('currentSettings');
statusEl.textContent = '正在发送...';
try {
const parsedJson = JSON.parse(rawJson); // 确保是合法的JSON
const response = await fetch('/api/profile/update', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(parsedJson) // 直接发送用户输入的JSON
});
const result = await response.json();
if (response.ok) {
statusEl.textContent = '成功: ' + result.message;
currentSettingsEl.textContent = JSON.stringify(result.settings, null, 2);
// 刷新页面以更新导航栏(如果isAdmin状态改变)
setTimeout(() => window.location.reload(), 1000);
} else {
statusEl.textContent = '错误: ' + result.message;
}
} catch (error) {
statusEl.textContent = '请求失败或JSON无效: ' + error.toString();
}
}
</script>

可以发现参数isAdmin,应用会根据这个参数判断用户是否为admin,可以在这里打JavaScript的原型污染

JavaScript中,可以通过 __proto__ 属性访问其原型(prototype),如果向对象中添加 __proto__ 字段,会修改该对象的原型链,影响所有继承自该原型的对象

可以先点更新设置,抓包获得格式

image-20250526205417936

1
{"settings":{"theme":"a","language":"a"}}

可以利用__proto__属性污染 settings 对象的原型

1
{"settings":{"theme":"a","language":"a","__proto__":{"isAdmin":true}}}

污染根对象的原型

1
{"settings":{"theme":"a","language":"a","__proto__":{"isAdmin":true}},"__proto__":{"isAdmin":true}}

发送请求

image-20250526205607968

然后在刷新一下页面就会看到一个管理员面板

image-20250526205633190

点进去得到flag

image-20251105160717380


JS原型链污染一个题
https://yschen20.github.io/2025/11/05/JS原型链污染一个题/
作者
Suzen
发布于
2025年11月5日
更新于
2025年11月5日
许可协议