From c6ad6fcc04dd0c27806bcf456f4debc0b6743024 Mon Sep 17 00:00:00 2001 From: Theodore Dubois Date: Sun, 1 Sep 2024 14:26:57 -0700 Subject: [PATCH 1/2] Obviate CSP Just load the sandbox from a blob url, then the extension CSP doesn't apply to it. --- pack.js | 1 - scripts/twchallenge.js | 46 ++++++++++++++++++++++++------------------ 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/pack.js b/pack.js index a73bb3a1..2b9266ea 100644 --- a/pack.js +++ b/pack.js @@ -59,7 +59,6 @@ copyDir('./', '../OldTwitterFirefox').then(async () => { "webRequest", "webRequestBlocking" ]; - manifest.content_security_policy = "script-src 'self' 'unsafe-eval' 'sha256-0c7AR7s38d85qcAifgyf/pxhEECsIYaQQxFzScXjrKI='; object-src 'self'", delete manifest.sandbox; delete manifest.host_permissions; delete manifest.declarative_net_request; diff --git a/scripts/twchallenge.js b/scripts/twchallenge.js index 7bde1fe2..37898b8c 100644 --- a/scripts/twchallenge.js +++ b/scripts/twchallenge.js @@ -1,3 +1,4 @@ +let solverIframe; let solveId = 0; let solveCallbacks = {}; let solveQueue = [] @@ -5,21 +6,30 @@ let solverReady = false; let solverErrored = false; let sentData = false; -let solverIframe = document.createElement('iframe'); -solverIframe.style.display = 'none'; -solverIframe.src = chrome.runtime.getURL(`sandbox.html`); -let injectedBody = document.getElementById('injected-body'); -if(injectedBody) { - injectedBody.appendChild(solverIframe); -} else { - let int = setInterval(() => { - let injectedBody = document.getElementById('injected-body'); - if(injectedBody) { - injectedBody.appendChild(solverIframe); - clearInterval(int); - } - }, 10); +let sandboxUrl = fetch(chrome.runtime.getURL(`sandbox.html`)) + .then(resp => resp.blob()) + .then(blob => URL.createObjectURL(blob)) + .catch(console.error); + +function createSolverFrame() { + if (solverIframe) solverIframe.remove(); + solverIframe = document.createElement('iframe'); + solverIframe.style.display = 'none'; + sandboxUrl.then(url => solverIframe.src = url); + let injectedBody = document.getElementById('injected-body'); + if(injectedBody) { + injectedBody.appendChild(solverIframe); + } else { + let int = setInterval(() => { + let injectedBody = document.getElementById('injected-body'); + if(injectedBody) { + injectedBody.appendChild(solverIframe); + clearInterval(int); + } + }, 10); + } } +createSolverFrame(); function solveChallenge(path, method) { return new Promise((resolve, reject) => { @@ -51,11 +61,7 @@ function solveChallenge(path, method) { setInterval(() => { if(!document.getElementById('loading-box').hidden && sentData && solveQueue.length) { console.log("Something's wrong with the challenge solver, reloading", solveQueue); - solverIframe.remove(); - solverIframe = document.createElement('iframe'); - solverIframe.style.display = 'none'; - solverIframe.src = chrome.runtime.getURL(`sandbox.html`); - document.getElementById('injected-body').appendChild(solverIframe); + createSolverFrame(); initChallenge(); } }, 2000); @@ -187,4 +193,4 @@ async function initChallenge() { } }; -initChallenge(); \ No newline at end of file +initChallenge(); From 7ea802a399276a83eb20e4704fc1968fdc122a4e Mon Sep 17 00:00:00 2001 From: Theodore Dubois Date: Sun, 1 Sep 2024 14:31:07 -0700 Subject: [PATCH 2/2] Bypass Enhanced Tracking Protection Load the abs.twimg.com in the sandbox which does not run into the firefox builtin blocker, i'm guessing because its js world is considered part of the twitter.com page and thus allowed to bypass the twitter.com blocker or some shit idk --- sandbox.html | 26 ++++++++++++++++++++++---- scripts/twchallenge.js | 17 ----------------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/sandbox.html b/sandbox.html index a9cbfc60..c31b36a4 100644 --- a/sandbox.html +++ b/sandbox.html @@ -22,6 +22,24 @@ let data = event.data; if (data.action === 'init') { try { + let url = `https://abs.twimg.com/responsive-web/client-web/ondemand.s.${data.challengeCode}a.js` + let challengeData; + try { + challengeData = await fetch(url).then(res => res.text()); + } catch(e) { + await sleep(500); + try { + challengeData = await fetch(url).then(res => res.text()); + } catch(e) { + await sleep(1000); + try { + challengeData = await fetch(url).then(res => res.text()); + } catch(e) { + throw new Error('Failed to fetch challenge data: ' + e); + } + } + } + let animsDiv = document.getElementById('anims'); for(let anim of data.anims) { animsDiv.innerHTML += `\n${anim}`; @@ -29,10 +47,10 @@ let verif = document.querySelector('meta[name="twitter-site-verification"]'); verif.content = data.verificationCode; let headerRegex = /(\d+):(.+)=>.+default:\(\)=>(\w).+,\w\(\d+\)\;/; - let headerMatch = data.code.match(headerRegex); + let headerMatch = challengeData.match(headerRegex); if(!headerMatch) { console.error('Uh oh, header not found!! Report to https://github.com/dimdenGD/OldTwitter/issues'); - event.source.postMessage({action: 'initError', error: `Header not found at ${data.challengeCode} (${String(data.code).slice(0, 500)}...)`}, event.origin); + event.source.postMessage({action: 'initError', error: `Header not found at ${data.challengeCode} (${String(challengeData).slice(0, 500)}...)`}, event.origin); initError = true; return; } @@ -41,7 +59,7 @@ // It only ever executes code from trusted Twitter domain, abs.twimg.com (specifically their script that generates security headers) // It's impossible to have it contained in extension itself, since it's generated dynamically // you can see where script is loaded in scripts/twchallenge.js - eval(data.code.replace(headerRegex, '$1:$2=>{window._CHALLENGE=()=>$3;')); + eval(challengeData.replace(headerRegex, '$1:$2=>{window._CHALLENGE=()=>$3;')); let id = headerMatch[1]; webpackChunk_twitter_responsive_web[0][1][id](); solver = window._CHALLENGE()(); @@ -85,4 +103,4 @@ }); - \ No newline at end of file + diff --git a/scripts/twchallenge.js b/scripts/twchallenge.js index 37898b8c..61f8a118 100644 --- a/scripts/twchallenge.js +++ b/scripts/twchallenge.js @@ -149,22 +149,6 @@ async function initChallenge() { let anims = Array.from(dom.querySelectorAll('svg[id^="loading-x"]')).map(svg => svg.outerHTML); let challengeCode = homepageData.match(/"ondemand.s":"(\w+)"/)[1]; - let challengeData; - try { - challengeData = await _fetch(`https://abs.twimg.com/responsive-web/client-web/ondemand.s.${challengeCode}a.js`).then(res => res.text()); - } catch(e) { - await sleep(500); - try { - challengeData = await _fetch(`https://abs.twimg.com/responsive-web/client-web/ondemand.s.${challengeCode}a.js`).then(res => res.text()); - } catch(e) { - await sleep(1000); - try { - challengeData = await _fetch(`https://abs.twimg.com/responsive-web/client-web/ondemand.s.${challengeCode}a.js`).then(res => res.text()); - } catch(e) { - throw new Error('Failed to fetch challenge data: ' + e); - } - } - } OLDTWITTER_CONFIG.verificationKey = verificationKey; @@ -173,7 +157,6 @@ async function initChallenge() { if(!solverIframe || !solverIframe.contentWindow) return setTimeout(sendInit, 50); solverIframe.contentWindow.postMessage({ action: 'init', - code: challengeData, challengeCode, anims, verificationCode: OLDTWITTER_CONFIG.verificationKey