Open28

実践アプリケーションセキュリティテスト

Kyohei FukudaKyohei Fukuda

アプリケーションセキュリティテストを実施する方法をまとめていく

  • 動的アプリケーションセキュリティテスト(DAST)

    • OWASP ZAP
  • 静的アプリケーションセキュリティテスト(SAST)

    • SonarQube

    で行う

    目標はローカルにあるNextjsのアプリに対してSAST、DASTを実行し脆弱性を見つけること


作業環境

Kyohei FukudaKyohei Fukuda

OWASP ZAP はセットアップはすでにやっているので別途まとめるとして、
まずは まだ使ったことがないSonarQubeのセットアップ、初回実行を行う

Kyohei FukudaKyohei Fukuda

zip解凍して /Users/xxx/sonarqube-x.y.z/bin/macosx-universal-64 の sonar.sh を起動

sh sonar.sh start
Kyohei FukudaKyohei Fukuda

サーバーを止める時は

sh sonar.sh stop
sh sonar.sh restart

もあるのでなんかおかしくなったら再起動してみてください

Kyohei FukudaKyohei Fukuda

起動しない。Java 17入れる。

brew install openjdk@17

PATHとか設定

java -version
openjdk version "17.0.16" 2025-07-15

http://localhost:9000/ にアクセス。初期化中
なんか、Javaの17が必要らしい。他のバージョンだとうまく行かなかった。

初期化に失敗した。dataディレクトリを削除して stop, starで再実行

Kyohei FukudaKyohei Fukuda

user/pass は admin でログインする

Create a local project からプロジェクトを作成(今回の目的はローカルのNextjsにSASTをすること)



プロジェクトが作成できた

Kyohei FukudaKyohei Fukuda

設定ができたので実行していく

とりあえず手順に従って一旦実行する

Kyohei FukudaKyohei Fukuda

Securityの問題を発見

Kyohei FukudaKyohei Fukuda

ちなみにこれはGoogle fontのAPIKeyなのでまぁどうでもいいかーと管理を雑にしていました(言い訳)

Kyohei FukudaKyohei Fukuda

保守性・重複コードなども出してくれる。
SASTはCodeQLでもいいかなーと思っていたがこれはこれであり!

Kyohei FukudaKyohei Fukuda

SonarQubeのまとめ

  • 公式サイトからコミュニティ版をダウンロード
  • zip解凍して、いい感じのところに置く
  • binディレクトリ内のsonar.shをstart
    • 成功条件
      • java 17を入れておく
      • PCのディスク空き容量が15%以上あること
  • localhost:9000 にログイン(user: admin/pass: admin)
  • プロジェクトセットアップ
  • @sonar/scan をインストール (npm install -g @sonar/scan)
  • Next.js アプリのあるローカルでコマンド実行
    sonar \
      -Dsonar.host.url=http://localhost:9000 \
      -Dsonar.token=sqp_xxx \
      -Dsonar.projectKey=xxx
    
  • ダッシュボードで結果をみて修正していく

一旦とりあえず走らせることはできたのでここらで。
次はOWASP ZAPの起動方法をおさらいしていく。

Kyohei FukudaKyohei Fukuda

OWASP ZAPのセットアップを行なっていきます

ZAP をダウンロードする
https://www.zaproxy.org/download/

Macの場合はプライバシーなどを許可し、起動する。
Windowsなどの場合ではJREが必要なのでそれらの設定をする必要があるかも。

Kyohei FukudaKyohei Fukuda

立ち上げるとセッションの保存方法を聞かれるが一旦「タイムスタンプでファイル名をつけて保存」でいいでしょう

Kyohei FukudaKyohei Fukuda

次にプロキシーの設定を行います。これはブラウザの通信をZAPに通過させることでブラウザが行う通信をZAP側で見れるようにするために必要な設定となります。

設定が簡単なのでまず、FireFoxをダウンロードします。
https://www.firefox.com/en-US/

FireFoxがダウンロードできたらZAPに戻り、設定を行なっていきます。ZAPの設定ウィンドウを開いてください。

ローカルサーバー/プロキシ を選択し、下記の画像のようにアドレスは「localhost」, ポートは「58888」を設定します。(デフォルトポートは 8080 ですが、衝突を避けるために 58888 に設定します)

OKボタンを押して設定を反映します。

次にFireFoxに戻り、上記で設定したプロキシとアドレスとポートを合わせていきます。


OKボタンを押して設定を反映します。
これでZAPとブラウザの設定は完了しました。

Kyohei FukudaKyohei Fukuda

次に、実際に使ってみる。

ZAPはブラックボックステストの動的アプリケーションセキュリティテスト(DAST)というアプローチで脆弱性を発見する。つまりアプリケーションの内情は知らない。

そのため、おおまかに説明すると下記の3ステップで使用する。

  1. 動いているアプリに関する情報を収集する
  2. 動いているアプリに上記の情報を元に攻撃を行う
  3. レポートを確認し、それを元に修正を行う。
Kyohei FukudaKyohei Fukuda

ここから動いているアプリの情報収取を行う。
まず、プロテクトモードを選択し、診断の範囲をコンテキストに絞る。この設定は後ほどやっていくが今のところは一旦これでOK

今回はマニュアルで探索を行うので「Manual Explore」をクリック
*「Manual Explore」を行う理由はZAPの使い方を学びたいというのが一点。
さらに認証フローや、自動スキャンでは見逃されるロジックの欠陥発見を期待しているからです。
スパイダーを利用した自動クローリングの機能を使うこともできますが、自分が何をやっているか理解を深めるために最初は「Manual Explore」を使うのがいいでしょう。

次に、私の場合はローカルのNext.jsをビルドし、起動しておく。

その後 localhost:3000で下記の手順でローカルプロキシ設定済みのFireFoxを起動する

起動が完了したらブラウジングする

ブラウジングをしていくとプロキシを通過したリクエストがヒストリーに現れ、サイドバーの「Sites」にサイトの構造が構築されていきます。

Kyohei FukudaKyohei Fukuda

ここで実際にアプリケーションに対して様々な操作を行うことでそのリクエストをベースに次のステップで攻撃を行なっていきます。

例えば、お問い合わせフォームがある場合、お問い合わせフォームを動かしてみてください。
攻撃の際にはそのリクエストを改竄しXSSやSQLインジェクションの問題がないかを試すことができます。

Kyohei FukudaKyohei Fukuda

次は攻撃の前に二つの設定を行なっていきます。

  1. コンテキストの設定
  2. ログインユーザーの設定

です。一つ一つ見ていきましょう。

Kyohei FukudaKyohei Fukuda

まず、コンテキストの設定です。下記のように今回攻撃する対象をコンテキストに含めてください。


これでプロテクトモードで攻撃を実行した場合、対象をlocalhost:3000 に絞ることができます。

Kyohei FukudaKyohei Fukuda

次に ログインユーザーの設定 です。

アプリケーションに対して意味のある脆弱性診断を行うためにはログイン後のページにアクセスできる必要があります。そこで必要なのでログインユーザーの設定です。

まず、起動したプロキシ設定済みのFireFoxでログインを行います。

すると、ログインの際に飛んだリクエストがサイトのリクエストとして表示されているはずです。
そのリクエストに対して右クリック→「Flag as Context」→「Form-based Auth Login Request」をクリックしてください。

Authentication の設定を行なっていきます。一般的なログインフォームの場合はここでリクエストボディの中の「ユーザー名」と「パスワード」のキーを設定します。
イメージ:

その後、ログイン成功を検出する正規表現パターンを設定します。この値はレスポンスボディ、ヘッダーの値から流用するといいでしょう。一般的なパターンとして「ログアウト」がレスポンスボディに含まれていればログイン済みにするなどです。

一般的な「ログインユーザーの設定」はこのように行います。

しかし、私のアプリケーションではNext.jsのServer actionsを使ってログインしているので少し設定が必要です。
次はNext.jsのServer actionsを使ってログインしている場合の設定方法を見ていきます。(不要な方はスキップしてOKです)

Kyohei FukudaKyohei Fukuda

* これはNext.jsのServer actionsを使ったログインじゃない場合はスキップ可能です

Next.jsのServer actionsを使ったログインの場合はヘッダーに「next-action」が必要なのでスクリプトでそれを実現してきます。
下記のようにスクリプトタブを開いてください。

スクリプトを新規作成します。

私のアプリの場合のスクリプトなのであくまでも参考ですが、下記のようにしてリクエストヘッダーを少しカスタマイズします。

// multipart/form-data認証スクリプト  
var HttpRequestHeader = Java.type("org.parosproxy.paros.network.HttpRequestHeader");  
var HttpHeader = Java.type("org.parosproxy.paros.network.HttpHeader");  
var URI = Java.type("org.apache.commons.httpclient.URI");  
var AuthenticationHelper = Java.type(
  "org.zaproxy.zap.authentication.AuthenticationHelper"
);

function authenticate(helper, paramsValues, credentials) {
    var loginUri = new URI(paramsValues.get("loginUrl"), false);
    var nextAction = paramsValues.get("nextAction");

    // multipart/form-dataのboundary文字列を生成
    var boundary = "----WebKitFormBoundary" + Math.random().toString(36).substring(2, 15);

    // multipart/form-dataのボディを構築
    var requestBody = "";

    // --------------------ここからアプリによって必要な項目が違うので注意--------------------------
    // 1. emailフィールド
    requestBody += "--" + boundary + "\r\n";
    requestBody += 'Content-Disposition: form-data; name="1_email"\r\n\r\n';
    requestBody += credentials.getParam("username") + "\r\n";

    // 2. passwordフィールド
    requestBody += "--" + boundary + "\r\n";
    requestBody += 'Content-Disposition: form-data; name="1_password"\r\n\r\n';
    requestBody += credentials.getParam("password") + "\r\n";

    // 3. localeフィールド (固定値)
    requestBody += "--" + boundary + "\r\n";
    requestBody += 'Content-Disposition: form-data; name="1_locale"\r\n\r\n';
    requestBody += "en\r\n";

    // 4. "0"フィールド (固定値)
    requestBody += "--" + boundary + "\r\n";
    requestBody += 'Content-Disposition: form-data; name="0"\r\n\r\n';
    requestBody += '["$K1"]\r\n';
// --------------------ここまで--------------------------

    requestBody += "--" + boundary + "--\r\n";

    // POSTリクエストを作成
    var post = helper.prepareMessage();
    post.setRequestHeader(
        new HttpRequestHeader(HttpRequestHeader.POST, loginUri, HttpHeader.HTTP10)
    );

    // multipart/form-dataのContent-Typeヘッダーを設定
    post.getRequestHeader().setHeader(
        HttpHeader.CONTENT_TYPE,
        "multipart/form-data; boundary=" + boundary
    );
    post.getRequestHeader().setHeader("next-action", nextAction);


    post.setRequestBody(requestBody);
    post.getRequestHeader().setContentLength(post.getRequestBody().length());

    helper.sendAndReceive(post, false);

    AuthenticationHelper.addAuthMessageToHistory(post);    
    return post;  
}

function getRequiredParamsNames() {  
    return ["loginUrl", "nextAction"];  
}  
  
function getOptionalParamsNames() {  
    return [];  
}  
  
function getCredentialsParamsNames() {  
    return ["username", "password"];  
}

次にこのスクリプトを使うように設定してNext.jsのServer action用の対応は終了です。
Script-based Authentication を選択し、先ほど作ったスクリプト(next-action-login)を読み込みます

loginUrl, nextActionに使う値をログインリクエストから取得します。(ちなみにNext-Actionの値はビルドごとに更新される点に注意)

Kyohei FukudaKyohei Fukuda

ログインユーザーの設定はまだ続きます。あと少しです。
次にコンテキストをダブルクリックしてコンテキストにログインユーザーを追加します

また、強制ログインユーザーが作成したユーザーであることを確認し、OKボタンを押し、設定を反映させます。

ユーザーを登録したら強制ログインモードにしてください。

ユーザーがちゃんとログインできるかテストする方法は再度「Manual Explore」に戻って、ログインしないとアクセスできないページからブラウザを立ち上げることです。

ログイン後のページが一発で開けたらOKです。

さらにログイン後のページでいろんな操作をして攻撃に必要な情報収集を進めていきましょう。

Kyohei FukudaKyohei Fukuda

おまちかね。ついに準備が完了したのでスキャンを実行していきます。

これまで「Manual Explore」で集めたサイトの構成・リクエストに対して攻撃を行なっていきます。
この攻撃を実行するとDBにはゴミデータが発生するので注意してください。

スキャンが始まります。

スキャンが終了するとアラートから発見された脆弱性を確認することができます。

また、レポートを生成することも可能です。このレポートを元に対応を行なってこれまでのスキャン->修正のフローを繰り返し脆弱性を潰していきます。

Kyohei FukudaKyohei Fukuda

Tips


コンテキストをダブルクリックし、セッションプロパティの「テクノロジー」でチェックする必要のないもののチェックを外すことで効率的にスキャンを行うことができます。


AJAXスパイダーを送り込むことで効率的にクローリングを行うことができます。ただ、マニュアルに比べると精度は低いので注意

Kyohei FukudaKyohei Fukuda

一旦ここらでおしまい。動画で説明した方が理解しやすいので別途動画を撮影します。

Youtubeにアップロードできたらここにも載せます!