Skip to content

Commit c7ed5f9

Browse files
CodFrmcyfung1031
andauthored
🔒 修复 CustomEvent 通信密钥泄露漏洞 (#1317)
* 🔒 修复 CustomEvent 通信密钥泄露漏洞 在模块顶层保存原生 CustomEvent、MouseEvent、dispatchEvent、addEventListener 引用, 防止页面脚本通过 hook 全局构造函数窃取 IPC 通信密钥,伪造 GM_* API 调用。 * 代码对齐最新做法 * 修正错误测试代码 * 处理jest问题 --------- Co-authored-by: cyfung1031 <44498510+cyfung1031@users.noreply.github.com>
1 parent c0e02d4 commit c7ed5f9

7 files changed

Lines changed: 44 additions & 12 deletions

File tree

.eslintrc.cjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ module.exports = {
2121
globals: {
2222
GMTypes: "readonly",
2323
GMSend: "readonly",
24+
cloneInto: "readonly",
2425
},
2526
plugins: ["react", "@typescript-eslint", "prettier"],
2627
rules: {

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,9 @@ CHANGELOG.md
2929
tailwind.config.js
3030

3131
.env
32+
33+
superpowers
34+
.claude
35+
CLAUDE.md
36+
37+
test-results

jest.config.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ module.exports = {
1313
"\\.m[jt]s$": "babel-jest",
1414
},
1515
transformIgnorePatterns: ["node_modules/(?!(uuid|dexi|yaml|webdav))"],
16-
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
17-
setupFiles: ["./pkg/chrome-extension-mock/index.ts"],
16+
setupFiles: ["./pkg/chrome-extension-mock/index.ts", "<rootDir>/jest.setup.js"],
1817
moduleDirectories: ["node_modules", "src"],
1918
watch: false,
2019
};

jest.setup.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
global.fetch = () => {
33
return Promise.reject(new Error("not implemented"));
44
};
5+

src/app/message/common.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// 避免页面载入后改动全域物件导致消息传递失败
2+
export const MouseEventClone = MouseEvent;
3+
export const CustomEventClone = CustomEvent;
4+
5+
const performanceClone = (process.env.VI_TESTING === "true" ? new EventTarget() : performance) as Performance;
6+
7+
// 避免页面载入后改动 EventTarget.prototype 的方法导致消息传递失败
8+
export const pageDispatchEvent = performanceClone.dispatchEvent.bind(performanceClone);
9+
export const pageAddEventListener = performanceClone.addEventListener.bind(performanceClone);
10+
export const pageRemoveEventListener = performanceClone.removeEventListener.bind(performanceClone);
11+
const detailClone = typeof cloneInto === "function" ? cloneInto : null;
12+
export const pageDispatchCustomEvent = <T = any>(eventType: string, detail: T) => {
13+
if (detailClone && detail) detail = <T>detailClone(detail, performanceClone);
14+
const ev = new CustomEventClone(eventType, {
15+
detail,
16+
cancelable: true,
17+
});
18+
return pageDispatchEvent(ev);
19+
};

src/app/message/content.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import LoggerCore from "../logger/core";
22
import { type Channel } from "./channel";
3+
import { CustomEventClone, MouseEventClone, pageAddEventListener, pageDispatchEvent } from "./common";
34
import {
45
ChannelManager,
56
MessageHander,
@@ -34,7 +35,7 @@ export default class MessageContent
3435
this.nativeSend(data);
3536
});
3637
this.relatedTarget = new Map<number, Element>();
37-
window.addEventListener(
38+
pageAddEventListener(
3839
(isContent ? "ct" : "fd") + eventId,
3940
(event: unknown) => {
4041
if (event instanceof MouseEvent) {
@@ -113,11 +114,14 @@ export default class MessageContent
113114
delete detail.data.relatedTarget;
114115
detail.data.relatedTarget = Math.ceil(Math.random() * 1000000);
115116
// 可以使用此种方式交互element
116-
const ev = new MouseEvent((this.isContent ? "fd" : "ct") + this.eventId, {
117-
clientX: detail.data.relatedTarget,
118-
relatedTarget: target,
119-
});
120-
window.dispatchEvent(ev);
117+
const ev = new MouseEventClone(
118+
(this.isContent ? "fd" : "ct") + this.eventId,
119+
{
120+
clientX: detail.data.relatedTarget,
121+
relatedTarget: target,
122+
}
123+
);
124+
pageDispatchEvent(ev);
121125
}
122126

123127
if (typeof cloneInto !== "undefined") {
@@ -132,10 +136,11 @@ export default class MessageContent
132136
}
133137
}
134138

135-
const ev = new CustomEvent((this.isContent ? "fd" : "ct") + this.eventId, {
136-
detail,
137-
});
138-
window.dispatchEvent(ev);
139+
const ev = new CustomEventClone(
140+
(this.isContent ? "fd" : "ct") + this.eventId,
141+
{ detail }
142+
);
143+
pageDispatchEvent(ev);
139144
}
140145

141146
public send(action: string, data: any) {

src/inject.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const logger = new LoggerCore({
1616
labels: { env: "inject", href: window.location.href },
1717
});
1818

19+
1920
message.setHandler("pageLoad", (_action, data) => {
2021
logger.logger().debug("inject start");
2122
const runtime = new InjectRuntime(message, data.scripts, flag);

0 commit comments

Comments
 (0)