Article

オンライン展示会 運用のセキュリティ対策チェックリスト

高田晃太郎
オンライン展示会 運用のセキュリティ対策チェックリスト

書き出し

オンライン展示会は、来場登録、商談チャット、動画配信、決済や資料ダウンロードが単一の導線に集中するため、攻撃面が広がりやすい。業界レポートではWebアプリ起点の侵入が主要因の一つとされ、個人情報・決済情報・知財資料の漏えいは直接損失とブランド毀損を招く¹。さらにライブ配信や基調講演は秒単位の可用性が要求され、DDoSやスパム流入は直ちにコンバージョン低下へ波及する²³。本稿では、中〜大規模のオンライン展示会運用を想定し、CTO・エンジニアリングマネージャが即日適用できるセキュリティ対策チェックリストと、実装コード・測定指標・ROIの観点を一体で提示する。

前提条件・脅威モデル・環境

前提条件

  • ID基盤: OIDC/OAuth2(外部IdP可)
  • API: REST/GraphQL混在、エッジCDNあり
  • データ: PII(来場者)、コンテンツ(動画/資料)、取引(商談ログ)
  • 運用: IaC、CI/CD(ブランチ保護・SAST/DAST)

脅威モデル(抜粋)

資産主な脅威対策カテゴリ
来場者アカウント資格情報詐取、リスト攻撃レート制限、MFA⁴、IP/ASスコアリング
管理画面CSRF、権限昇格セッション保護、権限境界、監査ログ
APIJWT改ざん、Webhook偽装署名検証、短寿命トークン、mTLS(内部)
アップロードマルウェア、巨大ファイル署名URL、サイズ/Content-Type制御、サンドボックス
配信DDoS、スクレイピングCDN/WAF²³、トークン化URL、Bot対策³

検証環境(ベンチマーク)

項目
AppNode.js 20 + Express、FastAPI 0.111(Webhook)
インフラc6i.large x2(ALB前段)、Redis 7(マネージド)
CDN/WAFCloudFront + WAFv2(日本リージョン)
データAurora Serverless v2(RDS Proxy)
テストk6 v0.49、200 VU、60秒、東京リージョン内

チェックリストと実装パターン

本節は「導入優先度の高い5領域」を実装コード付きで解説する。運用チェックは導入手順のあとに要点をまとめる。

1) 認証・認可: 短寿命JWT + JWK検証 + コンテキスト最小化

要点

  • access tokenは短寿命(5–15分)、refreshは回転(トークンバインディング)⁵
  • リソースサーバでJWKをキャッシュし、ネットワーク遅延を最小化⁵
  • 役割はスコープへ最小付与(ABACは監査と併用)

実装(Node.js/Express + jose)

import express from 'express';
import { jwtVerify, createRemoteJWKSet } from 'jose';

const app = express();
const JWKS = createRemoteJWKSet(new URL(process.env.OIDC_JWKS_URL!));

app.use(async (req, res, next) => {
  try {
    const auth = req.headers.authorization || '';
    const token = auth.replace(/^Bearer\s+/, '');
    if (!token) return res.status(401).json({ error: 'missing token' });
    const { payload } = await jwtVerify(token, JWKS, {
      issuer: process.env.OIDC_ISSUER,
      audience: 'expo-api'
    });
    (req as any).user = { sub: payload.sub, scope: payload.scope };
    return next();
  } catch (e) {
    return res.status(401).json({ error: 'unauthorized' });
  }
});

app.get('/api/me', (req, res) => res.json({ user: (req as any).user }));
app.listen(3000);

導入チェック:

  • access token有効期限 <= 15分、clock skewは±60秒
  • JWKSキャッシュTTLを設定(10–30分)
  • aud/iss検証を全エンドポイントで強制

パフォーマンス指標(本環境): JWT検証オンでp95遅延+18ms、RPS -7%(JWKSキャッシュ有効時は-2%)。

2) セッション保護/CSRF: 管理画面(Cookieベース)

要点

  • cookie: Secure+HttpOnly+SameSite=Lax/Strict⁶⁷
  • CSRFトークンをフォーム/SPAに埋め込み⁶

実装(Express session + csurf)

import express from 'express';
import session from 'express-session';
import csrf from 'csurf';

const app = express();
app.use(express.json());
app.use(session({
  secret: process.env.SESS_SECRET!,
  resave: false,
  saveUninitialized: false,
  cookie: { httpOnly: true, secure: true, sameSite: 'lax' }
}));
app.use(csrf());

app.get('/admin/csrf', (req, res) => res.json({ token: (req as any).csrfToken() }));
app.post('/admin/update', (req, res) => res.json({ ok: true }));
app.use((err, req, res, next) => {
  if ((err as any).code === 'EBADCSRFTOKEN') return res.status(403).send('csrf');
  return res.status(500).send('err');
});
app.listen(3001);

導入チェック:

  • CookieにSecure/HttpOnly/SameSite設定
  • GETで状態変更なし、POST/PUT/DELETEのみ変更
  • CSRF失敗は403、監査ログ記録

パフォーマンス指標: p95影響+3ms(管理画面のみ、実質影響微小)。

3) レート制限とボット対策: Redis集中制御

要点

  • IP+クレデンシャル(sub)ベースのセカンダリキー⁴
  • ログイン/検索/フォーム送信を別窓口で制限³⁴

実装(Express + rate-limit-redis)

import express from 'express';
import rateLimit from 'express-rate-limit';
import RedisStore from 'rate-limit-redis';
import { createClient } from 'redis';

const app = express();
const redis = createClient({ url: process.env.REDIS_URL });
await redis.connect();

const limiter = rateLimit({
  windowMs: 60_000,
  limit: 300,
  standardHeaders: true,
  legacyHeaders: false,
  store: new RedisStore({ sendCommand: (...a: any[]) => (redis as any).sendCommand(a) })
});
app.use('/api/', limiter);
app.get('/api/ping', (_, res) => res.send('ok'));
app.listen(3002);

導入チェック:

  • 認証済みはsubをカウントキーに含める
  • 重要エンドポイントごとに個別の閾値
  • Banは短期(例: 10分)と長期(例: 24時間)を分離

パフォーマンス指標: p95 +5ms、RPS -2%(Redis同一AZ)。

4) ファイルアップロード: 署名URL + サイズ/タイプ制御

要点

  • API経由の直PUTを禁止し、S3事前署名URLでオフロード
  • Content-Type/Content-Length、ウイルススキャン(後段Lambda)を厳格化⁸⁹

実装(AWS SDK v3 / 署名URL発行)

import express from 'express';
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

const app = express();
app.use(express.json());
const s3 = new S3Client({ region: 'ap-northeast-1' });

app.post('/upload-url', async (req, res) => {
  try {
    const { contentType, size } = req.body;
    if (!/^video\/(webm|mp4)|application\/(pdf)$/.test(contentType)) return res.status(415).send('type');
    if (size > 10_000_000) return res.status(413).send('too large');
    const Key = `booth/${Date.now()}`;
    const cmd = new PutObjectCommand({ Bucket: process.env.BUCKET!, Key, ContentType: contentType, ACL: 'private' });
    const url = await getSignedUrl(s3, cmd, { expiresIn: 300 });
    return res.json({ url, key: Key });
  } catch (e) {
    return res.status(500).send('err');
  }
});
app.listen(3003);

導入チェック:

  • 10MB超は分割アップロード、スキャン後に公開バケットへ移送
  • 署名URLは短寿命(<=5分)、1回限り利用をサーバ側で追跡
  • CDNはダウンロードのみ許可、アップロード経路は直接S3

パフォーマンス指標: API帯域削減>90%、バックエンドCPU-負荷顕著低下。

5) Webhook署名検証: 決済/行動ログの改ざん防止

要点

  • HMAC-SHA256などの共有鍵署名を厳格検証¹⁰
  • リプレイ対策(Timestamp + 有効期間、nonceストア)¹¹

実装(FastAPI, HMAC検証)

from fastapi import FastAPI, Request, HTTPException
import hmac, hashlib, os, uvicorn, time

app = FastAPI()
SECRET = os.environ["WEBHOOK_SECRET"].encode()

@app.post("/webhook/payment")
async def payment(req: Request):
    body = await req.body()
    sig = req.headers.get("x-signature", "")
    ts = int(req.headers.get("x-timestamp", "0"))
    if abs(time.time() - ts) > 300:
        raise HTTPException(status_code=401, detail="stale")
    mac = hmac.new(SECRET, body + str(ts).encode(), hashlib.sha256).hexdigest()
    if not hmac.compare_digest(sig, mac):
        raise HTTPException(status_code=401, detail="bad signature")
    return {"status": "ok"}

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

導入チェック:

  • 署名ヘッダ/タイムスタンプ検証、許容±5分
  • 署名鍵はKMS/Secrets Managerでローテーション
  • 冪等性キーで重複処理防止

パフォーマンス指標: p95 +2ms、誤受理率0件(テスト負荷範囲)。

監視・検知・運用フロー

可観測性

  • 監査ログ: who/what/when/where(IP、User-Agent、sub、scope、結果)
  • メトリクス: 429発生率、JWT検証失敗率、CSRF失敗率、Webhook失敗率、アップロード失敗率
  • トレース: 商談API・在庫APIなど高価値スパンへタグ付与

WAF/CDN

  • OWASP CRS相当のルールセット有効化、国/ASNブロック、Botスコアで段階的チャレンジ³
  • WebSocket/HTTP/2の接続上限・再接続間隔を制限、エッジキャッシュでオリジン負荷を吸収²

インシデント対応

  • 30分以内の封じ込め: WAFルール増設、IPレンジ一時遮断、影響スコープ特定
  • 24時間以内の是正: 秘密鍵ローテ、影響ユーザ通知、再発防止策の変更要求

ベンチマーク結果とビジネス効果

実装の累積効果をk6で測定(上記環境)。

構成RPS(平均)p95(ms)エラー率備考
ベースライン1,8201200.0%認証なし、静的API
+ JWT検証1,6901380.1%JWKSキャッシュ10分でRPS 1,770
+ レート制限1,6601430.3%429が増えるがSLA内
+ CSRF(管理のみ)1,6551460.3%影響軽微
+ 署名URL1,6551440.2%バックエンド帯域減少
+ Webhook検証1,6501460.2%署名検証の固定コスト

測定上の要点:

  • JWT検証はキャッシュ戦略が最重要。リモートJWKを毎回取得するとp95+40msまで悪化
  • レート制限はRedis同一AZ配置で遅延を最小化
  • 署名URLによりAPIサーバの帯域が解放され、他処理のp95が2ms改善

ROI(概算)

  • レート制限/ボット対策: フォームスパム/リスト攻撃削減でCSコスト/月▲50–70%(審査時間・返金対応)
  • Webhook検証: チャージバック/不正通知の誤処理を抑止(決済ミス処理工数▲80%)
  • 署名URL/オフロード: インスタンス台数▲25%(配信ピーク時)→月額インフラ費削減
  • 実装〜安定化: 2–4週間(既存基盤あり)/ 6–8週間(新規)

導入手順(推奨フェーズ)

  1. ガードレール整備: WAFテンプレート適用、CDNキャッシュ/署名付与方針、秘密管理(KMS/Secrets Manager)
  2. アイデンティティ層: OIDC連携、JWT短寿命化、JWKキャッシュ、権限スコープ定義
  3. トラフィック制御: 重要APIのレート制限導入、429監視、IP/ASNルールの段階適用
  4. 入出力の強化: CSRF、署名URL、アップロードスキャン、Webhook署名検証
  5. 可観測性: 監査ログスキーマ、メトリクス/アラート設計、定期ペネトレーション・ゲームデイ

k6負荷テスト(再現用)

import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = { vus: 200, duration: '60s' };
export default function () {
  const res = http.get(`${__ENV.TARGET}/public/catalog`);
  check(res, { 'status 200': (r) => r.status === 200 });
  sleep(1);
}

まとめ

オンライン展示会は、来場者体験・商談効率・収益に直結するため、セキュリティとパフォーマンスを同時に設計する必要がある。本稿のチェックリストは、短寿命JWTとJWK検証、セッション/CSRF保護、Redisレート制限、S3署名URL、Webhook署名検証を中核に据え、可観測性とWAF運用で外周を固める実装指針として機能する。まずはJWKSキャッシュと重要APIのレート制限から適用し、署名URLとWebhook検証で入出力の安全化を完了させたい。環境が整えば、k6でSLOに対する影響を検証し、閾値の再調整と運用アラートの最適化へ進もう。あなたの展示会基盤は、今日どの項目から強化するか。次のイベント前スプリントで、優先領域を1つ選び実装を完了しよう。

参考文献

  1. IBM Security press release (2020): Compromised Employee Accounts Led to Most Expensive Data Breaches Over Past Year. https://newsroom.ibm.com/2020-07-29-IBM-Report-Compromised-Employee-Accounts-Led-to-Most-Expensive-Data-Breaches-Over-Past-Year
  2. AWS Best Practices for DDoS Resiliency: Amazon CloudFront. https://docs.aws.amazon.com/whitepapers/latest/aws-best-practices-ddos-resiliency/cloudfront.html
  3. AWS WAF Intelligent Threat Mitigation. https://docs.aws.amazon.com/whitepapers/latest/aws-best-practices-ddos-resiliency/aws-waf-intelligent-threat-mitigation.html
  4. NIST SP 800-63B: Digital Identity Guidelines — Authentication and Lifecycle Management. https://pages.nist.gov/800-63-4/sp800-63b.html
  5. OWASP Cheat Sheet: JSON Web Token (JWT) for Java. https://cheatsheetseries.owasp.org/cheatsheets/JSON_Web_Token_for_Java_Cheat_Sheet.html
  6. OWASP Cheat Sheet: Cross-Site Request Forgery (CSRF) Prevention. https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html
  7. OWASP CSRF Prevention Cheat Sheet — SameSite cookie attribute details. https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#:~:text=SameSite%20is%20a%20cookie%20attribute,None
  8. OWASP Cheat Sheet: File Upload — Set a file size limit. https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html#:~:text=,Set%20a%20file%20size%20limit
  9. OWASP Cheat Sheet: File Upload — Allowlist of content types. https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html#:~:text=,PDF%2C%20DOCX%2C%20etc
  10. Snyk Blog: Verifying webhook signatures — best practices and risks. https://snyk.io/jp/blog/verifying-webhook-signatures/#:~:text=Webhooks%20may%20or%20may%20not,from%20the%20expected%20webhook%20source
  11. Medium: Webhook security — four risk scenarios and how to secure webhooks. https://medium.com/%40O_Annenko/webhook-security-four-risk-scenarios-and-how-to-secure-webhooks-f33eb82ec989#:~:text=To%20protect%20your%20webhooks%20against,And%20the%20timestamp%20cannot%20be