Latest Posts (20 found)
Himanshu Anand 1 weeks ago

a fake resume invoked China's defence-tech elite, then installed VShell

Disclosure: this research was conducted using an ANY.RUN account provided as part of a collaboration. All analysis and conclusions are my own. I did not execute the malware locally. Local work was limited to archive extraction, parsing, disassembly and decoding traffic already captured by the sandbox. I found a Chinese-language “resume” that is actually a Windows executable. It claims to come from a student at one of China’s Seven Sons of National Defence , opens a real Word document, which is nice of it and quietly delivers SNOWLIGHT and the VShell remote-access trojan , which is less nice. The chain is: What is it meant to do? Give an operator quiet, interactive access to a professor or research-lab workstation: command execution, file access, screen capture and a route further into the victim network. That is the most defensible objective. The malware proves remote access. The decoy tells us why that access was packaged as this particular document. The lure is most consistent with a mainland Chinese academic recipient , at moderate confidence. That describes the intended victim, not the attacker. Attribution is less exciting: this is best described as an unattributed actor using a mainland-China-oriented academic lure and the now widely available SNOWLIGHT/VShell ecosystem . The evidence does not establish the operator’s language, nationality, employer or government sponsor. UNC5174 is relevant history, not a conclusion. The sample appeared in ANY.RUN with a filename that translates roughly to: Inside the archive was an executable carrying nearly the same name. Windows hides known extensions by default, document icons remain a thing and humans remain human. This business model has survived more Windows releases than Internet Explorer. ANY.RUN gave it a 100/100 malicious score, labeled the process and showed the executable launching , opening a DOCX in Word and talking to one IP in a Hong Kong-registered netblock over two ports. Public geolocation sources disagree about where the server was physically located, so the registration is the claim I can actually support. Public task: ANY.RUN analysis 8d27f4bf-ed8c-461d-96e6-86968464dd86 The obvious answer was “it is a RAT.” The useful question was what does each stage do and what does the combination tell us about the operator’s objective? So I took it apart. The malware downloads a real Word document from: It saves it as: Then it launches Word. The victim gets the thing they clicked for, while the interesting part continues in memory. At first glance it looks normal: Zhang Yuguang, male, Beijing Institute of Technology, class of 2026, network engineering. Then you read past the first paragraph. The education section says the major is Electrical Engineering and Automation , not Network Engineering. The grades field is blank. The honors say “delete as needed.” The certificates say “adjust as needed.” There is no phone number, email address, employer or verifiable internship. It is a three-page resume that forgot the small detail of telling the reader how to contact the applicant. The research interests are much more specific: This is not really a corporate job application. It reads like an unfinished template for contacting a prospective graduate supervisor . That changes the victim model. The named university is probably the applicant’s claimed school, not the target. The likely recipient is a professor or research group working on electrical engineering, energy systems or applied AI, possibly at another institution. Beijing Institute of Technology is not just a convenient university name copied from a rankings table. ASPI’s China Defence Universities Tracker categorizes BIT as one of the Seven Sons of National Defence , a group of universities administered by China’s Ministry of Industry and Information Technology and closely associated with the country’s defence research ecosystem. The tracker lists BIT research areas including armament science, control systems, communications, optical engineering and unmanned aerial vehicles. BIT’s own laboratories page prominently lists the State Key Laboratory of Explosion Science and Technology and the Key Laboratory of Intelligent Control and Decision of Complex System. Subtle institution. Very difficult to imagine why a social engineer might consider the name technically prestigious. The harder-edged fact comes from the U.S. government. In December 2020, the Commerce Department added BIT to the Entity List . The published rule says BIT was added for “acquiring and attempting to acquire U.S.-origin items in support of programs for the People’s Liberation Army.” That makes the lure more interesting. A supposed student from a prestigious, defence-linked technical university is a credible reason for a professor working on power systems, intelligent control or applied AI to open a research statement. The affiliation adds authority and may signal that the sender’s work is worth reading. It does not prove any of the following: The malware gives us a claimed affiliation, not an enrollment record. The accurate headline is that the lure invoked the name of a defence-linked university . Anything stronger would turn an excellent hook into a false allegation. Metadata supports recent preparation but does not identify an operator: The sample ran on 21 August, eight days after the last edit. The WPS user ID and author fields may have come from a template. Treating inherited Office metadata as an operator passport is how threat-intel fan fiction begins. There are four different questions here and combining them produces bad attribution very efficiently: The first two are directly observable. The document uses Simplified Chinese and claims to be from a Beijing Institute of Technology student. The claim is unverified. The fourth is unknown. The third can be assessed, but only probabilistically. The mainland-China hypothesis is strongest because the document combines several local academic markers: A Taiwan-oriented version would be expected to use Traditional Chinese forms such as , , or . It does not. Hong Kong, Macau, Singapore and overseas Chinese-speaking supervisors remain possible, but an English-language, department- or laboratory-framed application would be less surprising in those settings. None of this proves the server operator was in mainland China. It says the lure writer wanted the file to look native to a mainland academic workflow. The likely recipient is a professor or research-group leader working on power systems, electrical engineering, renewable-energy control or applied AI. Beijing Institute of Technology is the claimed applicant’s institution, not necessarily the victim’s. Assessment: mainland China is the leading intended-victim geography at moderate confidence; attacker geography remains unknown. The first executable is a 5,073,728-byte, 32-bit Go binary built with Go 1.22.0 for Windows , with , a zeroed linker timestamp and a nominal Kingsoft signature that fails validation with . Its internal project name is and unlike the final payload it left useful symbols and source paths behind: The spelling is not mine. Malware developers also ship on Fridays. refers to Weibu / ThreatBook , a Chinese threat-intelligence and sandbox provider. The function obtains its own executable directory, lowercases it and checks whether it contains: If that sandbox-like path is present, it enumerates the directory and looks for a filename containing . Matching the condition terminates execution. If the path is absent, the function returns and execution continues. This is not generic “maybe I am in a VM” behavior. It is a check written for a particular analysis environment. The loader reads Go’s cached logical-processor count and exits when the machine has fewer than four. Small sandbox VMs often receive one or two. A real professor’s workstation probably has more. Very scientific threat model: if the computer can handle MATLAB, it can handle malware. Instead of trusting , the loader dynamically resolves , calls it at 30,000 Hz for ten seconds, records the time before and after and checks that ten whole seconds elapsed. If the sandbox accelerates time, it exits. On normal desktops that frequency is inaudible; the function name is funnier than the user experience. The loader’s strings are not stored as normal URLs. The decoding pipeline is: AES key and IV: Decoded values: This is reproducible directly from the embedded bytes. The first value begins as 128 printable Base32hex characters, becomes 80 bytes after Base32hex decoding, 72 after AES-CBC and padding removal, then 53 bytes after inversion and Base64 decoding: the full staging URL. The other two values follow the same pipeline. No live request is required to recover them. is the Word decoy. is encrypted shellcode. The names are camouflage for anyone glancing at HTTP logs and apparently losing interest after the file extension. The function named dynamically resolves: It allocates memory, copies the downloaded bytes, marks them executable, creates a thread at the payload and waits. The shellcode never needs to become a normal executable on disk. At this point the user is reading a resume and the loader is starting stage 2. Multitasking. The HTTP server identifies itself as . Its 6,224-byte response decodes to 1,454 bytes of x86 shellcode : It uses a classic ROR-13 API resolver and loads functions without a useful import table. All 16 hashes resolve to , , , , , , , , , , , , , , and . The behavior is a byte-for-byte conceptual match for the Windows SNOWLIGHT stager documented by SOCRadar , Sekoia and HPE Threat Labs . Its job is intentionally small: The exact client check-in was captured in the PCAP: That 40-byte packet is useful for detection because it is much more specific than “a Windows computer used TCP.” SNOWLIGHT does not authenticate the server, verify a signature or hash, validate an header or receive an explicit payload size. It keeps copying results into the fixed 30,000,000-byte region and appears not to enforce that boundary. The panel stager is small because it delegates both trust and memory safety to whoever answered the socket. What could go wrong has been scheduled for the next stage. The sandbox network table showed a 4 MB response, but no file appeared in the dropped-file list. That makes sense: SNOWLIGHT receives the payload directly into executable memory. The PCAP still had it. I downloaded the recorded PCAP from ANY.RUN, reassembled only the server-to-client stream for and XORed it with . No connection to the live server was made. The result is a valid nine-section, 32-bit Go PE with image base , entry point , image size 4,648,960 and a zeroed timestamp. Its 3,088,384-byte section has entropy , almost the theoretical maximum of 8, which is consistent with the embedded configuration and much of the program being encrypted or obfuscated. Go build information exists but reports ; symbols are obfuscated and there is no helpful plaintext label. The author did not leave a README inside the implant. Disappointing. Windows Defender blocked the cleartext PE immediately after reconstruction. I did not disable or bypass it. Further inspection decoded the captured stream only in the analysis process’s memory. Why call it VShell if the string is hidden? That is stronger than trusting one antivirus label. Independent static and network features all land on the same family. Execution did not stop at the final PE. Immediately after the 4.65 MB transfer, the host opened three new TCP sessions to the same listener from local ports , and . I reassembled both directions and parsed 198 complete VShell frames . Every frame uses the public protocol’s four-byte little-endian length followed by a 12-byte nonce, AES-GCM ciphertext and a 16-byte tag. Subtracting that 28-byte encryption overhead exposes a very recognizable startup sequence: The first connection is consistent with completing registration, including the 405-byte client record. The second performs another channel handshake. The third repeats small, nearly mirrored health frames for about 142 seconds. Public VShell protocol research documents the same version negotiation, MD5 verification, registration and heartbeat loop. VShell derives its AES-256-GCM key as the ASCII hex digest of . The salt is obfuscated inside this final payload. Default and common values including , , , , , and an empty string failed authentication, so I cannot show the plaintext fields. The sizes, ordering, direction-marked nonces and cadence nevertheless prove that the RAT initialized and exchanged its normal control protocol. They do not prove a human was at the console. I found no unusual frame sizes, file transfer, shell stream or other break from registration and health traffic. Observed: successful VShell registration and C2 health traffic. Not observed: an operator issuing a command. There are two answers: what this sample demonstrably does and what the delivered VShell platform allows the operator to do . Public VShell analysis and recovered console material document: This matters because VShell is not a one-shot password stealer. It is an operator platform . The initial executable does not need to know which documents are valuable or which neighboring machine should be targeted. A human can decide after the victim checks in. I did not observe an operator issuing commands in this sandbox run. I also did not prove persistence, document theft or lateral movement on this particular host. Those are capabilities and plausible follow-on actions, not facts I am going to promote to “observed” because the conclusion sounds cooler. Malware rarely contains a string saying . You infer objective by combining behavior, access level, targeting and concealment. Full VShell RAT, not a simple downloader: the operator wants interactive control after infection. That supports continued access, collection and human-directed follow-on decisions. Fileless staged execution: reducing obvious disk artifacts helps the implant stay quiet long enough for somebody to use the access. Real DOCX opened after infection: keeping the victim from suspecting failure is part of the operation, not a side effect. Graduate-supervisor language: the document is written for an academic recipient, making professor or research-group targeting more likely than corporate recruiting. AI, power-grid and renewable-energy themes: research data, institutional accounts and access to technical networks are all plausible targets. Screen, file, shell and proxy capabilities: the workstation is a foothold. It does not have to be the operator’s final destination. My confidence-graded assessment: High confidence: the immediate objective is remote access to the victim machine. Moderate confidence: the intended victim is mainland Chinese university faculty or a technical research group, not ordinary corporate HR. Moderate confidence: likely follow-on goals include collecting research material, credentials and internal access, with lateral movement available if the host is valuable. Low confidence: whether the ultimate motivation is state-directed espionage, commercial theft or access brokerage. One-line version: turn a believable academic document into quiet, full-featured control of a research workstation, then let the operator decide what is worth stealing. SNOWLIGHT has history. Mandiant named it while tracking UNC5174 and Sysdig linked SNOWLIGHT-delivered VShell to UNC5174 , a China-nexus contractor associated with espionage and access brokering. If this analysis stopped in 2025, “possible UNC5174” would be tempting. But the ecosystem changed. Cracked VShell releases and loader-generation panels circulated widely. More importantly, SNOWLIGHT is not merely malware that several actors happened to copy. It is a default stager generated by the VShell management panel . The operator selects Windows 32-bit, TCP and staged delivery; the framework produces the same broad implementation seen here. Trend Micro demonstrated why that distinction matters . Another report had assigned a SNOWLIGHT-to-VShell intrusion to UNC5174. Trend instead tied it to Earth Lamia with actor-specific VOIDMAW packaging and a repeated PDB path, while explicitly warning that SNOWLIGHT alone was unreliable because any VShell user could generate it. The panel-generated stager invalidated the tool-only attribution; the packaging artifacts supplied the positive one. HPE reached the same practical conclusion in a 2026 PyPI investigation: leaked and cracked VShell builds have moved the framework beyond an exclusive APT toolkit. What supports a mainland-China-oriented victim context here: Separate facts place parts of the development and tooling context in Chinese-language security communities: Neither category is a nationality test. The lure describes the intended social context; it does not tell us who sat behind the panel. The tooling facts may belong to an upstream loader author, copied source or a commodity framework rather than this operator. The technique was also published in Chinese offensive-development material before this sample, so copying it does not identify a private toolmaker. The last row requires the fewest leaps. Cisco Talos calls SNOWLIGHT a generic VShell stager in its UAT-8302 research and Google describes VShell as publicly available and used by actors with varying motivations in its UNC6586 reporting . Neither source links this campaign to those groups. What is missing for actor-level attribution: The infrastructure adds one useful but limited fact. A ThreatFox-derived feed listed as VShell on 15 April 2026, months before this sample used and . That supports repeated VShell use of the host. The original historical ThreatFox record is no longer available through the unauthenticated API, so I treat it as secondary-source evidence, not a campaign bridge. Best label: unattributed actor using a mainland-China-oriented academic lure and the leaked/commodity SNOWLIGHT and VShell ecosystem. Less exciting than naming an APT. More likely to survive contact with evidence. The exact campaign infrastructure is compact: An ANY.RUN TI search for the exact destination IP across the available 180-day window returned one analysis: this one. It exposed the two staging URLs, three recorded connections and the VShell network alerts, but no second sample or campaign link. Passive DNS tells a longer and mostly unrelated hosting story: OTX passive DNS directly supports the old DNS resolutions. urlscan independently observed serving nginx content from this IP in December 2024 with a certificate valid only for that hostname. No named domain appears in the malware configuration or PCAP, the hosts have no matching public urlscan results and no certificate, sample or URL joins them to VShell. Classification: historical tenants of the same IP, not campaign IOCs . The same applies to unrelated domains on the subnet or ASN. Infrastructure graphing becomes fiction if every former neighbor is invited to the conspiracy. Block or investigate: High-signal SNOWLIGHT check-in: This is , network-order port , the ASCII IP and zero-filled fields. Adapt the IP and port portions if hunting generically across generated SNOWLIGHT samples. The Kingsoft certificate attached to the first loader is invalid with . It is camouflage, not evidence that Kingsoft or WPS participated in the operation. The interesting part of this sample is not that a resume delivered a RAT. We have been putting malware in job applications since recruiters first learned to double-click. The interesting part is how cleanly the behavior and lure answer different halves of the objective question . The code says the operator wanted an interactive foothold. It spends effort on sandbox detection, time checks, encrypted staging and fileless execution, then installs a framework built for shells, files, screens and tunnels. The document says whose foothold might be useful: somebody reading graduate applications about AI, electrical systems, power grids and renewable energy, presented under the name of a university with real defence significance. Probably a professor or research lab. Not necessarily Beijing Institute of Technology and not necessarily a government target, but clearly more specific than “any Windows user with a mouse.” That is how I would present the objective: not “VShell equals espionage,” and not “China lure equals APT.” Instead: A targeted academic lure was used to establish stealthy VShell remote access, most likely enabling collection of research material and credentials and providing a pivot into the recipient institution. The exact operator and final motivation remain unconfirmed. Precise enough to be useful. Honest enough not to become malware astrology. If you are a professor reading unsolicited resumes: the applicant may be excellent, the research statement may be inspiring and the file extension should still be visible. Stay safe. And maybe do not let join the lab before the student does. A fake Beijing Institute of Technology graduate-school resume is opened. A custom Go loader checks for sandboxes, verifies the machine has at least four CPU cores and uses as a sleep timer. It downloads and opens a genuine DOCX decoy so the victim sees the document they expected. It reflectively runs a 1,454-byte Windows SNOWLIGHT stager. SNOWLIGHT connects to , downloads 4.65 MB, XOR-decodes it with and jumps into a fileless VShell RAT. AI-based power-grid fault diagnosis intelligent electrical systems renewable-energy converter control power-system automation joining “your research group” and completing experiments and papers That the applicant is real or has any connection to BIT That BIT created, distributed or knew about the file That BIT itself was the intended victim That defence research was the confirmed collection target That the operator was foreign to China, state-sponsored or conducting espionage What language and theme does the lure use? Who does the document claim the applicant is? Where is the likely recipient? Who built or operated the malware? , the standard mainland term for a fresh graduate , , and the deferential phrase , the mainland College English Test Band 6 , the National Computer Rank Examination’s second level , a PRC education-system honor WPS locale , plus and fonts A supervisor-centered pitch promising to follow the research group’s arrangements, complete experiments and publish papers Exit if that file exists. It is a kill switch or operator exclusion marker. Connect to , retrying every ten seconds. Send a 40-byte architecture and server check-in. Allocate exactly 30,000,000 bytes of executable memory. Receive the next payload in -byte chunks. XOR every byte with . Close the socket and call the decoded buffer. ANY.RUN’s Suricata rules identified the protocol as VShell. The delivery stub is the exact SNOWLIGHT family used to stage VShell. The final payload has the expected obfuscated Go core structure. The panel behavior, architecture tag, , XOR and cross-platform stager design all match public VShell/SNOWLIGHT research. Detect and avoid analysis environments Refuse low-CPU systems Detect accelerated sleep behavior Decrypt hidden network configuration Download a benign-looking decoy Open the decoy in Word to preserve the illusion Download shellcode from remote infrastructure Execute shellcode in its own memory Establish a raw TCP staging connection Download and decode a full RAT without writing it normally to disk Transfer execution into the VShell core Complete VShell registration and exchange sustained encrypted health traffic Interactive terminal and arbitrary command execution File browsing, upload and download Screenshot capture and interactive screen access Host, user, process and network reconnaissance Reverse proxying and tunneling into otherwise unreachable systems Multiple C2 transports, including raw TCP, WebSocket, KCP and others Simplified-Chinese academic lure and PRC-specific education vocabulary WPS Office locale Supervisor-centered research-group language A function specifically named Evasion tailored to the Chinese ThreatBook sandbox VShell’s origin and popularity in Chinese-language offensive-security communities A unique UNC5174 domain or known C2 A matching cryptographic configuration A reused final-payload hash Operator logs or identity artifacts A campaign-specific overlap stronger than commodity tooling Resume-themed executables launching and then Word or followed by and Processes allocating tens of megabytes as executable memory immediately after a large raw TCP receive Go binaries with an invalid or hash-mismatched certificate and document-style names Outbound TCP beginning with six-byte architecture tags such as , , , , or Post-stage TCP frames beginning with plausible little-endian lengths and direction-marked 12-byte AES-GCM nonces ANY.RUN public analysis ASPI China Defence Universities Tracker: Beijing Institute of Technology U.S. Commerce Department: 2020 Entity List additions Beijing Institute of Technology: research laboratories and centers China Education Examinations Authority: College English Test China Education Examinations Authority: National Computer Rank Examination Sysdig: UNC5174’s evolution from SNOWLIGHT to VShell Sekoia: SNOWLIGHT configuration extraction HPE Threat Labs: Unmasking the SNOWLIGHT stager SOCRadar: Tracing SNOWLIGHT Esonhugh: reverse engineering the VShell client and protocol Trend Micro: Earth Lamia and why SNOWLIGHT is not attribution Cisco Talos: UAT-8302 and its SNOWLIGHT/VShell deployment Google Threat Intelligence: UNC6586 React2Shell activity OTX: passive DNS for 38.207.178.192 urlscan: historical pan.qlam.cc result

0 views
Himanshu Anand 1 weeks ago

I had some free time, so I tried to pwn V8

I am not working full time right now, which means I have free time which is dangerous. You start by reading one V8 blog post and somehow end up trying to make Google’s Chrome open with a ROP chain. That is basically what happened. I chained three public V8 bugs against the exact Chrome build used by Google’s v8CTF . The first bug leaked a compressed object address, second turned a garbage collection mistake into a fake JavaScript array and read/write access inside the V8 cage and the third used a JSPI and JS Dispatch Table mismatch to pivot the native stack outside that cage. Then I reused code already inside Chrome to open, read and print the flag. Real Google flag: yes . V8 sandbox escape: yes . $10,000 bounty: no . Getting the flag and winning the challenge turned out to be two completely different things. This is the full story, including the ugly reliability numbers and how much LLM assistance went into the work. There is a very specific kind of confidence you get after reading three browser exploitation posts. You do not know enough to build an exploit, But you know enough to believe that you probably can. That is where this started. I have spent years around application security, old Internet Explorer exploitation, heap sprays, ROP, ASLR, DEP and the usual fun. Modern V8 exploitation still looked like a different planet. Every useful pointer seemed to live behind another table, every object had a compressed representation. Every time I thought I understood the heap, the garbage collector moved it. So I decided to learn it the only way that works for me: choose one concrete target, define a ridiculous finish line and keep going until either the finish line or my patience breaks. Google had already provided the finish line. v8CTF is Google’s continuous V8 exploit challenge. Google runs a pinned Chrome build on its infrastructure. You connect to the service, solve a proof of work and give it an HTTPS URL. Chrome opens your page and A flag exists at . Your job is to make Chrome print it. That sounds simple because the sentence has hidden almost all the work. the protocol is tiny. the amount of browser internals between the first and last box is not. The official rules say an eligible submission can receive $10,000 . But the word eligible is doing a lot of work there. If you found and reported the initial bug, Google can treat the chain as a 0 day. If the bug was already public or somebody else found it, the chain is an n-day. Mine was very much an n-day chain as I did not discover the original bugs. I combined public work, adapted it to the exact target and built the missing pieces between those bugs. Still difficult but not a 0-day. The target was Chrome for Testing on Linux x86-64. It contained V8 at commit . Browser exploitation is not “Chrome 150-ish”. A map value, object layout, builtin offset, ROP gadget or pointer table rule can change between nearby builds. An exploit for the wrong minor version is often an expensive way to launch a crash reporter. The challenge disabled the crash reporter too, so sometimes it was only an expensive way to stare at silence. Chrome was launched with flags including: The flag needs an explanation. Chrome has more than one security boundarys, The process sandbox was disabled for this challenge, but the in process V8 heap sandbox still existed. The outer kCTF and nsjail isolation also remained. Native code execution in the renderer was enough to read the challenge flag, but getting ordinary corruption inside the V8 heap was not. I escaped the V8 sandbox into the native renderer. I did not escape Chrome’s process sandbox because the challenge had disabled it and I did not escape the outer jail. This distinction matters. “Chrome sandbox escape” would make a much better headline. It would also be wrong. If you remember old browser exploitation, the rough plan looked like this: Modern V8 has the same family resemblance. It has also put several locked doors between every step. The modern exploit was not one magical bug that immediately produced native code execution. It was a ladder of smaller capabilities. every box had a test. if a stage did not produce an observable result, it did not exist, no matter how convincing the theory sounded. JavaScript values need to represent integers, doubles, strings, objects, arrays and functions. V8 uses tagged values so it can quickly distinguish a small integer from a heap-object reference. On a 64-bit pointer compressed build, many heap references are stored as 32-bit offsets inside a 4 GB region called the cage. V8 reconstructs the full pointer using the cage base. This saves memory and limits where corrupted compressed pointers can point. It also means leaking one compressed pointer is useful but does not reveal Chrome’s native image base. The V8 sandbox goes further. It assumes an attacker may already be able to corrupt memory inside the heap cage and tries to keep important native pointers outside it. The simplified list of things standing in the way looked like this: So even after gaining arbitrary read/write inside the cage, the exploit was not finished. It had only earned the right to start fighting the next layer. The target build contained several issues fixed later in Chrome 150. I looked at V8, Blink, PDFium, Skia, ANGLE and other routes. Most produced crashes, partial capabilities or technically fascinating ways to waste a week. The chain that finally worked used three public CVEs. CVE-2026-15903 was an optimizing compiler bug, A safe-integer assumption was lost while V8 lowered a number-like value to a 32-bit machine word. TurboFan’s reasoning said the value stayed inside a safe range. The generated machine code disagreed. That disagreement could make read outside its intended string. This was useful because it affected the exact target, triggered from an ordinary web page and provided a controlled byte-reading oracle. If I placed many references to the same object nearby, the oracle could scan memory and find the repeated compressed pointer. It could read but It could not give me the write I needed. CVE-2026-15776 was a RegExp representation mistake involving and the largest positive Smi, . The vulnerable path incremented that value across the Smi boundary. The result became a heap-allocated , but one fast path treated it enough like a Smi that the old-to-young reference was not recorded correctly. After minor garbage collections, the RegExp could still point to an allocation that the collector had already recycled. Salvatore Gulizia, also known as Serotav , published excellent work showing how to reclaim the stale slot as a fake array. His public exploit depended on layout assumptions that were not stable enough in my target. The address oracle from the first bug supplied the missing location information. Bug one knew where the object lived but could not write and Bug two could replace the object but needed help finding the right layout. The first two bugs produced arbitrary read/write inside the V8 cage. The V8 sandbox still protected native process memory. For that boundary I reused the public JSPI and JS Dispatch Table issue tracked as Chromium issue , fixed by Jihyeon Jeong ( ) in commit . The short version is that a hidden fixed-arity builtin and its JDT entry could disagree about how many native stack arguments needed cleanup. A disagreement about stack cleanup is not harmless metadata confusion. It can move the return slot into attacker influenced values. That was the path outside the cage. The final exploit ran in a normal web page. It could not use or other d8 shell helpers. I trained the vulnerable functions through ordinary DOM event dispatch instead: The optimized reader believed its last index remained inside a 256-byte string. Generated code could select a 256-byte window outside it. I placed arrays containing 64 references to the same nearby and scanned for a repeated odd 32-bit value. Odd mattered because compressed heap pointers carry the object tag in the low bit. Repetition mattered because random memory contains plenty of odd numbers too. Sixty-four copies of one candidate are a much better signal than “this number feels pointer-ish”. The live run eventually printed: That address belonged to that process. It was evidence, not a reusable magic constant. V8 uses a generational garbage collector. New objects normally start in young space. Objects that survive can move into old space. When an old object points at a young object, a write barrier records the relationship so a minor collection knows the young object is still alive. The RegExp bug broke that bookkeeping. The old retained a pointer to the young , but the collector did not remember the edge correctly. A minor collection reclaimed the number. The pointer stayed. I sprayed attacker-shaped allocations and tried to win the freed slot. same address, completely different object. the collector sees reusable memory. the RegExp still sees lastIndex. I see a fake array. The trigger looked roughly like this: The reclaimed bytes described a fake packed-double array: With an attacker-selected elements pointer and a very large length, ordinary JavaScript indexing could read and write outside the original array. This was the old heap-spray idea wearing a modern garbage-collector costume. A renderer crash is not an arbitrary read/write primitive. A fake object that survives one property access is not a stable primitive either. I used a sacrificial double and required a complete round trip: Only after all of that passed did I call the primitive read/write. There was also a four-byte alignment problem. Compressed object fields sit on a four-byte grid. JavaScript doubles occupy eight bytes. Some values fit inside one double. Others were split across two neighboring doubles. This sounds like a small implementation detail because the sentence is small. The debugging time was not small. The reclaimed master array was also fragile. The rest of the exploit needed Promises, WebAssembly modules, external strings and many allocations. Any collection could inspect or move something I desperately wanted left alone. The useful fix was V8’s large-object space. Large backing stores are not compacted like ordinary small objects. I prepared three stable allocations: The lesson was not “disable GC”. The lesson was “give GC legitimate references it knows how to maintain”. At this point I controlled memory inside the V8 cage. ASLR still hid Chrome’s native image base. The External Pointer Table stopped the obvious attack. An does not store a raw native resource pointer that caged corruption can simply replace. It stores a protected handle. But the JavaScript string still had a writable length inside the cage. I enlarged that length while preserving the legitimate EPT handle. then trusted the corrupted length far enough to read beyond the native external-string allocation. Repeated external resource objects exposed a repeated vtable pointer inside the Chrome binary. For this exact build: That defeated ASLR for the current renderer. It did not provide unrestricted native write. It did not need to. The next bug supplied control flow. This separation was important: one primitive disclosed native addresses, another primitive controlled the native stack. I kept looking for one perfect native read/write primitive when two narrower tools were enough. JavaScript Promise Integration or JSPI, lets WebAssembly suspend when a JavaScript import returns a Promise and resume later. V8 preserves native execution state to make that asynchronous trick work. I created two suspended WebAssembly computations with and . Their Promises contained internal reactions pointing to genuine hidden handlers. Using caged read/write and , I recovered those handlers as JavaScript values. The vulnerable build allowed the hidden builtin and its JDT metadata to disagree about stack cleanup. I supplied a forged receiver and a deliberately mismatched call frame. At the controlled return, a gadget moved the native stack pointer into the large carrier backing I had prepared inside V8. the caller and callee disagreed about where the call frame ended. the CPU eventually asked my carrier array for directions. That crossed from corrupted JavaScript objects to native renderer control. That was the V8 sandbox escape. I did not need a reverse shell, a calculator or an executable memory page. I needed to read one file. The ROP chain reused code already present in the exact Chrome binary: Open, read, write. ORW. W^X remained intact. The exploit used short instruction sequences and PLT calls already present in Chrome. There was one final piece of nonsense. V8 heap pointers are tagged, so the native receiver pointer landed one byte past the aligned location where the caged writer naturally wrote. The ROP stream had to be shifted by one byte without destroying its neighbors. That is modern browser exploitation in one sentence: after crossing several serious security boundaries, you lose another evening to one byte. Local testing used the exact Chrome build inside Docker, a read-only synthetic , fresh profiles for every attempt and no Docker networking. I did not touch Google’s service until the complete ORW chain printed the local synthetic flag. The live client solved the kCTF proof of work, sent the hosted exploit URL and captured Chrome’s output. The important part was this: I stared at the final line for a while. A web page had entered headless Chrome. A compiler bug leaked one compressed address. A RegExp bug let me reclaim a stale allocation. A fake array produced caged read/write. An leaked the native image. A JSPI mismatch unbalanced the native stack. Existing Chrome code opened one file and printed one line. I got the flag. That part was not theoretical. I did not build this alone and I do not want to pretend otherwise. I chose the target, ran the experiments, debugged the exact Chrome build and decided what evidence counted. ChatGPT using the Sol model did a large amount of source navigation, experiment design and code drafting. I used other models to research alternative paths and challenge claims before trusting them. The LLMs helped with: There was no prompt that said “pwn V8” and returned a working exploit. The real loop looked like this: The models were useful and confidently wrong on a regular basis. They invented object layouts, treated unrelated crashes as progress and proposed beautiful exploit chains that immediately fell apart against the binary. If an idea did not produce a marker, a controlled value or a successful run, it did not count. This work made me more optimistic about AI-assisted security research and much less interested in AI-generated exploit claims without logs. Now for the less cinematic part. This was not a credible $10,000 claim, even though it recovered the flag. Two independent eligibility problems were visible. First, this was an n-day chain. The initial vulnerabilities came from other researchers. Google’s public sheet already showed a confirmed M150 n-day from July 13, before this flag was captured on August 5. The rules normally allow only the first eligible n-day for a deployed version. Second, the exploit was nowhere near the required 80% reliability. The final direct package succeeded in 1 of 5 fresh local runs. A separate tunnel-hosted tuning batch reached 5 of 10 . Those were different delivery conditions, so I am keeping the numbers separate rather than combining them into one nicer-looking lie. one successful flag proves exploitability. it does not magically turn the other failed runs into successes. Some old notes said . The preserved raw summary said . The raw logs win that argument. Even would only have been 60% anyway. The reclaim depended on GC timing, heap occupancy, allocation order, JIT tiering, native resource placement and startup noise. Successful runs were fast, often around four to seven seconds. They simply did not happen often enough. So the honest scoreboard is: Was I disappointed? Obviously. I am a security researcher, not a monk. But “got the flag” and “won the competition” are not interchangeable sentences. This project taught me that in a very expensive dialect of JavaScript. The complete PoC and reproduction notes are available on GitHub. Everything is tied to Chrome on Linux x86-64. This is an exact-build historical exploit, not a paste-into-current-Chrome script. When people hear “V8 exploit”, they normally picture a malicious website opening in Chrome. That is the dramatic version, but V8 exists in more places than one browser tab. The surrounding security boundary changes in every environment. A browser has renderer and process sandboxes. An Electron app may expose privileged preload APIs. A server runtime may hold cloud credentials. An isolate platform may place code from several tenants in one process. This exact historical chain does not compromise all those systems. The broader questions still travel: V8 exploitation is interesting because language semantics, compiler optimization, garbage collection, object representation, WebAssembly, native ABI details and operating-system security all collide in one process. It is several different security disciplines wearing one trench coat. Pin the exact binary first. Not the milestone. Not a nearby patch. Exact binary, source revision, platform and launch flags. Prove one primitive at a time. An address-leak marker is better than a browser crash that might have happened six stages later. Treat every allocation after corruption as hostile. Logging allocates. Errors allocate. Compilation allocates. First-time typed-array use can allocate. Any of them can trigger the collection that destroys the fake object. Use legitimate runtime machinery when possible. A real tagged slot that GC knows how to update is much more stable than hoping a malformed interior object survives forever. Separate disclosure from control. The leak defeated ASLR. JSPI supplied native control. I did not need one perfect native arbitrary read/write primitive. Count every failure. Retrying until one flag appears proves exploitability. It says nothing about reliability. Make LLMs pass evidence gates. Ask for source paths, hypotheses, harnesses and competing explanations. Do not let a fluent paragraph replace a successful run. I keep coming back to the live transcript. No bounty No new CVE No Chrome process-sandbox escape. Still one of the coolest things I have built. The best part of having free time is finally learning the things you kept postponing. The dangerous part is that occasionally the learning project starts printing signed Google flags. If you are learning V8 exploitation, do not begin by memorizing every pointer table. Pick one boundary. Build one observable capability. Read the source. Read other researchers’ work. Use the LLMs, but make them show their work. Then keep moving one box to the right. If you are still reading this, you are awesome. Thanks for sticking with me! If you want to discuss the exploit, V8 internals or the painful economics of getting a flag without getting a bounty, find me on Twitter/X . Thanks for reading. The exploit has to recover a real flag from Google’s infrastructure. Only the first submission for a given initial memory corruption bug is eligible. Normally only the first submission for a deployed V8 version gets that version’s slot. A 0 day is exempt from that separate version limit. An n-day flag must be captured after Google opens the nday window. Average runtime has to stay below five minutes. Success rate has to be at least 80%. Trigger a use-after-free or overflow. Spray until controlled data lands where the old object lived. replace a pointer or vtable. Leak a module address to defeat ASLR. Build ROP to work around DEP. Jump somewhere useful. Mapping public fixes to the exact vulnerable V8 revision. Comparing object layouts and source paths. Generating small diagnostic pages and local harnesses. Reading long debugger transcripts. Suggesting alternatives when a route died. Keeping track of which security boundary a primitive had actually crossed. Fact-checking this post against the exploit logs and official rules. Chrome and other Chromium-based browsers. Node.js server applications. Electron desktop applications. Multi-tenant systems built around V8 isolates . Embedded Chromium environments, CEF applications and WebViews. Official v8CTF overview Official v8CTF rules Chrome for Testing 150.0.7871.46 manifest Exact V8 source snapshot CVE-2026-15903 fix CVE-2026-15776 fix Serotav: From Regex to RCE V8 pointer compression The V8 sandbox V8 sandbox source documentation Orinoco garbage collector Introducing JSPI JSPI/JDT fixed-arity fix Start Your Engines: Capturing the First Flag in Google’s v8CTF Fuzzing to Zero-Day: Pwning v8CTF

0 views
Himanshu Anand 2 weeks ago

someone is filing your GST return, and it is not your CA

Disclosure: this research was conducted using an ANY.RUN account provided as part of a collaboration. All analysis and conclusions are my own. Found an unreported Silver Fox campaign serving ValleyRAT to Indian taxpayers with a fake “GSTR-3B overdue” lure, timed to the real 20 August GST filing deadline. The delivery is a disk image containing a genuinely Microsoft-signed SystemSettings.exe that sideloads a patched SystemSettings.dll (Microsoft cert still attached, hash broken cute). Stage 2 injects into RuntimeBroker.exe with a full UACMe kit, a Defender tamperer and an AV process-killer. Reversed both stages, recovered the full config: 3 C2 endpoints, a dormant backup domain, build date Aug 2 2026,and a 15 subdomain delivery platform that is serving per victim lure links as I type this. I was doing something completely different measuring how much India-targeted APT tooling even shows up in public sandbox feeds (spoiler: the APT36 stuff barely does, that is a whole separate post). While tag-hunting on ANY.RUN’s public submissions , the feed was the usual suspects: fake VPN installers, something literally named (加载器测试 “loader test”, they are not even trying), the usual Chinese-locale noise. And then this, submitted 7 August 2026 : If you are not Indian: GSTR-3B is the monthly GST return every registered business files, GSTIN is the tax ID, and the 20th of the month is the actual deadline. The GSTIN in the filename even uses the correct format is Maharashtra. Somebody on the operator side did the homework . Sandbox verdict: Malicious . Tracker: Backdoor, RAT, ValleyRAT. Tags: , , . Public coverage of this file, its C2s or a GST lure wave: zero . Nada, One lonely urlscan scan of the delivery domain. Quick refresher, because context matters. Silver Fox (SwimSnake / Void Arachne / 银狐) is a China-nexus crew running ValleyRAT (built on the WinOS 4.0 framework plugin-based RAT, keylogger, screen capture, the works). The group has been targeting Indian users since at least December 2025 with Income Tax Department lures (nice work by CloudSEK on that one) and separately running SEO-poisoned fake software installers (NCC Group) and even a Russian false flag operation to muddy attribution. Their comfort food: DLL sideloading behind signed binaries, disposable free-domain C2, tax-season timing. What nobody had reported: a GST wave, this infrastructure or this exact sideload pair. Until the ANY.RUN feed coughed it up. The ZIP contains a 1.2 MB disk image . Why a disk image? Because files inside a mounted image don’t inherit Mark-of-the-Web the “this came from the internet, are you sure?” prompts never fire. Double click and it mounts like a USB drive. Very 2024 technique still printing money in 2026. Inside the image, two files: And here is where it gets spicy. Signature check: The EXE is the actual, legit, Microsoft-signed SystemSettings.exe. The DLL was a real Microsoft binary until someone patched it. The signature is still there, it just no longer validates. To a reputation based filter, both files “are Microsoft.” No unsigned attacker code ever touches disk. The whole stage-1 lives inside a tampered system DLL that a signed Microsoft process happily loads. I opened the DLL in Ghidra DllMain? Stock CRT boilerplate Exports? Stubs If your static scanner keys on entry point weirdness, it sees nothing the implant is grafted into the CRT init path instead. The fun starts in the orchestrator ( ). This function never touches a readable string. Everything API names, C2, paths is built as stack constants , decoded in place, used, zeroed. With junk noop calls sprinkled between real instructions, because apparently my time is worthless. The decoder is a single-byte XOR: First decoded block resolves -> . Standard “resolve everything at runtime so the import table says nothing” tradecraft: And then my favorite screenshot of this entire analysis the C2, hiding in a : Thirteen bytes, XOR 0x70: . Which is exactly the domain the sandbox saw on the wire. Static and dynamic analysis shaking hands. But wait the DLL’s has a full XOR’d config block. Decoding it: had zero hits anywhere on the internet. A dormant failover channel, pulled out of the binary with tweezers. This is why you do static analysis even when the sandbox report looks “complete.” The downloader itself is dressed up as Edge 131 full fake UA, proper Accept headers… and one beautiful mistake: . A Chinese-locale fallback copied straight from the operator’s dev template. Attribution fibers are the best fibers. The downloaded stage 2 is then mapped into memory by a hand-rolled reflective PE loader (copy sections, VirtualProtect per section). It never exists as a file. Of course. ANY.RUN showed the lure EXE injecting into RuntimeBroker.exe and bless them, the task exposes process dumps of the injected regions. Downloaded both, opened in Ghidra the dump is where this RAT stops pretending. The config (UTF-16, sitting right there): Translation: three C2 endpoints (this exact one showed up in sandbox traffic), plus and which are NOT in any public feed . Build date 2026-08-02 five days before the lure dropped. And the victim group field is literally Chinese for “default.” Operator console confirmed Chinese. We knew, but it’s nice when they label it. The toolkit. The dumped module’s manifest identifies itself as “Akagi” as in UACMe , the open-source UAC-bypass collection, complete with the aircraft-carrier description string intact. Alongside it: references to , , (signed auto-elevating Microsoft binaries the UAC-bypass menu), Defender’s private COM interfaces / , service-control APIs for stopping security services, token theft ( -> ), a keylogger ( polling), GDI+ screenshot capture, clipboard theft, event-log tampering, persistence via Startup and AppCompatFlags. And it’s not theoretical. The sandbox’s Suricata alerts read like the RAT’s to-do list being completed in real time: the AV-killer literally phones home to say it’s ready. Then the actual C2 channel: custom binary TCP (length-prefixed frames, port 8888), SilverFox init -> WinOS4.0 login -> encrypted tasking -> keep-alives. The HTTPS from stage 1 was just the delivery boy. Full task is public if you want to poke it yourself: app.any.run/tasks/f6eeccd8-… Here is where it went from “nice find” to “oh, this is big.” I pivoted on the delivery IP ( ) using only passive sources. It is hosting 15 randomized subdomains , , , and friends. urlscan has 236 scans against this IP, including four different lure links scanned TODAY , each following the pattern: Every is a per victim, one-time download token. Individually tracked targets. One of today’s links served which matches the ValleyRAT submission sitting in the same ANY.RUN feed. So the GST lure is one plank of a multi-region, multi-lure operation, and it is actively serving victims right now . Oh, and the server’s TLS certificate? Let’s Encrypt, issued to , dated June 8, 2026 . The “backup” domain from the config is the server’s actual identity, and this platform was staged a full two months before the GST wave. Infra June 8 -> build Aug 2 -> lure Aug 7 -> still hot Aug 17. Code tells you how and targeting tells you why. A GSTR-3B lure selects for exactly one population: Indian accountants, finance teams, CA firms. Those machines hold corporate net-banking (used for tax payments), GST-portal creds and the crown jewel the email identity that approves payments. Silver Fox’s documented monetization for finance-staff compromise is payment-diversion fraud : persist quietly (UAC bypass + Defender tampering + AV-killer = long-term silence), watch how payments get approved, then divert them. Espionage grade collection comes free with the implant. The per-victim token links seal it: this is a crew that monetizes specific finance departments, not bulk infections. One line: quiet, AV-immune, persistent control of Indian corporate finance machines during filing season, monetized through credential theft and payment diversion. Network (live at time of writing and answering) YARA (stage-1 DLL) Quick wins for your SOC: alert on running from anywhere except ; flag Microsoft-named DLLs with HashMismatch signatures; block if you can (you can); mail-gateway rule for GSTR/GSTIN + / attachments during filing windows; hunt RuntimeBroker.exe making outbound TCP to hosting providers. Report to CERT-In if you see hits I have a full reporting pack ready. Two things stuck with me from this one. One: the public sandbox feed is underrated as a hunting ground. This whole campaign unreported by every vendor was sitting in ANY.RUN’s public submissions with a giant GST-themed name tag on it. The samples, the dumps, the network telemetry: all public. The expensive part was knowing what to pull on the thread. Two: the defense-evasion bar keeps rising in the boring direction. No zero-days here, No novel exploit. Just a signed Microsoft binary, a patched Microsoft DLL, a disk image, and a cloud URL every individual component “trusted” by something. The only broken signature in the entire chain still says “Microsoft” on it. If your detections are still waiting for something that looks malicious, this is what sails past. The GST deadline is the 20th. If you work with Indian finance teams, today is a good day to send them a very boring, very lifesaving email: tax notices don’t arrive as disk images. Stay safe, patch your detections, and remember RuntimeBroker.exe has no business calling Hong Kong.

0 views
Himanshu Anand 3 weeks ago

The Anti-India Influence Machine: Troll Farms, Fake News, Newsrooms, Algorithms and AI

Scope and disclaimer: This is a cybersecurity research article focused on India because I am from India, India is the information environment I notice most and the change became personally obvious to me after Operation Sindoor. It is not an accusation against an entire country, nationality, religion or political group. The same playbook is used against many communities around the world and this article separates verified platform attribution from research-based indications and unverified allegations. Criticism of India, the Indian government or any Indian political party is not automatically anti-India hate. The subject here is coordinated deception, fake identities, manufactured amplification and language that attacks Indians as people. Content warning: Some sections discuss racist stereotypes, threats and alleged calls for violence. I have avoided reproducing slurs or naming people from unauthenticated screenshots unless a reliable public investigation established the connection. Evidence-media note: The local evidence images are low-resolution excerpts from public takedown reports and fact-checking investigations, used here for criticism, verification and research commentary. Keep each caption and source link attached to the image, do not present an investigator’s annotation as an original social-media post and review the source publisher’s terms before commercial or syndicated republication. Video shortcodes load the original YouTube or Vimeo host rather than copying the video into this package. I had seen anti-India posts for years and mostly treated them as the normal background radiation of the internet. After Operation Sindoor, however, my timeline began filling with the same small set of insults in places where they made no sense: Indians are dirty, Indians do not use deodorant, cow urine, cow dung, street-defecation memes and random videos from anywhere in South Asia relabelled as India. The repetition felt less like spontaneous criticism and more like somebody had handed the internet a very boring script. The research shows that coordinated networks are real, but the phrase “one giant anti-India botnet” is usually the wrong model. What exists is closer to an influence supply chain in which state-linked employees, political interests, public-relations firms, fake media brands, account operators, volunteers, influencers, AI tools, genuine racists and recommendation algorithms can all play a part. Some networks are automated, many are human-operated nd most are hybrids. The strongest public findings include: My conclusion is not that one country controls everything. It is that identity-based hatred has become an inexpensive cyber operation . The target is not a server. The target is the human mind and the objective is often not to make everybody believe one perfect lie. It is to make contempt feel normal, exhaust the people being targeted, create confusion during a crisis and push regular users into doing the amplification for free. I had seen anti-India content before Operation Sindoor, but it never mattered enough for me to investigate. The internet has always contained people who wake up, make a good cup of tea, open a social network and immediately decide that insulting 1.4 billion strangers is a productive use of electricity. I normally scrolled past it. After Operation Sindoor, the pattern became harder to ignore. I would open a post about Indian technology, an athlete, a company, immigration or something completely unrelated to politics and the replies would suddenly contain the same hygiene jokes. A bad video from Pakistan, Bangladesh, Nepal or an older Indian event would be posted as “India today.” A discussion about foreign policy would be pulled down into cow urine and cow dung jokes within minutes. It was not the existence of one insult that stood out; it was the repeated arrival of the same payload across unrelated conversations. At first, I assumed this was only the recommendation algorithm learning that anti-India content made me stop scrolling, that’s certainly part of it. A feed is not a neutral survey of world opinion; it is a prediction engine that watches what makes us pause, open replies, argue, quote-post and come back later. The algorithm saw that I was angry and interpreted it as five-star customer feedback. But an algorithm can amplify a pattern without creating the first pattern. That led me to a more useful question: what is the attack chain behind the content? Who creates the first claim, who gives it the first thousand interactions, how does it move from anonymous accounts into verified profiles and news clips, where does AI fit and what can we actually prove about the operators? That is a cybersecurity question. Cybersecurity people normally describe an attack using assets, adversaries, infrastructure, tactics, techniques and impact. We ask how the attacker performed reconnaissance, created resources, established access, evaded detection, maintained persistence and achieved an objective. Influence operations can be studied in almost the same way, except the final endpoint is not a Linux server or an employee laptop. The endpoint is a person’s perception of another person. The DISARM Red Framework exists for exactly this reason. It provides a common language for documenting influence operation behaviour, in the same broad spirit that MITRE ATT&CK provides a common language for technical adversary behaviour. The details are different, but the analyst’s job is familiar: collect indicators, connect infrastructure, measure coordination, separate confidence from speculation and avoid declaring attribution because two accounts used the same meme. A simplified influence-operation kill chain looks like this: The original accounts do not need to convince the whole internet. They only need to push the content across the first few trust boundaries. Once real people become angry, frightened or patriotic enough to share it, the operation gains an enormous unpaid workforce. In technical security, a botnet is a collection of compromised or automated systems controlled by an operator. Social media discussions use the word much more loosely, often meaning “many accounts I dislike.” That creates bad analysis because a coordinated influence network can contain several different kinds of participants at the same time. Some accounts may be fully automated. Others may be controlled by a human operator managing dozens of profiles, while another group uses scheduling software, AI generated replies and prewritten message templates. There may also be genuine volunteers following instructions in a private group, paid account farms, influencers who understand exactly what they are promoting, influencers who do not ask enough questions nd ordinary users who simply repeat a viral joke. The better term is often coordinated inauthentic behaviour . The important features are not only automation they are deceptive identity, hidden coordination and an attempt to manipulate public debate. A hundred humans pretending to be a thousand independent citizens can be more persuasive than a thousand obvious bots. This also explains why bot detection websites frequently disappoint people. Posting frequency, account age and repetitive language are useful signals, but none of them is conclusive alone. A news bot can be harmless, a very online human can post every three minutes and a professional influence operator can deliberately behave slowly to look normal. The strongest finding comes from several correlated indicators: shared infrastructure, unusual phrase reuse, synchronized timing, common administrators, repeated early engagement and cross-platform coordination. The easiest way to ruin an investigation is to mix three different evidence levels into one dramatic conclusion. A platform attribution based on internal login and infrastructure data is not equal to a screenshot from a private group and a network being linked to a country is not automatically evidence that the government of that country ordered it. I use three labels throughout this article: The country question needs another warning because it is where online discussions lose their minds fastest. These three sentences are not interchangeable: Evidence for the first sentence does not automatically prove the third. In one important 2019 case, Meta did reach the second level by linking a network to employees of Pakistan’s ISPR. In many other cases, Google reported only that an operation was linked to Pakistan and did not name a state sponsor. Both findings matter, but they are not the same finding. The same caution applies everywhere else. Content originating on a China-based platform is not automatically a Chinese-government campaign. A network operating from India is not automatically controlled by the Indian government. An Israeli company running an influence operation does not mean “Israel controls the internet.” Attribution should become more specific only when the evidence becomes more specific. A 2026 report from the Network Contagion Research Institute examined high engagement anti-Indian posts on X during 2025. The sample included posts with at least ten likes and excluded posts originating in India, so this was not a complete census of every hateful post. It was a study of material that had achieved at least some traction outside India. The reported dataset contained: The research also found that a very small number of prolific accounts captured a disproportionate share of engagement. Three accounts produced more than ten percent of the likes and twenty percent of the reposts in the dataset, according to the report’s published summaries. This is a classic power law pattern: most accounts contribute a little, while a small number of high-output or high-reach nodes shape what everybody else sees. There are two important caveats. First, 300 million views do not mean 300 million unique people. One person can see the same post more than once, automated traffic can create impressions and platforms count views differently. Second, much of the material in this dataset was tied to Western immigration politics, H-1B arguments, nativist communities and “replacement” narratives. It would be dishonest to look at those numbers and declare that one neighbouring country created all of it. That second point is central to this investigation. The anti-Indian hate market has several suppliers. A geopolitical operator may want to damage India’s reputation. A Western extremist may want to attack Indian immigrants. An engagement farmer may not care about either issue and simply notice that a deodorant joke produces replies. These actors do not need to coordinate with one another because the platform gives all of them the same incentive: content that makes people angry travels farther. A large PNAS study of more than 2.7 million political posts found that posts mentioning an opposing group were shared roughly twice as often as posts about the in-group. Each additional term referring to the political out-group increased the odds of sharing by 67 percent in that study. The research was about US political groups rather than India, but it helps explain the machinery: attacking an out-group is unusually effective engagement bait. Read the PNAS paper . My personal observation became much stronger after Operation Sindoor in May 2025 and independent research shows that the wider information environment was flooded during the crisis. The Center for the Study of Organized Hate collected roughly 1,200 relevant posts across X, Facebook, Instagram and YouTube while studying misinformation and disinformation around the India–Pakistan confrontation. Its closer analysis of X covered 437 posts. Of those, 179 came from verified accounts, while only 73 had Community Notes at the time of analysis. Verification did not guarantee accuracy; in several cases, it simply gave a false claim a nicer badge and a faster launch. The false visuals were not small. The report documented an AI-generated image claiming that Rawalpindi Cricket Stadium had been destroyed which received around 9.6 million views, 129,000 likes and 12,700 reposts. Flight-simulator footage presented as real combat received roughly 2.2 million views, while separate video-game clips received around 1.6 million and 1.3 million views. Old Gaza footage relabelled as part of the India–Pakistan crisis received about 727,000 views. The report found misleading content on both sides. Pakistan linked accounts circulated recycled footage, video game clips and fabricated claims about Indian losses. Indian influencers and television channels also amplified false victory claims and unrelated visuals. This is where a patriotic article could conveniently stop reading, but the evidence refuses to cooperate. Information warfare is reciprocal and the lie does not become clean because it arrives wrapped in the tricolour. The Reuters Institute’s account of Indian fact-checkers during the crisis describes the first hours as producing roughly a month’s normal volume of misinformation. One fact-checker had examined about 70 unique false posts by the end of 7 May. The same material moved from X into television, Facebook, WhatsApp and Instagram, which meant a false claim could escape the original account long before a correction arrived. The Indian government later said that more than 1,400 URLs were ordered blocked during Operation Sindoor . That number shows the size of the government response, but it should not be misread as proof that all 1,400 URLs belonged to one network or that every block decision was independently verified. A block order is an enforcement action, not a forensic attribution report. Charts are useful, but disinformation is easier to understand when we put the claim beside the source material and follow the route it travelled. This section therefore works like a small incident-response notebook. For every example, I separate the payload that people saw, the original artefact that investigators found, the distribution path and the confidence level . I am not claiming that every account shown below belonged to one command centre. Some cases show coordination, some show opportunistic copying and some show mainstream newsrooms turning an unverified social-media post into a national broadcast before anybody had located the reverse-image-search button. The screenshots below are low-resolution excerpts from public takedown reports and fact-checking investigations, included for criticism, verification and public-interest research. Each image links back to the report or article that explains it. A screenshot proves that an account published a particular post; it does not, by itself, prove who controlled the account or who paid for the campaign. Pakistan-side misinformation used the same context-replacement method. Posts claimed a fire near Islamabad’s Faisal Mosque showed damage from an Indian drone attack. AFP traced the footage to local reporting from 28 May 2024 , when a roadside fire occurred near the mosque during a heatwave. An AFP reporter visited the mosque after the 2025 posts circulated and found no evidence of attack damage, while Islamabad’s deputy commissioner publicly said the rumour was false. Source: CSOH report, page 33 , independently verified by AFP Fact Check . This example matters because it is a near mirror image of the Karachi cases. On one side, old footage became evidence of an Indian victory. On the other, old footage became evidence of an Indian attack. The flags changed, but the exploit was identical: take a real frightening visual, overwrite its metadata in the caption and publish while the audience is too alarmed to ask why the same video existed a year earlier. The same trick worked in the opposite direction. The Quint traced a viral clip claiming to show the Pakistan Air Force shooting down an Indian aircraft to an ARMA 3 simulation uploaded by the YouTube channel BattleDragons on 25 December 2024. The original description openly called it a military simulation. The false posts simply removed that inconvenient detail, which is the misinformation equivalent of deleting and calling the malware undocumented. Source: The Quint WebQoof fact-check . Here is the older simulation clip identified by The Quint as the source material: Verification method: extract key frames, run reverse-image searches, compare the scene with older uploads and inspect descriptions or interfaces that identify the game. This is basic OSINT, but it works because many operators rely on speed and audience emotion rather than perfect fabrication. CSOH also documented a Pakistan-side campaign claiming the Indian Army had raised a white flag to surrender. Multiple users shared the same narrative with small wording changes, while the visual evidence included unrelated footage and an image openly marked as AI-generated by one publisher. The repeated message created the appearance of several independent confirmations even though the source material did not support the claim. Source: CSOH report, page 35 . This is a common influence technique called false consensus . The audience does not see one unsupported claim; it sees six accounts apparently confirming one another. If the accounts have different logos, writing styles and follower communities, the repetition feels independent even when all roads lead to the same weak source. The report found a sustained AI-driven narrative falsely claiming that Indian Squadron Leader Shivangi Singh had been captured. The package included synthetic images, fake audio and clips claiming to show her in custody. A related fabricated video depicted Indian military personnel surrendering. This is more dangerous than a generic explosion image because it attaches a fake event to an identifiable person, potentially affecting the person’s family, colleagues and public safety while the country is in a military crisis. Source: CSOH report, page 37 . The personal cost is easy to miss when we talk only about “narratives.” A fake casualty or capture report can reach relatives before an official channel does. It can lead to harassment of the named person, panic among military families, pressure on authorities to respond to something that never happened and a permanent cloud of search results that survives after the correction. The cases above are the ones I wanted to put first because they show the kind of manipulation that originally made me investigate this subject: false or synthetic narratives aimed at India, Indian institutions and Indian people. The mirror-image failures from Indian accounts and newsrooms matter too, but I have deliberately kept them later in the article so the threat model is clear before we examine our own side. The following table is not a blacklist and should not be read as one. It groups public names according to what the evidence actually establishes . A page listed in a platform-attributed takedown is different from an account that a fact-checker saw posting several deepfakes and both are different from a television channel that aired one false clip. I am including the names because hiding every identifier makes the research impossible to audit, but the label beside each name is more important than the name itself. The strongest list is the first one because the pages were part of a network removed by Meta and attributed at the organisational-employee level. The BOOM list is useful but belongs one confidence step lower: it is a well-documented cluster with suspicious behaviour and repeated synthetic media, while the ultimate operator remains unknown. The last two rows should be read as editorial incident reports, not attribution. A newsroom can become an unwilling amplifier because it is careless, competitive or ideologically excited; none of those explanations requires a secret payroll. Source: Facebook/Meta takedown samples republished by Dawn . Dawn reported that DFRLab identified the page names from the removed dataset. Source: DFRLab, Pakistan Army’s Covert Social Network . The page samples are important because they show the disguise. A covert network does not need every page to say “official military propaganda department.” It can use hobby pages, Kashmir community pages, military fan pages, local-news brands and apparently harmless cultural accounts. The diversity is part of the security model: each page reaches a different audience and makes the wider network look less centralised than it is. A rumour becomes dangerous when it stops looking like a rumour. The original anonymous account may have twelve followers and a profile photo stolen from a dentist in another country, but that does not matter if a verified influencer reposts it, a television channel puts it inside a red “BREAKING” frame, a short video account removes the source nd somebody forwards the clip into ten family groups. Each hand off adds borrowed credibility. The influencer implies that the claim is important the news clip implies that a newsroom checked it. The WhatsApp forward implies that a trusted relative believes it. By the time the content reaches most people, the original account and its suspicious behaviour have disappeared. This is why news organisations are part of the attack surface. They may not be part of the hostile network, but they can become high-trust relay infrastructure when speed beats verification. In a technical intrusion, an attacker compromises a trusted service to reach more victims. In an information operation, a false claim compromises the credibility of a trusted broadcaster and uses that credibility to reach millions. The correction then faces a completely different problem. The false clip is short, emotional and easy to understand. The correction requires reverse image search, timestamps, geolocation and an explanation of where the original footage came from. The lie has dramatic music and d correction has a PDF. The strongest public attribution in this investigation comes from Meta’s April 2019 coordinated-behaviour report . Meta removed a Pakistan-origin network containing 24 Facebook Pages, 57 Facebook accounts, seven Facebook Groups and 15 Instagram accounts, for a total of 103 assets. Approximately 2.8 million accounts followed one or more of the Facebook Pages. Roughly 4,700 accounts had joined the Groups, around 1,050 followed the Instagram accounts and the operation had spent only about $1,100 on advertising. The content discussed the Indian government, political leaders, the military, Kashmir and other regional subjects, while some Pages presented themselves as general news or community resources. Meta said its investigation linked the activity to employees of ISPR , the Pakistani military’s public-relations organisation. That is unusually specific platform attribution and should not be diluted into “probably some trolls.” Meta had internal access to signals such as account relationships, administration and technical behaviour that outside observers could not see. It is equally important not to inflate the finding. The public report did not name the individual employees, identify the officer who may have approved the activity, publish internal orders or establish that every later Pakistan-linked operation belonged to the same team. The accurate statement is that Meta linked this specific network to employees of ISPR. The inaccurate statement is that ISPR controls every anti India account on the internet. The advertising number is also interesting. A network reaching Pages followed by millions of accounts had spent only around $1,100 on ads. Influence operations do not always need enormous media budgets when they can build Pages over time, reuse existing communities and persuade real users to distribute the content. The expensive part may be the patient construction of trust, not the final promoted post. In 2021, Meta removed another Pakistan-origin network containing 40 Facebook accounts, 25 Pages, six Groups and 28 Instagram accounts. Meta linked it to individuals associated with AlphaPro , a Pakistan-based public-relations firm. Meta’s report is available here . Graphika independently analysed the network and found something more sophisticated than a pile of low-quality anonymous accounts. The operation used pages posing as independent media outlets, fake identities, stolen profile photographs, original video production, professional script readers, paid actors and freelance journalists acting as presenters. The network promoted narratives supportive of Pakistan and its armed forces, backed the China–Pakistan Economic Corridor and published material denigrating India or highlighting attacks by Hindu nationalists against religious minorities. Graphika’s report found multiple connections between the network and AlphaPro, including suspended accounts belonging to employees, pages promoting AlphaPro-produced material and overlap with the company’s commercial marketing activity. Graphika also found open-source links between AlphaPro and ISPR. However, Graphika did not find a direct connection proving that the 2021 AlphaPro-associated operation and the 2019 ISPR-linked operation were the same network. It also did not publish a client contract, payment trail or order showing who commissioned the campaign. That missing layer matters because a contractor can perform the hands-on work while keeping the beneficiary one step away from the keyboard. This is influence-as-a-service. A client wants a narrative, a marketing firm supplies fake media brands, presenters, accounts and distribution and the public sees what appears to be independent journalism. It is basically a managed security service provider, except the service is making the internet worse. The AlphaPro case is useful because it breaks the “government employee manually runs every troll account” mental model. Modern influence campaigns can be outsourced through commercial firms, just as advertising, analytics and software development are outsourced. That makes attribution harder, provides plausible deniability and creates a market in which the same company skills used for ordinary brand marketing can be applied to hidden political persuasion. Influence operations do not only spread content. They can also attempt to remove opposing content by abusing platform moderation systems. In August 2020, Facebook removed 103 Pages, 78 Groups, 453 Facebook accounts and 107 Instagram accounts in a Pakistan-based coordinated network. The company shared part of the network with Stanford Internet Observatory, whose researchers found that it organised mass reporting against accounts viewed as critical of Islam, Pakistan’s government or military and in some cases members of the Ahmadi religious community. The network primarily targeted users in Pakistan and India. The operation had access to Pages followed by about 70,000 accounts and Groups containing roughly 1.1 million members. Researchers identified 208 targeted profiles that were no longer available, but they could not establish that the reporting operation caused those suspensions. Some targets may have independently violated Facebook policies, so “reported by the network” and “removed because of the network” are different claims. A key tool was a Chrome extension called Auto Reporter , which automated parts of the reporting workflow. Stanford reported that the extension had more than 2,000 users and included delays that appeared designed to reduce detection, including a pause after batches of reports. Stanford identified the extension’s public developer as Nasir Ali, who at the time described himself as founder and chief executive of Tigerzplace . Researchers also documented related public pages containing pro-Pakistan and anti-India material. That establishes a connection to the tool development layer. It does not automatically establish that the developer selected every target, controlled all the accounts, received orders from a government organisation or managed the entire campaign. A person who writes a tool may also operate it, but that requires separate evidence. The researchers’ final conclusion was deliberately cautious: they did not know who controlled the wider operation. Read the full Stanford Internet Observatory report . The mass-reporting case is important for regular users because it targets their ability to speak, not only their ability to know what is true. A coordinated group can flood a moderation system with complaints, create the appearance that a target is widely violating rules and force the victim to spend days appealing. Even when the platform eventually restores the account, the interruption may arrive during the exact political event when the person’s voice matters most. Google’s influence-operation bulletins show that Pakistan-linked campaigns supporting Pakistan and criticising India continued into 2026. Google reported removing 170 YouTube channels in the fourth quarter of 2025, 127 channels in the first quarter of 2026 and 150 channels plus one advertising account in the second quarter of 2026. That is a total of 447 channels across three reported quarters . The original Google reports are: These numbers prove repeated platform enforcement against Pakistan-linked networks with a clear pro-Pakistan and anti-India orientation. They do not prove that all 447 channels were one continuous network, that 447 separate humans operated them or that a Pakistani government organisation funded them. Google’s public bulletins identify origin and content orientation but usually do not reveal the complete sponsor or contractor chain. That may feel unsatisfying, but “unknown sponsor” is a valid research result. Cybersecurity reports routinely identify malware infrastructure, command patterns or regional operators without naming the final customer. Influence analysis should be allowed the same honesty. By late 2025, the operation had moved beyond recycled explosion footage. BOOM documented a cluster of X accounts repeatedly seeding AI-manipulated videos, fake quotes, bogus letters and fabricated news articles aimed at India’s military, political leadership and internal social tensions. BOOM said X’s “About this account” feature showed Pakistan as the location for the central accounts it examined, but it also stated that the identity of the operator remained unclear . The recurring seed handles named by BOOM were , , and . The investigators also found fake personas such as , and presenting themselves as Indian users with tricolour imagery or political slogans while the platform metadata showed Pakistan as their location. Some central accounts deleted posts after fact-checks, periodically purged their histories or were later withheld or suspended, which made the final network size difficult to estimate. One doctored video posted by falsely made Army chief General Upendra Dwivedi appear to say that the military would reduce non-Hindu soldiers by more than 50 percent. BOOM compared it with the original footage and reported that two voice-analysis systems found strong evidence of an overlaid AI voice. Another account, , shared four deepfake videos falsely claiming that climate activist Sonam Wangchuk had died in custody. BOOM also documented synthetic videos using the faces or voices of journalists to push a fabricated financial link between Indian political figures and a Pakistani party. Source: BOOM’s synthetic-media investigations . This graphic is an example from BOOM’s fact-checking coverage; it is not proof that every account in the wider cluster distributed this exact clip. This cluster is important because it shows an operational shift. Recycled media is fast but limited by whatever old footage the operator can find. AI allows a campaign to manufacture the missing sentence, insert it into the mouth of a real official and publish the clip while the official’s original speech is still in the news cycle. It is essentially quote injection against a human identity. The attribution line still matters. BOOM described behaviour consistent with a troll-farm-style influence operation: synchronized activity, rapid amplification, central seed accounts and fake Indian personas. It did not publish evidence proving that a government department ordered the campaign. The safe conclusion is that BOOM identified a Pakistan-located, coordinated-looking cluster using large amounts of synthetic media; the ultimate controller and client were not publicly established. That may sound less exciting than “AI BOT ARMY EXPOSED,” but it is much more useful. A cybersecurity analyst does not convert an IP address into a cabinet-level attribution without intermediate evidence. We should not convert a country label in X metadata into an intelligence-agency command structure either. AI did not invent propaganda, fake newspapers, sockpuppet accounts or racial stereotypes. It makes all of them cheaper to produce, easier to translate and faster to adapt. An operator can use a generative model to create hundreds of variations of the same comment, which helps avoid exact-duplicate detection. AI can translate a slogan into several languages, generate names and biographies for fake personas, create profile images, summarise news, draft fake articles, produce political cartoons and write replies that make an account appear conversational. It can also help with simple scripts, website maintenance and the boring operational work that previously required a larger team. Humans still choose the target, grievance, political objective, launch time and acceptable level of risk. AI is the intern who never sleeps; it is not the general. A confirmed India-related example appeared in OpenAI’s investigation of “Zero Zeno” . OpenAI connected the operation to STOIC , an Israeli political-campaign-management company. In May 2024, part of the network began generating English-language comments aimed at Indian audiences, criticising the BJP and praising Congress. Accounts also shifted between unrelated campaigns and used generated material for persona building and replies. OpenAI disrupted the India-focused activity less than 24 hours after it began. More importantly, it found little authentic engagement outside the operation’s own inauthentic accounts and rated the campaign as Category 2 on a six-level influence scale. That is useful evidence against both complacency and panic: foreign commercial interference happened, but the specific campaign did not appear to persuade a large real audience. OpenAI later said it had disrupted more than 40 malicious networks between February 2024 and October 2025 and its broad conclusion was that threat actors were adding AI to existing playbooks rather than gaining a magical new capability. OpenAI’s October 2025 report describes AI as a productivity tool inside larger systems involving websites, social accounts and human operators. Meta reached a similar conclusion after the major 2024 elections. Generative AI gave covert influence networks incremental gains in content production, but many operations still struggled to build authentic audiences. Meta’s election review is worth reading because it separates the scary capability to generate content from the much harder capability to earn trust. The main AI risk is therefore not one fully autonomous army making perfect decisions. It is that ten mediocre operators can now produce the volume, language coverage and visual variety that previously required a much larger organisation. The cost of trying a campaign falls, so more actors can afford to fail repeatedly until something catches fire. The May 2025 crisis created a large library of speeches, press conferences, interviews and television clips involving Indian military officers and politicians. Once those clean source videos were online, an influence operator no longer needed to invent an entire scene. The easier attack was to keep the real face, uniform, studio and camera angle, replace the audio, adjust the mouth movement and publish the counterfeit sentence while the original event was still familiar to the audience. This is a form of synthetic quote injection . It resembles business-email compromise more than a traditional fake photograph: the attacker borrows a trusted identity and uses it to deliver a message the real person never sent. In July 2026, AFP examined a video that appeared to show newly appointed Indian Army chief General Dhiraj Seth speaking in English about hidden Indian deaths during Operation Sindoor and accusing the previous military leadership of misleading the government. AFP traced the pictures to his real 1 July speech, where he spoke mainly in Hindi about military strategy and did not make those claims. Hive Moderation assessed the altered clip as 97.6 percent likely to contain AI-generated speech . Detection scores should not be treated as magic truth machines, but in this case the score supported stronger provenance evidence: the original recording existed and contained different words. The authentic speech used as source material is embedded below. Watching the original is often more useful than staring at one suspicious lip movement because it lets a reader compare the language, pacing, audio and complete context. Read AFP’s full verification of the manipulated General Dhiraj Seth clip . On 14 August 2026, AFP published another investigation involving an altered video of Congress MP Shashi Tharoor. Social-media posts made him appear to praise Pakistan’s diplomatic position, criticise Narendra Modi and describe Indian diplomacy as ineffective. AFP found that the visual source was an older NDTV interview from 9 April about Pakistan’s role in Middle East ceasefire talks, months before the agreement discussed in the false caption. Tharoor’s press team said the circulating version was fake, the original interview contained no such criticism and Hive Moderation assessed the counterfeit speech as 97.2 percent likely to be AI-generated . Here is the authentic NDTV interview that supplied the face, studio and visual credibility: Read AFP’s full verification of the altered Shashi Tharoor video . A third AFP investigation, published on 11 August 2026, concerned an image used to claim that an Indian official had secretly joined a protest connected to Pakistan-administered Kashmir. Investigators found the original rally footage and showed that a different person stood in the crowd. The circulating image had placed an Indian official’s face over that person’s face, while a separate portrait in the graphic actually showed another Indian diplomat. AFP reported that OpenAI’s verification system detected a SynthID watermark and concluded that the altered image had been generated using OpenAI tools. Read AFP’s image-provenance investigation . I have linked these AFP reports rather than copying their photographs into the local image folder because AFP’s page carries explicit restrictions on commercial republication. That is also a useful reminder for researchers: proving that a picture is fake does not make the fact-checker’s comparison graphic copyright-free. Evidence collection still needs a legal and ethical layer, which is annoying but generally preferable to receiving an invoice from a news agency. These examples show why the threat continues after the immediate military crisis. Old-fashioned context manipulation depends on finding a convenient explosion. Synthetic media can manufacture the missing confession, insult or admission on demand. The attacker can target whichever institution currently needs destabilising: the Army, an opposition politician, a journalist, a protest movement or an ethnic community. The defence is not to trust every AI detector score. The stronger workflow is to combine several checks: AI has made the forgery faster. It has not made source verification obsolete; it has made source verification the main event. The Cambridge Analytica comparison is useful as long as we do not turn it into a slogan. The US Federal Trade Commission found that Cambridge Analytica used deceptive practices to obtain Facebook information for voter profiling and targeting. FTC records describe data collected from roughly 250,000 to 270,000 US app users and approximately 50 million to 65 million of their Facebook friends, including at least 30 million identifiable US consumers. The common logic is straightforward: learn about an audience, divide it into useful segments, test which messages produce a response, target emotion and repeat what works. Cambridge Analytica’s scandal centred on deceptive data collection, personality profiling and voter targeting. The exact degree to which its work changed election outcomes remains much harder to establish than the fact that the data practices occurred. Today’s influence market can use a wider and sometimes simpler approach. An operator may not need a secret psychological profile for every user because social platforms already reveal trending topics, public communities, engagement patterns and advertising audiences. Recommendation systems perform continuous experiments on what keeps users watching. The operator can inject many pieces of content, measure which one receives anger or support and then scale the successful version. There is also a change in the target. Cambridge Analytica is usually discussed as an attempt to influence voters. Modern identity operations may aim to change the sentiment of whole populations toward people from a particular country, colour, religion or migration group. The desired result may not be “vote for candidate X.” It may be “when you hear Indian, immediately think dirty, dishonest, dangerous or unwanted.” That is slower, less measurable and potentially more damaging. Election campaigns end. A racial association can remain inside ordinary conversation for years. The recurring stereotypes about smell, deodorant, cow urine, cow dung and street defecation are effective online because they are simple, visual and emotionally unpleasant. The person sharing them needs no knowledge of Indian history, policy or economics. They need one image, the word “India” and a caption short enough to fit above it. This content also works through association rather than argument. A false policy claim can be disproved with a document. A dehumanising stereotype does not need to be logically consistent; it only needs to be repeated until it becomes the first mental image associated with a group. The goal is often not to win a debate but to lower the social cost of contempt. India has real sanitation, pollution and public-health problems, just as other countries have real problems. Saying that a city needs better waste management is criticism. Saying that every Indian is dirty or smells is racism. The difference is not complicated, although the replies section will pretend it is doing a PhD on the matter. The payload is also portable across political groups. A geopolitical propagandist can use it to damage India. A Western nativist can use it against Indian immigrants. A religious extremist can attach it to anti-Hindu content. An engagement farmer can post it with no ideology at all. These actors may hate one another, yet they can still reuse the same meme because the meme produces reach. Once the phrase becomes common, organic users do the rest. Some repeat it as a joke, some use it to signal membership in a group, some reply because they are angry and some quote-post it to condemn it. All of those actions can increase distribution. The operation lights the match; the public supplies the petrol. I also came across the widely shared Telegram screenshot in which a participant allegedly discussed creating so much hatred toward Indians that people would become willing to attack them in real life. That screenshot is one reason the subject stopped feeling like annoying internet trolling and started feeling like a safety issue. However, I could not independently authenticate the original Telegram archive. Public copies do not provide the complete export, original message and channel identifiers, reliable account-ownership evidence, full timestamp context or an independent forensic report. Several online threads went much further, naming an alleged operator and claiming links to Pakistani state organisations, but I did not find enough public technical evidence to repeat those claims as fact. I have therefore chosen not to embed an unredacted screenshot or identify the alleged individual in this post. That is not because the message is unimportant. It is because a screenshot is a lead, not a court judgment, no matter how many red circles, siren emojis and words like “EXPOSED” somebody adds around it. In cybersecurity terms, the screenshot is an indicator of interest , not attribution. Anyone holding the original export should preserve the complete files, message identifiers, timestamps, hashes and surrounding conversation, then provide them to a reputable platform investigation, digital forensics team or law-enforcement body. Publishing addresses, family details or unrelated private information would not make the evidence stronger; it would only create another harassment campaign. If the message is authenticated, its intent is deeply serious because it describes the normalisation of hatred as a route toward physical violence. Until authentication is available, the responsible statement is narrower: a disturbing screenshot circulated publicly and deserves investigation, but its authorship, network size and alleged state links remain unverified. The honest answer is that there is no public evidence of one controller behind all anti India hate. The best-supported picture contains several distinct networks and several different control layers. The influence supply chain may include a state-linked employee setting an objective, a commercial firm producing content, an account farm creating fake consensus, an influencer giving the material visibility and a recommendation engine completing the distribution. Another campaign may contain no government at all and simply be a group of ideological accounts farming anger. One giant Bond-villain control room would be easier to explain. The evidence points to something more boring and possibly worse: several actors with different motives have discovered that the same small collection of stereotypes is cheap, portable and profitable, while the platform coordinates their incentives for free. A credible investigation cannot describe India only as a victim. In the same April 2019 announcement that linked the Pakistan-origin network to ISPR employees, Meta separately removed 687 Facebook Pages and accounts connected to individuals associated with the Indian National Congress IT Cell. It also removed a smaller group of 15 Pages, Groups and accounts linked to people associated with the Indian IT company Silver Touch . Meta said these were separate and unrelated operations. Google’s 2026 bulletins also listed multiple India-linked YouTube clusters promoting the Indian government, political parties or public figures. The Q1 report alone described several large groups, including one containing 628 channels, while the Q2 report documented additional India-linked clusters. The Operation Sindoor research similarly found misinformation from Indian accounts, influencers and mainstream media as well as Pakistan-linked sources. This does not cancel the ISPR attribution, excuse anti-Indian racism or make an alleged threat less serious. It shows that covert influence tactics are tools, not national personality traits. India can be targeted by influence operations and Indian political actors can participate in influence operations. Both statements can be true. Hiding the second statement would make the first one easier to dismiss as partisan propaganda. A research article becomes propaganda very quickly if it only preserves evidence that is convenient. So after looking at the Pakistan-linked networks, foreign influence operations and anti-India synthetic-media cases, here are the examples where Indian accounts, influencers or newsrooms amplified false material during the same information war. I am keeping these later deliberately: they are important context, but they are not the reason I began this investigation. One of the clearest examples of context manipulation began with reports that the Indian Navy had destroyed Karachi port. The claim moved through social media and several Indian news outlets even though the official military briefings had not announced such an attack. The Center for the Study of Organized Hate documented multiple outlets using an image from a 2023 naval exercise as supposed evidence of a 2025 strike. Alt News separately found that the ship shown in the widely reused image was INS Vikramaditya, not INS Vikrant and that the picture came from an earlier drill rather than combat. Source: Center for the Study of Organized Hate, Inside the Misinformation and Disinformation War, page 6 . The report lists Prabhat Khabar, Lokmat Times, Live Hindustan, The Mojo Story and ABP News among outlets that used the old drill image. Then the geography became even more ambitious. ABP Ananda broadcast footage as destruction at Karachi port, but Alt News traced it to the aftermath of a January 2025 plane crash in Philadelphia , thousands of kilometres away. Other accounts copied the same clip and added captions about Pakistan being destroyed. This technique is called context laundering: the pixels may be real, but the date, place and meaning are replaced. Source: CSOH report, page 15 , with further verification by Alt News . The following is the broadcast clip preserved by Alt News. It is useful evidence because it shows how a false social-media claim acquired studio graphics, an anchor and the visual authority of television. It also shows why “mainstream media” and “social-media troll farm” should not be analysed as separate universes; one can ingest the other’s untrusted input and then send it back to the internet with a much larger amplifier. Open the preserved ABP Ananda clip directly on Vimeo . What this proves: old and unrelated visuals were used by identifiable accounts and news outlets to support a false Karachi-port narrative. What this does not prove: every outlet or account involved was part of one secret network. Some may have copied one another, trusted a bad source or simply abandoned verification because the red BREAKING NEWS template was already open. The next technique required almost no image editing. Footage of bombings and destruction in Gaza was reposted as Indian strikes on Karachi, Sialkot and other locations in Pakistan. The visual remained the same; the caption performed the entire operation. CSOH documented the material being shared by influential Indian accounts and pages including The Jaipur Dialogues, Megh Updates and Kreately Media. Source: CSOH report, page 14 . One post from received more than 727,000 views , according to the report. This is the kind of number that helps explain why a correction arriving six hours later can feel like a person whispering into a hurricane. The false post has already been clipped, translated, forwarded to family groups and absorbed into people’s mental record of the conflict. This example also shows why the word morphed can sometimes be misleading. Many successful fakes do not require Photoshop, face replacement or a clever AI model. A genuine image plus a false sentence is often enough. From an attacker’s perspective, this is excellent return on investment: no GPU bill, no complicated prompt, just a recycled explosion and confidence. Military simulation footage is especially effective because it was designed to look dramatic. A clip already contains missiles, aircraft, tracers and explosions, so the operator only needs to remove the game context, add patriotic music and write “exclusive footage.” During Operation Sindoor, CSOH documented posting flight-simulator footage as Pakistani jets being shot down over Bhuj; the post received about 2.2 million views . The report also documented sharing game footage that received roughly 1.6 million views , with another clip reaching around 1.3 million . Source: CSOH report, page 16 . Not every fake reused old material. Some were created from scratch. CSOH documented an AI-generated image claiming Rawalpindi Cricket Stadium had been destroyed. One post received about 9.6 million views, 129,000 likes and 12,700 reposts . A separate account posting the same general claim received around one million views. The image was visually dramatic, immediately understandable and perfectly shaped for a crisis feed, which is another way of saying it had everything except the minor administrative detail of being real. Source: CSOH report, page 18 . The attack surface here is larger than one image. Generative models allow operators to produce the same event from several angles, create a fake “citizen journalist” photograph, add smoke to a real landmark, generate a matching witness account and translate the package into several languages. The result does not need to survive a forensic examination for months. It may only need to survive for twenty minutes, which is long enough for a verified account or a television producer to pick it up. A single wrong post can be a mistake. A group of accounts publishing near-identical language, using the same old hospital photographs, at roughly the same time and promoting the same fabricated document is a stronger coordination signal. CSOH reported such a pattern around a supposed nuclear-radiation leak in Pakistan. The report named Kreately Media, , Voice of Hindus, The Jaipur Dialogues and Cyber Hunts among the accounts amplifying the claim and said keyword searches showed near-identical posts appearing around midnight. Source: CSOH report, pages 26–27 . This case is closer to what security analysts look for when they say coordinated behaviour . The strongest indicator is not that several people discussed radiation; a major incident would naturally produce that. The stronger pattern is shared wording, shared source assets, synchronized publication and a common fabricated artefact. It resembles a phishing campaign in which many emails contain the same lure and attachment, except the attachment is a story designed to execute inside public opinion. These examples do not cancel the foreign influence cases described earlier. They show why the defensive lesson has to be broader than nationality: once a crisis rewards speed, certainty and outrage, domestic actors can become part of the same misinformation supply chain even without being connected to a hostile network. The impact of an influence operation is often described using abstract words such as sentiment, polarisation and narrative dominance. Regular people experience it in much simpler ways. An Indian student abroad may receive hygiene abuse under an ordinary post and begin hiding where they are from. A worker may watch colleagues repeat a stereotype as a joke and wonder whether it affects hiring or promotion. A family may panic during a military crisis because a fake video claims that a city, airport or base has been attacked. A journalist or researcher may lose access to an account after coordinated reporting. A shop, temple, mosque or community organisation may receive threats after an online narrative turns a whole group into an enemy. The danger is not limited to content that directly orders violence. Repetition can make dehumanising language feel normal and normal language changes what people believe they are allowed to say or do. The transition from “it is only a meme” to discrimination does not happen in one dramatic moment. It happens through thousands of small permissions. A useful example appeared in Singapore in 2026. The Ministry of Home Affairs ordered platforms to block 14 posts targeting Singapore’s Indian community. The posts used crowded footage from Little India and religious events to portray the country as being overrun by Indians and used language assessed as attempting to incite ill-will. The government later said the material likely originated on a China-based platform but found no evidence that it was a coordinated campaign by a particular government or organisation . Read the initial action and the investigation findings . That case demonstrates both halves of responsible analysis. The posts were serious enough to threaten social cohesion, yet investigators did not claim a government campaign without evidence. “No proven state sponsor” does not mean “no harm” and “harmful content came from abroad” does not mean “an entire foreign population is responsible.” Influence operations also damage the people who fight them. Fact-checkers, moderators and researchers must repeatedly view violent, racist or traumatic content while working against a clock. Ordinary users become suspicious of every account, every video and sometimes every person from the country they believe is responsible. A campaign designed to create hatred succeeds twice if its target responds by hating another nationality in return. Cybersecurity investigations work best when the analyst collects repeatable evidence rather than relying on instinct. The same rule applies here One suspicious profile is an anecdote. Several accounts sharing the same unusual language, infrastructure, timing and engagement network are a case. A good evidence package should preserve the full URL, account handle, numeric account ID where available, exact timestamp, full-page screenshot, screen recording showing context, archived copy, original media file and cryptographic hash. It should also document matching posts and explain why the behaviour is suspicious. Do not crop away the timestamp and then complain that nobody can verify the timestamp. Do not edit the screenshot, add filters or cover the only useful account identifier with a giant red arrow. Do not publish home addresses, family photographs or unrelated personal information. Evidence is meant to establish behaviour, not start a revenge mob. Reverse-image search remains one of the fastest checks during a crisis. Look for the earliest known upload, landmarks, weather, language on signs, uniforms and whether the “war footage” contains a video-game interface. AI-image detection tools can be useful leads, but they are not reliable enough to serve as the only proof. Provenance, source history and visual inconsistencies together are stronger than one detector score. The most important habit is to write confidence levels into the notes. Use phrases such as “consistent with coordination,” “linked by the platform,” “likely shared infrastructure” and “unverified allegation.” This may sound less exciting than “MASSIVE BOT ARMY EXPOSED,” but it has the small advantage of being useful. Platforms should detect behaviour, not only prohibited words. A generated comment may contain no banned phrase, while the suspicious part is that 500 newly created accounts posted variations of it within ten minutes and immediately liked one another. Shared administrators, payment instruments, domains, device patterns, account-creation bursts and synchronized amplification are often more valuable than the text alone. Takedown reports should also contain more structured information. Researchers need safe access to account identifiers, creation periods, shared domains, network relationships, engagement measurements and removal reasons. When a platform deletes every asset without preserving a research archive, it removes the operation and much of the evidence at the same time. New accounts should face reasonable friction before gaining massive political reach, especially when they immediately post high-volume content or operate as part of a coordinated cluster. Platforms should preserve evidence of violent threats for lawful investigation rather than simply deleting an account and declaring the problem solved. Finally, recommendation systems should stop treating angry quote-posts as unconditional endorsements. When thousands of Indians repost a racist account to condemn it, the platform often rewards the original account with more distribution. The algorithm understands engagement; it does not understand dignity. Every newsroom covering a military or communal crisis needs a verification desk with authority to slow publication. The basic process is not glamorous: locate the original upload, reverse-search key frames, inspect timestamps, contact the claimed source, compare landmarks and wait for independent confirmation. It is still cheaper than broadcasting video-game footage as an air strike and spending the next week quietly deleting clips. Corrections should receive visibility comparable to the original false report. A ten-second correction at 2 a.m. does not repair a false claim shown for hours under a “BREAKING” banner. News organisations should preserve a public correction log and explain how the failure happened, particularly when a social-media account was the only source. Newsrooms should also treat anonymous viral posts as potentially hostile input, just as a security team treats an unexpected attachment. Verification is the content-security gateway. Bypassing it because a clip looks dramatic is the editorial version of disabling antivirus because the file name says . Governments should publish influence-operation findings with explicit confidence levels and separate foreign-linked activity, state-attributed activity, commercial influence work, organic misinformation and unverified allegations. Combining everything under a phrase such as “anti-national propaganda” may be politically convenient, but it makes serious attribution less believable. India would benefit from an independent influence-operations research centre involving digital forensics specialists, linguists, civil-society organisations, journalists, legal experts, diaspora groups and platform representatives. Its reports should publish evidence regardless of which political party benefits. A centre that investigates only the opposition is not a research centre; it is a campaign office with better WiFi. During crises, a public evidence dashboard could show each major false claim, earliest known source, authentic source material, reverse-image result, platforms involved, estimated reach and confidence level. The response must also be precise enough that fighting disinformation does not become a general excuse to block inconvenient journalism. Governments and embassies should treat identity-based disinformation as a community-safety issue, not only a national-image problem. Students, workers, businesses and religious institutions need clear routes for reporting credible threats, impersonation, doxxing and calls for violence. Before sharing a dramatic post, find the original source rather than the largest account repeating it. Check the date, location and whether the visual appeared during an older war, disaster or protest. Search a few frames, inspect the account’s history and ask whether the caption provides evidence or only confidence. When a post is racist, avoid giving it free distribution through an angry quote-post unless there is a clear public-interest reason. Save the URL, preserve the context, report the content and send credible threats to the appropriate authorities. Replying with racism against Pakistanis, Chinese people, Muslims, Hindus, Jews, Westerners or anybody else does not defend India. It gives the original operator exactly the screenshot they wanted. The most useful personal defence is emotional delay. Influence content is designed to make the share button feel urgent. Waiting five minutes, checking one source and refusing to become unpaid distribution infrastructure is a small but real security control. I started looking into this because my feed felt different after Operation Sindoor. The same insults appeared too often, under too many unrelated posts and with too little originality to dismiss as random noise. The research confirms that organised anti India influence networks exist, including one Meta linked directly to employees of Pakistan’s ISPR, another connected to people associated with a PR company and repeated Pakistan linked channel clusters removed by Google. The research also confirms that the wider hate wave is not controlled by one country or one company. Western nativist accounts, commercial operators, political campaigns, state-linked employees, fake media pages, AI tools, real influencers and ordinary users all contribute. India-linked influence networks exist as well. AI accelerates production, while recommendation algorithms reward the resulting conflict. The goal is not always to make the world believe one exact falsehood. Sometimes the goal is to fill the information space with so much noise that nobody knows what happened. Sometimes it is to force Indians to spend all day defending their humanity. Sometimes it is to turn a nationality into a punchline, then turn the punchline into a permission structure for discrimination. Sometimes it is simply to earn money from anger. The right response sits between two bad extremes. We should not believe every dramatic screenshot and declare a state conspiracy without evidence. We should also not pretend that repeated platform takedowns, fake media networks, mass-reporting tools and millions of views are imaginary because complete attribution is difficult. Cybersecurity has a useful discipline for this problem: preserve the artefact, document the behaviour, state the confidence level, identify what remains unknown and update the conclusion when better evidence arrives. No dramatic background music required. If you are still reading, thanks for surviving the longest replies-section investigation I have ever done. Please do not celebrate by opening the replies section. Meta linked one specific 2019 Pakistan-origin network to employees of Inter-Services Public Relations or ISPR , the Pakistani military’s communications organisation. That network used 103 Facebook and Instagram assets and reached Pages followed by roughly 2.8 million accounts. Meta and Graphika connected a separate 2021 network to individuals associated with AlphaPro , a Pakistan-based PR firm. It used fake media outlets, fake identities, professional presenters, paid actors and freelance journalists to push political narratives, including material attacking India. Stanford Internet Observatory documented a Pakistan-based mass-reporting network that used a Chrome extension called Auto Reporter . Researchers identified the public developer and his company connection, but explicitly said they did not know who controlled the wider operation. Google removed 447 Pakistan-linked YouTube channels across three reported quarters from late 2025 to mid-2026. The channels supported Pakistan and criticised India, although Google did not publicly identify a government sponsor. A 2026 report on high-engagement anti-Indian posts on X counted more than 24,600 posts from nearly 14,000 authors , with more than 300 million reported views, 8.5 million likes and 901,000 reposts during 2025. Much of this activity came from Western nativist and immigration politics, which is an important reminder that not every anti-Indian account is Pakistani or state-controlled. During the May 2025 India–Pakistan crisis, one research group collected roughly 1,200 misleading or relevant posts across major platforms. Its closer X sample contained 437 posts, 179 from verified accounts, while only 73 had Community Notes at the time of analysis. Individual fake visuals reached millions of views. The visual evidence is not limited to charts. This version follows real cases involving a 2023 naval-drill image presented as a Karachi attack, Philadelphia crash footage moved to Pakistan, Gaza footage relabelled as Operation Sindoor, ARMA 3 and flight-simulator clips promoted as combat, an old Islamabad fire described as a drone strike and AI-generated stadium, surrender and captured-pilot narratives. BOOM documented a recurring X cluster that seeded AI-manipulated videos through handles including , , and , alongside fake personas posing as Indian users. BOOM found strong signs of a troll-farm-style influence operation but said the ultimate operator was unknown. OpenAI caught an Israeli political-campaign company, STOIC , using AI-generated material and fake personas in an operation that briefly entered India’s 2024 election conversation. OpenAI disrupted the India activity within roughly 24 hours and found little authentic reach. India-linked political influence networks also exist. Meta and Google have removed coordinated networks promoting Indian parties, leaders and government positions. Any article that hides this is not research; it is a fan club with footnotes. Reconnaissance: Find an angry audience, a political fault line, a vulnerable community and a stereotype that already produces engagement. Resource development: Create fake personas, pages, websites, Telegram groups, backup accounts and a library of reusable videos and images. Seeding: Publish the first claim, fake news report, meme or edited clip. Amplification: Use coordinated accounts, paid influencers, volunteers, advertisements and reply brigades to create the appearance of momentum. Laundering: Move the claim through verified accounts, news channels, short-video pages and WhatsApp until the original anonymous source disappears. Impact: Change sentiment, create panic, exhaust critics, intimidate a community, damage trust or make hostility feel socially acceptable. Platform-attributed: A platform such as Meta, Google or OpenAI investigated the network using internal signals that outsiders cannot normally access. This is the strongest public evidence available, although the public report may still omit details. Research-linked: Independent researchers found shared employees, domains, administrators, content assets, posting patterns or other connections, but the final client or command chain remains unknown. Alleged or unverified: A screenshot, anonymous document or social media thread makes a claim that has not been independently authenticated with original records and supporting evidence. A network was linked to accounts operating from Pakistan. A network was linked to employees of a named Pakistani organisation. Pakistan’s government ordered and funded the operation. Threat Analysis Group bulletin, Q4 2025 Influence Operations Bulletin, Q1 2026 Influence Operations Bulletin, Q2 2026 Find the earliest available upload and the complete original recording. Compare the spoken words, language, room sound, cuts and lip movement. Look for the same speech on official or reputable channels. Inspect content credentials or provenance signals where available. Use detection tools as supporting indicators, not as the only evidence. Preserve the false post, because accounts frequently delete it after a fact-check appears. Meta: Removing Coordinated Inauthentic Behavior and Spam From India and Pakistan, April 2019 Meta: May 2021 Coordinated Inauthentic Behavior Report Graphika: Lights, Camera, Coordinated Action DFRLab: Pakistan Army’s Covert Social Network Coda Story: Fiverr actors in Pakistan’s fake-media network Stanford Internet Observatory: Reporting for Duty — Pakistan-Based Mass Reporting Network Google Threat Analysis Group Bulletin, Q4 2025 Google Influence Operations Bulletin, Q1 2026 Google Influence Operations Bulletin, Q2 2026 Center for the Study of Organized Hate: Inside the Misinformation and Disinformation War Center for the Study of Organized Hate: India–Pakistan Digital War summary Reuters Institute: How Indian fact-checkers handled the India–Pakistan crisis Press Information Bureau: Government statement on more than 1,400 blocked URLs Alt News: The fictional strikes on Karachi port and what they say about Indian media The Quint WebQoof: ARMA 3 simulation shared as Pakistan attacking India AFP Fact Check: Old Faisal Mosque fire relabelled as an Indian drone attack OpenAI: Operation Zero Zeno OpenAI: Disrupting deceptive uses of AI by covert influence operations OpenAI: Disrupting malicious uses of AI, October 2025 Meta: What We Saw During the 2024 Global Elections BOOM: How AI-driven deepfakes are fuelling disinformation against India on X AFP Fact Check: Manipulated video of Indian Army chief General Dhiraj Seth AFP Fact Check: Altered Shashi Tharoor interview AFP Fact Check: AI-altered image used to frame India in Kashmir protests Network Contagion Research Institute: From Policy Drift to Purity Grift American Bazaar summary of the NCRI anti-Indian content findings PNAS: Out-group animosity drives engagement on social media Singapore Ministry of Home Affairs: Action against 14 posts targeting the Indian community Singapore Ministry of Home Affairs: Investigation findings and attribution limits FTC: Cambridge Analytica decision FTC: Cambridge Analytica data-collection allegations and figures DISARM Red Framework MITRE ATT&CK

0 views
Himanshu Anand 3 weeks ago

I found a KVM guest-to-host heap corruption bug and someone else got there first

I independently found a heap out-of-bounds read/write in KVM’s SEV-SNP Page State Change handler. A malicious guest VM can corrupt host kernel heap memory and leak its layout, across the VM boundary, as many times as it wants. I reported it to [email protected] on May 9, 2026 and they told me someone else reported it a few weeks earlier. This post is the story of that bug, why my fix was wrong, why theirs was better, how bad this thing actually is, and what you can learn from all of it. I also built a safe CTF challenge so you can practice the same primitive without setting anything on fire. CVE-2026-53360. Fixed in mainline commit . Affected every kernel with SNP PSC support since ~v6.10. Patched in v7.0.12, v6.18.35, v6.12.93. I have been reading KVM code on and off for a while. Not because I enjoy pain (becasue I got no day job), but because the hypervisor boundary is one of the most interesting attack surfaces in modern computing. A bug there means a guest can punch through the wall and mess with the host and that’s the whole game. (Kinda wow, you spin up a VM in cloud and pwned whole cloud. Is this what they call the God syndrome?? ) In early May I was staring at , specifically the Page State Change handler for SEV-SNP guests. If you do not know what any of those words mean, do not worry, I will try to explain all of it in a minute. TLDR is : SNP guests talk to the host through a shared page called the GHCB and PSC is one of the things they can ask for. The host parses a buffer the guest provides and the buffer has a header and an array of entries. I noticed something that made me sit up. The guest gets to choose how big the buffer is, The host allocates exactly that many bytes But then the host loops over the entries using a count from the header and the only bounds check on that count is against a protocol constant (253)and not against the size of the buffer it just allocated. So, if the guest says “here is an 8-byte buffer” and then says “please process 252 entries,” the host happily walks 2 KB past the end of an 8-byte allocation into whatever else is sitting on the kernel heap. I wrote a proposed fix and I verified it compiled cleanly, sent it to [email protected] on May 9. I felt pretty good about myself (oh! no god syndrome again). Then I got this back: Hi Himanshu, this was reported already a couple weeks ago. It’s not an easy fix because the issue is bigger than what you found. Sorry about that, I appreciate trying to work on the fix! If you have read my post about disclosure timelines , you know this feeling. I talked about being reporter number eleven on a different bug. This time I was reporter number two Progress, I guess. The first reporter was Stan Shaw, who reported this on April 8, about a month before mine. He got the CVE (CVE-2026-53360), he got the Reported-by credit in the commit and he published a detailed writeup and a PoC . His analysis was thorough and his PoC produced 73 KASAN reports from a single . Credit where it is due. But the part that stung was not losing the CVE. It was the second sentence: “the issue is bigger than what you found.” That turned out to be completely true, and understanding why taught me more than finding the bug did. If you already know what SEV-SNP is, skip ahead. If you do not, here is the minimum you need to follow the rest of this post. AMD makes server chips called EPYC. Starting with the Milan generation (2021), these chips support something called SEV-SNP: Secure Encrypted Virtualization with Secure Nested Paging.The idea is simple: the guest VM’s memory is encrypted with a key that even the hypervisor cannot read. The CPU hardware enforces this, hypervisor is treated as untrusted. This is built for cloud computing, uou are running your workload on someone else’s server, do not trust the cloud provider, do not trust their hypervisor. SEV-SNP says: We will encrypt your memory so even if the hypervisor is compromised, your data stays private. Nice one That is the marketing pitch And reality, hardware does enforce it. But here is the thing nobody puts in the marketing materials: the host also has to defend itself against the guest. The whole point of confidential computing is running someone else’s code that you do not trust. The guest is untrusted from the host’s perspective too. Both directions matter. This bug is in the second direction Guest attacks host. The direction everyone forgets about because the SEV-SNP marketing is all about protecting the guest. SEV-SNP guests cannot just talk to the hypervisor normally. Their memory is encrypted, So there is a shared page called the GHCB (Guest-Hypervisor Communication Block). Think of it as a 4 KB mailbox that both sides can read and write. When the guest wants something from the host, it fills in some fields on the GHCB and triggers a VMGEXIT. The host reads the GHCB, does whatever the guest asked, writes the response and lets the guest resume. The GHCB has a few important parts: For GHCB version 2 and later, the spec says the scratch area should live inside the Shared Buffer. This matters a lot Keep it in mind. PSC stands for Page State Change. It is how an SNP guest tells the host “hey, I want this page to be private” or “hey, I want this page to be shared.” The guest fills in a PSC descriptor: an 8-byte header followed by an array of 8-byte entries. The host processes entries from to . Both values are coming from the guest. How many entries can fit? Well, (2032 - 8) / 8 = 253. That is where the protocol maximum comes from. It is the capacity of the GHCB Shared Buffer after the header. Makes perfect sense. If the buffer is actually the Shared Buffer. Here is the vulnerable path, step by step. I am walking through the v7.0.5 code because that is what I was reading when I found it. Step 1: the guest sets up the request. The guest puts (0x80000010), points at a guest page containing a crafted PSC descriptor nd puts the descriptor length in . That length is completely guest-controlled. Step 2: allocates the buffer. If the scratch area is inside the GHCB, the host just uses its existing mapping. No allocation needed But if the guest points the scratch area outside the GHCB (which SNP should never do, but nothing stopped it), the host allocates a kernel buffer: comes from , the guest chose it. puts it in the cgroup-accounted slab caches, which is why KASAN says later. Set and you get a 24-byte allocation in a 32-byte slab slot. That is room for the 8-byte header and exactly two entries. and . starts at byte 24. That is 8 bytes of slab slack. is another object entirely. Step 3: processes the entries. Here is where it goes wrong: The check asks: “Is this index valid for the biggest possible PSC buffer?” The right question is: “Is this index valid for the buffer I actually allocated?” 253 is the capacity of the 2032 byte Shared Buffer. But the host allocated a 24 byte buffer. Two entries fit and check allows 252. Set and you read 8 entries past the end. Set it to 252 and you walk about 2 KB into adjacent slab objects. Step 4: the write. For each OOB entry, the host reads 8 bytes of neighboring memory and decodes it as a . If the decoded entry passes some validation checks, the completion code writes back: That is a 12-bit write into the low bits of a u64 that belongs to another kernel object. The value (1 or 512) depends on bit 56 of the victim memory, not on anything the attacker chooses. The upper 52 bits are preserved. Sounds small? indeed it is small but small writes have a long and storied history of being enough. Here is what I proposed: I was proud of this. It checks against the actual buffer size, not the protocol constant, It compiles ¯_(ツ)_/¯ It produces the right instruction sequence and It stops the OOB. Here is what they shipped instead: Four lines. And they are better than my fix in every way, Here is why. (In my defense I am not a dev, me inside ┻━┻ ︵ヽ(`Д´)ノ︵ ┻━┻ ) Problem 1: I fixed the symptom, they fixed the disease. My patch says “the loop should not go past the buffer.” Their patch says “the guest should not be choosing the buffer size at all.” For GHCB v2 and later, the spec requires the scratch area to be inside the GHCB Shared Buffer. The host should never allocate a separate buffer for SNP guests. By rejecting external scratch at the input layer, the entire class of “guest picks a tiny allocation” attacks disappears. The loop bounds do not matter because the buffer is always the known, fixed size Shared Buffer. I was adding a guardrail to a road that should not exist. Problem 2: I missed the TOCTOU. The v7.0.5 code reads and straight from the guest-accessible buffer. No . The PSC handler is re-entrant (it exits to QEMU userspace and comes back), which means the guest can change those values between the check and the use. My patch adds a bounds check, but the value it checks can change before the loop reads it. The upstream series caches the indices into private per-vCPU state on first read: And the loop uses the cached copies, guest cannot touch them. I did not even think about this <(^_^)> , The code had a comment saying “the buffer can be modified by a misbehaved guest after validation,” and then went ahead and re-read the values in the loop, I read that comment and still missed it. Problem 3: I missed the offset variant. Even if the scratch is inside the GHCB, the guest can place the descriptor at an offset into the Shared Buffer. If it sits near the end and is close to 253, the loop walks off the end of the Shared Buffer (and the page). My check works for external allocations but does not cover this case. The upstream series bounds against derived from the actual remaining length. The lesson. I fixed one bug and they closed three bugs and an entire invalid state, with fewer lines. The kernel team said “the issue is bigger than what you found” and they meant it literally. If you take one thing from this section: do not patch the handler, patch the input. Fix the state that makes the bug possible, not the bug itself. When you eliminate invalid state at the boundary, every downstream consumer is safe automatically. When you add checks inside each consumer, you have to get every single one right, forever. I am going to go deep. If you want to understand VM escape primitives, this is the section. Everything above was the story. This one is free and reliable. When hits an OOB entry that fails validation (bad value, misaligned GFN), it returns an error with the index it stopped at. The guest sees this in the PSC response. By setting to increasing values one at a time and checking the response, the guest learns per-slot whether the adjacent 8 bytes decoded as a valid or invalid PSC entry. That is a 1-bit-per-8-bytes oracle of the neighboring heap. What can you learn from 1 bit per 8 bytes? It is not a full read. But it is enough to find your target. When an OOB entry passes validation, the completion code writes back: This is a compiler-generated read-modify-write on the full u64: Bits [12:63] are preserved. The value written depends on bit 56 of the victim memory, not on anything the attacker controls. “Oh come on, you can only write 1 or 512 into the bottom 12 bits? That is useless.” Is it though? If bits [0:11] of a neighboring object happen to be a length field , you just changed a length from (say) 0x020 to 0x200. That is 32 to 512. A 16x buffer expansion. If that length controls how much data gets copied in or out of the object, you now have a much more powerful OOB read/write through a completely different code path. That is primitive amplification. If bits [0:11] are part of a kernel pointer , you just redirected it within the same 4 KB page. Low 12 bits = page offset. The pointer now points at a different offset in the same page. If the page contains freed objects that have been reclaimed with attacker-influenced data, that redirect might land somewhere useful. If bits [0:11] are a refcount , you just changed 1 to 512. The object will not free when the last reference drops. 511 more reference decrements needed. That is a use-after-free setup: the code thinks the object is freed, but the refcount disagrees. If bits [0:11] are a state flag , bit 0 or bit 9 being flipped can change object behavior. Access control bits, lock states, “is-initialized” flags. I am not proving any of these work for a specific target object. But none of them are ridiculous. They are all shapes that have shown up in real kernel exploits before. When an OOB entry has or (the valid PSC operations) and passes validation, the host forwards bits [12:51] of the OOB memory to QEMU as a GPA: That is 40 bits of adjacent heap content leaked to the host userspace process. The guest does not see this directly (QEMU does), but if the guest has a second bug in QEMU, or if QEMU’s behavior in response to a nonsensical GPA is observable to the guest (timing, error handling, device state), there is an indirect leak channel. This is what makes the whole thing dangerous instead of just interesting. Each VMGEXIT re-allocates the scratch buffer. New slot on the freelist, New neighbors. The guest can fire unlimited VMGEXITs. Over hundreds or thousands of requests, the guest sweeps across different heap positions, building a picture of the slab layout and landing the constrained write at different targets. This is not a one-shot bug, this is a scanner with a built-in spray. The guest controls , which is the allocation size. Set it to 24 and you land in . Set it to 60 and you land in . The guest picks which slab class to attack, which determines what objects are in reach. That is target selection at the cache level. You still need luck or grooming for slot-level adjacency, but you get to pick the neighborhood. Individually, each primitive looks weak. Together, with unlimited retries and slab selection, they form the kind of toolkit that real exploitation research starts from. I am going to be honest. The primitives are real. Guest-triggered host kernel heap OOB read/write, repeatable, with slab selection and an information oracle. That is the hard part of VM escape and this bug gives it to you. But the raw write is constrained. You can only write or into the bottom 12 bits of an 8-byte slot. You do not even pick which of the two values you get. Finding a 32-byte cgroup-accounted kernel object where that specific corruption leads to something useful is the real research problem. The most realistic path is primitive amplification: That chain is plausible, it is the same shape as many real kernel exploits. But the gap between “plausible” and “reliable” is where months of target-object research live. My assessment: This bug sits in a specific class that kernel exploitation researchers call “interesting but constrained.” The cross-boundary aspect (guest to host) makes it more interesting than a typical local kernel bug. The constraint (12-bit write, two possible values) makes it harder to exploit than a typical heap overflow. In a responsible-for-the-cloud scenario, “interesting but constrained” is still a five-alarm fire, because the attacker has unlimited time and retries inside their own VM. Do I think a motivated attacker with AMD EPYC hardware and a few months of kernel exploitation research could turn this into an escape? Yeah, I do. The pieces are there. But I have not done it and I am not going to pretend I have. Real world VM escapes are almost never one bug. There are multiple issue schained togather to chieve it. Here is how I would think about this if I were planning the chain (and to be clear, I am writing this for defenders to understand the threat model, not as a recipe). If the constrained write is not enough alone , you need a second bug that gives you one of: A better information leak. KASLR bypass, heap pointer leak, something that tells you exactly where things are instead of the 1-bit oracle. Candidates: KVM instruction emulation bugs that leak register state, QEMU device model bugs that expose host addresses, side-channel attacks (like the APIC MMIO leak class or speculative execution variants). A more powerful write primitive. If you can find a second KVM or QEMU bug that gives you a wider write, you use the PSC oracle to find the target and the second bug to hit it. KVM has had OOB writes in other handlers before. QEMU device emulation (virtio-net, USB passthrough, display backends) has been a rich source of memory corruption bugs historically. A host-side privilege escalation. Maybe the PSC primitive only gets you limited kernel corruption. Maybe you can crash a specific object into a state that gives you a lesser capability, like writing a file or calling a restricted ioctl. Then you chain that with a separate local privilege escalation bug on the host. The Dirty Pipe / Dirty Frag class of bugs would be perfect partners if any were unpatched on the host. Historical context. VM escapes that have been publicly demonstrated tend to go through the device emulation layer (QEMU) rather than through the hypervisor kernel code directly. VENOM (CVE-2015-3456) was a floppy controller bug in QEMU. Cloudburst (2009) was a display driver bug. The Pwn2Own 2024-2025 VM escapes targeted QEMU device models and VMware display handling. Going through KVM kernel code directly is harder because the kernel has stronger mitigations (KASLR, SMAP, SMEP, CFI). But it is also more powerful, because a kernel primitive gives you full host control without needing to escape QEMU’s sandbox first. The PSC bug is interesting specifically because it is in the kernel path, not in QEMU. If you could amplify the primitive, you skip the entire QEMU sandbox. I built two practice challenges so you can experience the primitives without needing AMD EPYC hardware or risking anyone’s infrastructure. A C program that models the core bug in a fake heap arena. Your goal: use the OOB PSC write-back to open a toy “host vault” without hitting the tripwire. kvm-sev-snp-psc-research/ctf-challenge/psc-vault/ Build it ( ), run for vulnerable mode, for patched mode. The solve: send an 8-byte header with . That indexes past the 2-entry scratch allocation into the vault object. The write-back sets the gate’s low bits to , which opens the vault. In fixed mode, the same input gets rejected because the entry count is checked against the actual allocation size. That is the whole bug in one interaction. The “protocol max” check passes (17 < 253). The “actual buffer” check fails (17 >= 2). Vulnerable mode only has the first check. A Python toy hypervisor with a randomized fake heap. Your goal: use the failure oracle to scan for a target object, then land the constrained write to trigger a fictional “escape.” kvm-sev-snp-psc-research/ctf-challenge/psc-vault/ Run to see the staged exploit workflow: The randomized target index changes every run, so you cannot hardcode it. You have to use the oracle. Neither challenge touches real KVM, real hardware, or real guests. They model the same primitive at a conceptual level. If you design hypervisors, sandboxes or any other kind of trust boundary, here is what this bug teaches you. 1. Bound against the real buffer, not protocol constants. The 253 was correct for the protocol. It was wrong for memory safety. Every time you have a “maximum count” from a specification and a “buffer size” from an allocation, those are two different numbers. Check the one that matters for memory safety. This sounds obvious. It was not obvious to the person who wrote the code, and it was not obvious to the reviewers who approved it. 2. Reject invalid state at the boundary, not inside the handler. The upstream fix does not add a check in the PSC loop. It rejects the invalid scratch allocation before the PSC loop exists. Fix the input, not every place that reads the input. If you find yourself adding bounds checks inside a loop that processes attacker data, ask: why does this loop even have access to a buffer that could be the wrong size? Can I prevent that earlier? 3. Treat guest data as attacker input. Every size, offset, count, and index the guest writes into the GHCB is untrusted. Parse it like you would parse a network packet from the internet. This is easy to forget in SEV-SNP because the marketing is about protecting the guest from the host. The host still has to protect itself from the guest. Both directions. 4. Use on shared memory. If the data can change between the time you validate it and the time you use it, you have a TOCTOU. The PSC handler had a comment about this and then ignored it. and caching into local variables costs nothing. 5. Test with KASAN. One produced 73 KASAN reports. Sixty-two slab-out-of-bounds, seven slab-use-after-free, four use-after-free. All against . If this code had been fuzz-tested with KASAN enabled, the bug would have been found before it shipped. KASAN is not optional for security-critical code paths. Build a KASAN kernel. Run your tests. Read the output. 6. The protocol is not the implementation. The protocol says the maximum PSC entry count is 253. The protocol says the scratch area must be inside the GHCB for v2+. Both of those things were true. The implementation did not enforce either of them. Specifications do not write bounds checks. Engineers do. And engineers miss things, especially when the spec is “obvious” and the code looks like it should work. This ties back to my post about disclosure timelines . I found the same bug as someone else, independently, about a month later. If two unrelated people found the same kernel VM escape bug within weeks of each other, how many others also found it and decided to use it instead of report it? The 90-day disclosure window is not protecting anyone here either. The bug existed for two years. It was independently found by at least two people in April-May 2026. The fix took about six weeks from first report to mainline. In the old world, that is fast. In the world where LLMs help people find bugs and turn patches into exploits, six weeks is a long time to leave a VM escape primitive sitting in production kernels. I am not going to pretend this experience did not sting. You find a bug in the hardest attack surface in computing, you write it up, you send it in, and you get told someone else was there first. It happens and honestly, the learning was worth more than the CVE credit would have been. Finding the bug took me a few hours. Understanding why my fix was wrong took longer. Understanding why their fix was better took even longer than that. The gap between “I can find a bug” and “I can architect the right fix” is the gap between a vulnerability researcher and a kernel engineer and I have a lot of respect for the people who closed that gap in this case. If you work on hypervisors, sandboxes or anything that parses attacker-controlled data at a trust boundary, I hope the primitives analysis was useful. The defensive lessons are real. The CTF challenges are there if you want to feel it in your hands. And if you made it this far, you are awesome. Thanks for sticking with me. (Liek subscribe and share my blog ʕ·͡ᴥ·ʔ ) The code and challenges from this post are at: kvm-sev-snp-psc-research/ctf-challenge/psc-vault/ The CTF challenges do not require SEV-SNP hardware. Hit me up on X ( @anand_himanshu ) if any of this resonated. And if you think my primitives analysis is wrong, especially hit me up. I would rather be corrected than confident. SW_EXITCODE : what the guest wants (like a function number) SW_EXITINFO1, SW_EXITINFO2 : parameters SW_SCRATCH : points to a scratch area for requests that need more data Shared Buffer : a 2032-byte region inside the GHCB itself Zero vs non-zero memory. Object boundaries (transition from data to freelist metadata). Which slab slots are allocated vs free. Rough structure of adjacent objects. Read 8 bytes at . Clear bits [0:11]. Set bits [0:11] to or . Write 8 bytes back. A better information leak. KASLR bypass, heap pointer leak, something that tells you exactly where things are instead of the 1-bit oracle. Candidates: KVM instruction emulation bugs that leak register state, QEMU device model bugs that expose host addresses, side-channel attacks (like the APIC MMIO leak class or speculative execution variants). A more powerful write primitive. If you can find a second KVM or QEMU bug that gives you a wider write, you use the PSC oracle to find the target and the second bug to hit it. KVM has had OOB writes in other handlers before. QEMU device emulation (virtio-net, USB passthrough, display backends) has been a rich source of memory corruption bugs historically. A host-side privilege escalation. Maybe the PSC primitive only gets you limited kernel corruption. Maybe you can crash a specific object into a state that gives you a lesser capability, like writing a file or calling a restricted ioctl. Then you chain that with a separate local privilege escalation bug on the host. The Dirty Pipe / Dirty Frag class of bugs would be perfect partners if any were unpatched on the host. Probe : scan OOB slots one at a time using the failure oracle. Find : locate the target object (where the oracle response changes from “invalid” to “completed”). Write : the completion write-back modifies the target. Escape : the toy “host door” is now open. Fixed mode : same request gets rejected.

0 views
Himanshu Anand 1 months ago

Two Parsers, One JSON and a Flag: Intigriti's July 2026 Challenge

Intigriti’s July challenge is called “Canonically Yours” and the whole bug is one silly question: what happens when you put the same key in a JSON object twice? Turns out two different bits of code answer that differently and that gap give you the flag. Its a mini package registry called Registry Observatory . You register you get a private namespace like and you can generate signed “compatibility reports” for packages you own. The flag lives in a report for a package you do not own. A quick read of the app “Observatory archive” endpoints tells you the target for free: there is a package in platform scope called the treasure is . The manifest looks like this: The sign step is the bouncer It only signs manifests for packages you actually own swapping to gets you a flat The title says it: canonicalization it kinda mostly means two pieces of code read the same input differently. The reproduction steps even mention your namespace going in the “first” package object which means there can be a second one. So send the key twice : The bet: the signer reads the first (mine, so it approves) and the report generator reads the last ( it builds that report). Same bytes, two readers with two answers. you cannot use , it silently drops the duplicate key, So You have to build the raw JSON as a plain string and Base64 that yourself. The sign step returns , the report comes back for and its release notes are the flag. Two quick dead ends that pin down exactly how the bug works: putting first gets rejected at signing (the bouncer reads the first key and bounces you) and duplicating inside one package object also fails the two parsers split at the level not deeper. The same signed blob was read by two JSON parsers that disagree on duplicate keys. One kept the first value and the other kept the last. The signature was valid it just covered the wrong half of the message. This class of bug is a parser differential (JSON duplicate-key confusion) and it has caused real auth bypasses and signature-smuggling in the wild. The fix is boring: use one parser, reject duplicate keys instead of guessing and act on the exact bytes you signed. Thanks to Intigriti and @silent_web3_ for a clever one. Go read your JSON parsers, they might not agree with each other. Build a small JSON “manifest” describing the package. Base64 it and send it to . The server checks it and, if it likes it, signs it. Send the same manifest plus the approval to . Read the report at .

0 views
Himanshu Anand 1 months ago

Reporter 11: 10 people found the WPForms PayPal bug before me (CVE-2026-4986)

WPForms Lite is a WordPress form plugin with 5 million plus active installations . Public advisory data identifies versions 1.10.0.1 through 1.10.0.4 as affected by CVE-2026-4986 : the PayPal Commerce webhook processed incoming events without first verifying that PayPal actually sent them. In my local lab, a forged event could change the state of a matching payment record. A forged event could mark a matching pending transaction as completed and trigger downstream payment-completed actions. A forged event could mark a matching transaction as denied. That is the kind of bug where the database starts telling a story that PayPal never actually told. WPForms fixed the issue in 1.10.0.5 . The 5 million plus number is the plugin’s reach, not a claim that 5 million sites were exploitable. Exposure depended on running an affected version and using the PayPal Commerce path. Please do not turn an install count into a casualty count. Cybersecurity already has enough dramatic arithmetic. I independently found and reported the issue, but I was not the first researcher. The public advisory credits Sudhanshu Chauhan of RedHunt Labs , and the response to my submission told me I was reporter number 11 . I was very late to the party. The snacks were gone. The CVE was already being prepared. Ten researchers were inside asking whether anyone had validated the webhook signature. If you read my earlier post on the death of the 90 day disclosure policy , you saw story one: I found a bug, sent it in and the triage team replied, “you are reporter eleven”. I left the technical details vague because I did not want to accidentally turn my blog into a free exploit feed. Rude to users, generous to attackers. Bad trade. The issue has now been patched, the public records exist and users have had time to update. So this is the technical epilogue I promised: the bug, the patch, the duplicate reports and the uncomfortable question hiding behind reporter number 11. I am calling it an epilogue because another article in this series already called itself the “final” post. Apparently my blog series has the same relationship with the word final that software projects have with . Late April 2026. I was poking at WordPress plugins and looking specifically at payment integrations. Payment code is interesting audit territory because the security cost of a bug can be real money, the attack surface is often public and developers have to glue several third-party systems together without dropping one tiny, extremely important check on the floor. WPForms is one of the biggest form plugins on WordPress, so I assumed the code would be well audited. Mostly is doing a lot of work in that sentence. I opened the PayPal Commerce integration folder and searched for . In a WordPress REST route, that is the part that answers a useful question: “Who is allowed to hit this endpoint?” Within about 90 seconds, I found this: means the endpoint is publicly reachable. That is not automatically a vulnerability. Webhook endpoints have to be public. PayPal cannot log in to your WordPress site, complete a CAPTCHA and whisper “please process my payment” into . The security question is what happens after the request arrives. I followed the callback. The handler read the raw request body, decoded the JSON, checked the event type against an allowlist and sent the payload to the relevant event handler. In the affected versions, I could not find the step that established that the request had actually come from PayPal before the event was processed. No validation of PayPal’s transmission signature. No verification callback to PayPal before processing. No meaningful rejection path for a completely unsigned request. The endpoint was basically saying: If you can spell , please come in. The money room is to your left. I built a curl request and tested it against a local installation I controlled. It worked on the first attempt. The matching payment record changed state and the payment-completed side effects ran. Well, cool in the very specific security-research sense where “cool” means “I now have paperwork and somebody else has a patch to write.” The triage reply on my report. They already knew. The first report had arrived weeks earlier and mine was report number eleven. the moment you realise 10 other people beat you to the same bug. yes, I screenshotted it. yes, it stings. and yes, this is the exact bug behind the story in my previous post. There was one more correction I had to make. My first mental mapping connected this behavior to CVE-2026-40764 , a separate CSRF issue fixed in 1.10.0.3. That was related only in the broad “payment integration security went sideways” sense. It was not the public record for the webhook-forgery behavior I reproduced. The payment-state issue discussed in this post is publicly tracked as CVE-2026-4986 and fixed in 1.10.0.5 . The corrected timeline is: The feature-introduction version and the published affected-version range are two different facts. I am keeping both explicit instead of asking the CVE spreadsheet goblin to infer one from the other. Public-record note: This article focuses on the payment-state behavior tracked as CVE-2026-4986. CVE-2026-7792 separately describes subscription state manipulation through the PayPal Commerce webhook path. CVE-2026-48835 is a broader broken-access-control record fixed in 1.10.0.5 but its public description is too sparse for me to map confidently to the exact behavior in this post. Three CVEs walked into one webhook path and made everyone do extra paperwork. This is the part where I tap the sign: Always re-check the final advisory before publishing. The CVE spreadsheet goblin is not your QA team. If you write webhook handlers for a living, you can skip this section and spend the saved time checking one of yours. For everyone else: a webhook is an HTTP callback. Service A wants to tell Service B that something happened. PayPal wants to tell WordPress that a customer paid, a payment failed or a subscription changed. The slow way would be for WordPress to keep asking PayPal, “did anything happen yet?” every few seconds. That is polling. It works, but it has the energy of a child asking “are we there yet?” for the entire drive. The smarter pattern is for PayPal to call WordPress when something happens. The mechanics are simple: The problem is that the URL has to be reachable from the internet. If WordPress trusts whatever appears at that URL, anyone can pretend to be PayPal. That is why providers document ways to verify webhook authenticity. PayPal documents two broad approaches: verify the transmission cryptographically using the delivery headers or send the event details back to PayPal’s endpoint for verification. Other providers use the same security idea with different packaging. Stripe uses a header and Square uses its own HMAC signature. Different logos, same rule: If you do not verify, you do not trust. A webhook handler without authenticity verification is just a public API endpoint that changes state while wearing a payment-provider name badge. Here is the whole asymmetry in one picture: The top block authenticates the sender before trusting the story. The bottom block checked whether the story looked plausible, but not who was telling it. That distinction is the whole article: Payload validation checks whether the story is internally consistent. Signature verification checks who is telling the story. The WPForms PayPal Commerce integration registered a REST route under with . Depending on the configured communication mode, a fallback listener could also reach the webhook-processing path, so this was not just a story about the REST API being public. The handler read the request body, decoded the JSON, checked the event type and dispatched it to a per-event handler. In the affected path I tested, a matching payment record could be updated without the request first being authenticated as a genuine PayPal event. Public route: normal. Public route that trusts unsigned payment events: considerably less normal. The exact production patch has to handle more than one connection mode, but the missing security boundary can be expressed like this: Authenticate first. Decode and process second. That is the whole bug, spiritually. Every check after JSON decoding can test the shape of the story. The authenticity check asks who is telling it . When reviewing a path like this, it is easy to find several checks and feel reassured. The handler did validate parts of the event. Those checks were useful, but none of them established that PayPal sent the request. These checks constrained the payload. They did not authenticate its source. if you have to spell correctly to forge a payment event, that is not source authentication. that is a spelling bee with financial consequences. There is a second trust issue worth calling out. Webhook processing normally happens without a logged-in WordPress user. Downstream payment code may therefore run in a trusted system context or bypass capability checks that would make no sense for a webhook. That can be perfectly reasonable after the webhook has been authenticated . Without authentication, the path is effectively saying: “No user account? No problem. The JSON has a lanyard.” I tested only against a local installation that I controlled. The public advisory already contains a minimal request for the denied-payment path, and fixed versions reject unauthenticated events. I am keeping this at the level needed to explain and reproduce the fixed bug in a lab, not at the level needed to make random WordPress shops have a bad afternoon. That check establishes reachability. It does not prove a vulnerability. A publicly reachable webhook route is expected. The bug was not: the route is public. The bug was: the route is public and then processes unsigned payment events. That request has no , no , no , no and no . A genuine PayPal delivery includes verification material. The affected handler processed the event without first validating that material. In a separate local test, I exercised the completed-payment path using a matching transaction and amount. The payment record changed state and the downstream completion actions ran. The important point is the same for both paths: the server accepted the event because the JSON looked internally consistent, not because PayPal had proved it was genuine. the exploit shape in one screen: unsigned request in, accepted response out, no PayPal in the room just vibes and JSON. One embarrassing housekeeping note: the HTTP header visible in this old screenshot does not match the 2026 release timeline. M keeping the original image because it was part of the original write up, but I am not asking anyone to treat that timestamp as evidence. Apparently the lab server also wanted to demonstrate time travel. The request shape is public nd the security claim rests on the code path, the local state change and the advisory not on that header. Some WordPress hardening guides recommend disabling unauthenticated REST API access. That can reduce some attack surface, but it is not magic dust. WPForms also supported a fallback webhook listener for installations using a non-REST communication mode. In the affected code path I reviewed, the fallback reached the same webhook-processing logic and therefore inherited the same missing authenticity check. That matters because “we disabled REST” is a very common WordPress security blanket. Sometimes the blanket is warm. Sometimes the window is still open. The lesson is simple: a fallback path needs the same security property as the primary path. A fallback that skips the primary path’s trust boundary is not a fallback. It is a side door with excellent documentation. Here is the part that made the PayPal path stand out: in the code I reviewed, WPForms already knew the correct webhook pattern. Stripe authenticated the incoming event before trusting it. Square used provider-specific signature verification before processing. PayPal was the odd one out. A simplified comparison looks like this: Square followed the same general rule with Square’s verification helper: authenticate the provider before trusting the event. Same project. Same broad problem. Different security boundary. That kind of internal asymmetry is gold during a code review. It is often the difference between “this project has a deliberate design” and “someone forgot the important line on a Tuesday.” I am assuming oversight here, not a grand philosophical statement about trusting strangers on the internet. WPForms 1.10.0.5 added a verification guard before event decoding and dispatch. The important shape is: The real implementation has to account for different connection and forwarding modes, so the production code is more complicated than my six line spiritual patch. The security property is what matters: Unsigned random JSON from the internet no longer gets to change payment state. The patch did not need a wizard a machine learning model or a blockchain. It needed the webhook equivalent of checking the person’s ID before giving them the keys. According to the triage response, eleven researchers submitted this issue. That number tells us something precise: independent discovery converged on the same trust failure at least eleven times. It does not tell us how many people found it in total. We do not know the reporting rate. We do not know how many people saw it and moved on, assumed it was already reported, kept it private or chose a less friendly use for it. I could invent a 50% reporting rate and announce 22 finders. I could invent a 20% rate and announce 55. I am not doing that. Those numbers would be vibes wearing a spreadsheet, and nobody should put them in a Gartner quadrant. The defensible conclusion is simpler: Eleven reports are a lower bound, not the whole finder population. Now read that with attacker incentives in mind. This was a public payment endpoint. No administrator account was required to reach it. No exotic browser chain. No twelve-step race condition that works only during a solar eclipse. It was a webhook handler trusting a stranger with a clipboard. This is the part of the 90-day disclosure model that I keep returning to. The model tends to focus on the time between the first report and the public disclosure. But the risk clock also includes every independent rediscovery after report number one. By the time I arrived, the same trust boundary had already attracted ten other reports. If eleven of us reported it, the useful question is not only: Why were there so many duplicates? It is also: How many people found it and never joined the queue? the 11 visible reporters are above the waterline. below it is an unknown population: people who found the same bug and did something else, or nothing at all. the meme is not a measurement. it is a reminder that the queue only shows the people who entered the queue. A duplicate wave is not proof that a vulnerability was exploited. It is evidence that the vulnerability was rediscoverable. Those are different claims, and both matter. For people who do this kind of work, here are the lessons I want to keep in writing. Search for early. In a WordPress plugin review, it is a productive starting point. means the route does not require ordinary WordPress authentication. Every match deserves a look at what the callback actually does. Do not stop at . Webhook routes are supposed to be public. The question is not merely “can anyone reach this?” The question is “after they reach it, how does the plugin prove they are the provider?” Public route plus signature verification is normal. Public route plus vibes is a bug. Look for asymmetries inside one project. If Stripe authenticates before processing and PayPal does not, that difference deserves attention. The same trick works elsewhere: one download route checks permissions and another does not; one form validates input and its sibling trusts it; one integration rejects replayed events and another accepts them forever. Project-internal asymmetries are gold. Audit fallback paths separately. Plugins love alternate routes, URL parameters, forwarding modes, legacy compatibility layers and emergency side doors labelled “temporary” since 2019. Every fallback needs the same trust boundary as the primary path. Do not let the CVE label replace your own verification. Public records can overlap, change or describe different parts of the same code path. Re-run the original proof of concept against the patched version. The CVE database is useful. It is not your QA team. Submit a solid report even when you suspect a duplicate. I was reporter 11. I will probably receive no public CVE credit and no bounty for being eleventh through the door. That is fine. Another well-supported report is still evidence that independent rediscovery is happening. Ten researchers were already waving flags. Reporter eleven can still bring a tiny flag and stand next to them. A few notes for the receiving side. Webhook handlers are payment infrastructure. Treat them like it. A public endpoint that can change payment state or trigger fulfilment actions belongs in the same security conversation as the rest of the payment flow. “It is just a webhook” is how the webhook ends up running the business. Build verification once and make it unavoidable. Every provider integration should pass through a provider-appropriate authenticity check before event dispatch. If a new integration can reach a payment handler without crossing that guard, the design has made the dangerous path too easy. Put fallback URLs in the threat model. REST endpoints, query-parameter listeners, forwarded webhooks and legacy modes should all enforce the same security property. Add negative tests showing that missing, invalid and stale verification material is rejected wherever appropriate. Be careful with trusted webhook contexts. A webhook may need to run without a logged-in user and may legitimately bypass user capability checks. That makes authenticating the entry point more important, not less. Otherwise the trusted context becomes an admin costume for unsigned JSON. Re-run the original proof of concept against the patch. I know this sounds obvious. It remains not obvious enough. A patch that fixes a nearby CSRF condition does not necessarily fix an unauthenticated webhook trust boundary. Test the exact thing the reporter demonstrated. Treat duplicate volume as a rediscovery signal. Eleven reports do not prove exploitation. They do show that multiple researchers could independently find the same flaw. That should increase urgency, not merely increase the size of the duplicate folder. Small bug and small patch but the product reach was not small. That is the horror triangle of WordPress plugin security: tiny mistake, tiny patch, enormous distribution. (Mentally insert Horror meme here) The important correction to my earlier notes is straightforward. The PayPal webhook forgery issue discussed here was fixed in 1.10.0.5 , and the public record I should point to is CVE-2026-4986 , not the earlier CSRF CVE. I am writing that plainly because future me will absolutely forget, open six tabs, become overconfident and try to be clever again. The larger lesson is reporter number 11. WPForms had more than 5 million active installations, although that does not mean 5 million exploitable PayPal configurations. The trust failure was in a public payment path. Similar integrations in the same project showed the correct authentication pattern. At least eleven researchers independently submitted the issue. That is the kind of duplicate count that should make a triage team sit upright and spill coffee on the keyboard. The old disclosure assumptions are under pressure. When many people can independently find the same bug in a short period, the vendor is not racing only the publication deadline. It is racing rediscovery. For site owners, the action is simple: update WPForms to the latest available version. Do not stop at 1.10.0.5 just because it is the first fixed release; use the current supported release. Then reconcile suspicious PayPal status changes against PayPal’s own records. Look for completed, denied, cancelled or reactivated states that do not line up. If you log webhook headers, preserve them. If you do not, this is a good time to start thinking about it. For researchers: submit the next one even when you suspect somebody already did. For vendors: a duplicate is not merely administrative noise. Sometimes it is the sound of the same door being found over and over again. And if you are still reading this, you are awesome. Thanks for sticking with me through the webhooks, the duplicate queue, the spreadsheet goblin and the time travelling lab server. What is the highest duplicate count you have seen on a vulnerability report and did it change how the vendor responded? related posts: references: If any of this resonated, hit me up on Twitter/X at @anand_himanshu . If you disagree, especially hit me up. Disagreement with evidence is just peer review wearing casual clothes. Thanks for reading. WPForms introduced PayPal Commerce in version 1.10.0 on March 17, 2026 . Public CVE data identifies 1.10.0.1 through 1.10.0.4 as affected by CVE-2026-4986. CVE-2026-40764 was a separate CSRF issue affecting earlier releases and fixed in 1.10.0.3. WPForms 1.10.0.5 , released on May 12, 2026, fixed the webhook-authentication issue. CVE-2026-7792 separately describes forged PayPal subscription-state events through the same broad unauthenticated webhook trust boundary. the 90 day disclosure policy is dead (the framing post) defender playbook for the LLM era (the fourth and final numbered post; this article is the technical epilogue because apparently “final” needed a patch release) CVE-2026-4986 : NVD CVE-2026-7792 : NVD CVE-2026-40764 : NVD CVE-2026-48835 : NVD WPScan advisory: WPForms Lite < 1.10.0.5 Unauthenticated PayPal Webhook Forgery WPForms changelog WPForms Lite on WordPress.org PayPal webhook verification documentation WordPress Trac changeset referenced by CVE-2026-7792

0 views
Himanshu Anand 2 months ago

Fine-tune an LLM on Vertex AI, own the whole GCP project

If your team trains models or fine tunes LLMs on Vertex AI, one training permission is all it takes to take over the whole project. A principal with one permission can run code as google’s managed Custom Code Service Agent, which hands out a cloud platform token (the exact scope Google’s docs says it can’t have) and can mint tokens for any service account in the project. That is low priv ML role turning into effective project Editor, no actAs, no user interaction. It’s the same primitive published by Unit 42 (Ofir Balassiano & Ofir Shaty) on November 12, 2024 - ModeLeak: Privilege Escalation to LLM Model Exfiltration in Vertex AI . Guess what, it still works. Google marked my report “Won’t Fix (Infeasible)” for lacking a “reproducible proof of concept” on a report that is mostly reproducible proof of concept. the one permission Vertex AI custom jobs are simple: hand Google a container, Google runs it. The catch is who it runs as. By default that’s a Google-managed identity: Your code Google’s identity. To submit a job you essentially need one meaningful permission, aiplatform.customJobs.create, the thing orgs hand to every data scientist. You do not need actAs, getAccessToken, a token-creator role, Editor, or Owner. So I built exactly that: a custom role with customJobs.create/get/list + locations.get, bound to a fresh service account with rights over nothing else. An intern badge. the docs literally say this is impossible This is the whole bug. From Google’s own custom service account docs ( https://cloud.google.com/vertex-ai/docs/general/custom-service-account) : “If you want your custom training code to obtain an OAuth 2.0 access token with the https://www.googleapis.com/auth/cloud-platform scope, then you must use a custom service account for training. You can’t give this level of access to the … Custom Code Service Agent.” The default agent cannot have cloud platform scope. That promise is the reason is supposedly safe to hand out. The promise is false. so I did it The “training code” is just a shell script that interrogates the metadata server and tries things it shouldn’t be allowed to: Submitted it as the intern-badge SA (–impersonate-service-account=$VX), then made tea while Vertex committed the crime in the background, with full Cloud Logging. what came back tokeninfo on the agent’s own metadata token, the scope the docs deny exists: And the rest of the chain, straight from the logs: So: minimal ML permission -> managed agent -> impossible cloud-platform token -> impersonate any SA -> read the whole project -> effective Editor. It even chained into a second Editor SA, because why stop at one. hasn’t someone seen this already? Yes. This is functionally ModeLeak Primitive #1, published by Unit 42 in November 2024. Same shape, same agent, same escalation. Google publicly said they “implemented fixes to eliminate these specific issues.” It’s 2026 and the door is still open. Fix didn’t cover it, was incomplete, or regressed. Pick one. I mentioned in my bug report to Google I filed it with the Cloud VRP, flagged the prior art explicitly and linked the tracker → https://issuetracker.google.com/issues/522648848 . I included the role YAML, the gcloud commands, the probe config, the captured output and three job IDs. The verdict: Status: Won’t Fix (Infeasible). Hi, Our team has analyzed this report and decided not to track it as a security bug. … At this time, we have not seen a reproducible proof of concept that demonstrates how this issue could be exploited to attack Google or other users. Without a clear demonstration of such impact, we are unable to prioritize this as a security-related fix. The report contains the exact commands, the captured tokeninfo, a successful generateAccessToken against an Editor SA, a getIamPolicy on the whole project, and three job IDs you can pull from Cloud Logging. I reproduced it three times. The job IDs are literally labeled baseline, low-priv, and decisive. “No reproducible proof of concept” is a bold review for a report you can copy-paste. The real gripe is not the bounty, it is setting the bar at “demonstrate cross-tenant attack on Google” for a single-tenant privesc primitive. Escalating inside my own project is what a privesc is. The same path runs anywhere customJobs.create is delegated, which is nearly everywhere. why it matters, and the fix Orgs hand customJobs.create to ML engineers believing the docs, which scope the blast radius to “editor-level access to GCS and BigQuery.” The real radius: impersonate any SA, dump the full IAM policy, inherit Editor (Compute, KMS, Secret Manager, networking), exfiltrate the minted tokens. The defenders’ mental model is the documented one, and the documented one is wrong. The fix isn’t exotic, pick any: final thoughts A managed Google identity is quietly carrying a token its own documentation calls impossible, handing project-Editor to anyone with one ML permission, via a primitive a major team already published, and the official position is that it’s “Infeasible.” If you run GCP: go check what your custom-job submitters can actually reach. Don’t trust the GCS-and-BigQuery framing. Spin up the probe in a throwaway project and read your own tokeninfo. Ten minutes and a cup of tea. If you think I’m wrong about the severity, especially hit me up ( https://x.com/anand_himanshu) . I’d love to hear the case for “Infeasible.” Thanks for reading. Strip getAccessToken/signJwt/signBlob from roles/aiplatform.customCodeServiceAgent. Add an actAs gate like Cloud Functions and Cloud Build already require. This is solved one product over. Honor the docs: don’t give the agent cloud-platform by default. At minimum, fix the docs so customers stop trusting a boundary that isn’t there.

0 views
Himanshu Anand 2 months ago

Reading a patch tuesday diff for fun: the dhcp client memcpy that copies more than four bytes (CVE-2026-44815)

June 2026 was the biggest Patch Tuesday Microsoft has ever shipped 208 CVEs. One of them, CVE-2026-44815 (This is as bad as Etner blue, Wanna cry isse), is a CVSS 9.8 “DHCP Client Service Remote Code Execution.” I pulled the patched , diffed it against last month’s build and the whole bug fits on one screen: a function called does a into a 4-byte buffer using a length field that came off the wire from a DHCP server, with no check that the length is actually 4 . The June patch adds exactly one check: . This post walks the entire 1 day workflow end to end how I got the binaries, how I diffed them, how I read the patch and how I reasoned about reachability so that if you are new to 1-day analysis (PAtch diffing) you can do the next one yourself. I am deliberately not publishing the last mile to a weaponized exploit and there’s a Snort rule at the bottom for my blue team bros. When I triage a Patch Tuesday for 1-day candidates, I sort by three things: hits all three. It’s in the DHCP client , which means the attacker is a server a rogue or compromised DHCP server on the local link answering your . The client is the victim. That’s a classic, underrated surface: every laptop that joins a coffee shop network is a DHCP client. One more thing made this irresistible. The public ZDI writeup flagged an “odd incongruity”: the CVSS vector says no privileges required , but Microsoft’s text says it needs an authenticated user. Those two statements look contradictory. By the end of this post you’ll see they are both true and why is the most interesting part of the bug. You need two versions of : the patched one (June) and the previous one (May). The classic way to do this is to download the MSU from the Microsoft Update Catalog and unpack it but modern Windows updates ship forward-delta patches that need the Windows-only API to reconstruct. Annoying if you’re on macOS or Linux. The shortcut: Winbindex . It indexes essentially every Windows binary ever shipped, by version and hash, and links each one to the Microsoft symbol server , which serves the full PE (not a delta). So you can grab any historical full binary with . The symbol-server URL is keyed by the PE’s and , concatenated as hex: Pull the Winbindex JSON for , find the June 2026 entry and the one right before it, build those two keys, and download both. On Win11 24H2 that’s: While you are on this page, grab the PDBs too. Microsoft publishes public symbols for system binaries on the same server. Parse the PE’s CodeView (RSDS) debug record to get the PDB GUID+age and download: This step is the single biggest force multiplier in Windows 1-day work. With public PDBs applied, every function in your decompiler has its real name . You diff vs , not vs . Do not skip the PDBs. I run Ghidra headless and dump every function’s decompiled C to JSON, once per build, then compare by function name. Two beginner-grade gotchas worth calling out, because both cost me time: For , after filtering the CRT/thunk noise, the meaningful changes were tiny: grew by ~50% and moved. A small parsing function that suddenly gains code on a 9.8-RCE month is exactly where you look first. Here’s the real diff straight out of the decompiler (Ghidra’s , OLD vs NEW, public PDB applied). I’ve trimmed the pure variable-rename noise the marks omitted lines but the security relevant change is verbatim: Here’s the same function in the patched (June) build, written out cleanly: And the old (May) build is just the branch with no sibling i.e unconditionally: That’s the whole bug. Let me spell out why it’s bad. The fix is one line: a subnet mask is always 4 octets (RFC 2132 §3.3 literally says “its length is 4 octets”), so reject anything that isn’t 4. The bug existed because nobody enforced an invariant the RFC already guaranteed if the other side plays by the rules . Attackers don’t. This is the single most common shape of bug you will find in TLV parsers: trusting the length field of a value whose size is “supposed to be” fixed. Once you’ve seen it once, you’ll see it everywhere. A diff tells you what changed. It does not tell you whether an attacker can reach it. That’s the part beginners skip and reviewers reject findings over. So let’s trace it. Two questions: who fills the linked list at , and who calls . Who fills the list. The same per-context option list at is touched by the functions that process a received lease option storage, context refill, the “media connected” path that kicks off a new DISCOVER/REQUEST when you join a network. In other words, the node tagged and its / fields are populated from the DHCP server’s reply options . The attacker who controls those bytes is whoever answers your DHCP request: a rogue server, or an on-path box doing a DHCP race. No authentication, no client trust relationship just “be the first DHCP server to answer.” That matches the CVSS AV:Network / PR:None exactly. Who calls it. has exactly two callers, and they’re both RPC entry points: and . So the dangerous fires when something invokes that RPC against the DHCP client service (running in as ). That’s the “authenticated user” half of Microsoft’s description a local caller pokes the RPC that reads back the stored mask. Put the two halves together and the “incongruity” dissolves: Both statements in the advisory are true. They describe two different ends of the same data flow. Knowing this is what turns “interesting diff” into “I understand the bug class and the threat model,” which is the actual skill 1-day analysis is teaching you. The answer from static analysis alone: this is a controlled-content overflow of a small output buffer inside a network service , reachable with attacker-controlled data and an attacker-controlled overflow length (the option length field is a ). That is a textbook memory-corruption primitive, and Microsoft rating it 9.8 RCE is consistent with the destination being a heap/stack buffer adjacent to something useful in the RPC server context. What I am not going to hand you, because the four-byte fix is the kind of thing an LLM can weaponize in an afternoon and a lot of machines won’t have rebooted yet: If yo are learning, that’s fine the value here is the method , not a copy paste 0day. You now know how to get the binaries, how to diff them, how to read the patch and how to argue reachability. Reproducing the corruption in a VM with Driver Verifier / pageheap on the DHCP client service is a great next exercise, and it stays on your own lab network. Look at that patched function again: The fix is behind a runtime feature flag ( a WIL/Velocity gate). Microsoft increasingly ships security fixes this way so they can roll them out gradually and roll them back via KIR (Known Issue Rollback) if the patch breaks something. I found the same pattern on every flagship June fix I looked at: , , , the RDP client. The implication for defenders is uncomfortable: a machine can be fully “patched” the June is on disk and still run the vulnerable branch if the feature isn’t enabled yet (staged rollout fraction, a KIR rollback, or an admin override). “Patched” and “fixed” are no longer the same statement. When you’re tracking exposure, you can’t just diff the file version anymore; you have to know the flag state too. You can detect this on the wire because the malicious packet has to violate an RFC: a Subnet Mask option (DHCP option code 1) whose length byte is not 4 , sent from a DHCP server to a client. Legitimate servers never do this. Same idea generalizes to any fixed width option arriving over length in a BOOTP reply. A couple of caveats so you deploy this with your eyes open: Zeek one-liner equivalent (concept): in , if contains a subnet-mask option whose raw length field isn’t 4, log it. That’s far more robust than byte-matching. And the meta-point the same one I keep hammering: the gap between “patch ships” and “exploit exists” is basically gone. I read this entire bug off a public diff in an afternoon with commodity tools. Assume someone less friendly did too, the same Tuesday. Patch and verify the feature flag is actually on now, not next maintenance window. Stay safe out there. If you reproduce the corruption in a lab, keep it in the lab. Severity + “exploitation more likely.” CVSS 9.8 with RCE in the title goes to the top. A single, self-contained binary. Kernel TCP/IP is great but is 3.5 MB of pain. A client DLL that parses one protocol is a much friendlier first target. Attacker-controlled input with an obvious wire format. DHCP is a TLV (type length value) protocol. TLV + “RCE” in the same sentence is almost always a length-handling bug. That is a pattern you learn to smell. Apple Silicon. Ghidra’s decompiler is a native binary that only ships for . On an arm64 host (or arm64 container) silently fails and every function decompiles to an empty string and then your diff cheerfully reports “nothing changed.” Run Ghidra in an amd64 container under emulation and it just works. If your diff says a security patch changed nothing, suspect your tooling before you believe it. Diff noise. Don’t diff raw decompiler text. Microsoft rotates WPP trace GUIDs and bumps trace message IDs every month, the linker shuffles addresses, and the decompiler renumbers locals. All of that makes unchanged functions look different. Normalize first: strip hex literals, autonames, and local-variable suffixes, collapse whitespace, then compare. After normalizing, the real patch pops out of the noise. is the caller’s output buffer for a subnet mask . A subnet mask is an IPv4 address 4 bytes . The caller hands over a pointer to a 4-byte slot. ( ) is the length of a stored DHCP option , which was populated from a DHCP server’s response packet. The old code copies bytes into the 4-byte slot. If , you overflow the destination by bytes with fully attacker-controlled content ( is the option’s value, also straight off the wire). The malicious data is planted with no privileges by a DHCP server on the link. The trigger that copies it into the undersized buffer is a local RPC call. the exact DHCP option that lands under internal tag , and the precise wire layout that stores an over-length value there; the specific RPC method sequence and arguments that drive the read-back; the allocation/grooming details that turn the overflow into control of execution. DHCP options are a flat TLV stream; the + relative approach can mis-fire if the byte appears as option data rather than an option code . For production, prefer a proper DHCP parser (Suricata’s keyword / a Zeek analyzer script) that walks options correctly and alerts when option 1’s length field . The rule above is the “I need something in the IDS today” version. Scope it to server->client (sport 67, dport 68). The bug is in the client ; a normal client never sends option 1 as a mask, so direction matters. Pair it with host telemetry: crashes or WER reports in hosting , and the operational log around lease events. Patches are the best vuln reports you’ll ever get. They tell you the exact function and the exact missing check. Reading / a binary diff is reading the answer key. PDBs change everything on Windows. Named functions turn a 9000-function haystack into a short list. Trust your method, not your tools. “The security patch changed nothing” almost always means your decompiler or your normalization is broken (hello, arm64 Ghidra). A diff is half the work. Reachability is the other half. Always answer “who controls the input” and “who calls the sink.” That’s what separates a finding from a daydream and it’s what resolved the CVSS-vs-advisory contradiction here. TLV + fixed-width field = check the length. This bug is “copy a server-controlled length into a 4-byte mask.” You will meet its cousins constantly. “Patched” is now a flag state, not a file version. Feature-gated fixes mean the vulnerable code can still be live on an updated box.

0 views
Himanshu Anand 2 months ago

Defender playbook for the LLM era

TLDR; Three posts ago I wrote baout house was on fire the most import question arises was what we should do next? The attackers already point LLMs at your code. This post is just: point the same LLMs at your own code first read the headers if you are busy. So far this series has been caffeinated me, telling everyone that the sky is falling. The 90 day window is dead . Score bugs by collisions . I turned five patches into exploits in 30 minutes while an LLM did the actual work and I mostly drank coffee. One question that remains was : “what do I actually do?” This is the post No new architecture (post 2 covered the walls). The first half is the pipeline, the boring CI/CD plumbing that lets you run the attacker’s exact workflow against yourself before they get a turn. And in the second half is who actually does what because the SOC the people signing the budget and the developer pushing code at 5pm all have a different job here. Here is the deal with this post: every section is a big heading and two or three lines that is the whole story if you are skimming. If a bit makes you go “wait, how though,” click the box. The details lives in the boxes so the rest of you can keep moving. Attackers are automating their side and most defenders have not caught up, which is basically what this whole post is about. They read your patch diff and have a working exploit before lunch, while you read a mailing list, sigh and open a Jira ticket. Only one of those two workflows is moving at machine speed. Stack the last three posts on top of each other and the picture is grim: Now your pipeline: linters on every push, tests on every push, SAST maybe weekly (ignored becasue too noisy??), dependency updates whenever something breaks and patch monitoring done by a tired human reading email. So the offense is running on automation while the defense is running on that tired human and everything below is about automating the defensive side too, mostly with tools you already pay for. My whole “30 minutes” loop was: grab the diff, ask the model what broke, find the door, write the PoC. Point that exact same loop at your own code and voila you got defense. The five things below are really just that one loop plugged into five different spots in your pipeline, cheapest spot first. An LLM on its own is a noise cannon. Secon post’s “400 false positives for every real bug” complaint is real and I am not going to pretend otherwise. So every single step below pairs the model with something dumb and deterministic SAST, a test suite, a parser, a diff. The boring tool narrows it down and the model explains the few things that survive. You are not replaceing human reviewer here just trying to hand them a short list of the ten things actually worth looking at each morning. The cheapest bug is the one that never merges. A finding caught in a PR just costs a comment, while the same bug after a CVE drops costs an incident bridge and maybe apology emails to customers. Run LLM like a linter, on every diff, automatically, leaving comments, instead of saving it all up for some quarterly audit that everyone dreads. Mental model: every PR now has a second reviewer who has read every CVE ever filed and never gets sleepy. At times It will get it wrong, but so is your reviewers and we keep both of them around anyway. This is just post 3 held up to a mirror I pulled a public diff and asked “how do I exploit this,” so your pipeline should pull the same diff and ask “am I exploitable and where.” Same input, model, opposite intent and you get to run it before the attacker finishes theirs. This is the most useful thing on the whole list, so if you only build one of the five, build this. If I can derive a PoC from a public patch in few minutes on a my not so high end laptop, your team can derive the defensive version in the same time, automatically, every time a dependency ships a fix. Post 2 said “stop on autopilot.” This is the robot version of that discipline. Your code is yours, but your dependencies are not and their transtive dependencies are basically strangers you invited straight into production. So watch them like you would watch any stranger you let into the building. This is the genuinely scary one from post 3, flipped. An LLM turned my reading of a patch into an exploit in minutes. So before you publish a fix, send that same devil after yourself. If it still gets through, your patch is incomplete. Better to hear that from your pipeline than from a “lol bypassed” reply the morning after. Post 2 said pre build virtual patching and post 3 teased deriving a WAF rule in the same five minutes as the PoC and this is where those two ideas meet. When you can’t ship a code fix in four hours, you need a rule at the edge that buys time and the same diff that hands an attacker the payload also hands you the signature to block it, which is about the cheapest tourniquet you will ever get. Stack the five and you get a defense that finally moves at offense speed: Every one of those is the 3rd post workflow pointed inward and set to run on a timer. None of this is hard technically, the hard part is just committing to run the attacker’s playbook against yourself, on a schedule before they do. So far this has all been pipeline talk, which is great if you own the pipeline. Most of us do not. So here is the same fight, broken down by who you actually are when you read this. You are already drowning in alerts and I am not about to tell you to stare at more dashboards. The LLM goes on the triage side, doing the first pass so a human only sees the handful that matter. The same diff your dev team turns into a WAF rule, you turn into a detection. When a patch drops do not just block it, write the alert for the exploit too. You cannot buy your way out of this with one tool and nobody on your team can build any of the above if the org still treats “critical” as a next-sprint problem. Your job is the SLA and the rails, not the YAML. The clock starts ticking the moment a report lands, not when triage finishes. If that sentence makes your stomach drop, that is the work. Good news: you do not need a platform team or a budget meeting to start. You can run this entire loop on your own laptop this afternoon. Run the review on your own diff before you open the PR and stop running like it is a slot machine. A robot reviewer will not save you on its own, so here are three limits before you @ me about it. The whole series really comes down to the fact that the offense automated and the defense mostly did not and the 90 day window broke because finders got cheap and exploits got fast. Wishing the LLMs away is not going to work because they are not leaving, so the move is to drag them onto your side of the line and put them in your pipeline, pointed at your own code, running on every push and every upstream patch. The same wave that broke the old model is also the only thing fast enough to keep up with it on defense. Whether you are triaging alerts, signing the budget, or pushing code at 5pm, the attackers are already running this loop against you, so the only real question left is whether you run it against yourself first. If any of this resonated, hit me up . If you think I am wrong, especially hit me up. The whole point of writing this stuff is to get holes punched in it before an attacker does. Thanks for reading, If you made it through all four posts you are genuinely awesome. This is the fourth and final post in the series. Previous: A public patch becomes a working exploit in minutes ( post 3 ). Ten unrelated people find the same bug in six weeks and the unfriendly ones are not on a 90 day clock ( post 1 ). The advisory tells you the category while the diff tells the attacker the exact payload . Feed it the diff, not the whole repo. Reading your entire tree on every push is slow, expensive and noisy. Give it the diff plus nearby context, which is exactly how an attacker reads your patch: they look at what changed . Put a deterministic tool in front. Run Semgrep / CodeQL first to flag candidate lines, then hand only those bits to the model and ask “is this exploitable and how.” SAST drops the noise floor; the LLM murders the false positives SAST is famous for. Output a PR comment, not a dashboard. An inline comment gets fixed today. A dashboard finding gets a meeting next quarter and then dies. Watch what attackers watch. Follow the actual commit log of your critical deps, not only the advisory feed. The Next.js SSRF advisory said “SSRF via crafted WebSocket upgrade requests,” while the diff spelled out with upgrade headers. Attackers read and your pipeline should too. On every security ish commit, run the analysis. Pull the diff, ask: what broke, what triggers it, do we touch that path. Grep your own tree for the same pattern. This is the step humans skip and the one that pays. The model already gets the root cause, so ask it whether the same shape exists in code you wrote. Post 3’s Drupal bug was “user-controlled keys glued into a query.” That pattern lives in a hundred codebases that never heard of Drupal. Open the ticket pre filled. Diff, affected file, trigger condition, suggested fix, suggested virtual patch. A human clicks approve nobody starts from a blank page at 2am. Continuous, not weekly. A weekly scan means up to seven days between a poisoned package landing and you noticing. In a 24 hour exploitation world, weekly is basically never. Triage the diff, not the version number. “4.1.2 → 4.1.3” tells you nothing. Feed the model the actual changelog and diff and ask: feature, fix or does this smell like a backdoor. The next compromised package will look exactly like a normal patch release. Trace whether you even call it. Old scanners scream because a vulnerable version exists in your lockfile. The useful question is whether you actually hit the vulnerable path. The model answers that and turns a 200 line scanner tantrum into the three findings that can actually hurt you. Does the fix actually fix it? Tell the model to attack the patched code the way it would attack the unpatched code. If it still finds the door, you are not done. Write the regression test from the bug. The model just understood the root cause, so make it write the test that would have caught it. Vercel literally shipped the PoC inside the test file for the SSRF. Right instinct, make it standard, let the machine write it. Find the same bug elsewhere before you announce. The most valuable question after any fix: “where else does this pattern live.” Attackers will ask it about you the second your patch is public. Ask first, fix all of them in one go, not one embarrassing CVE at a time. Diff → trigger → rule. The Next.js DoS fix was literally “strip a header.” You can do that at the edge in seconds, no deploy, no maintenance window. The model reads the diff and drafts the exact WAF rule or header strip. Plug it into the post-2 zero-day playbook. You already decided who writes the rule during an incident. This just means they start from a generated draft instead of a blank wirefilter box at 3am. It is a tourniquet, not a cure. It buys the hours you need to ship and test the real fix from step 4 Do not move in. Code gets written → LLM reviews the diff at push time. A dependency ships a patch → pipeline reads it, greps your tree, files a pre filled ticket. You write the fix → AI attacks your own patch and writes the regression test. You can’t deploy in four hours → pipeline drafts the WAF rule from the diff. First-pass triage. Point the model at the alert queue to enrich, dedupe and group, then surface the ten things worth a human eyeball this shift. Same “ten things before coffee” idea from the dev side, just aimed at alerts instead of pull requests. Detection from diffs, not just blocks. Step 5 above turned a patch diff into a WAF rule and you take that same diff and generate the Sigma / Suricata / EDR detection for the exploit attempt. The advisory says “SSRF via WebSocket upgrade,” while the diff gives you the exact bytes to alert on. Threat intel that maps to your environment. Paste a report or an IOC dump, ask the model for pivots and whether anything matches assets you actually run. Turns a 40-page report into “these three things touch us.” Same warning as everywhere else. It drafts the detection and triages the queue, but a human still approves the auto-isolate before it quarantines a prod box or pages the on call at 2am. Redefine the critical SLA in hours, not sprints. Post 1 made this case: treat every critical issue as P0 and start the clock when the report arrives. CISA gave federal agencies days for the Drupal SQLi your internal cycle has to beat the people who already weaponised the diff. Fund the boring rails. Fast patching is a fantasy without canary deploys, auto-rollback and feature flags. Engineers cannot ship a fix in four hours on a pipeline that takes six. Pay for the rails before the incident, not during it. Measure ‘mean time to patch’ against attacker speed. Microsoft saw Dirty Frag in the wild within 24 hours. If your MTTP is measured in weeks, you are tracking the wrong number against the wrong opponent. Put it on a dashboard you actually look at. Don’t treat researchers as a nuisance. Shorten your disclosure window, reward reports that ship a patch and do not punish the person who told you the building is on fire. The friendly finder is the cheapest security team you will ever have. Review your own diff before you push. Same loop as step 1, just run by you, before the PR exists. Catch it before a reviewer, or an attacker, ever sees it. Read the diff of a dependency bump, not just the version. Post 2’s “stop on autopilot” is your rule too. A model can read the changelog and the actual code change and tell you if a patch release smells like a backdoor. After you fix a bug, grep for it everywhere. The most useful question you can ask after any fix: where else does this exact pattern live in my code. Ask it before you announce, because the attacker asks it the second your patch is public. If it is open source, send a patch with the report. Post 2 and Linus both made this point. A report with a patch attached gets fixed faster every single time, and a blank page helps nobody. Patch your own stuff fast and assume the worst. The moment an upstream patch ships, assume the exploit already exists. Your monthly update habit is now a 30 day open door. A little Noisy Without a deterministic tool in front, you can drown. Every step above assumes SAST / tests / parsers do the first pass. Still needs the humans. Third post’s middleware bypass needed me to understand deployment modes the model could not guess. Secon post’s Orange Tsai point stands, the top of the pyramid is human. This eats the bottom of the pyramid, which is exactly the part attackers automated first. Fast analysis dies without fast rails. If your deploy takes six hours and has no kill switch, all this speed evaporates at the deploy step. Build canary, auto rollback, feature flags, blue-green before the incident. score by collisions, patch by panic → Link 10 people found my bug before me (the duplicate finder problem and what it means for bounties) → Link 30 minutes from patch to exploit (the React story and the death of the n-day gap) → Link

0 views
Himanshu Anand 3 months ago

30 Minutes from patch to exploit

I read five security patches and I derived working exploits from all five. The slowest took 30 minutes and the fastest took two. An LLM did most of the heavy lifting while I pushed buttons, this is the working behind my blog the 90 day disclosure policy is dead : the gap between “patch ships” and “exploit exists” is now measured in minutes. In the first post I mentioned that a patch can be turned into a working exploit in 30 minutes. In this blog we will go though detailed analysis of it. I picked five CVEs from the last three weeks all real and now patched. Impacting the software you probably run. I did patch diffs and an LLM and timed myself withput any insider knowledge and with no prior research on the targets, only public advisory, public commit and a model that can read code. On May 6, Vercel dropped twelve security advisories for Next.js in a single release, yes you read this right “12” in one day. I started with the worst one CVE-2026-44578 : server-side request forgery through WebSocket upgrade requests. CVSS 8.6. Affects every self-hosted Next.js deployment from 13.4.13 to 15.5.15. Three years of exposure. The advisory says: “We now apply the same safety checks to WebSocket upgrade handling that already existed for normal HTTP requests.” One sentence that tells me the upgrade path had no safety checks before. Si, I pulled the commit and the fix was six lines two boolean checks that should have been there from day one. The server was forwarding WebSocket upgrade requests to any URL an attacker put in the request line. No allow-list and No routing approval just “does the URL have a protocol? Proxy it” The exploit is an HTTP request with an absolute URL in the request line and WebSocket upgrade headers. Send to any self-hosted Next.js app, the server opens a TCP connection to your target and relays the response back with Cloud metadata endpoints, Internal APIs, Docker internal services or anything the server can reach. No login, No rewrite rules needed. Reading the advisory: 30 seconds. Understanding the diff: 2 minutes. Writing the PoC: 2 minutes. Total: under 5 minutes. The thing that made me lol. Vercel shipped the working PoC inside the patch commit . The test file contains a function called . It asserts the patched server does NOT proxy the request. Which means the unpatched server DOES. You do not even need an LLM, just need to read the test. The vulnerable code in had a simple flow for WebSocket upgrade requests: parse the URL, check if it has a protocol field and if so proxy it. The patch adds two guards: the routing pipeline must have explicitly (meaning a rewrite rule matched and approved the destination) AND the result must not carry a (meaning it was not a redirect or error). The root cause is that Node.js’s URL parser sets on any absolute URL. The HTTP/1.1 spec allows absolute URLs in request lines browsers never send them, but nothing stops a raw socket from doing so. The upgrade handler trusted this parsed protocol as a signal to proxy, while it should have required explicit routing approval. The fix file is , commit . The exploit payload (raw HTTP): The PoC Vercel shipped in the test file ( ): Python PoC: Lab reproduction: The Docker Compose lab at the bottom of this post includes a vulnerable Next.js 15.5.15 instance and an internal canary service the SSRF PoC confirms the server enters the proxy code path for any absolute-URL upgrade request. In the lab, ’s WebSocket mode has a connectivity nuance with the canary (it expects a real WebSocket handshake), but the server-side proxy trigger is confirmed via the error log: the server attempted to connect to the attacker-controlled URL. In production with a real internal HTTP service, the response relays back cleanly. Okay maybe I got lucky with the SSRF, maybe that one was unusually easy. I had never looked at the Next.js source before this and yet here I am with a working 1-day exploit. Let’s try the other patches from the same May 6 release and see if we can keep the streak going. The fix was one line, they have added a header name to a strip-list and that was it. The header is an internal header used by Vercel’s edge proxy to resume Partial Prerendered pages. This should NOT come from a client but on self-hosted deployments, nobody was stripping it. An attacker sends a POST with and a small body. The server enters a request-body handling deadlock, the connection stays open and eats a file descriptor. Repeat this 500 times and legitimate users get connection refused. The PoC is in a loop, 2 minutes from advisory to exploit. The fix was 1 line of code. Next.js maintains an array in Headers in this list are stripped from incoming requests before they reach application code the header was missing from this list, so external clients could inject it. When the server receives a header on a POST to a server action endpoint, it enters the Partial Prerendering resume flow. This flow expects a specific binary format in the request body a malformed or minimal body causes the handler to wait indefinitely for more data, holding the connection open and consuming a file descriptor and worker slot. The fix: add to the array, 1 string per array. The diff (commit ): Note: This requires the target to use Cache Components (Partial Prerendering). If PPR is not enabled the header is ignored and the deadlock does not trigger. The image optimizer at fetches local images through a mocked internal request external images already had a size limit while internal images did not. Drop a large file in , request it through the image optimizer five times in parallel and the server buffers hundreds of megabytes into process memory. Node.js OOMs and crashes BOOM. The default , a default install is vulnerable. (3 minutes to exploit) The function in creates a mocked HTTP request/response pair to fetch local assets the mocked response in buffered the entire response body with no size limit. This patch adds a parameter to both and and when the accumulated buffer exceeds the limit, the response throws before it can exhaust process memory. This was a regression in the security model external image fetches had this limit via , but the internal fetch path was added later and missed the check. The diff (commit ): Each request forces the server to buffer 200 MB into process memory, five concurrent requests = ~1 GB of memory pressure. On a container with 512 MB memory limit, two requests should be enough. At this point the jokey narrative writes itself 3 CVEs, all trivial, all derived in under five minutes. TBH Were they all this easy? let’s grabbed the fourth Next.js patch from the same batch to find out. This is the one where the LLM hit a wall. The vulnerability lets an attacker bypass middleware authorization on dynamic routes. The URL path stays the same, Middleware sees and allows it. But a crafted query parameter secretly replaces the dynamic route value, so the page renders content from . The middleware check passes -> wrong content loads ->Authorization bypassed. I stared at this for 45 minutes the diff is 48 lines across 6 files. It involves the standalone build pipeline, an internal parameter serialization format and the subtle interaction between middleware route matching and page-level parameter resolution. The LLM got me to “there is a parameter injection here” in 5 minutes but it could not construct a working bypass without me explaining how standalone mode’s routing diverges from . This one took 45 minutes. The LLM replaced the first 80% of the work. The last 20% still needed someone who understands how Next.js deployment modes actually behave. LLMs have not replaced humans (yet). The vulnerability exists only in standalone mode deployments when Next.js is built with and served through a custom proxy that consumes . In this mode the proxy adds query parameters with an prefix to pass dynamic route values to the page renderer pre patch the page renderer’s function honor these parameters from any source including external client requests. The fix adds an flag When set (meaning the request came through the full pipeline) runs normally when NOT set (standalone proxy mode), only runs that strips the injection vector. The difficulty: you need to know that 1: standalone mode exists 2: it uses prefixed params 3: these params override the dynamic route values 4: the middleware runs against the URL path, not the resolved params None of this is in the advisory you have to trace it through the route-module code. The LLM identified as the dangerous function and the prefix as the injection vector but it proposed testing against first (wrong that path was already safe) and needed me to explain the standalone routing divergence before the PoC worked. The diff (commit ): Attack flow: 1: Target has a dynamic route protected by middleware 2: Middleware checks : sees allows it 3: Attacker appends to the URL 4: In standalone mode, replaces the param with 5: The page renders admin content, but middleware never saw it Why the LLM missed it? The model correctly identified the injection vector but proposed testing against which already had the flag set (the fix was already the default behavior for that path). The exploit only works against standalone deployments using the proxy pattern. Understanding this required knowing Next.js deployment architecture not just reading the diff. Derivation timeline: 5 min to identify the concept and 40 min to build the deployment mode specific PoC. If in case you are thinking this is JS problem, then you are worng this is a software problem. Five days ago May 20, 2026 Drupal published SA-CORE-2026-004 CVE-2026-9082 SQLi in Drupal core CVSS 9.8 from Drupal’s own assessment impacting every version since 8.9.0 released in 2020. (Wooping 6 yeas of exposure) CISA added it to the Known Exploited Vulnerabilities catalog within 48 hours of disclosure Federal agencies were given until May 27 to patch 5 days for a bug that had existed for six years. The entire patch is three lines. reset associative array keys to numeric indices. The bug: Drupal’s SQL query builder concatenated user-supplied array keys into prepared-statement placeholder names. If you control the keys and JSON:API lets you control the keys, you control the SQL. The LLM identified the injection pattern in under a minute LLM’s human work was finding where user input arrives as an associative array. JSON:API’s filter syntax was the answer enabled by default on many installations with no authentication required. Total: about 25 minutes. Reading the advisory, pulling the commit, understanding the fix, finding the entry point and writing the PoC Drupal’s Entity Query SQL condition builder in builds prepared-statement placeholders by concatenating a field name prefix with the array key of each condition value: When is an associative array (string keys), those keys are concatenated directly into the WHERE clause. PDO’s placeholder regex only matches identifier characters, so anything after the first non identifier character ( , space, ) in the key leaks as raw, unparameterized SQL. The fix: force numeric keys before the loop runs. The attack surface is JSON:API, which allows filter parameters with string keys: The attacker controlled key becomes part of the SQL. The diff (commit ): Same fix in and The PoC (time based blind injection via JSON:API): What the SQL becomes: PDO binds (stops at ) and . The sits in the raw SQL, unparameterized. The database executes it. For PostgreSQL targets: For data exfiltration, replace SLEEP with a conditional subquery: If the response takes 3 seconds, the first character is (which it is Drupal password hashes start with ). Repeat for each position. Classic blind SQLi. Derivation timeline: Total: ~25 minutes. The LLM did the code analysis LLM’s human found the entry point. Four out of five in under 10 minutes, fifth one took 45 and required domain specific knowledge LLM did the first 80% every single time. When I wrote “the 90 day disclosure policy is dead” , I mentioned the React story: 30 minutes from patch to exploit “I was not exaggerating”. The patch itself documents the bug well enough to weaponise. The diffs tell you what was broken, where and how to trigger it. An LLM can read a diff and explain the vulnerability in plain English in seconds. The “reverse engineering” that used to take days now takes minutes because the model understands code flow, identifies missing safety checks and can even write the PoC. CISA added the Drupal SQLi to KEV within 48 hours, Microsoft saw Dirty Frag in the wild within 24 hours. The n-day gap the comfortable buffer between “patch ships” and “exploit lands” is gone becasue the diff shows attackers exactly what broke and how to trigger it. The only thing protecting users is the speed at which they apply the update. And if you are still on a monthly patch cycle you are giving attackers 30 days of free entry ticket. I covered the defensive playbook in detail in score by collisions, patch by panic . Short version: Patch critical issues in hours, not days. The Drupal SQLi went from disclosure to CISA KEV in 48 hours. Your patch cycle needs to beat that. Monitor patch diffs, not just advisories. The advisory for the Next.js SSRF says “server-side request forgery through crafted WebSocket upgrade requests.” The diff tells you the exact payload format. Attackers read diffs. You should too. Run LLM-assisted analysis on every upstream patch. If I can derive a PoC in 5 minutes, your security team should be deriving the same PoC and writing a WAF rule in the same 5 minutes. Virtual patching is not optional. When you cannot deploy a code fix in 4 hours, a WAF rule or reverse-proxy header strip buys time. The Next-Resume DoS fix is literally “strip a header.” You can do that at the edge in seconds. Assume the exploit exists the moment the patch ships. Assume the exploit already exists the moment the patch ships. Everything in this post was tested against a local Docker Compose lab. No external targets were touched. Components: PoC scripts: Each script has with usage instructions. The README in the repo has full setup and reproduction steps. If any of this resonated, hit me up . If you think I am wrong about any of the PoCs especially hit me up. The whole point is to get holes punched in this before someone less friendly does. Thanks for reading. This is the third post in the series. Previous: Reading advisory: 1 min Pulling commit: 30 sec Understanding as the fix: 2 min LLM identifies injection pattern: 1 min Finding JSON:API as the entry point: 10 min (grepping for where user input becomes associative arrays) Writing the PoC: 5 min Testing variants (MySQL vs PostgreSQL): 5 min Patch critical issues in hours, not days. The Drupal SQLi went from disclosure to CISA KEV in 48 hours. Your patch cycle needs to beat that. Monitor patch diffs, not just advisories. The advisory for the Next.js SSRF says “server-side request forgery through crafted WebSocket upgrade requests.” The diff tells you the exact payload format. Attackers read diffs. You should too. Run LLM-assisted analysis on every upstream patch. If I can derive a PoC in 5 minutes, your security team should be deriving the same PoC and writing a WAF rule in the same 5 minutes. Virtual patching is not optional. When you cannot deploy a code fix in 4 hours, a WAF rule or reverse-proxy header strip buys time. The Next-Resume DoS fix is literally “strip a header.” You can do that at the edge in seconds. Assume the exploit exists the moment the patch ships. Assume the exploit already exists the moment the patch ships. Next.js 15.5.15 (vulnerable) running via on port 3000 Internal canary service on port 8888 (Python HTTP server, Docker-internal only) Drupal 11.2.11 (vulnerable) with MariaDB on port 8080 : CVE-2026-44578 SSRF via WebSocket upgrade : CVE-2026-44577 Image Optimization OOM : CVE-2026-44579 Next-Resume connection exhaustion : CVE-2026-9082 Drupal JSON:API blind SQL injection The SSRF PoC confirms the proxy trigger (server attempts to connect to attacker controlled URL) but the lab’s WebSocket mode expects a real WebSocket handshake from the canary in production with a real HTTP service as the SSRF target, the response relays back cleanly. The server side behavior (outbound connection attempt) is the proof of vulnerability. The Image DoS was not fully validated because the lab container needs a large file in and sufficient memory limits to observe the OOM vs the kernel killing the process first. The Cache DoS requires PPR enabled routes to trigger the deadlock a basic Next.js app without Cache Components will ignore the header. The Drupal SQLi requires completing the Drupal installation wizard and enabling JSON:API before the PoC works. the 90 day disclosure policy is dead score by collisions, patch by panic Defender playbook for the LLM era (practical integration patterns for defenders) → Link 10 people found my bug before me (the duplicate finder problem and what it means for bounties) → coming soon

0 views
Himanshu Anand 3 months ago

Score by collisions, Patch by panic

Score severity by collision count. Researchers ship patches not just reports. Companies redesign for a world where the exploit lands before the patch. No vendor pitch just a concrete playbook. The last post went further than I expected. NYT’s Hard Fork picked it up. The Lobsters thread had sharp questions. A few people made a fair point. “The model is broken” is a complaint not a proposal. So here is the proposal. The current model treats every report as if it lives in a vacuum, The old model assumed one reporter, one bug, one timeline. That was the assumption the old playbook ran on It no longer holds. Here is what severity should look like in 2026. One reporter and No exploit. Standard severity. Standard window. Business as usual. Two or more reporters of the same bug. Severity goes up a notch. If unrelated researchers are finding the same flaw a less friendly party probably has it too. Shrink the window. Working exploit attached. A working exploit makes it critical and collapses the window to days from weeks to days. Working exploit and a public PoC. A public PoC makes it a P0 stop the line and patch. A threat actor still beat them by two months. Linus said the quiet part out loud last week on LKML : So just to make it really clear: if you found a bug using AI tools, the chances are somebody else found it too. If you needed proof, Searchlight Cyber’s cPanel writeup just made the case better than I can, Strong team. Years of experience on the target. A real head start. Custom tooling that decompiled cPanel’s Perl binaries back to source. They still got beaten by a threat actor by two months. Two months. If a team operating at that level can be late, the math has changed for everyone. Here is the part my proposal does not solve cleanly. If you are a solo researcher you have no telemetry, No customer logs, No threat feed. You find a bug You filed a report and You sit on it. You have no clue if the bug is already being burned in the wild while you wait. I do not have a clean fix the best I can offer is this assume the worst, Assume you are not the only one, File the report and Push hard for a short window. If the vendor stalls that is now a vendor problem. The other thing Linus said is worth quoting in full: If you actually want to add value, read the documentation, create a patch too and add some real value on top of what the AI did. Don’t be the drive-by “send a random report with no real understanding” kind of person. I have been guilty of the drive-by. You find a bug you have the impact you want to ship and move on. But a report with a patch attached gets fixed faster, every single time and it builds trust you will need next time you walk into that vendor’s inbox. If the project is open source read the code. Find the fix. Send a PR. Even a wrong patch gives the maintainer a starting point. A blank page does not. I get it duplicates everywhere the bounty pools shrinking and the model ships faster than your weekend project. Look at this year’s Pwn2Own Orange Tsai dropped a full chain Logic bugs only No memory corruption No LLM in the loop No collisions. That’s my chain. A full chain w/ logic bugs only! No memory corruption, no AI, and of course no collisions at all @orange_8361 The skill ceiling is still up there. LLMs eat the bottom of the pyramid. The work that needs three months of context, weird intuition and a deep feel for how a system actually behaves is still very human. Sharpen up. There is light at the end of the tunnel. Not very bright at the moment. Maybe because of the current Strait of Hormuz situation. But it is there. This part is going to hurt because the answer is “do more work”, I do not have a shortcut. Your code is yours your dependencies are not. Your dependencies’ dependencies are definitely not, The world your stack runs in now has automated bug finders that are getting better every quarter. The plan has to account for that. If you are not doing these yet start here. Stop on autopilot. Supply chain is a real risk not a slide deck risk, Pin versions, Read changelogs. Scan the diff before you accept it. The next compromised package is already in someone’s repo. Defense in depth. One control will fail, Always. The next one needs to catch it. If your only protection is “the WAF will block it” you have one control. Validate before deploy. Every environment, Staging that does not mirror prod is a placebo. The bug that hits prod is the one that did not exist in staging. Continuous runtime validation. Stop treating security like a release-time event. Run checks in prod. Live. All the time. Virtual patching and WAF rules. When a CVE drops and you cannot patch in four hours you need a rule that buys time, Pre-build that capability. Do not invent it during the incident. Zero-day playbook. Write it before you need it, Who runs the call, Who writes the WAF rule, Who calls the vendor, Who tells the customers, Who flips the feature flag. If you are deciding any of this during the incident you have already lost the first four hours. If you have the basics here is where you go next. Everyone obsesses over ingress, Stop the bad guys from getting in. But when a zero-day fires in a third party package the attacker is already inside. What do they do next: They phone home C2 server, Pull a payload, Exfiltrate data So make that impossible Block all outbound internet by default. Treat every microservice like a hostile entity. If a service needs to talk to the payment gateway allowlist that exact domain. Nothing else. If the exploit fires but the call home fails the exploit fails. Attackers love persistence. Once they exploit a zero-day they want a foothold. A backdoor. Time to look around. The longer your server lives the more it leaks. Treat servers like cattle not pets. Aggressive recycling. Containers and instances get destroyed and rebuilt from a clean image every 12 to 24 hours, No exceptions for “but our service is special”. Immutable infrastructure. No persistent changes on live boxes, If an attacker writes a backdoor at 3pm it is vaporized at midnight when the box is replaced from a clean image. They have to re-exploit every cycle. Most operators will give up before you do. If an attacker exploits a zero-day in a web app they inherit the permissions of whatever user runs that app. If that user is root they own the box. So stop running as root. Rootless containers. No app should ever run as root, Period. If your container ships with fix it this week before anything else on this list. System call filtering. Use seccomp or AppArmor. A web server has no business calling or mounting a new filesystem. Block it at the kernel. When the zero-day tries to spawn a shell the kernel just says no. In finance when a stock drops too fast trading halts. Same idea for software. Automated isolation. If an endpoint suddenly reads the database a hundred times faster than normal route traffic away from it. Quarantine the container into a separate VLAN. Page someone. Do not wait for a human to notice the metric on a dashboard at 2am. Feature flags for security not just product. Wrap every high-risk third party integration in a flag. Vendor announces a breach at 9pm. You flip the flag. The feature dies. The rest of the business stays up. No emergency deploy. No 3am call. One toggle. The Lobsters thread asked real questions. Let me try them here. “Will LLMs hit a ceiling on bugs?” Maybe. Fuzzers did. AFL ate everything for a couple of years then the easy bugs dried up and the work moved up the stack. LLMs might play out the same way. The easy bugs at the bottom dry up; the hard ones at the top stay hard. The middle is where the arms race lives and the side that scales smarter wins it. “LLM scanners return 400 false positives for every real bug.” True today. Less true every month. Tune the prompts. Stack a deterministic SAST tool ahead of the LLM to cut the noise floor before the model ever sees the code. The job is not “LLM replaces human”. It is “LLM filters the ten things a human should actually look at this morning”. “Change management exists for good reasons. Fast patching breaks things.” Also true. The honest answer is this. Build the rails for fast safe deploys before the next zero-day. Canary. Auto-rollback. Feature flags. Blue-green. If your pipeline takes six hours and has no kill switch that is a separate problem and it will kill you on incident day no matter what disclosure model the industry agrees on. “What about closed source? Cisco firmware diffs?” Worse problem not better. Frontier models are already strong at decompiled binary analysis. A side-by-side firmware diff turns into a patch analysis exercise. The “security through obscurity” buffer is shrinking too. “What about formal verification?” Promising for new code. Slow. Expensive. Worth it for the kernel, the crypto, the auth layer. Not realistic for the microservice you ship next sprint. Use it where it matters most. “What does the world look like at 10x or 100x incidents?” I do not know. But the side that automates fastest wins. Right now that is the offense. Defense has to close the gap. Severity by collision count, Researchers ship patches not just reports, Companies redesign for a world where the patch lands after the exploit not before. None of this is free. None of this ships next week. But the old playbook is not coming back. The faster we accept that the faster we start building the new one. If any of this resonated hit me up . If you disagree especially hit me up. The whole point of writing this stuff is to get holes punched in it before an attacker does. Thanks for reading. This is the second post in the series. Previous: the 90 day disclosure policy is dead Next: 30 minutes from patch to exploit Defender playbook for the LLM era (practical integration patterns for defenders) → Link 10 people found my bug before me (the duplicate finder problem and what it means for bounties) → coming soon defender playbook for the LLM era (practical integration patterns) → coming soon

0 views
Himanshu Anand 4 months ago

The 90 day disclosure policy is dead

The 90 day responsible disclosure window was built for a world where bug finders were rare and exploit development was slow. That world is gone. LLMs have compressed both timelines to near-zero. I have seen it first hand, and so has everyone else paying attention. This post lays out why the old model is broken, with real stories, and makes one ask to the industry: treat every critical security issue as P0 and patch it immediately. patch it immediately, not over the next sprint. I have been doing security work for a while now, and the last 12 months feel different. Not in a “AI is going to take over the world” way. In a much more boring, much more practical way. The tools we use, the tools attackers use, and the tools researchers use to find bugs have all gotten smarter at roughly the same speed. And that has quietly killed some of the fundamental assumptions the security industry has been running on for over a decade. Let me walk you through what I mean, with stories. Pretend it is 2019. You find a critical bug. You write up a report. You send it to the vendor. The vendor takes a few days to triage, a couple of weeks to fix, maybe a month to roll out. If you follow Google Project Zero style disclosure, you give them 90 days before going public. During those 90 days, you assume: In late April, I reported a pretty bad bug to a company. I am keeping the details vague because the issue is still not patched, but the shape of it goes like this: an attacker can buy anything from the website, send back their own crafted response to the server, and because there is no signature verification on the response, the server happily accepts it. Buy a $5000 item for $0. Mark your purchase as completed without paying. Critical, easy to exploit, very bad day for the company. Cool. I write it up, I send it in, I feel good about myself for about 10 minutes. Then the triage team comes back and says “yeah we know, first reported in March. You are reporter number eleven.” Eleven Freaking people found the same critical bug in roughly six weeks. A friend from BlueWater CTF had flagged this pattern months ago, that LLM-assisted hunters were converging on the same bugs almost simultaneously, across totally unrelated reporters using totally unrelated workflows. And it is not just me noticing this. @d0rsky , who works on the triage side, posted this: “Once a new vulnerability is discovered - especially via some LLM prompt/skills/automation, we start getting a wave of duplicate reports within days. Same root cause, slightly different wording. […] What concerns me more, is, if researchers can replicate these findings so quickly, what’s stopping blackhats from doing the same before the issue is fixed? Feels like the window between ‘first discovery’ and ‘mass awareness’ is getting dangerously short.” Exactly. The triage teams are seeing it too. This is not a researcher’s paranoia. It is a pattern. At first I thought, okay, same tools, same prompts, makes sense. But then I did the uncomfortable math. If 10 people reported the bug, how many found it and did not report it? The same LLM that helped 10 honest researchers is also available to everyone else. It does not check your intentions at the door. Out of those 10 reporters, only 1 gets the CVE credit. Only 1 gets the bounty. What about the other 9? How many get frustrated? How many decide to sell it instead of wait? And the people who never reported it at all they are not sitting on a 90 day clock. They are not sitting on any clock. The 90 day window is not protecting users. It is giving everyone who already has the bug a 90 day head start. Recently, React patched a bunch of security issues ( CVE-2026-23870 , CVE-2026-44575 , CVE-2026-44579 , CVE-2026-44574 , CVE-2026-44578 ) and wrote a public blog post about it. Standard practice. Show your work, explain the fix, give the community a heads up. I read the post out of curiosity. Then I thought, let me see how hard it would be to turn this patch into a working exploit. Just an experiment, on my own machine, against a local test app. 30 minutes. From reading the patch to having a working exploit (DOS, as it was DoS only). AI did most of the heavy lifting: understanding the diff, identifying the vulnerable code path, writing the PoC. The published issue was a denial of service, but the underlying primitive could go further with more work. In the old world, turning a public patch into a working exploit (n-day exploitation) took skilled reverse engineers days to weeks. That gap was the safety net. “We shipped the patch, admins have a few days to update.” That safety net is gone. The gap is now measured in minutes for simple bugs, maybe hours for complex ones. The skilled reverse engineer is optional. The LLM does the boring parts and the human just steers. The moment a patch ships, assume the exploit exists. There is no grace period. Companies cannot afford to “schedule” patch deployment for the next maintenance window. The maintenance window is now. If you want the clearest possible proof that the 90 day disclosure model is dead, look at the last two weeks of the Linux kernel. Two back-to-back critical vulnerabilities. Both with public exploits. Both affecting every major distribution. The timeline reads like a horror movie. On April 29 , Xint Code (the team behind Theori , nine-time DEF CON CTF champions) publicly disclosed Copy Fail CVE-2026-31431 . A straight-line logic flaw in the kernel crypto subsystem. No race condition needed. 100% reliable. A 732-byte Python script that gives you root on every single Linux distribution shipped since 2017. Every single one Ubuntu, RHEL, Amazon Linux, SUSE, all of them. One away from game over. The terrifying detail: they found it using an AI-assisted workflow at unprecedented speed. The discovery didn’t come from blindly running a scanner, but began with a human insight. Theori researcher Taeyang Lee was studying how the Linux crypto subsystem interacts with page-cache-backed data. Once he had that initial thread, he used Xint Code to scale his research across the entire crypto subsystem. What would normally take weeks of manual code auditing took about an hour of AI-assisted scaling. It came down to one researcher’s insight, scaled by about an hour of AI work against a bug that had been exposed for nine years. For the full technical breakdown, read Xint’s writeup . Copy Fail did get a patch (mainline commit ) and a straightforward mitigation: disable the module. People started patching. Deep breath. Okay. Maybe we can handle this. Then threat actors showed up. Iranian adversaries were observed leveraging the vulnerability to compromise Ubuntu servers and repurpose them as nodes for DDoS campaigns. A kernel privilege escalation found by AI, disclosed publicly, weaponized by nation-state actors, used to build attack infrastructure. All within days. Barely one week later , on May 7 , researcher Hyunwoo Kim ( @v4bel ) published Dirty Frag CVE-2026-43284 and CVE-2026-43500 . Two chained vulnerabilities in the kernel’s IPSec ESP ( / ) and RxRPC networking modules. Same bug class as Copy Fail and Dirty Pipe . Same page-cache corruption technique. Different attack path. The critical part: Dirty Frag works even if you applied the Copy Fail mitigation. Even if you blacklisted . Dirty Frag does not use that module. It takes a completely different route to the same result: unprivileged user to root, deterministically, on every major distro. Ubuntu, RHEL 10.1, openSUSE, CentOS Stream, AlmaLinux, Fedora 44. A one-liner to compile and run. And here is where the disclosure model completely fell apart. Hyunwoo Kim reported to on April 29-30. He submitted patches publicly. He coordinated with the mailing list on May 7, with a 5-day embargo agreed upon. On that same day within hours an unrelated third party published detailed exploit information for the ESP vulnerability, breaking the embargo. After consulting with the distro maintainers, Hyunwoo published the full Dirty Frag writeup , exploit code, and a working PoC. At that moment, zero Linux distributions had a patch available. As of today, only CVE-2026-43284 (the ESP side) has a mainline fix . CVE-2026-43500 (the RxRPC component) still has no upstream patch . And the chained exploit that combines both works on basically everything. ( Ubuntu , Red Hat , and others have published their own advisories.) Microsoft’s Defender team confirmed limited in-the-wild exploitation within 24 hours of disclosure. Attackers gaining SSH access, deploying an ELF binary, popping root via , modifying authentication configs, wiping session files, moving laterally. The full playbook, live, in production environments. CTS ( @gf_256 ) summed it up in five words: “responsible disclosure is dead🤦” https://x.com/gf_256/status/2052480591489122747 Yeah. Let me be specific about what I think is broken beyond repair. The 90 day disclosure window is dead. Not “needs reform”, actually dead. It was designed for a world where finders were rare and exploit development was slow. LLMs have made finders abundant and exploit development fast. When 10 unrelated researchers find the same bug in 6 weeks, and AI can turn a patch diff into a working exploit in 30 minutes, what exactly is the 90 day window protecting? It isn’t protecting anyone It is just exposure that we have given a polite name. Copy Fail went from AI scan to public PoC to nation-state weaponization in days. Dirty Frag’s embargo was broken within hours by a third party who independently found the same bug class. You cannot coordinate disclosure when the same vulnerability is being independently rediscovered by multiple researchers and AI tools at the same time. The information does not stay contained anymore. Monthly patch cycles are dead too. A 30 day window between vulnerability and fix assumes attackers are slower than your release train. They are not. They have been faster for a while now, and the gap is only widening. Microsoft saw Dirty Frag in the wild within 24 hours. Your monthly maintenance window is not a safety margin. It is an attack window. “Wait for the advisory” is dead. If you are reading CVE descriptions while attackers are reading , you are already behind. By the time the advisory is written, the patch diff has already told attackers everything. I have one ask. One. And I know it sounds extreme. I know it is a lot. But everything I have shown you above points to the same conclusion: Treat every critical security issue as P0 and fix it immediately. Not “within 24 hours”. Not “in the next sprint”. Not “after we assess impact”. Now. As in, stop what you are doing and fix it now. I know that sounds unreasonable. I know production deployments are complicated. I know change management exists for good reasons. But the threat landscape does not care about your change management process. Here is what “immediately” actually looks like in practice: If you are a vendor receiving a critical bug report , your clock starts the moment the report lands. Not when you finish triaging. Not when engineering picks it up. The moment it lands. Because if someone reported it to you, assume 10 other people have it and at least one of them is not friendly. If you are a researcher , stop sitting on critical bugs. Push for the shortest possible disclosure window. If the vendor cannot fix it in a week, that is a vendor problem, not a disclosure problem. The old “give them time” courtesy made sense when you were the only finder. You are not the only finder anymore. If you are running vulnerability management , it needs to be real-time. The old cadence of “scan weekly, triage in sprint, patch in cycle” is a timeline that attackers left behind months ago. The new maximum response time for a critical issue is hours. Not days. Hours. And even that might be too slow. This part is important enough that it gets its own section. The attackers have already integrated LLMs into their exploit pipelines. If you have not done the same on the defensive side, you are bringing a clipboard to a gunfight. Here is what I think every engineering and security team should be building toward right now: Integrate LLMs at the point of code push. Every pull request, every merge, every deploy. Run AI-assisted security review as part of your CI pipeline, the same way you run linters and unit tests. Not as an afterthought, not as a quarterly audit. At push time. If the code has a vulnerability, catch it before it reaches production. The cost of fixing a bug in a PR review is orders of magnitude lower than fixing it after a CVE drops. Integrate LLMs for patch analysis. When an upstream dependency releases a security patch, your pipeline should automatically pull the diff, analyze what changed, determine if your codebase is affected, and flag it. This should not require a human to read a mailing list and open a Jira ticket. It should happen in minutes, automatically, the moment the patch hits the public repo. If Xint Code found Copy Fail in one hour of automated scanning, what is your excuse for not scanning your own dependencies the same way? Integrate LLMs for dependency scanning. Your supply chain is only as strong as your weakest transitive dependency. AI-powered dependency scanners can now trace vulnerability impact through dependency trees, flag affected versions, and even suggest upgrade paths. Run them continuously, not weekly. Test your patches with AI before you ship them. One of the scariest things about the React story is that an LLM can turn a patch into an exploit in 30 minutes. Flip that on its head: before you publish a security patch, use AI to verify that the patch actually fixes the issue and does not introduce a new one. Use it to generate regression tests. Use it to check if the same pattern exists elsewhere in your codebase. If attackers will do this the moment your patch lands, you should do it first. I know this sounds like a lot. I know not every team has the resources to build all of this tomorrow. But the trajectory is clear. The window between “vulnerability exists” and “vulnerability is exploited” is shrinking to zero. The only way to keep up is to automate the defensive side at the same speed the offensive side is already moving. We are going to see more and more zero-days getting exploited in the wild, faster and faster. That’s not a guess the inputs all point the same way: lower barrier, more finders, shorter timelines. Same tools, lower barrier to entry, more finders, shorter timelines. The teams that survive this shift will be the ones who made AI a first-class citizen in their security pipeline before they were forced to. I keep coming back to the same image in my head. It is a sysadmin reading the Dirty Frag advisory on May 7, realizing that there is no patch available, that the exploit is already public, that Microsoft is already seeing it in the wild, and that the mitigation is “disable your IPSec modules”. And this person has 400 servers to touch. That is the new reality. Not a hypothetical. Not a war game scenario. That was last Wednesday. The 90 day disclosure policy is dead. Monthly patch cycles are dead. The assumption that you have time between disclosure and exploitation is dead. What is not dead is the ability to move fast, automate hard, and treat critical bugs like the emergencies they are. The same AI wave that broke the old model also enables the new one. Faster patching, automated scanning, real-time threat intel, AI-assisted code review. The tools exist. The question is whether defenders will use them before attackers do. Right now, the attackers are winning that race. Let us fix that. If you’re still reading this, you’re awesome. Thanks for sticking with me! I will go deeper on several of these points in follow-up posts: Thanks for reading. You are probably the only person who found this bug Even if someone else finds it, they will take their own time The vendor has a comfortable head start on writing the patch After the patch lands, attackers need days or weeks to reverse engineer it into a working exploit Every single one of these assumptions is now wrong. score by collisions, patch by panic → Link 10 people found my bug before me (the duplicate finder problem and what it means for bounties) → Link 30 minutes from patch to exploit (the React story and the death of the n-day gap) → Link Defender playbook for the LLM era (practical integration patterns for defenders) → Link If any of this resonated, hit me up on Twitter/X ( https://x.com/anand_himanshu) . And if you disagree, especially hit me up. I would love to hear the other side.

0 views
Himanshu Anand 4 months ago

I Read OpenSSL for Fun and Found a Nonce Leak

I was poking around the OpenSSL source code recently. Not really hunting for anything specific (one of the most heavily audited codebases), just curious about how the new post-quantum crypto stuff was wired up in version 4.0.0. I went in expecting to find nothing interesting. Instead I tripped over a single-character logic bug that leaks cryptographic randomness onto the stack on every signing call. Quick disclaimer: I am not a crypto person. I had to look up (In current world look up means, asking LLM please explain me like a kid ) half of these acronyms while writing this. So if you also feel a bit lost when people start saying things like “FIPS 205 addrnd nonce” and your brain just freezes, you are in the right place. We will go slow. Let me walk you through what I found. A tiny crash course before we get to the bug. SLH-DSA stands for Stateless Hash-based Digital Signature Algorithm. It is one of the post-quantum signature schemes that NIST standardized in FIPS 205. The “post-quantum” part means it is built to survive against future quantum computers that would shred classical schemes like RSA and ECDSA. The cool thing about SLH-DSA is that it only relies on hash functions. No fancy lattice math, no elliptic curves, just hashes all the way down. The not-so-cool thing is that signatures are huge (think tens of kilobytes) and signing is slow. Alongside SLH-DSA there is also ML-DSA. Same NIST batch, different math. ML-DSA uses lattices and is way faster but the API in OpenSSL looks almost identical for both. That detail matters, hold onto it. When you sign something with SLH-DSA in randomized mode, the algorithm needs a fresh random nonce called . This nonce gets mixed into the signature. It does not need to stay secret forever (the signature itself is public anyway) but it should not be left lying around in memory after we are done with it. That is just basic crypto hygiene. You wash your hands after handling raw chicken. You wipe nonces after signing. I was reading through to learn how the provider plumbing worked. I had the equivalent ML-DSA file open in another tab for comparison because they share the same shape. Both files have a function that does roughly this: Read that last statement carefully. is the local stack buffer. That is the variable we want to wipe, because that is where our fresh secrets sit. is a pointer that ends up pointing at one of three things: - , if the caller supplied a test value (heap memory in the context) - , our stack buffer, if we generated fresh random - , in deterministic mode So the check says: if is NOT pointing at our stack buffer, wipe whatever it is pointing at. Which translates to: in the normal random signing path where IS pointing at the stack buffer, do nothing. That is exactly backwards. Let me walk through what actually happens in each path. Path 1: normal random signing and . The code generates fresh entropy into , then sets . They point at the same place. is false. The cleanse never runs. The nonce sits on the stack waiting for someone to read it. Path 2: test entropy override . The caller supplied test bytes through the context. Now which lives on the heap. is true. Cleanse runs. Except is still 0 because the code never assigned it in this branch. So we call . A cleanse of zero bytes. Useless. Path 3: deterministic mode . Neither branch in the runs. stays and stays 0. The check is true (NULL is not equal to the stack address). Cleanse runs as . Defined behavior in OpenSSL but completely pointless. So in three paths, three different ways of being wrong. The most common path leaves a real secret on the stack. The other two do nothing useful. A perfect score. This bug stings even more once you look at the sister file in the same directory. ML-DSA has the exact same structure and gets it right: Look at the differences from the SLH-DSA version: Spider-Man pointing at Spider-Man. Left Spidey labelled “ml_dsa_sign”. Right Spidey labelled “slh_dsa_sign”. They look identical. Except one of them is leaking nonces and does not know it. It is a one-line change. Or two depending how you count. Two reasons to use instead of : I have to be honest here. By itself this bug is not a critical-severity find. It does not let an attacker forge signatures, recover private keys or directly do anything mean to your server. But. Cryptographic nonces leaking onto the stack is the kind of thing that becomes a real problem when chained with another bug. Some examples: Galaxy brain meme. Level 1: “I scrub my secrets”. Level 2: “I scrub my secrets but with the wrong condition”. Level 3: “I scrub my secrets but with the wrong condition AND zero bytes”. Level 4 glowing brain: “I scrub NULL”. I wrote a small standalone reproducer that mirrors the structure of . It uses a magic byte ( ) in place of real random data so we can spot it on the stack. There are two versions of the function: a buggy one that mirrors OpenSSL 4.0.0, and a fixed one that mirrors the ML-DSA pattern. After each call we probe the stack to see what is still there. The way this works is declares a local stack buffer at roughly the same offset that the previous function call used. Compilers reuse stack frames a lot, so whatever was on the stack at offset X in one function is often visible at offset Y in the next one if Y is close to X. With and it becomes very predictable. When I ran it the buggy version showed dozens of bytes still in the probe buffer. The fixed version showed zero. Smoking gun. After I filed the issue, Mounir IDRASSI opened PR #31029 titled “slh_dsa: cleanse generated add_random buffer”. The patch is basically the same one-liner I proposed, with an added comment to make the intent clearer: The OpenSSL bot tagged it with “severity: fips change” because changing FIPS provider sources triggers the formal FIPS process, which is a thing I learned exists thanks to this. After the standard 24-hour grace period the change was merged on May 3, 2026. Even better, it was backported to the 3.5, 3.6 and 4.0 release branches as well as master. So if you are running any of those versions, the fix will be in your distro’s next OpenSSL point release. No action needed beyond or whatever your platform calls it. You can see the actual merged commit here (it also shows up on mirrors like this fork ). Total time from “huh, that condition looks weird” to “merged into upstream and backported to four branches”: about ten days. That is shockingly fast for a project the size of OpenSSL. Big thanks to Mounir for picking it up so quickly and to Shane and Paul for the review. A few things stuck with me from this whole journey: Read sister code together. I would not have spotted this if I had only been looking at . Putting the two files side by side made the inversion obvious. Whenever there are two files in a codebase that do almost the same thing, diff them. Pull up both. Look for the spot where they disagree on a small detail. That is where the bugs are. Cleanup code is high-value review material. Bugs in cleanup paths do not show up in tests because tests usually only check that the right value comes out, not that the wrong value gets erased. Cleanup is the place where security bugs love to hide. It runs after the success case, often nobody looks at it. Tiny conditions matter. One character. versus . That is the entire bug. The compiler does not care, the tests pass, the code looks fine on a quick read. Slow review beats fast review. Post-quantum crypto is new code. SLH-DSA and ML-DSA only landed in OpenSSL recently. New crypto code in any library is worth reading. It has been touched by fewer eyes than the OG stuff like AES or RSA, which means the easy wins have not been picked clean yet. Filing a clean issue gets a quick fix. This one surprised me. I included a focused write-up, a small reproducer and a one-line diff that matched the existing pattern in the sibling file. Six days later there was a PR. I think putting in a little extra effort on the report saved everyone time downstream. I filed the issue as openssl/openssl#30950 on April 23, and ten days later the patch was merged into master and backported to 3.5, 3.6 and 4.0 via PR #31029 . Pretty satisfying turnaround. The main takeaway is honestly more about how I found it than what I found. I wandered into a codebase I do not deeply know, picked a file, found a similar file, read them next to each other and noticed something off. Anyone can do that. You do not need to be a crypto expert to spot dumb logic bugs. You just need to read code and ask “wait, that does not look right”. If you read all the way to the end, thanks for hanging out. Next time you are cleaning up sensitive memory in your own code, double check the condition. Maybe even triple check it. Your future self standing over the core dump will thank you. Thanks to Bas, Jordan and Thea for your time and help. Allocate a small stack buffer for randomness Either copy in a test value from the context, or fill the buffer with fresh entropy Call the actual signing routine Wipe the buffer with Step four is the important one. When you are done with sensitive bytes, you scrub them. Otherwise they sit on the stack until someone else’s function call happens to write over them. Which might be never if your signing function returns and the program does something else on a different code path. Here is what looks like, lightly trimmed (lines 244 and 245 of the file in 4.0.0): The check is against (the heap context value) not against (the stack buffer) The cleanse always targets (the stack buffer) with (a compile-time constant) In ML-DSA the logic reads as: if we did not use the supplied test entropy, we must have used the stack buffer, so wipe the stack buffer. Easy. Correct. Boring. The SLH-DSA version reads like someone copied this pattern from somewhere and got the variables mixed up halfway through. is conditionally set. If a future refactor moves things around it is easy to land in a code path where is zero and we silently cleanse nothing is , evaluated at compile time. Always correct. Always the full buffer You always want to wipe the whole buffer anyway. Wiping just bytes leaves the rest of the buffer untouched, which might still contain whatever previous garbage was there. Most of the time fresh entropy fills the whole thing, but defense in depth is cheap. Core dumps. If your process crashes and dumps core, the stack contents go straight to disk. If your crash dump handler ships those off to a Sentry-style service, your nonces just left the building Swap files. The OS pages your process to disk. The unwiped stack pages go with it. Now the nonces live on a spinning disk somewhere until the page is overwritten Information disclosure bugs. Pair this with any unrelated bug that lets an attacker read uninitialized stack memory, like an out-of-bounds read elsewhere in the same process, and now they have something interesting to look at Side channels. Knowing the exact addrnd value used for a signature lets an attacker do more precise work on side-channel attacks against the SLH-DSA hash inputs. Not a panic-button thing, but it is a useful primitive FIPS 140-3 compliance. Organizations in finance, healthcare or government often have hard requirements that “sensitive security parameters” must be zeroized after use. A randomized signing nonce qualifies. So if you are using OpenSSL 4.0.0 SLH-DSA in a FIPS context you are technically out of compliance The thing about leaking secrets to the stack is, it does not bite you until it does, and then it bites everyone at once. Better to stamp it out before that happens.

0 views
Himanshu Anand 9 months ago

how i found a europa.eu compromise (thanks to cricket)

TLDR While looking for a way to stream the India vs Pakistan cricket match on 14th September 2025, I stumbled across a suspicious search result on a europa.eu dev subdomain. It was being abused for blackhat SEO and redirecting users to scam streaming sites. I traced similar behavior across other high-profile domains, reported the issue to CERT-EU via email (after some Twitter help) and the problem was later confirmed as fixed on 6th November 2025. This post walks through how I found it, how I reported it and what we can learn from it. On 14 September 2025 , India played Pakistan in one of those absolutely wild, high-stakes cricket matches. If you are from India or Pakistan, you already know: this is not just a “match”. It is a festival . The celebrations are huge. What I did not expect was that this festival would somehow lead me to a compromised europa.eu dev server . India vs Pakistan -> europa.eu compromise. Yeah, I was also confused. I searching for which OTT services is aurtorised for “India vs Pakistan live”. That’s when a very strange search result showed up: That alone set off my blue-teamer brain. Why is an EU domain telling me how to stream a cricket match between India and Pakistan? Suspicious search result from a europa.eu domain claiming to help stream the match. I clicked the link (safely, in a controlled environment) and instead of any EU content, it redirected me to a random scammy streaming site . At this point one thing was clear: this looked exactly like SEO poisoning using a trusted domain (in this case europa.eu) to funnel users into suspicious streaming sites. On closer inspection of the URL, I noticed this was the impacted host: A dev server . Exposed to the internet. Being used for blackhat SEO-related redirects. That combination alone is already a red flag. When I tried visiting some of the URLs I had captured from search results, I observed: Caption: Example of a scam streaming site reached after redirection. This rotating behavior is pretty typical for SEO spam / poisoning campaigns. Payloads and keywords change over time to ride whatever is trending. At this point I thought: okay, this probably needs to be reported to the relevant CERT but I am not sure which contact is correct. So I did the most natural 2025 move. I first put my observation on X (Twitter) to document it and to see if anyone could guide me on the right reporting channel: Caption: First tweet where I shared the suspicious europa.eu behavior. There was no immediate response from any official EU account. So I followed up and tagged a few security folks who I knew might have better visibility or contacts. Caption: Follow-up tweet tagging friends from the security community. Special thanks to: They helped point me towards the right CERT-EU contact email . Pro tip from this whole thing: even for big organizations, having a clear security.txt or disclosure page makes everyone’s life easier. Armed with the correct email, I finally reached out to: Caption: Initial email to CERT-EU describing the behavior. They replied but they were unable to reproduce the issue right away: Caption: CERT-EU asking for details and reproducible evidence. This is where the rotating / inconsistent behavior of SEO campaigns becomes annoying: by the time defenders go to check, the payload might already have moved, rotated or partially broken. I shared more screenshots and context to help them see what I had observed. While doing my analysis, I remembered a great writeup that described mass SEO exploitation via a virtual tour framework: I won’t claim this was exactly the same attack but the TTPs were very similar : At minimum, it looked like the same family of problems : compromised pages being weaponized not to drop malware but to hijack SEO for traffic. While digging deeper and using the same patterns and dorks, I realized this wasn’t just an EU issue. I also observed similar behavior on other high-profile domains, including: And if you want to explore this yourself here is one very telling Google dork: Caption: Google dork results showing multiple sites with the same SEO payload pattern. One of the more notable hits was michelin.com , which pretty much confirms that attackers had gone for breadth, not just niche or small domains. Caption: Meme-worthy moment: when you just wanted to watch cricket and end up mapping an SEO spam campaign across major domains. At some point in the exchange, CERT-EU clarified that: they could not treat this as a vulnerability report eligible for Hall of Fame publication. Caption: CERT-EU confirming the case is not HoF-eligible. Honestly, that’s fair. This was not a critical RCE or some zero-day that could bring the EU offline. But it does highlight a funny reality of security: Still worth doing it every time. Here is the rough sequence of events: Caption: CERT-EU confirming the issue has been fixed on their side. I also asked whether they could share anything from an incident response perspective for the community and whether they were okay with me blogging this. I have not seen a detailed IR writeup yet but I have given this a reasonable amount of time before publishing. This section is my hypothesis not an official statement from CERT-EU. Based on what I observed and what we know about similar campaigns: A dev server was exposed to the internet Attackers found a way to inject or modify SEO-relevant content They rotated keywords based on trending topics The redirection targeted scam streaming pages Deeper compromise (like webshells or long-term RCE) feels unlikely The server was likely taken offline or cleaned as part of IR A few takeaway points for defenders, blue-teamers and anyone running public-facing infrastructure: Just because it is a “dev” host does not mean it won’t be: If a dev subdomain lives under a high-trust parent like , it inherits a lot of credibility. It’s easy to ignore SEO spam as “just” nuisance. But it: Even if the worst case here isn’t data exfiltration, it’s still worth fixing. The fact I had to go via Twitter and friends to find the right reporting contact is… not ideal. A simple well-maintained security.txt or even a clear “Report a vulnerability” page can: I fully understand not every incident can be disclosed in detail. But where possible, sharing even a sanitized, high-level IR summary is incredibly helpful: This all started because: You don’t need a zero-day to be helpful. If you notice weird redirects, unexpected search results or strange behavior on big domains: Worst case: it’s nothing. Best case: you help someone clean up a compromise. This was not a nation-state APT or a dramatic multi-stage intrusion with custom malware. It was something quieter: But these smaller things matter too. They erode trust slowly. They teach attackers that abusing big brands for SEO spam is easy and low-risk. And they serve as gentle reminders that even very mature organizations can still have dev subdomains exposed in ways they did not expect. If you work in defense: And if you’re just here for the story: stay curious, stay safe and maybe next time your match-day Google search will uncover something interesting too. Tweet 1 (initial finding): https://x.com/anand_himanshu/status/1967325757602136238 Tweet 2 (asking for help): https://x.com/anand_himanshu/status/1967571763929473520 @UK_Daniel_Card @vxunderground 360XSS: Mass Website Exploitation via Virtual Tour Framework for SEO Poisoning https://olegzay.com/360xss/ Hack one site and brag -> hero status. Quietly report that a big domain is being abused -> often nobody notices. A dev server was exposed to the internet was reachable publicly when it probably shouldn’t have been. Attackers found a way to inject or modify SEO-relevant content This might have been a stored XSS, misconfigured template or some CMS/plugin endpoint. The goal was not to deface the site, but to hijack search engine results. They rotated keywords based on trending topics Big matches like India vs Pakistan are perfect bait. Titles like strongly suggest SEO-driven campaigns. The redirection targeted scam streaming pages Once users clicked the search result, they would end up on random streaming or scam sites. This is great traffic for shady affiliates, subscription scams or ad fraud. Deeper compromise (like webshells or long-term RCE) feels unlikely If they had long-term, reliable RCE on high-profile domains, using them only for SEO spam would be a waste. SEO campaigns benefit more from wide, shallow compromise than from deep, single target persistence. The server was likely taken offline or cleaned as part of IR Given that CERT-EU confirmed the issue is fixed, it is safe to assume: exposure was removed and/or malicious content was removed and underlying misconfigurations were corrected. reduce the time from discovery to report avoid reports getting lost in generic inboxes encourage more people to report issues responsibly

0 views
Himanshu Anand 11 months ago

look mom HR application look mom no job

TLDR I have recieved a legit Zoom doc email from HR “while on job hunt” . It redirected to a site with a fake “bot protection” gate and then to a Gmail credential phish. The attackers exfiltrate creds live over WebSocket and even validate them in the backend. Keep reading for detailed analysis. Okay, this is kind of funny (in a “please tell me this is not my life” way). I have been on the job hunt lately and an email landed in my inbox that I almost ignored. Only later did I realize: this one actually came from legit Zoom. Cool, right? Except not. Turns out bad people are now using Zoom’s legit features to phish people. Welcome to 2025, where your meeting app doubles as a cybercrime vector. I got an email that looked like a normal Zoom doc/share notification. Header looked official. Sender looked legit. I almost clicked and moved on. Caption: Email header with valid SPF, DKIM and DMARC. On deeper inspection I realized the document link led to an offsite page that redirected to a classic Gmail credential harvesting page. Caption: Zoom UI showing the shared document/link. The attackers used Zoom’s document-sharing flow as the trusted vector. People trust Zoom, so they click. Caption: Screenshot of zoom website. The phishing page had a “bot protection / phishing protection” gate that a user has to pass first. That is not to protect you - it is to protect the attackers from automated analysis and to make the page feel legitimate. Paste screenshot here: Caption: Fake bot protection gate that blocks sandboxes and looks legit. One user entered their Gmail user ID and password. The phishing page immediately sent the credentials to a C2 using a WebSocket connection. Live exfiltration. Paste screenshot here: Caption: The Gmail credential harvest page. I also captured a WebSocket snapshot showing the credentials being pushed out. Paste screenshot here: Caption: WebSocket connection showing live exfiltration. initial link from Zoom UI: once gate passed, redirected to: Yes, the URLs are ugly and tell you everything you need to know. Given the validation and the slower response times I observed, they are probably validating credentials in the backend. That means they are not just collecting creds, they are checking them for usability. Report to Zoom and Google. Use Zoom’s abuse/reporting and Google’s phishing report forms and attach screenshots and headers. Block the domain at DNS or proxy level if you are an admin. For example, block overflow.qyrix.com.de. Warn coworkers. These links are social engineering. People will click if it looks work-related. Scan for suspicious outbound connections. WebSocket C2 exfil is a thing. Watch for unknown outbound ws/wss traffic from browsers. Use a password manager. It will not autofill on off-domain pages and that provides another layer of defense. I appreciate that Zoom wants to help me share stuff. I really do. And apparently attackers appreciate Zoom, too. They treat it like a trusted doorbell where people actually answer. So next time you get a Zoom doc email titled “HR application” and feel your job prospects spike for a second, do not. Take two seconds to check the domain, the header and whether the login is actually on google.com. Your future self and your inbox will thank you. I got an email that looked like a normal Zoom doc/share notification. Header looked official. Sender looked legit. I almost clicked and moved on. Caption: Email header with valid SPF, DKIM and DMARC. On deeper inspection I realized the document link led to an offsite page that redirected to a classic Gmail credential harvesting page. Caption: Zoom UI showing the shared document/link. The attackers used Zoom’s document-sharing flow as the trusted vector. People trust Zoom, so they click. Caption: Screenshot of zoom website. The phishing page had a “bot protection / phishing protection” gate that a user has to pass first. That is not to protect you - it is to protect the attackers from automated analysis and to make the page feel legitimate. Paste screenshot here: Caption: Fake bot protection gate that blocks sandboxes and looks legit. One user entered their Gmail user ID and password. The phishing page immediately sent the credentials to a C2 using a WebSocket connection. Live exfiltration. Paste screenshot here: Caption: The Gmail credential harvest page. I also captured a WebSocket snapshot showing the credentials being pushed out. Paste screenshot here: Caption: WebSocket connection showing live exfiltration. initial link from Zoom UI: this hosts the bot protection gate once gate passed, redirected to: this is the Gmail phishing page Use a trusted platform (Zoom) to deliver the initial link. People click because it looks like a shared document. Redirect to a “bot protection” gate. Two jobs: keep automated analysis and sandboxes away, and increase perceived legitimacy for the victim. If the user passes the gate, show a credential harvest page that mimics Gmail login UI and asks for username and password. On submit, open a WebSocket back to the attacker server and push the credentials in real time to C2. The server can validate them and mark hits. They likely run a backend that validates credentials so they know which ones work. That is why the response felt slower than a static phishing page. bot-protection gate - not to protect you, to protect them from analysis and to look legit. real-time exfil via WebSocket - gives attackers immediate hits and lets them triage validated creds quickly. using Zoom’s document flow as the social engineering vector - people trust Zoom notifications, so the click rate is higher. Email claims to come from Zoom, but the link domain does not match Zoom or Google. Always check the full link. The page shows a “bot protection” widget or quiz before a login - that is suspicious in this context. The login form is not hosted on the real provider domain. The login experience is slower or asks extra steps that normal logins do not. Any login prompt that is not on the official provider domain is suspect. Live WebSocket connections that open when you submit a form is a red flag. Do not enter credentials. If you already did, change your password immediately and enable two-factor authentication. Use the real provider site to change the password. Check the email header and sender carefully. If your mail client allows full headers, inspect Received, DKIM, SPF and related fields. Paste redacted header here if you want to show readers: Report to Zoom and Google. Use Zoom’s abuse/reporting and Google’s phishing report forms and attach screenshots and headers. Block the domain at DNS or proxy level if you are an admin. For example, block overflow.qyrix.com.de. Warn coworkers. These links are social engineering. People will click if it looks work-related. Scan for suspicious outbound connections. WebSocket C2 exfil is a thing. Watch for unknown outbound ws/wss traffic from browsers. Use a password manager. It will not autofill on off-domain pages and that provides another layer of defense.

0 views
Himanshu Anand 11 months ago

A step by step guide how to hack webhooks

A step by step guide how to hack webhooks kinda this is a crossover post between threat hunters and bug hunters. at the end of the day it is all about hunting. this post will talk about hunting and threat hunting using webhooks for those who don’t know what webhooks are: webhooks are an easy way to send messages from one service to another. they are simple HTTP callbacks that notify endpoints when events happen Webhooks are convenient for automation. bad actors love convenience too. here are common ways webhooks become an attack surface Attackers can use webhooks to exfiltrate data to their servers If a webhook URL leaks publicly other people can spam the receiving servers. what’s the worst that can happen from spamming servers right? overload data leakage or workflow abuse Webhooks mistakenly trusted by backend systems can be a gateway into business logic If a backend trusts incoming webhook calls without proper verification attackers could trigger actions that should be protected. this is not always a flashy RCE exploit many times the damage is business logic abuse Pipeline webhooks can reveal internal build or deployment workflows A webhook used to trigger a CI pipeline can hint at how a company deploys code. with that context you can look for weak spots like unsafe handling of inputs naive template rendering or backend endpoints that echo back unvalidated data Combine this with an email analogy A leaked webhook can act like a gateway into a company. attackers can spam webhooks and cause phishing style flows without even sending email. the attack might be blind at first because you do not know how the backend is implemented however if you find the webhook in context on a website the surrounding code or assets may reveal enough to make a focused higher impact test possible I have seen ecommerce sites use webhooks for order notifications. when implementations assume the webhook is authoritative there can be business logic abuse. conceptually you could cause an order to appear to be paid if the system trusts the webhook to confirm payment. if the backend cross checks payment status then there is no issue. if the site relies solely on the webhook then fulfillment might happen without payment (most modern systems cross-validate payment status) the devil lies in the details of how webhooks are implemented. small differences in timing verification or workflow ordering change the impact dramatically This part is the fun bit for threat hunters. you can use internet search engines that index web assets to find webhook endpoints living publicly. as an informal check i ran a quick search on FOFA and found thousands of results for discord and slack webhook patterns. that shows how many webhooks leak into public indexes Keep in mind public counts are a signal not a final verdict. many results will be benign or intentionally public. use additional context to prioritise targets such as where the webhook appears what page triggers it and whether the surrounding code reveals internal workflows When you find a webhook used to trigger a pipeline that alone is a useful hint. that hint can lead to blind code injection reflected XSS or other issues depending on how the pipeline uses data. sometimes it is low noise and high payoff. kinda fun right for bug bounty submissions you often need to show impact not just possibility. many webhook issues are out of scope for a program so be clever in demonstrating a realistic impact. show how the webhook maps to a meaningful business function. provide a proof of concept that respects program rules and follows responsible disclosure Explicitly state what you are testing: public webhook URLs found in code repos pages or search indexes versus internal webhooks discovered by credential leakage. this clarifies whether an action is discovery only or active testing Note race conditions where webhook fires before payment confirmation or where webhook ordering can be abused to replay or reorder events. recommend idempotency tokens and status checks List concrete signals defenders can add to logs: source IPs user agents request body hashes timestamp headers signature headers response status codes unusually high delivery rates. add SIEM alert examples Search patterns credentials scanning repository history look for hostnames like check JS embedded in pages network calls visible in browser devtools and use git secrets scanning Rotate webhook URLs and secrets enforce HMAC signatures and timestamp checks add IP allowlists when possible require secondary confirmation for financial actions add rate limits log deliveries with request ids Simple regex to detect typical webhook URLs in code or logs SIEM rule idea Trigger when outbound POSTs to known webhook hosts exceed baseline by X within 5 minutes or when same webhook receives more than N deliveries from diverse source IPs within T minutes The examples below show how to send benign messages to incoming webhook endpoints. replace with the webhook you control or a test webhook belonging to you. Do not send tests to other people systems document the context where you found the webhook the surrounding assets and the likely workflow. that context often turns a blind guess into a reproducible finding Attackers can use webhooks to exfiltrate data to their servers If a webhook URL leaks publicly other people can spam the receiving servers. what’s the worst that can happen from spamming servers right? overload data leakage or workflow abuse Webhooks mistakenly trusted by backend systems can be a gateway into business logic If a backend trusts incoming webhook calls without proper verification attackers could trigger actions that should be protected. this is not always a flashy RCE exploit many times the damage is business logic abuse Pipeline webhooks can reveal internal build or deployment workflows A webhook used to trigger a CI pipeline can hint at how a company deploys code. with that context you can look for weak spots like unsafe handling of inputs naive template rendering or backend endpoints that echo back unvalidated data Slack pattern: Discord pattern: Treat webhook URLs as secrets do not embed them in client side code Verify signatures when supported and enforce timestamp checks Rotate secrets and revoke leaked URLs immediately Use idempotency tokens and require secondary checks for financial flows Rate limit and monitor webhook traffic and add alerts for spikes

0 views
Himanshu Anand 11 months ago

Magecart Skimmer Analysis: From One Tweet to a Campaign

It all began with a tweet: sdcyberresearch on X This tweet hinted at a Magecart-style campaign involving malicious JavaScript injection to skim payment data. The script was hosted at: The original code was heavily obfuscated: I used two quick approaches to deobfuscate the code: Voila — decoded. For more convenience, I later discovered https://obf-io.deobfuscate.io which automates this process. After cleanup, the code looks like this: The logic is clear: My next step was to pivot from this single domain to identify related infrastructure. Searching for on urlscan.io revealed injections like: Example DOM reference: URLScan result The script was injected via two places on compromised ecommerce websites. From URLScan transaction logs, I extracted the hosting IP: 45.61.136.141 WHOIS info: DomainTools result By pivoting on IP and searching on URLScan, I found additional domains serving similar malicious scripts: Example: getnjs.com/util.js The JS payloads are nearly identical, showing reuse across campaigns. A larger set of potentially associated domains (from passive DNS and infra pivoting using validin.com ): ⚠️ Do not blindly block these. Validate first before taking action. This investigation started from a single tweet about . By following simple pivots on domains, IP addresses, and injected scripts, we uncovered a broader Magecart campaign. The takeaway is that small public signals can lead to bigger discoveries . Free tools like URLScan, publicWWW, and WHOIS are enough to map out attacker infrastructure and build intelligence. Enjoy threat hunting. Feel free ot Email if I can help with this anything or some questiosn went unanswered. Debugger method – prepend to the script and run in browser dev tools. Python trick – since the obfuscation used hex and strings, simply printing the string in Python revealed the content. Hook into checkout and payment form fields. Collect credit card and billing information. Send the stolen data to . These domains have been active for at least one year . Threat actors recycle infrastructure and re-use domain naming patterns ( , ). Compromised ecommerce sites host injected tags pointing to attacker infrastructure. Detection relies on finding these script references — tools like are effective for tracking.

0 views
Himanshu Anand 12 months ago

Typosquat Detective : a tiny game to train your eye

In light of recent npm/Node.js supply-chain news , I am resurfacing a small game I built: Typosquat Detective . It’s a quick way to practice spotting look alike domains that phishers love. Play it here → https://typo.himanshuanand.com/ If your browser/theme blocks iframes, just use the link above Supply chain incidents and domain look-alikes often go hand in hand. Training your eye to catch subtle domain tricks is a simple layer of defense for users, developers and maintainers are alike. If you want to add levels/brands or peek at the code, ping me I am happy to extend it. Play & share your score: https://typo.himanshuanand.com/ Numbers-for-letters like , , Unicode homoglyphs (Cyrillic/Greek letters that look Latin) Punycode tricks ( style domains) You will see a domain. Decide Typosquatted or Real 10 quick rounds, bonus for streaks

0 views
Himanshu Anand 1 years ago

Why Relying on LLMs for Code Can Be a Security Nightmare

I wanted to quickly highlight something I have noticed in multiple cases but this time let me explain with a real example. When code is written purely using LLMs (vibe coded) security often takes a back seat. LLMs work on data and most of what is available at training time (essentially scraping the internet) is NOT “secure by default” A lot of examples floating around online are meant to demonstrate functionality not teach security best practices. When those patterns are blindly reused or auto generated by an LLM the same insecure logic ends up in production code . This is NOT my first time stumbling across such issues. Some time ago, I found a bug in Coinbase’s x402 pay-per-view example code (not in the core library itself, just the example). When I reported it through HackerOne, it was marked as informational —since the problem was only in the sample code. That experience stuck with me because it shows that even official documentation can contain insecure examples . If developers copy paste blindly, those vulnerabilities end up in live apps even when the code is not generated using LLM. And that’s the point: security issues in example code are not new. But with LLMs the problem scales faster, because LLMs often mimic those same insecure patterns. Now, let me share a live example I came across recently. While browsing, I came across a JavaScript file on a site hosted on railway.com popular PaaS. The file contained this (lightly paraphrased) code: If you have even a basic sense of security, the problem jumps out: The entire email API endpoint and parameters are exposed in client side code. That means anyone can send arbitrary requests to the backend without using the website at all. This trivial PoC demonstrates why exposing a mail-sending API client-side is risky: Note: All identifiers and endpoints are redacted. The goal is to highlight the pattern, not to point at a live system. Reporting the Bug I reported it to the hosting provider; since the vulnerable app was third-party code hosted on their platform, it was understandably out of scope. The real risk here isn’t theoretical: Attackers could spam arbitrary email addresses. They could target the app’s customers with convincing messages. They could target the organization itself by spoofing trusted senders. The Bigger Point The takeaway isn’t about bug bounties, it is baout knowing that LLM generated code is not security aware. LLMs do NOT understand business risk and If you ship AI written examples as it is you may be exposing real attack surface. LLMs are helpful assistants but they are NOT really understand the need. Keep humans in the loop for threat modeling, abuse cases thinking and defense design. Else you might end up shipping an app that can be abused with a single curl command.

0 views