|
|
|
@ -0,0 +1,179 @@
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
|
|
|
<div class="p-5 space-y-5 !bg-gray-100">
|
|
|
|
|
|
|
|
<el-card header="Hack:修改/扩展闭包内的对象">
|
|
|
|
|
|
|
|
<div class="mb-4">
|
|
|
|
|
|
|
|
<p class="text-gray-600 mb-2">
|
|
|
|
|
|
|
|
场景描述:有一个闭包函数(假设是第三方库提供的),内部包含一个私有对象,且<b>仅暴露了一个根据 key 获取值的方法</b>。
|
|
|
|
|
|
|
|
<br />
|
|
|
|
|
|
|
|
我们在<b>不修改该函数源码</b>的情况下,试图:
|
|
|
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
<ul class="list-disc ml-6 text-sm text-gray-500">
|
|
|
|
|
|
|
|
<li>1. <b>新增属性</b>:让闭包返回我们自定义的新属性(利用原型链污染)。</li>
|
|
|
|
|
|
|
|
<li>2. <b>修改属性</b>:修改闭包内部对象的属性(利用引用类型泄露)。</li>
|
|
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<el-divider content-position="left">目标闭包函数(模拟不可变代码)</el-divider>
|
|
|
|
|
|
|
|
<pre class="bg-gray-800 text-white p-4 rounded text-xs overflow-x-auto">
|
|
|
|
|
|
|
|
// 这是一个即使你看不见源码,也无法修改的闭包
|
|
|
|
|
|
|
|
const getValue = (function() {
|
|
|
|
|
|
|
|
const privateData = {
|
|
|
|
|
|
|
|
id: 1001,
|
|
|
|
|
|
|
|
owner: 'admin',
|
|
|
|
|
|
|
|
// 注意:这是一个对象类型的属性
|
|
|
|
|
|
|
|
config: {
|
|
|
|
|
|
|
|
theme: 'dark',
|
|
|
|
|
|
|
|
editable: false
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return function(key) {
|
|
|
|
|
|
|
|
// 漏洞:没有过滤 key,直接返回属性
|
|
|
|
|
|
|
|
return privateData[key];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
})();
|
|
|
|
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<el-divider content-position="left">Hack 演示</el-divider>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div class="grid grid-cols-2 gap-4">
|
|
|
|
|
|
|
|
<!-- 操作区 -->
|
|
|
|
|
|
|
|
<div class="space-y-4">
|
|
|
|
|
|
|
|
<el-card shadow="never" class="!border-blue-200">
|
|
|
|
|
|
|
|
<template #header>
|
|
|
|
|
|
|
|
<span class="font-bold text-blue-600">挑战1:新增属性 (原型链污染)</span>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<p class="text-xs text-gray-500 mb-2">
|
|
|
|
|
|
|
|
原理:利用 JS 对象属性查找机制。如果对象本身没有该属性,会去原型链(Object.prototype)上找。
|
|
|
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
<div class="flex gap-2 mb-2">
|
|
|
|
|
|
|
|
<el-input v-model="hackKey" placeholder="新属性名 (如: hackID)" size="small" />
|
|
|
|
|
|
|
|
<el-input v-model="hackValue" placeholder="新属性值" size="small" />
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<el-button type="danger" size="small" @click="applyPrototypePollution">
|
|
|
|
|
|
|
|
污染 Object.prototype
|
|
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
|
|
<el-button size="small" @click="testGetValue(hackKey)">
|
|
|
|
|
|
|
|
尝试 getValue('{{ hackKey }}')
|
|
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<el-card shadow="never" class="!border-green-200">
|
|
|
|
|
|
|
|
<template #header>
|
|
|
|
|
|
|
|
<span class="font-bold text-green-600">挑战2:修改属性 (引用泄露)</span>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<p class="text-xs text-gray-500 mb-2">
|
|
|
|
|
|
|
|
原理:虽然无法修改 'owner' (基本类型),但 'config' 是对象。getValue('config') 返回的是引用,修改它会影响闭包内部!
|
|
|
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
<el-button type="success" size="small" @click="modifyRefProperty">
|
|
|
|
|
|
|
|
修改 config.theme = 'light'
|
|
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
|
|
<el-button size="small" @click="checkConfig">
|
|
|
|
|
|
|
|
查看当前 config
|
|
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 结果展示区 -->
|
|
|
|
|
|
|
|
<div class="bg-white p-4 rounded border h-full">
|
|
|
|
|
|
|
|
<h4 class="font-bold mb-2">测试结果 / 控制台日志:</h4>
|
|
|
|
|
|
|
|
<div class="bg-gray-900 text-green-400 p-2 rounded text-xs h-64 overflow-y-auto font-mono">
|
|
|
|
|
|
|
|
<div v-for="(log, index) in logs" :key="index" class="mb-1 border-b border-gray-800 pb-1">
|
|
|
|
|
|
|
|
<span class="text-gray-500">[{{ log.time }}]</span> {{ log.msg }}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
|
|
|
import { ref, onUnmounted } from 'vue';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ==========================================
|
|
|
|
|
|
|
|
// 模拟的闭包环境 (假设这是第三方库代码,不可修改)
|
|
|
|
|
|
|
|
// ==========================================
|
|
|
|
|
|
|
|
const getValue = (function() {
|
|
|
|
|
|
|
|
const privateData = {
|
|
|
|
|
|
|
|
id: 1001,
|
|
|
|
|
|
|
|
owner: 'admin',
|
|
|
|
|
|
|
|
config: {
|
|
|
|
|
|
|
|
theme: 'dark',
|
|
|
|
|
|
|
|
editable: false
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} as any; // TS ignore for demo
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return function(key: string) {
|
|
|
|
|
|
|
|
console.log(`[System] 正在访问 key: ${key}`);
|
|
|
|
|
|
|
|
return privateData[key];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
})();
|
|
|
|
|
|
|
|
// ==========================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const logs = ref<{time: string, msg: string}[]>([]);
|
|
|
|
|
|
|
|
const hackKey = ref('hackerSignature');
|
|
|
|
|
|
|
|
const hackValue = ref('I was here');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function addLog(msg: string) {
|
|
|
|
|
|
|
|
const time = new Date().toLocaleTimeString();
|
|
|
|
|
|
|
|
logs.value.unshift({ time, msg });
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 挑战1:原型链污染
|
|
|
|
|
|
|
|
const applyPrototypePollution = () => {
|
|
|
|
|
|
|
|
if (!hackKey.value) return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 核心 Hack 代码
|
|
|
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
|
|
|
Object.prototype[hackKey.value] = hackValue.value;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
addLog(`已执行: Object.prototype.${hackKey.value} = '${hackValue.value}'`);
|
|
|
|
|
|
|
|
addLog('警告:这不仅影响了闭包对象,也污染了全局所有对象!');
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const testGetValue = (key: string) => {
|
|
|
|
|
|
|
|
if (!key) return;
|
|
|
|
|
|
|
|
const result = getValue(key);
|
|
|
|
|
|
|
|
addLog(`调用 getValue('${key}') 返回: ${JSON.stringify(result)}`);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (result === hackValue.value) {
|
|
|
|
|
|
|
|
addLog('-> 成功!虽然 privateData 中没有这个 key,但通过原型链取到了值。');
|
|
|
|
|
|
|
|
} else if (result === undefined) {
|
|
|
|
|
|
|
|
addLog('-> 返回 undefined。可能未注入或被遮蔽。');
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
addLog('-> 返回了现有值(如果是 owner 等自有属性,无法被原型链覆盖)。');
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 挑战2:引用修改
|
|
|
|
|
|
|
|
const modifyRefProperty = () => {
|
|
|
|
|
|
|
|
// 1. 获取引用
|
|
|
|
|
|
|
|
const configObj = getValue('config');
|
|
|
|
|
|
|
|
addLog(`获取到 config 引用: ${JSON.stringify(configObj)}`);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 修改引用指向的对象的属性
|
|
|
|
|
|
|
|
if (configObj && typeof configObj === 'object') {
|
|
|
|
|
|
|
|
configObj.theme = 'light';
|
|
|
|
|
|
|
|
configObj.hackedBy = 'User';
|
|
|
|
|
|
|
|
addLog(`已执行: configObj.theme = 'light'`);
|
|
|
|
|
|
|
|
addLog('由于是引用传递,闭包内部的 privateData.config 已经变了。');
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const checkConfig = () => {
|
|
|
|
|
|
|
|
const config = getValue('config');
|
|
|
|
|
|
|
|
addLog(`当前闭包内的 config: ${JSON.stringify(config)}`);
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 清理副作用:页面销毁时还原原型链,以免影响其他页面
|
|
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
|
|
|
|
if (hackKey.value) {
|
|
|
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
|
|
|
delete Object.prototype[hackKey.value];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
|
|
</style>
|