feat: 修改闭包对象属性

master
LCJ-MinYa 2 weeks ago
parent 7f2b339eb2
commit b806d077f5

@ -1,141 +1,179 @@
<template> <template>
<div class="p-5 space-y-5 !bg-gray-100"> <div class="p-5 space-y-5 !bg-gray-100">
<el-card header="如何修改闭包内对象属性"> <el-card header="Hack修改/扩展闭包内的对象">
<div class="mb-4"> <div class="mb-4">
<p class="text-gray-600 mb-2"> <p class="text-gray-600 mb-2">
JavaScript 闭包内的变量通常是私有的如果闭包内定义了一个对象我们有几种方式可以修改它的属性 场景描述有一个闭包函数假设是第三方库提供的内部包含一个私有对象<b>仅暴露了一个根据 key 获取值的方法</b>
</p> <br />
<ul class="list-disc ml-6 text-sm text-gray-500"> 我们在<b>不修改该函数源码</b>的情况下试图
<li>通过闭包返回的 Setter 方法进行修改</li> </p>
<li>如果闭包直接返回了该对象的引用可以直接修改其属性</li> <ul class="list-disc ml-6 text-sm text-gray-500">
<li>通过原型链或特定的 Hack 方式不推荐但存在</li> <li>1. <b>新增属性</b>让闭包返回我们自定义的新属性利用原型链污染</li>
</ul> <li>2. <b>修改属性</b>修改闭包内部对象的属性利用引用类型泄露</li>
</div> </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">示例展示</el-divider> <el-divider content-position="left">Hack 演示</el-divider>
<div class="space-y-4"> <div class="grid grid-cols-2 gap-4">
<div class="bg-white p-4 rounded border"> <!-- 操作区 -->
<h4 class="font-bold mb-2">闭包内部状态</h4> <div class="space-y-4">
<pre class="bg-gray-50 p-2 rounded text-xs">{{ JSON.stringify(closureState, null, 2) }}</pre> <el-card shadow="never" class="!border-blue-200">
</div> <template #header>
<span class="font-bold text-blue-600">挑战1新增属性 (原型链污染)</span>
<div class="flex gap-4"> </template>
<el-button <p class="text-xs text-gray-500 mb-2">
type="primary" 原理利用 JS 对象属性查找机制如果对象本身没有该属性会去原型链(Object.prototype)上找
@click="handleUpdateViaSetter" </p>
> <div class="flex gap-2 mb-2">
通过 Setter 修改年龄 (age + 1) <el-input v-model="hackKey" placeholder="新属性名 (如: hackID)" size="small" />
</el-button> <el-input v-model="hackValue" placeholder="新属性值" size="small" />
<el-button
type="success"
@click="handleUpdateDirectly"
>
直接修改引用属性 (name = 'New Name')
</el-button>
<el-button
type="warning"
@click="resetState"
>
重置状态
</el-button>
</div>
</div> </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-divider content-position="left">核心代码原理</el-divider> <el-card shadow="never" class="!border-green-200">
<pre class="bg-gray-800 text-white p-4 rounded text-xs overflow-x-auto"> <template #header>
function createClosure() { <span class="font-bold text-green-600">挑战2修改属性 (引用泄露)</span>
// </template>
const privateObj = { <p class="text-xs text-gray-500 mb-2">
name: 'Initial Name', 原理虽然无法修改 'owner' (基本类型) 'config' 是对象getValue('config') 返回的是引用修改它会影响闭包内部
age: 25 </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>
return { <!-- 结果展示区 -->
// 1 <div class="bg-white p-4 rounded border h-full">
updateAge(newAge) { <h4 class="font-bold mb-2">测试结果 / 控制台日志</h4>
privateObj.age = newAge; <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">
// 2 <span class="text-gray-500">[{{ log.time }}]</span> {{ log.msg }}
getData() { </div>
return privateObj; </div>
} </div>
}; </div>
} </el-card>
</pre </div>
>
</el-card>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue'; import { ref, onUnmounted } from 'vue';
import { message } from '@/utils/message';
// ==========================================
/** // ()
* 模拟一个产生闭包的函数 // ==========================================
*/ const getValue = (function() {
function createClosure() { const privateData = {
const privateObj = { id: 1001,
name: '张三', owner: 'admin',
age: 20, config: {
}; theme: 'dark',
editable: false
return { }
// } as any; // TS ignore for demo
getData() {
return privateObj; return function(key: string) {
}, console.log(`[System] 正在访问 key: ${key}`);
// return privateData[key];
setAge(age: number) { }
privateObj.age = age; })();
}, // ==========================================
//
reset() { const logs = ref<{time: string, msg: string}[]>([]);
privateObj.name = '张三'; const hackKey = ref('hackerSignature');
privateObj.age = 20; const hackValue = ref('I was here');
},
}; function addLog(msg: string) {
const time = new Date().toLocaleTimeString();
logs.value.unshift({ time, msg });
} }
const closureManager = createClosure(); // 1
const closureState = ref({ name: '', age: 0 }); const applyPrototypePollution = () => {
if (!hackKey.value) return;
// Hack
// @ts-ignore
Object.prototype[hackKey.value] = hackValue.value;
// addLog(`已执行: Object.prototype.${hackKey.value} = '${hackValue.value}'`);
const refreshDisplay = () => { addLog('警告:这不仅影响了闭包对象,也污染了全局所有对象!');
const data = closureManager.getData();
// Vue
closureState.value = { ...data };
}; };
const handleUpdateViaSetter = () => { const testGetValue = (key: string) => {
const currentAge = closureManager.getData().age; if (!key) return;
closureManager.setAge(currentAge + 1); const result = getValue(key);
refreshDisplay(); addLog(`调用 getValue('${key}') 返回: ${JSON.stringify(result)}`);
message('通过 Setter 修改成功', { type: 'success' });
if (result === hackValue.value) {
addLog('-> 成功!虽然 privateData 中没有这个 key但通过原型链取到了值。');
} else if (result === undefined) {
addLog('-> 返回 undefined。可能未注入或被遮蔽。');
} else {
addLog('-> 返回了现有值(如果是 owner 等自有属性,无法被原型链覆盖)。');
}
}; };
const handleUpdateDirectly = () => { // 2
const data = closureManager.getData(); const modifyRefProperty = () => {
// // 1.
data.name = '被直接修改的名称 ' + Math.floor(Math.random() * 100); const configObj = getValue('config');
refreshDisplay(); addLog(`获取到 config 引用: ${JSON.stringify(configObj)}`);
message('通过引用直接修改成功', { type: 'success' });
// 2.
if (configObj && typeof configObj === 'object') {
configObj.theme = 'light';
configObj.hackedBy = 'User';
addLog(`已执行: configObj.theme = 'light'`);
addLog('由于是引用传递,闭包内部的 privateData.config 已经变了。');
}
}; };
const resetState = () => { const checkConfig = () => {
closureManager.reset(); const config = getValue('config');
refreshDisplay(); addLog(`当前闭包内的 config: ${JSON.stringify(config)}`);
message('状态已重置');
}; };
onMounted(() => { //
refreshDisplay(); onUnmounted(() => {
if (hackKey.value) {
// @ts-ignore
delete Object.prototype[hackKey.value];
}
}); });
</script> </script>
<style scoped lang="scss"> <style scoped>
pre {
font-family: 'Courier New', Courier, monospace;
}
</style> </style>

Loading…
Cancel
Save