Menu

SQL Injection and beyond: OWASP ZAP, Burp Suite and the ethics of pentesting

10 min read Manuel

SQL injection is the most studied vulnerability in the history of computer security, and in a way that is its shame: sixty years after it was discovered, whoever still introduces it takes websites down every day.

It is not an exotic mistake. It is a mistake you make out of laziness, because the quick route is concatenating strings and the safe one requires stopping to think.

What actually happens

The point is that SQL is a language, and a language accepts sentences. If user input gets pasted inside an SQL sentence, the user is not writing a value: they are writing code.

A normal query:

SELECT nome FROM utenti WHERE email = 'pippo@esempio.it'

With input that passes through unprocessed, the user can close the quote and add conditions:

-- input: pippo@esempio.it' OR '1'='1
SELECT nome FROM utenti WHERE email = 'pippo@esempio.it' OR '1'='1'

The query returns all the users, because the condition is always true. It is not a bug: it is the query working exactly as written, with an input nobody had anticipated.

From there you move on to UNION, to data extraction, in some cases to executing commands on the operating system.

The real hole is almost always the same one

In practice, 90% of the SQL injections you find have the same cause: code that puts input inside a string.

// ❌ concatenazione: l'input diventa parte della query
$sql = "SELECT * FROM utenti WHERE nome = '" . $_GET['nome'] . "'";
$risultato = $wpdb->get_results($sql);

// ❌ interpolazione: identico problema, solo più elegante
$sql = "SELECT * FROM utenti WHERE nome = '{$nome}'";

// ✅ prepared statement: il driver tratta l'input come DATO, mai come codice
$stmt = $pdo->prepare("SELECT * FROM utenti WHERE nome = ?");
$stmt->execute([$_GET['nome']]);

The difference between the third line and the first two is not a matter of style. In a prepared statement the input is sent separately from the SQL code: the database treats it as a plain string, it does not interpret it.

This is why a prepared statement is not "a protection": it is the absence of a condition. There is no code to guess, there is no protection to guess.

Why it still happens

The real reason is that concatenation is convenient and feels intuitive. $wpdb->query() accepts a string, and building a prepared statement takes two lines. On a project with a deadline, the convenient line wins.

There is also the worse reason: the idea that the input is "already clean". The @ field in a form is not a user, it is something that passes through the browser. If the validation lives in the form's JavaScript, it is not validation: it is a suggestion.

Looking for SQL injection in a systematic way

By hand you look for patterns, but for serious work you need tools that try hundreds of variants in your place.

Burp Suite: the Requests tab

Burp is the standard for manual and semi-automatic work. The workflow:

  1. You browse the site with the browser configured to use Burp's proxy
  2. Burp intercepts every request and shows it in Proxy → HTTP history
  3. You look for the requests that have parameters
  4. You select one and send it to Repeater
  5. In Repeater you modify the parameter and observe the response

The key step is the comparison. You put an apostrophe in the parameter:

# baseline
nome=prova

# primo test: l'apostrofo
nome=prova'

If the response changes with an SQL syntax error, you have found something. With ' you typically get:

You have an error in your SQL syntax; check the manual that
corresponds to your MySQL server version for the right syntax to use

That message is a treasure map: it confirms that the input ends up in a query.

Then you move on to the classic boolean tests, looking for a difference in behaviour:

' OR '1'='1' -- commento
' AND '1'='2' -- commento

If the first variant returns more rows and the second fewer, you have an injectable query. In MySQL the comment is -- (with the trailing space) or #; in SQL Server --; in PostgreSQL --.

There is also time, which is the quietest method:

' AND SLEEP(5) -- commento

If the response arrives five seconds later, the input is being executed by the database. Nothing changes on screen, but the response is delayed: it is the most reliable test when the page always returns the same data.

OWASP ZAP: the automatic scan

ZAP automates what you would do by hand, and it is free. The useful mode to start with is passive scan, which watches the browser traffic while you browse and flags what looks dangerous, without sending anything aggressive.

# Avvia ZAP in ascolto come proxy
zaproxy -daemon -port 8080

# Oppure, per una singola pagina, la spider di ZAP
zap-baseline.py -t https://target.example -r passive-scan-report.html

Passive scan is the correct starting point because it works on what the site actually does: it does not invent URLs, it looks at the ones that exist. It produces an HTML report with the findings, and from there you choose what to investigate further.

To go further you need the active scan, which ZAP calls ascan. It must be used with caution, and only on systems you have written authorisation for. The reason is technical, not ethical: active techniques can alter data, and on a real database a DROP TABLE is not a joke.

# Active scan mirata, solo a una parte del sito
zap-cli.py -t https://target.example -a ascan \
  -T 60 \
  --config=attack-params=id,name \
  -r active-report.html

--config=attack-params limits the attack to the parameters you chose: useful for not hammering the entire surface.

ZAP also has Active Rules, which you can write yourself. If you have SQL injection in your software and you want whoever develops it next to find it before you do, you can write a rule that sends ' OR 1=1-- to every numeric parameter and checks whether the response changes. It is a pragmatic way to make verification mandatory.

Defence: the real checklist

In order of effectiveness, from the top down:

Prepared statements, always. Not "when the input is dangerous": always, even for a string that cannot contain injection. The cost is zero and it removes the need to think.

Validation by type. An ID must be an integer, and absint() in WordPress or intval() in PHP guarantee that. An email field must match a pattern, not "not contain apostrophes".

Principle of least privilege. The database user must not be able to run DROP, and in many applications does not even need SELECT *. An injection that reads everything is bad enough; one that cannot write is far less dangerous.

Hide the errors. Showing the user "SQL syntax error on line 3" is handing the attacker a map. Errors go in the log, not on screen.

WAF as a safety net, not a solution. ModSecurity or Cloudflare's WAF block the cruder patterns. They do not stop an attacker who knows what they are doing, but they reduce the noise from a thousand to ten requests a day.

Ethics and hacker identity: the things no tutorial writes

Up to here it is technique. The rest is the part that decides whether a person becomes a professional or a problem for others.

The difference is not technical

There is no technical dividing line between "hacker" and "criminal". The very same tools — Burp, ZAP, sqlmap — serve whoever is looking for flaws to report and whoever looks for them to extort. The tools are the same. Intent, authorisation and transparency are not.

So the right question is not "is it legal?" but three, in this order:

  1. Do I have written authorisation? Not verbal, not "they told me it was fine", not "it is a friend's website". Written, with a defined scope: which URLs, which times, what I am allowed to do if I find something.
  2. Am I inside the perimeter? A test that goes outside the agreed terms is a violation even if the pentest is authorised, and in many contracts it is the easiest way to lose insurance cover.
  3. What do I do when I find something?

Point 3 is the one that defines identity

The difference between a security researcher and a criminal is not in the technique: it is in what happens after the discovery.

Someone who works seriously finds a SQL injection and writes a clear email: where, how to reproduce it, what the impact is, a proposed fix. Public or private, but in either case before using the data. Someone who does not do this is not being discreet: they are just deciding when it is convenient.

The unwritten rules of the industry, the ones that hold:

  • Do not take the data. A database with users' credentials is a liability, not a prize. If you read them to demonstrate the impact, do not pull them out to show off.
  • Do not use the access for persistence. An account created "for safety" is unauthorised access, full stop.
  • Do not publish the detail before it is fixed. A working exploit against a website that is still vulnerable is damage, not research.
  • Do not stop at the first CVE. Anyone can find an error with sqlmap. The value is in understanding why it is there, and in making yourself useless as a target.

Hacker, white hat, black hat: labels are not very useful

Labels are convenient but imprecise. The distinction that holds is simpler and more useful: a hacker is someone who understands how things work, and has not yet decided what to do with that. They become a white hat when they put their skills at the service of whoever needs them and answer for it. They become a black hat when they start using them for someone else.

And there is a fourth path, the one the industry honours most: bug bounty. Platforms like HackerOne and Bugcrowd pay for valid vulnerabilities, with precise rules, within a declared perimeter. It is not charity: it is a market, and a market is the most effective way we have found to put those who look for holes in competition with those who have the motivation to find them.

The rule that is not negotiable

None of this is worth anything without permission. It is not a moral, it is an operational observation: the difference between a tester and a criminal is a document, and that document is the only thing that protects you when things go wrong. The attacker needs no documents, and that is their competitive advantage. You can have the same skill and have nothing to lose: and that is the only ground worth playing on.

If you want to learn, do it on your own systems. A virtual machine, a test WordPress, a lab. It costs an afternoon and it leaves you the freedom to make mistakes, which is the only way you really learn.


TAG: #ai #sql #security