2026年1月28日水曜日

【Azure】WAFとの死闘168時間。ASP.NET CoreアプリをAzure WAFで守ろうとして学んだこと

 



1. イントロダクション

Azureを勉強して、ASP.NET Coreでポートフォリオ兼用のWebアプリを公開してみた。

「せっかくなら実戦的に」とAzure Web Application Firewall (WAF) を導入したが、ここからが本当の戦いの始まりだった……。

2. 最初の壁:ログインできない!

公開直後、ログインしようとすると謎の403エラー。ログを確認すると……

Restricted SQL Character Anomaly Detection (args): # of special characters exceeded (12)

何が起きていたのか?

ASP.NETの認証用Cookieに含まれる記号の多さが、「攻撃用コードを隠している」と判定されていた。

• 原因: WAFの「異常検知ルール(942450など)」。

• 気づき: セキュリティを強くしすぎると、標準的なフレームワークの挙動すら「悪」とみなされる。

3. 第二の壁:いたちごっこの泥沼

特定のCookieを除外設定(Exclusion)して「勝った!」と思ったのも束の間。今度は別のログが。

• SQL Injection Attack: SQL Tautology Detected(1=1攻撃の誤認)

• Multiple URL Encoding Detected(二重エンコードの誤認)

しかも、よく見るとCookieだけでなく、URLの引数(ARGS)でも発生している。

ここで私は、「1つ1つ除外設定を書いていたら、いつまでもアプリがまともに動かない」という現実に直面した。

4. 決戦:ログから読み解く「真犯人」

WAFのログに記録された detail_message_s をデコードして、じっくり眺めてみた。

そこには、攻撃コードではなく、ただの「日付データ」や「パスワードの記号」があった。

今回の学び(技術的な急所)

1. 多重エンコードの正体: URLの中にURLを入れる(ReturnUrl)際、記号が %252F のように多重変換される。これはモダンなWebフレームワークの「標準仕様」であって攻撃ではない。

2. パスワードの宿命: 強力なパスワードには記号が必須。これをWAFのSQLIルールで検査すること自体が、実はアンチパターン。

5. 解決策:多層防御の再設計

「WAFの設定をいじり続ける」のをやめ、「どこをWAFに任せ、どこをアプリに任せるか」の役割分担を見直した。

• WAF側: パスワードやReturnUrlなど、正規の処理で記号が入り混じる箇所は、勇気を持って「検査除外」または「特定ルールの無効化」を行う。

• アプリ側: ASP.NET Core Identityを正しく使い、「パラメータ化クエリ(静的プレースホルダ)」と「アカウントロック機能」を有効にする。

6. おわりに:本当のセキュリティとは

WAFは「魔法の杖」ではなかった。

ただボタンをポチポチして「最強」に設定するのではなく、「自分のアプリがどういうURLを吐き出し、どういうCookieを使うのか」を理解して、初めて正しく運用できる。

「いたちごっこ」を終わらせたのは、WAFの知識ではなく、自作したASP.NET Coreのソースコードの中にあった一行のエビデンスだった。


2026年1月25日日曜日

【Azure】 ASP.NETアプリを公開したら、正常な通信がブロックされまくった話(解決編)




Azureを勉強して、ASP.NETでWebアプリを作ってみました。
Application Gateway(WAF)も立てて、セキュリティは万全!……のはずが、いざ公開してみると、自分や友人のログインが突然ブロックされるという謎の現象に遭遇しました。
今回は、初心者がハマりがちな「WAF運用いたちごっこ」の正体と、スマートな解決策を共有します。

1. 犯人は「ランダムすぎるCookie」

ASP.NET(特にCore)には、セキュリティを守るための仕組みが標準で備わっています。
その代表格が、以下のようなCookieやトークンです。
• .AspNetCore.Mvc.CookieTempDataProvider
• .AspNetCore.Antiforgery
これらの中身は、セキュリティのために「推測不可能なランダム文字列」になっています。
ところが、このランダムな文字列の中に、たまたまSQLインジェクション攻撃に似たパターン(-- など)が紛れ込んでしまうことがあるのです。

2. 初心者が陥る「ID除外の罠」

WAFのログ(Log Analytics)を見ると、「ルールID: 942440 でブロック」と出ます。
そこで「よし、この 942440 を許可リストに入れよう!」と設定するのが、最初の落とし穴です。
実は、Cookieの中身は毎回変わるため、「昨日は 942440 だったけど、今日は 942441 でブロックされる」という現象が起きます。これが「いたちごっこ」の始まりです。

3. 正解は「変数のグローバル除外」

特定のルール番号を許可するのではなく、「この名前のCookieについては、WAFの全検査をスキップしていいよ」という設定をするのが正解です。
設定のコツ(Azureポータル)
WAFポリシーの「管理ルール」→「除外」設定で、以下のように登録します。
• 一致変数: 要求のCookieの名前
• セレクター: .AspNetCore.Mvc.CookieTempDataProvider
• ルールセット: ここを特定のIDにせず、全体(Global)にするのがポイント!
これで、どんなランダムな値が入ってきても、WAFが「あ、このCookieは安全な枠組みのやつだね」とスルーしてくれるようになります。

4. もっと安心するために:Azure Advisorを活用しよう

「除外設定を増やすとセキュリティが弱くならない?」と不安になるかもしれません。
そんな時は、Azure Advisor や Microsoft Defender for Cloud の「セキュリティ態勢」をチェックしましょう。
「基盤となる CSPM(無料版)」を有効にするだけで、Azureが「他にも設定ミス(暗号化漏れなど)はない?」と自動で診断してくれます。今回のWAF設定だけでなく、サイト全体の健康診断をセットで行うのが、モダンなクラウド運用のコツです。

最後に

WAFは「ただ置くだけ」では、時に味方を攻撃してしまうことがあります。
「いたちごっこ」に疲れたら、ぜひ除外設定の「範囲(スコープ)」を見直してみてください。
「ログを見て、原因を突き止め、ベストプラクティスを適用する」。この流れを一度経験すると、クラウドのセキュリティがぐっと身近に感じられるようになりますよ!  

【おまけ】WAFのブロック理由を特定するKQLクエリ

Azure WAFが「何を」「どのルールで」ブロックしたのかを調べるためのクエリです。
Azure Portalの Log Analytics で実行してください。

1. 直近のブロック履歴を一覧表示する

まずは「何が起きているか」の全体像を把握するクエリです。

// WAFのブロックログを新しい順に100件表示
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.NETWORK" 
| where Category == "ApplicationGatewayFirewallLog"
| where action_s == "Blocked"
| project TimeGenerated, clientIp_s, requestUri_s, ruleId_s, Message, details_data_s
| order by TimeGenerated desc
| take 100


2. 特定のCookieが原因でブロックされているか特定する

今回の「犯人(Cookie)」を追い詰めるためのクエリです。

// 特定のCookie名が含まれるブロックログを抽出
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.NETWORK" 
| where Category == "ApplicationGatewayFirewallLog"
| where action_s == "Blocked"
// ↓ここに調べたいCookie名(.AspNetCore.Mvc.CookieTempDataProviderなど)を入れます
| where details_data_s contains "CookieTempDataProvider" 
| project TimeGenerated, clientIp_s, ruleId_s, details_data_s, details_message_s
| order by TimeGenerated desc

3. どのルールIDによく引っかかっているか集計する

「いたちごっこ」が起きていることを証明するためのクエリです。

// ブロックされたルールIDのランキングを表示
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.NETWORK" 
| where Category == "ApplicationGatewayFirewallLog"
| where action_s == "Blocked"
| summarize Count = count() by ruleId_s, Message
| order by Count desc


ワンポイントチェック
ruleId_s: どのルールに触れたかがわかります。

details_data_s: Cookieやリクエストの中身の「どの文字列」がダメだったのか、具体的な中身が表示されます。ここを見て Password=... ではなく、ランダムな英数字の羅列であれば、誤検知の可能性大です!





2026年1月2日金曜日

【解決】Azure VMで「The target machine has denied access」エラー!原因はパスワード間違いによるロックアウト?(0xC0000234)

 


Azureの仮想マシン(VM)に突然RDP接続できなくなったことはありませんか?

「The target machine has denied access to this connection」というエラーが表示された場合、原因はネットワーク設定ではなく、パスワードの入力ミスによる「アカウントロックアウト」かもしれません。

本記事では、シリアルコンソールを使った原因特定方法から、具体的な復旧手順までを解説します。

1. 発生した事象とエラーメッセージ

まずは、どのような状況で接続できなくなったのかを記載します。

• 現象: RDP(リモートデスクトップ)接続時に拒否される。

• エラー文: The target machine has denied access to this connection.

• 心当たり: パスワードを数回間違えて入力した。

2. 原因の特定:シリアルコンソールでログを確認する

「本当にパスワード間違いが原因か?」を外側から特定する方法を紹介します。

手順:

Azureポータルから対象VMの「シリアルコンソール」を開く。

cmd → ch -si 1 でコマンドプロンプトを起動。

以下のコマンドでイベントログを確認。

wevtutil qe Security /q:"*[System[(EventID=4625)]]" /c:5 /f:text /rd:true


特定ポイント:

ログの中に以下の情報があれば、ロックアウトが確定です。

• 失敗の原因: アカウントのロックアウト

• 状態コード: 0xC0000234

3. 解決策:アカウントロックを解除する方法

ロックアウトされた場合の対処法は3つあります。

① 自動解除を待つ(30分〜1時間)

多くの設定では、一定時間経過するとロックが自動解除されます。急ぎでない場合は、「一切の操作をせず待つ」のが最も安全です。

② Azureポータルからパスワードをリセット(即時)

急ぎの場合は、Azureポータルの「パスワードのリセット」機能を使います。

Tips: 既存のパスワード(チームで共有しているもの等)を変えたくない場合は、一度「別パスワード」に変更してログインした後、OS内部の設定から元のパスワードに戻すのがコツです。


③ ネットワーク設定(NSG)の確認

もし上記でもダメな場合は、念のためNSG(ファイアウォール)でRDPポート(3389)が開放されているか、自分のIPアドレスが許可されているかを確認しましょう。

まとめ:予防策

• パスワード管理ツールを使用し、打ち間違いを防ぐ。

• 踏み台サーバー(Bastion)の利用を検討する。


2025年12月31日水曜日

Azure Pipelinesで.NET Coreビルドが失敗する原因と解決策|デフォルトYAMLの修正ポイント

 



以前書こうと思っていたのですが、バタバタしていてまとめる機会がなかなか無く、やっと時間が取れたので、昔を思い出しながら書いてみます。


1. はじめに:Azure Pipelinesは「おまかせ」で動かない?

Azure DevOpsを使い始めたばかりの頃、私は感動しました。

「.NET Core」というテンプレートを選ぶだけで、勝手にYAMLファイルが生成され、あとは実行ボタンを押すだけ。

「これでCI/CD対応完了だ!」と意気揚々と実行した結果、画面に表示されたのは無情な赤いバツ印(Failed)でした。

なぜ、Microsoft純正のテンプレートなのにそのままでは動かないのか?

今回は、初心者が必ずハマる「デフォルトYAMLの落とし穴」と、その修正ポイントをまとめます。

2. 【罠1】SDKのバージョンが合っていない

デフォルトのYAMLには、多くの場合「どのバージョンの.NET SDKを使うか」という指定が抜けています。その結果、Agent(ビルド用マシン)にインストールされている最新バージョンが使われ、古いプロジェクトや新しすぎるプロジェクトがビルドエラーになります。

解決策:UseDotNet@2 タスクを最初に追加する

ビルド(Build)タスクの前に、明示的にバージョンを指定するタスクを差し込みます。


steps:

- task: UseDotNet@2

  displayName: 'Install .NET SDK 8.x'

  inputs:

    packageType: 'sdk'

    version: '8.x' # 自分のプロジェクトのバージョンに合わせる



3. 【罠2】プロジェクトファイルが見つからない

デフォルトでは、リポジトリのルート(一番上の階層)に .csproj や .sln があることを想定しています。しかし、実際の開発では src/ フォルダの中にコードをまとめていることが多いはずです。

解決策:projects プロパティを書き換える

テンプレートのままだと、以下のようなエラーで止まります。

##[error]Project file(s) matching the specified pattern were not found.

これを防ぐために、ワイルドカードを使ってプロジェクトファイルを指定します。

修正前(デフォルト):

- task: DotNetCoreCLI@2

  inputs:

    command: 'build'

    arguments: '--configuration $(buildConfiguration)'


修正後:

- task: DotNetCoreCLI@2

  inputs:

    command: 'build'

    # リポジトリ全体から.csprojを探すように指定

    projects: '**/*.csproj' 

    arguments: '--configuration $(buildConfiguration)'



4. 【罠3】NuGetリストアの失敗

「ビルドタスクの中で一緒にリストア(ライブラリの復元)もやってくれるだろう」と思いがちですが、ここでも認証エラーやパスの問題が起きることがあります。

解決策:Restoreタスクを分離して明示する

ビルドとは別に restore コマンドを明示的に実行するのが、安定させるコツです。

- task: DotNetCoreCLI@2

  displayName: 'Restore NuGet Packages'

  inputs:

    command: 'restore'

    projects: '**/*.csproj'


5. まとめ:デフォルトは「完成品」ではなく「下書き」

Azure Pipelinesが用意してくれるデフォルトのスクリプトは、あくまで「最低限の構成案」です。

1. SDKバージョンを指定する

2. プロジェクトの階層(パス)を正しく伝える

3. リストア、ビルド、テストを順序よく構成する

この3点を意識するだけで、あの赤いエラー画面から卒業できるはずです。






2025年12月29日月曜日

【Azure】App ServiceのIP制限でハマった話。「確認くん」と違うIPが届く原因と調査法




Azure App Serviceで特定の拠点のみアクセスを許可しようと「アクセス制限」を設定したのに、なぜか自分すら弾かれる……。

「ログストリームを見れば原因がわかるはず」と思いきや、実はここには大きな罠があります。
今回は、App Service(特にLinuxベース)を使い始めたばかりの人が必ずハマる「IP制限の正体」と、正しいトラブルシューティング術について実体験を元に解説します。

1. 罠:403エラーのログは「ログストリーム」には出ない

ApacheやNginxの感覚でいると、「アクセス拒否(403エラー)されたならログが出るはず」と考えがちです。しかし、App Serviceのログストリームをどれだけ眺めても、IP制限で拒否されたログは1行も流れてきません。
なぜ出ないのか?
App ServiceのIP制限は、アプリに届く前の「Azureのプラットフォーム階層」で遮断されるからです。
• アプリ側のログ: アプリまで到達したリクエストの記録。
• IP制限: アプリに届く前に門前払いするため、アプリ側は「アクセスがあったこと」すら知りません。

2. 解決策A:ログが見れないなら「外側」から叩く

App Service側のログが見れない状況で、私が「真の接続元IP」を特定した方法が curl や tcpping です。
「確認くん」などのWebサービスで見えるIPは、ブラウザ(プロキシ経由)のIPに過ぎません。実際の通信がどう見えているかを直接叩いて確認します。
• curlで叩く: curl -v https://your-app.azurewebsites.net
• tcpping / pspingで叩く: ポート443に対して直接疎通を確認。
私のケースでは、これらを試す中で「確認くん」とは別のIPがゲートウェイとして使われていることが判明し、そのIPを許可することで解決しました。

3. 解決策B:どうしてもログを見たいなら「診断設定」

「どうしてもAzure側で拒否されたIPの履歴を特定したい」という場合は、診断設定(AzureAppServiceHTTPLogs)を有効にする必要があります。
ただし、ここで注意したいのがコストです。
インフラチームから「Log Analyticsは高額になりがち」と釘を刺されることがありますが、これは大量のログを溜め続けると課金が膨らむためです。
• 賢い使い方: 調査時だけ有効にし、接続元IPが特定できたらすぐにオフにする。
これでコストを最小限に抑えつつ、確実に「Azureが拒否したIP」をあぶり出せます。

4. まとめ:クラウドを使いこなす「引き出し」を持とう

今回の教訓は以下の3点です。
1. IP制限のログは、アプリのログストリームには出ない。
2. Web用のIPと通信用のIPが違う可能性を疑い、curl等で直接叩く。
3. 診断設定は強力だが、コストを意識して「使い終わったら消す」のがプロの作法。
「設定したのに動かない、ログも出ない」と焦る前に、この構造を知っていれば数分で解決できます。プログラムのコードだけでなく、こうした「クラウドの仕様」を知ることこそが、開発をスムーズに進める鍵となります。