Skip to content

Commit 58b9899

Browse files
authored
Merge pull request #180 from Javier-bat/hotfix/security_patch_bypass
update for firefox
2 parents 81a5458 + df00580 commit 58b9899

File tree

5 files changed

+261
-74
lines changed

5 files changed

+261
-74
lines changed

WPlace-Helper-FireFox/public/popup.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,16 @@
152152
<span>Copy</span>
153153
</button>
154154
</div>
155+
<div class="input-row">
156+
<input type="text" id="xpaw-input" readonly value="No xpaw token captured yet...">
157+
<button id="copy-xpaw" class="btn" title="Copy xpaw token">
158+
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
159+
<path d="M9 8.5A2.5 2.5 0 0 1 11.5 6h5A2.5 2.5 0 0 1 19 8.5v8A2.5 2.5 0 0 1 16.5 19h-5A2.5 2.5 0 0 1 9 16.5v-8Z" stroke="currentColor" stroke-width="1.5"/>
160+
<path d="M7.5 16.5A2.5 2.5 0 0 1 5 14V7.5A2.5 2.5 0 0 1 7.5 5H14" stroke="currentColor" stroke-width="1.5"/>
161+
</svg>
162+
<span>Copy</span>
163+
</button>
164+
</div>
155165
<div class="input-row">
156166
<div class="input-pair">
157167
<input type="text" id="world-x" readonly value="-" title="World X" placeholder="World X">

WPlace-Helper-FireFox/scripts/background.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,25 @@
11
chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
22
if (msg && msg.type === 'wplace_token_found' && msg.token) {
33
const toStore = { wplace_token: msg.token };
4+
if (msg.xpaw) toStore.wplace_xpaw_token = msg.xpaw;
45
if (msg.worldX) toStore.wplace_world_x = String(msg.worldX);
56
if (msg.worldY) toStore.wplace_world_y = String(msg.worldY);
67
chrome.storage.local.set(toStore, () => {
78
sendResponse({ ok: true });
89
});
10+
// Also try to POST the token to local app for instant paste
11+
// Try localhost and 127.0.0.1, ignore errors
12+
const payloadObj = {
13+
token: String(msg.token),
14+
xpaw: msg.xpaw || null,
15+
fp: msg.fp || null,
16+
worldX: msg.worldX || null,
17+
worldY: msg.worldY || null,
18+
};
19+
const payload = JSON.stringify(payloadObj);
20+
const headers = { 'Content-Type': 'application/json' };
21+
try { fetch('http://localhost:3000/api/token', { method: 'POST', headers, body: payload, mode: 'no-cors' }).catch(() => {}); } catch (_) {}
22+
try { fetch('http://127.0.0.1:3000/api/token', { method: 'POST', headers, body: payload, mode: 'no-cors' }).catch(() => {}); } catch (_) {}
923
return true;
1024
}
1125
});

WPlace-Helper-FireFox/scripts/content.js

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,20 @@
1010
})();
1111

1212
window.addEventListener('message', function(ev) {
13-
if (!ev || !ev.data) return;
14-
const msg = ev.data;
15-
if (msg && msg.__wplace && msg.type === 'token_found' && msg.token) {
16-
try { chrome.runtime.sendMessage({ type: 'wplace_token_found', token: msg.token, worldX: msg.worldX, worldY: msg.worldY }); } catch (e) {}
17-
}
13+
if (!ev || !ev.data) return;
14+
const msg = ev.data;
15+
if (msg && msg.__wplace && msg.type === 'token_found' && msg.token) {
16+
try {
17+
chrome.runtime.sendMessage({
18+
type: 'wplace_token_found',
19+
token: msg.token,
20+
xpaw: msg.xpaw,
21+
fp: msg.fp,
22+
worldX: msg.worldX,
23+
worldY: msg.worldY,
24+
});
25+
} catch (e) {}
26+
}
1827
});
1928

2029
(function syncToggle() {

WPlace-Helper-FireFox/scripts/pageHook.js

Lines changed: 199 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,111 @@
44
const targetOrigin = 'https://backend.wplace.live';
55
const targetPathPrefix = '/s0/pixel/';
66

7-
function postToken(token, worldX, worldY) {
8-
try {
9-
window.postMessage({ __wplace: true, type: 'token_found', token, worldX, worldY }, '*');
10-
} catch (e) {}
11-
}
7+
function postToken(token, worldX, worldY, xpaw) {
8+
try {
9+
const fp = genFp();
10+
window.postMessage({ __wplace: true, type: 'token_found', token, xpaw, fp, worldX, worldY }, '*');
11+
} catch (e) {}
12+
}
13+
14+
function genFp() {
15+
const seed = Date.now().toString() + Math.random().toString(16).slice(2);
16+
return md5(seed);
17+
}
18+
19+
function md5(str) {
20+
function add(x, y) { return (x + y) & 0xffffffff; }
21+
function rol(x, c) { return (x << c) | (x >>> (32 - c)); }
22+
function cmn(q, a, b, x, s, t) { return add(rol(add(add(a, q), add(x, t)), s), b); }
23+
function ff(a, b, c, d, x, s, t) { return cmn((b & c) | ((~b) & d), a, b, x, s, t); }
24+
function gg(a, b, c, d, x, s, t) { return cmn((b & d) | (c & (~d)), a, b, x, s, t); }
25+
function hh(a, b, c, d, x, s, t) { return cmn(b ^ c ^ d, a, b, x, s, t); }
26+
function ii(a, b, c, d, x, s, t) { return cmn(c ^ (b | (~d)), a, b, x, s, t); }
27+
function md51(str) {
28+
const n = ((str.length + 8) >> 6) + 1;
29+
const blks = new Array(n * 16).fill(0);
30+
for (let i = 0; i < str.length; i++) blks[i >> 2] |= str.charCodeAt(i) << ((i % 4) * 8);
31+
blks[str.length >> 2] |= 0x80 << ((str.length % 4) * 8);
32+
blks[n * 16 - 2] = str.length * 8;
33+
let a = 1732584193, b = -271733879, c = -1732584194, d = 271733878;
34+
for (let i = 0; i < blks.length; i += 16) {
35+
const oa = a, ob = b, oc = c, od = d;
36+
a = ff(a, b, c, d, blks[i], 7, -680876936);
37+
d = ff(d, a, b, c, blks[i+1], 12, -389564586);
38+
c = ff(c, d, a, b, blks[i+2], 17, 606105819);
39+
b = ff(b, c, d, a, blks[i+3], 22, -1044525330);
40+
a = ff(a, b, c, d, blks[i+4], 7, -176418897);
41+
d = ff(d, a, b, c, blks[i+5], 12, 1200080426);
42+
c = ff(c, d, a, b, blks[i+6], 17, -1473231341);
43+
b = ff(b, c, d, a, blks[i+7], 22, -45705983);
44+
a = ff(a, b, c, d, blks[i+8], 7, 1770035416);
45+
d = ff(d, a, b, c, blks[i+9], 12, -1958414417);
46+
c = ff(c, d, a, b, blks[i+10], 17, -42063);
47+
b = ff(b, c, d, a, blks[i+11], 22, -1990404162);
48+
a = ff(a, b, c, d, blks[i+12], 7, 1804603682);
49+
d = ff(d, a, b, c, blks[i+13], 12, -40341101);
50+
c = ff(c, d, a, b, blks[i+14], 17, -1502002290);
51+
b = ff(b, c, d, a, blks[i+15], 22, 1236535329);
52+
a = gg(a, b, c, d, blks[i+1], 5, -165796510);
53+
d = gg(d, a, b, c, blks[i+6], 9, -1069501632);
54+
c = gg(c, d, a, b, blks[i+11], 14, 643717713);
55+
b = gg(b, c, d, a, blks[i], 20, -373897302);
56+
a = gg(a, b, c, d, blks[i+5], 5, -701558691);
57+
d = gg(d, a, b, c, blks[i+10], 9, 38016083);
58+
c = gg(c, d, a, b, blks[i+15], 14, -660478335);
59+
b = gg(b, c, d, a, blks[i+4], 20, -405537848);
60+
a = gg(a, b, c, d, blks[i+9], 5, 568446438);
61+
d = gg(d, a, b, c, blks[i+14], 9, -1019803690);
62+
c = gg(c, d, a, b, blks[i+3], 14, -187363961);
63+
b = gg(b, c, d, a, blks[i+8], 20, 1163531501);
64+
a = gg(a, b, c, d, blks[i+13], 5, -1444681467);
65+
d = gg(d, a, b, c, blks[i+2], 9, -51403784);
66+
c = gg(c, d, a, b, blks[i+7], 14, 1735328473);
67+
b = gg(b, c, d, a, blks[i+12], 20, -1926607734);
68+
a = hh(a, b, c, d, blks[i+5], 4, -378558);
69+
d = hh(d, a, b, c, blks[i+8], 11, -2022574463);
70+
c = hh(c, d, a, b, blks[i+11], 16, 1839030562);
71+
b = hh(b, c, d, a, blks[i+14], 23, -35309556);
72+
a = hh(a, b, c, d, blks[i+1], 4, -1530992060);
73+
d = hh(d, a, b, c, blks[i+4], 11, 1272893353);
74+
c = hh(c, d, a, b, blks[i+7], 16, -155497632);
75+
b = hh(b, c, d, a, blks[i+10], 23, -1094730640);
76+
a = hh(a, b, c, d, blks[i+13], 4, 681279174);
77+
d = hh(d, a, b, c, blks[i], 11, -358537222);
78+
c = hh(c, d, a, b, blks[i+3], 16, -722521979);
79+
b = hh(b, c, d, a, blks[i+6], 23, 76029189);
80+
a = hh(a, b, c, d, blks[i+9], 4, -640364487);
81+
d = hh(d, a, b, c, blks[i+12], 11, -421815835);
82+
c = hh(c, d, a, b, blks[i+15], 16, 530742520);
83+
b = hh(b, c, d, a, blks[i+2], 23, -995338651);
84+
a = ii(a, b, c, d, blks[i], 6, -198630844);
85+
d = ii(d, a, b, c, blks[i+7], 10, 1126891415);
86+
c = ii(c, d, a, b, blks[i+14], 15, -1416354905);
87+
b = ii(b, c, d, a, blks[i+5], 21, -57434055);
88+
a = ii(a, b, c, d, blks[i+12], 6, 1700485571);
89+
d = ii(d, a, b, c, blks[i+3], 10, -1894986606);
90+
c = ii(c, d, a, b, blks[i+10], 15, -1051523);
91+
b = ii(b, c, d, a, blks[i+1], 21, -2054922799);
92+
a = ii(a, b, c, d, blks[i+8], 6, 1873313359);
93+
d = ii(d, a, b, c, blks[i+15], 10, -30611744);
94+
c = ii(c, d, a, b, blks[i+6], 15, -1560198380);
95+
b = ii(b, c, d, a, blks[i+13], 21, 1309151649);
96+
a = ii(a, b, c, d, blks[i+4], 6, -145523070);
97+
d = ii(d, a, b, c, blks[i+11], 10, -1120210379);
98+
c = ii(c, d, a, b, blks[i+2], 15, 718787259);
99+
b = ii(b, c, d, a, blks[i+9], 21, -343485551);
100+
a = add(a, oa); b = add(b, ob); c = add(c, oc); d = add(d, od);
101+
}
102+
return [a, b, c, d];
103+
}
104+
function toHex(num) {
105+
let s = '';
106+
for (let j = 0; j < 4; j++) s += ('0' + ((num >> (j * 8)) & 0xff).toString(16)).slice(-2);
107+
return s;
108+
}
109+
const out = md51(str);
110+
return toHex(out[0]) + toHex(out[1]) + toHex(out[2]) + toHex(out[3]);
111+
}
12112

13113
function isTarget(url) {
14114
return typeof url === 'string' && url.startsWith(targetOrigin) && url.includes(targetPathPrefix);
@@ -40,19 +140,40 @@
40140
return Promise.resolve('');
41141
}
42142

43-
function tryExtractTokenFromText(text) {
44-
if (!text) return null;
45-
try {
46-
const obj = JSON.parse(text);
47-
if (obj && obj.t) return obj.t;
48-
} catch (_) {}
49-
try {
50-
const params = new URLSearchParams(text);
51-
const t = params.get('t');
52-
if (t) return t;
53-
} catch (_) {}
54-
return null;
55-
}
143+
function extractBodyFields(text) {
144+
const out = { token: null };
145+
if (!text) return out;
146+
try {
147+
const obj = JSON.parse(text);
148+
if (obj && typeof obj === 'object' && obj.t) out.token = obj.t;
149+
} catch (_) {
150+
try {
151+
const params = new URLSearchParams(text);
152+
out.token = params.get('t');
153+
} catch (_) {}
154+
}
155+
return out;
156+
}
157+
158+
function extractHeader(headers, name) {
159+
if (!headers || !name) return null;
160+
const lowerName = name.toLowerCase();
161+
try {
162+
if (headers instanceof Headers) {
163+
return headers.get(name) || headers.get(lowerName);
164+
}
165+
if (Array.isArray(headers)) {
166+
for (const h of headers) {
167+
if (Array.isArray(h) && h[0] && h[0].toLowerCase() === lowerName) return h[1];
168+
}
169+
} else if (typeof headers === 'object') {
170+
for (const k in headers) {
171+
if (k && k.toLowerCase() === lowerName) return headers[k];
172+
}
173+
}
174+
} catch (_) {}
175+
return null;
176+
}
56177

57178
try {
58179
window.addEventListener('message', function(ev) {
@@ -63,60 +184,73 @@
63184
});
64185
} catch (e) {}
65186

66-
const originalFetch = window.fetch;
67-
window.fetch = async function(input, init) {
68-
const url = typeof input === 'string' ? input : (input && input.url);
69-
if (isTarget(url)) {
70-
try {
71-
if (ENABLED) {
72-
const body = init && init.body;
73-
const text = await decodeBodyToText(body);
74-
const token = tryExtractTokenFromText(text);
75-
const { x, y } = extractWorldXY(url);
76-
if (token) postToken(token, x, y);
77-
}
78-
} catch (e) {}
79-
if (ENABLED) {
80-
return new Response(null, { status: 204, statusText: 'No Content' });
81-
}
82-
}
83-
return originalFetch.apply(this, arguments);
84-
};
187+
const originalFetch = window.fetch;
188+
window.fetch = async function(input, init) {
189+
const url = typeof input === 'string' ? input : (input && input.url);
190+
if (isTarget(url)) {
191+
try {
192+
if (ENABLED) {
193+
const body = init && init.body;
194+
const text = await decodeBodyToText(body);
195+
const { token } = extractBodyFields(text);
196+
const headersSource = (init && init.headers) || (input && input.headers);
197+
const xpaw = extractHeader(headersSource, 'x-pawtect-token');
198+
const { x, y } = extractWorldXY(url);
199+
if (token) postToken(token, x, y, xpaw);
200+
}
201+
} catch (e) {}
202+
// Block the pixel POST after capturing token to avoid sending from page directly
203+
if (ENABLED) {
204+
return new Response(null, { status: 204, statusText: 'No Content' });
205+
}
206+
}
207+
return originalFetch.apply(this, arguments);
208+
};
85209

86-
const originalOpen = XMLHttpRequest.prototype.open;
87-
const originalSend = XMLHttpRequest.prototype.send;
88-
let lastUrl = null;
89-
XMLHttpRequest.prototype.open = function(method, url) {
90-
lastUrl = url;
91-
return originalOpen.apply(this, arguments);
92-
};
93-
XMLHttpRequest.prototype.send = function(body) {
94-
if (isTarget(lastUrl)) {
95-
try {
96-
if (ENABLED) {
97-
decodeBodyToText(body).then(text => {
98-
const token = tryExtractTokenFromText(text);
99-
const { x, y } = extractWorldXY(lastUrl);
100-
if (token) postToken(token, x, y);
101-
});
102-
}
103-
} catch (e) {}
104-
}
105-
return originalSend.apply(this, arguments);
106-
};
210+
const originalOpen = XMLHttpRequest.prototype.open;
211+
const originalSend = XMLHttpRequest.prototype.send;
212+
const originalSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
213+
let lastUrl = null;
214+
XMLHttpRequest.prototype.open = function(method, url) {
215+
lastUrl = url;
216+
this.__xpaw = null;
217+
return originalOpen.apply(this, arguments);
218+
};
219+
XMLHttpRequest.prototype.setRequestHeader = function(name, value) {
220+
if (name && name.toLowerCase() === 'x-pawtect-token') {
221+
try { this.__xpaw = value; } catch (_) {}
222+
}
223+
return originalSetRequestHeader.apply(this, arguments);
224+
};
225+
XMLHttpRequest.prototype.send = function(body) {
226+
if (isTarget(lastUrl)) {
227+
try {
228+
if (ENABLED) {
229+
decodeBodyToText(body).then(text => {
230+
const { token } = extractBodyFields(text);
231+
const { x, y } = extractWorldXY(lastUrl);
232+
const xpaw = this.__xpaw || null;
233+
if (token) postToken(token, x, y, xpaw);
234+
this.__xpaw = null;
235+
});
236+
}
237+
} catch (e) {}
238+
}
239+
return originalSend.apply(this, arguments);
240+
};
107241

108242
const originalSendBeacon = navigator.sendBeacon ? navigator.sendBeacon.bind(navigator) : null;
109243
if (originalSendBeacon) {
110244
navigator.sendBeacon = function(url, data) {
111245
if (isTarget(url)) {
112246
try {
113-
if (ENABLED) {
114-
decodeBodyToText(data).then(text => {
115-
const token = tryExtractTokenFromText(text);
116-
const { x, y } = extractWorldXY(url);
117-
if (token) postToken(token, x, y);
118-
});
119-
}
247+
if (ENABLED) {
248+
decodeBodyToText(data).then(text => {
249+
const { token } = extractBodyFields(text);
250+
const { x, y } = extractWorldXY(url);
251+
if (token) postToken(token, x, y, null);
252+
});
253+
}
120254
} catch (e) {}
121255
if (ENABLED) {
122256
return false;

0 commit comments

Comments
 (0)