feat: 修改闭包对象属性

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

@ -1,141 +1,179 @@
<template>
<div class="p-5 space-y-5 !bg-gray-100">
<el-card header="如何修改闭包内对象属性">
<el-card header="Hack修改/扩展闭包内的对象">
<div class="mb-4">
<p class="text-gray-600 mb-2">
JavaScript 闭包内的变量通常是私有的如果闭包内定义了一个对象我们有几种方式可以修改它的属性
场景描述有一个闭包函数假设是第三方库提供的内部包含一个私有对象<b>仅暴露了一个根据 key 获取值的方法</b>
<br />
我们在<b>不修改该函数源码</b>的情况下试图
</p>
<ul class="list-disc ml-6 text-sm text-gray-500">
<li>通过闭包返回的 Setter 方法进行修改</li>
<li>如果闭包直接返回了该对象的引用可以直接修改其属性</li>
<li>通过原型链或特定的 Hack 方式不推荐但存在</li>
<li>1. <b>新增属性</b>让闭包返回我们自定义的新属性利用原型链污染</li>
<li>2. <b>修改属性</b>修改闭包内部对象的属性利用引用类型泄露</li>
</ul>
</div>
<el-divider content-position="left">示例展示</el-divider>
<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">
<div class="bg-white p-4 rounded border">
<h4 class="font-bold mb-2">闭包内部状态</h4>
<pre class="bg-gray-50 p-2 rounded text-xs">{{ JSON.stringify(closureState, null, 2) }}</pre>
<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>
<div class="flex gap-4">
<el-button
type="primary"
@click="handleUpdateViaSetter"
>
通过 Setter 修改年龄 (age + 1)
<el-button type="danger" size="small" @click="applyPrototypePollution">
污染 Object.prototype
</el-button>
<el-button
type="success"
@click="handleUpdateDirectly"
>
直接修改引用属性 (name = 'New Name')
<el-button size="small" @click="testGetValue(hackKey)">
尝试 getValue('{{ hackKey }}')
</el-button>
<el-button
type="warning"
@click="resetState"
>
重置状态
</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>
</div>
<el-button size="small" @click="checkConfig">
查看当前 config
</el-button>
</el-card>
</div>
<el-divider content-position="left">核心代码原理</el-divider>
<pre class="bg-gray-800 text-white p-4 rounded text-xs overflow-x-auto">
function createClosure() {
//
const privateObj = {
name: 'Initial Name',
age: 25
};
return {
// 1
updateAge(newAge) {
privateObj.age = newAge;
},
// 2
getData() {
return privateObj;
}
};
}
</pre
>
<!-- 结果展示区 -->
<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, onMounted } from 'vue';
import { message } from '@/utils/message';
/**
* 模拟一个产生闭包的函数
*/
function createClosure() {
const privateObj = {
name: '张三',
age: 20,
};
import { ref, onUnmounted } from 'vue';
return {
//
getData() {
return privateObj;
},
//
setAge(age: number) {
privateObj.age = age;
},
//
reset() {
privateObj.name = '张三';
privateObj.age = 20;
},
};
// ==========================================
// ()
// ==========================================
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 });
}
const closureManager = createClosure();
const closureState = ref({ name: '', age: 0 });
// 1
const applyPrototypePollution = () => {
if (!hackKey.value) return;
// Hack
// @ts-ignore
Object.prototype[hackKey.value] = hackValue.value;
//
const refreshDisplay = () => {
const data = closureManager.getData();
// Vue
closureState.value = { ...data };
addLog(`已执行: Object.prototype.${hackKey.value} = '${hackValue.value}'`);
addLog('警告:这不仅影响了闭包对象,也污染了全局所有对象!');
};
const handleUpdateViaSetter = () => {
const currentAge = closureManager.getData().age;
closureManager.setAge(currentAge + 1);
refreshDisplay();
message('通过 Setter 修改成功', { type: 'success' });
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 等自有属性,无法被原型链覆盖)。');
}
};
const handleUpdateDirectly = () => {
const data = closureManager.getData();
//
data.name = '被直接修改的名称 ' + Math.floor(Math.random() * 100);
refreshDisplay();
message('通过引用直接修改成功', { type: 'success' });
// 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 resetState = () => {
closureManager.reset();
refreshDisplay();
message('状态已重置');
const checkConfig = () => {
const config = getValue('config');
addLog(`当前闭包内的 config: ${JSON.stringify(config)}`);
};
onMounted(() => {
refreshDisplay();
//
onUnmounted(() => {
if (hackKey.value) {
// @ts-ignore
delete Object.prototype[hackKey.value];
}
});
</script>
<style scoped lang="scss">
pre {
font-family: 'Courier New', Courier, monospace;
}
<style scoped>
</style>

Loading…
Cancel
Save