← Back to list

SQLMap Explained: How It Works, GET and POST Testing, WAF Bypass

The Path from SQL Injection to RCE

Yamini Yadav_369 in OSINT Team · 2026-06-26 02:33 · 52 claps · 10.7 min read paywalled
#cybersecurity #ethical-hacking #medium #bug-bounty #sql
Open on Medium ↗
Wiki topics: 🔒 · Cybersecurity

SQLMap Explained: How It Works, GET and POST Testing, WAF Bypass

The Path from SQL Injection to RCE

Photo by Caspar Camille Rubin on Unsplash

Photo by Caspar Camille Rubin on Unsplash

A few months back, I was testing a client's web app during a VAPT engagement. The login form looked clean. No errors on bad input, no weird behaviour, nothing that screamed vulnerable. I almost moved on. Then I ran sqlmap against the request just to be sure, and within two minutes, it confirmed a Boolean-based blind injection in a parameter I had completely ignored. That one finding eventually chained into full database access. This is the kind of thing sqlmap is built for, and this post is going to walk you through it properly, from the basics to advanced WAF bypass to the question everyone eventually asks: can SQL injection become RCE, and if yes, how?

This is a long post. Take your time with it. I have tried to explain not just the commands but why they work, because memorizing flags without understanding the mechanics behind them will only get you so far.

What sqlmap actually is

sqlmap is an open source penetration testing tool written in Python that automates the process of detecting and exploiting SQL injection vulnerabilities. Before sqlmap existed, testers had to manually craft payloads, observe responses, and guess column counts by hand. sqlmap takes that manual process and turns it into a structured, repeatable workflow.

At its core, sqlmap does four things really well. It detects whether a parameter is injectable. It identifies what type of injection is possible. It extracts data from the database once injection is confirmed. And it can, in certain conditions, get you a shell on the underlying server.

How sqlmap actually works under the hood

People treat sqlmap like a magic button, but understanding the logic behind it makes you a much better tester. Here is the real flow.

Step one: sqlmap sends the original request as is and records the baseline response. This includes status code, response length, response time, and content.

Step two: sqlmap starts injecting test payloads into the parameter you specify or every parameter if you do not specify one. It tries different injection techniques one by one. These techniques are:

Boolean-based blind: where SQLMap sends a true condition and a false condition and compares the two responses. If the page behaves differently for true versus false, that is a strong sign of injection even without visible errors.

Error-based: where SQLMap tries to force the database to throw a visible SQL error in the response, which often leaks information directly in that error message.

Union-based: where sqlmap tries to use the UNION SQL operator to combine the original query with its own query and pull data directly into the visible output.

Time-based blind: where sqlmap sends a payload that causes the database to pause for a few seconds if the condition is true. If the response takes longer than it should, that confirms injection even when there is zero visible difference in the response content.

Stacked queries: where sqlmap tries to chain a second query after the original one using a semicolon, which can allow much more dangerous actions like inserting data or even calling system commands, depending on the database engine.

Step three: Once a working technique is found, SQLMap fingerprints the database. It identifies whether you are dealing with MySQL, PostgreSQL, MSSQL, Oracle, SQLite, and so on, because the syntax and exploitation techniques differ for each.

Step four: sqlmap uses that confirmed technique to extract whatever you ask for. Database names, table names, column names, actual data, current user, privileges, and more.

This is why sqlmap is not just throwing random payloads. It is running a structured detection and exploitation pipeline behind a single command.

Setting up sqlmap

If you are on Kali Linux, sqlmap is already installed. On any other system, you can clone it directly.

git clone https://github.com/sqlmapproject/sqlmap.git cd sqlmap python sqlmap.py — version

Always keep it updated since database engines and WAF behaviors change often, and the sqlmap team pushes fixes and new tamper scripts regularly.

python sqlmap.py—update

Testing a simple GET request

This is the most common scenario. You have a URL with a parameter in the query string and you want to check if it is injectable.

python sqlmap.py -u “http://target.com/product.php?id=1"

Here is what each part means. The -u flag tells sqlmap the target URL. sqlmap automatically detects the id parameter since it has a value in the query string and tests it for injection.

If you want sqlmap to test every parameter instead of guessing, you can be explicit.

python sqlmap.py -u “http://target.com/product.php?id=1" -p id

The -p flag tells sqlmap exactly which parameter to focus testing on. This is useful when a URL has multiple parameters and you only want to test one to save time and avoid noisy traffic.

If the page requires you to be logged in, you need to pass your session cookie so sqlmap is testing as an authenticated user.

python sqlmap.py -u “http://target.com/product.php?id=1" — cookie=”PHPSESSID=abc123xyz”

Once sqlmap confirms injection, it will ask if you want to test other potential injection points it found along the way. You can say yes if you have time, or no if you already know what you are after.

Testing a POST request

A lot of real injections live in POST data, especially in login forms, search forms and account update forms, because developers tend to be more careless with parameters that are not visible in the URL bar.

The simplest way is to capture the raw POST request from Burp Suite and save it to a text file, then feed that file directly to sqlmap.

python sqlmap.py -r request.txt

The -r flag tells sqlmap to read the entire request straight from a file, including headers, cookies and POST body. This is honestly the most reliable method because you are giving sqlmap the exact request the browser sent, with zero guesswork involved.

If you do not want to use a file, you can pass POST data directly using the — data flag.

python sqlmap.py -u “http://target.com/login.php" — data=”username=admin&password=test123"

sqlmap will automatically test both the username and password fields unless you tell it to focus on one specific parameter.

python sqlmap.py -u “http://target.com/login.php" — data=”username=admin&password=test123" -p username

Combining GET and POST in one test

Sometimes a request has parameters in both the URL and the body at the same time, like a search form that also carries a session token in the query string. sqlmap can test both simultaneously.

python sqlmap.py -u “http://target.com/search.php?category=1" — data=”query=phone&sort=price”

In this single command, sqlmap will test category from the URL and both query and sort from the POST body. This is extremely useful in real applications because injectable parameters are not always where you expect them to be.

Testing headers too

Injection is not limited to URL and body parameters. Headers like User Agent, Referer, X Forwarded For and custom headers are tested by developers far less often, which makes them a good hunting ground.

*python sqlmap.py -u “http://target.com/page.php?id=1" — headers=”X-Forwarded-For: 1”**

The asterisk tells sqlmap exactly where to inject the payload within that header value. You can do the same for cookies.

*python sqlmap.py -u “http://target.com/page.php?id=1" — cookie=”session=abc”**

Useful detection and risk flags

sqlmap has two important settings that control how aggressively it tests, called level and risk.

python sqlmap.py -u “http://target.com/page.php?id=1" — level=5 — risk=3

Level controls how many payloads and test points sqlmap tries, ranging from 1 to 5. Higher level means more tests including headers and cookies, but it also means more requests and more time. Risk controls how dangerous the payloads are, ranging from 1 to 3. Risk 3 includes payloads that can modify data, like OR based payloads that affect UPDATE or DELETE queries, so use that carefully and only with proper authorization.

Now the part everyone actually wants, bypassing a WAF

Web Application Firewalls sit in front of the application and try to block obvious SQL injection patterns. The good news is most WAFs are pattern based, meaning they look for known bad keywords and syntax, not actual understanding of SQL logic. That gap is exactly what tamper scripts exploit.

sqlmap comes with built in tamper scripts that modify your payload on the fly to slip past these filters while keeping the same SQL logic intact. Here is how you use them.

python sqlmap.py -u “http://target.com/page.php?id=1" — tamper=space2comment

space2comment replaces spaces in your payload with inline comments like /**/, which often bypasses filters that specifically block the space character in suspicious queries.

You can stack multiple tamper scripts together for tougher WAFs.

python sqlmap.py -u “http://target.com/page.php?id=1" — tamper=space2comment,between,randomcase

Let me explain what each of these actually does, because just copying flags without understanding them defeats the purpose.

space2comment swaps spaces with comment syntax to dodge space based filters.

between replaces operators like greater than and less than with the BETWEEN clause, which many WAFs do not recognize as equivalent logic.

randomcase randomly changes the capitalization of SQL keywords, since some WAFs only match lowercase or exact case keyword patterns and miss mixed case versions like SeLeCt.

A few other tamper scripts worth knowing. charencode URL encodes the entire payload to bypass filters that only inspect raw decoded text. apostrophemask replaces single quotes with their UTF8 full width equivalent, which some WAFs fail to normalize before inspection. equaltolike replaces the equals sign with the LIKE operator, since LIKE achieves similar logic but is rarely on a blocklist.

If you want sqlmap to automatically figure out which WAF is in front of the target before choosing a strategy, run this first.

python sqlmap.py -u “http://target.com/page.php?id=1" — identify-waf

This identifies common WAF signatures like Cloudflare, Akamai, ModSecurity, Imperva and others, so you know what kind of filtering logic you are up against before wasting time on the wrong tamper combination.

You can also slow sqlmap down and randomize its behavior so it does not trigger rate based detection rules.

python sqlmap.py -u “http://target.com/page.php?id=1" — delay=2 — random-agent — tamper=space2comment

The delay flag adds a pause between requests, random-agent rotates the user agent header on every request so traffic does not look like an obvious automated scanner, and together with tamper scripts, this gives you a much quieter and more successful run against protected targets.

Extracting data once the injection is confirmed

Once sqlmap confirms a parameter is injectable, the actual data extraction is straightforward.

To list available databases.

python sqlmap.py -u “http://target.com/page.php?id=1" — dbs

To list tables inside a specific database.

python sqlmap.py -u “http://target.com/page.php?id=1" -D databasename — tables

To list columns inside a specific table.

python sqlmap.py -u “http://target.com/page.php?id=1" -D databasename -T tablename — columns

To dump actual data from specific columns.

python sqlmap.py -u “http://target.com/page.php?id=1" -D databasename -T tablename -C username, password — dump

This last command is the moment that usually matters most in a real engagement, because it proves actual data exposure rather than just theoretical injection.

To check the current database user and privileges.

python sqlmap.py -u “http://target.com/page.php?id=1" — current-user — privileges

This matters a lot because if the current database user has FILE privileges or is running as a high privilege account like root or sa, that directly affects whether RCE is even possible later.

Now the big question: can SQL injection lead to RCE

Yes, it absolutely can, but it depends heavily on three things. The database engine being used, the privileges of the database user, and the server configuration. SQL injection itself does not automatically mean remote code execution. It is a chain of conditions that has to align.

Here is how the path actually works for each major database.

MySQL path to RCE

If the MySQL user has FILE privilege and secure_file_priv is not restrictive, sqlmap can write a PHP web shell directly to the web root using its built in os-shell feature.

python sqlmap.py -u “http://target.com/page.php?id=1" — os-shell

When you run this, sqlmap asks you for the web application’s absolute path on the server, which you can sometimes find through error messages, default install paths, or by guessing common paths like /var/www/html. sqlmap then writes a small PHP file that accepts and executes commands you send, and you get a basic interactive shell straight from your terminal. Behind the scenes this works because sqlmap uses the INTO OUTFILE technique to physically write a file to disk through the SQL injection point, and that file becomes your web shell.

If FILE privilege is not available, this path is closed, and you stay limited to data extraction only.

MSSQL path to RCE

MSSQL has a stored procedure called xp_cmdshell that, if enabled, lets you run operating system commands directly through SQL. sqlmap automates this entire chain.

python sqlmap.py -u “http://target.com/page.php?id=1" — os-shell

sqlmap automatically detects it is dealing with MSSQL, checks if xp_cmdshell is enabled, and if it is disabled but the account has sufficient privilege, sqlmap can even enable it for you on the fly using sp_configure, then proceeds to give you command execution. This is honestly one of the most reliable RCE paths in the entire SQL injection world when the conditions are right, because xp_cmdshell essentially hands you a direct line to the operating system.

PostgreSQL path to RCE

PostgreSQL can achieve RCE in certain configurations using a feature involving large objects or the COPY command combined with superuser privileges, which lets you write files to disk, similar to the MySQL technique. sqlmap automates this detection too.

python sqlmap.py -u “http://target.com/page.php?id=1" — os-shell

If conditions are not met, like the user not being superuser, this path is not available and sqlmap will tell you clearly rather than failing silently.

A simpler, direct command path

Beyond a full interactive shell, you can also just ask sqlmap to run a single OS command if you already know exploitation is possible. This is faster when you just need a quick confirmation of RCE rather than a full session.

python sqlmap.py -u “http://target.com/page.php?id=1" — os-cmd=”whoami”

If this returns a username back to you, that alone proves command execution is happening on the underlying server, and you have your critical finding documented right there with clear evidence.

Why RCE does not always happen

I want to be honest here because a lot of beginners assume every SQL injection eventually becomes RCE, and that is simply not true. In most real-world engagements, especially well-configured cloud-hosted applications, the database user has restricted privileges, FILE permission is disabled, xp_cmdshell is locked down, and the web root is not writable by the database service account. In those cases, your finding stops at data extraction, and that is completely fine. A confirmed SQL injection with data dump is still a critical or high severity finding on its own. RCE is the worst-case outcome, not the expected one, and reporting it accurately as either possible or not possible based on actual testing matters far more than chasing a shell that the configuration simply will not allow.

Putting it all together, a realistic workflow

When I approach a target now, this is roughly the order I follow. First, capture the request properly in Burp, GET or POST, including all headers and cookies. Second, save it as a request file and run sqlmap with -r against it rather than typing flags manually, since this avoids encoding mistakes. Third, if there is any sign of a WAF or filtering, run identify-waf first, then layer in tamper scripts and slow the request rate down. Fourth, once the injection is confirmed, check the current user and privileges before attempting anything destructive. Fifth, only attempt os-shell or os-cmd if privileges genuinely support it, and always with proper written authorization since this step touches the actual server, not just the database.

sqlmap is one of those tools that looks intimidating from the outside because of how many flags it has, but once you understand the actual logic behind detection types, tamper scripts and privilege requirements, it becomes a tool you can reason about instead of one you just blindly run. The real skill is not memorizing commands. It is understanding why a particular technique works against a particular database and configuration, and being able to explain that clearly in your report.

If you found this useful, follow me for more deep dive security content where I break down tools and techniques in the same practical way I use them in real engagements. Drop a comment if you want a follow-up post specifically on manual SQL injection without sqlmap, since understanding the manual process makes you far better at using automated tools like this one anyway.

Tags: SQL Injection, sqlmap, Penetration Testing, Web Security, Bug Bounty, Cybersecurity, OWASP, Ethical Hacking


메타데이터
post_id
d69f08e6c49e
slug
sqlmap-explained-how-it-works-get-and-post-testing-waf-bypass-d69f08e6c49e
url
https://osintteam.blog/sqlmap-explained-how-it-works-get-and-post-testing-waf-bypass-d69f08e6c49e
canonical_url
https://osintteam.blog/sqlmap-explained-how-it-works-get-and-post-testing-waf-bypass-d69f08e6c49e
author_url
https://medium.com/@yamini369
status
ok
fetched_at
2026-06-28 04:42:08