ストーリー
佐藤CTOがOWASP ZAPの画面を見せました。
DAST(Dynamic Application Security Testing)
DASTの仕組み
DASTは実行中のアプリケーションに対してHTTPリクエストを送信し、レスポンスを分析して脆弱性を検出します。
| 特徴 | SAST | DAST |
|---|---|---|
| テスト方式 | ホワイトボックス(コード可視) | ブラックボックス(外部から) |
| テスト対象 | ソースコード | 実行中のアプリケーション |
| 検出対象 | コードの欠陥 | ランタイムの脆弱性、設定ミス |
| 言語依存 | あり | なし(HTTP通信のため) |
| False Positive | 多い | 比較的少ない |
| 実行タイミング | ビルド前 | デプロイ後(テスト環境) |
主要DASTツール
| ツール | 特徴 | ライセンス |
|---|---|---|
| OWASP ZAP | 最も広く使われるOSSツール | OSS |
| Burp Suite | プロ向けの包括的ツール | Commercial |
| Nuclei | テンプレートベースの高速スキャン | OSS |
| Nikto | Webサーバーの設定不備検出 | OSS |
OWASP ZAP のCI/CD統合
# GitHub Actions での OWASP ZAP 自動スキャン
name: DAST Scan
on:
deployment_status:
# ステージング環境へのデプロイ完了後に実行
jobs:
zap-scan:
if: github.event.deployment_status.state == 'success'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: OWASP ZAP Full Scan
uses: zaproxy/action-full-scan@v0.10.0
with:
target: ${{ github.event.deployment_status.target_url }}
rules_file_name: .zap/rules.tsv
cmd_options: >-
-j
-z "-config api.disablekey=true
-config scanner.strength=HIGH
-config scanner.threadPerHost=5"
allow_issue_writing: true
fail_action: true
- name: Upload ZAP Report
uses: actions/upload-artifact@v4
if: always()
with:
name: zap-report
path: report_html.html
# .zap/rules.tsv -- アラートレベルのカスタマイズ
# ルールID アクション(IGNORE, WARN, FAIL)
10016 IGNORE # Web Browser XSS Protection Not Enabled(CSP で対応済み)
10021 WARN # X-Content-Type-Options Header Missing
10038 FAIL # Content Security Policy Header Not Set
40012 FAIL # Cross Site Scripting (Reflected)
40014 FAIL # Cross Site Scripting (Persistent)
90033 FAIL # Loosely Scoped Cookie
APIセキュリティテスト
// OpenAPI仕様からの自動セキュリティテスト
interface ApiSecurityTest {
endpoint: string;
method: string;
tests: SecurityTestCase[];
}
interface SecurityTestCase {
name: string;
category: string;
payload: Record<string, any>;
expectedStatus: number;
description: string;
}
const apiSecurityTests: ApiSecurityTest[] = [
{
endpoint: '/api/users/:id',
method: 'GET',
tests: [
{
name: 'IDOR Test',
category: 'Authorization',
payload: { id: 'other-user-id' },
expectedStatus: 403,
description: '他ユーザーのIDでアクセスした場合に403を返すこと',
},
{
name: 'SQL Injection',
category: 'Injection',
payload: { id: "1' OR '1'='1" },
expectedStatus: 400,
description: 'SQLi ペイロードが拒否されること',
},
{
name: 'Path Traversal',
category: 'Injection',
payload: { id: '../../../etc/passwd' },
expectedStatus: 400,
description: 'パストラバーサルが拒否されること',
},
],
},
{
endpoint: '/api/auth/login',
method: 'POST',
tests: [
{
name: 'Rate Limiting',
category: 'DoS',
payload: { email: 'test@example.com', password: 'wrong' },
expectedStatus: 429,
description: '10回連続失敗後にレート制限されること',
},
],
},
];
コンテナセキュリティ
コンテナの攻撃面
graph TD
Title["コンテナの攻撃面"]
Title --> A["1. ベースイメージの脆弱性<br/>- OSパッケージの既知の脆弱性<br/>- 不要なパッケージの存在"]
Title --> B["2. アプリケーション依存関係<br/>- npm/pipパッケージの脆弱性<br/>- 古いバージョンの使用"]
Title --> C["3. Dockerfileの設定不備<br/>- rootユーザーでの実行<br/>- シークレットのレイヤーへの混入"]
Title --> D["4. ランタイムの設定不備<br/>- 特権コンテナ<br/>- 過剰なケーパビリティ<br/>- ホストパスのマウント"]
classDef title fill:#1a1a2e,stroke:#e94560,color:#fff
classDef attack fill:#e94560,stroke:#c23050,color:#fff
class Title title
class A,B,C,D attack
セキュアなDockerfile
# ---- BAD: セキュリティの問題がある Dockerfile ----
# FROM node:18 # フルイメージ(脆弱性多)
# COPY . . # 不要なファイルも含む
# RUN npm install # devDependencies も含む
# EXPOSE 3000
# CMD ["node", "app.js"] # root で実行
# ---- GOOD: セキュアな Dockerfile ----
# Stage 1: ビルドステージ
FROM node:18-alpine AS builder
WORKDIR /app
# 依存関係のみ先にコピー(キャッシュ活用)
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force
COPY src/ ./src/
COPY tsconfig.json ./
RUN npm run build
# Stage 2: 実行ステージ
FROM gcr.io/distroless/nodejs18-debian12
# 非rootユーザー(distroless は nonroot ユーザーを内蔵)
USER nonroot:nonroot
WORKDIR /app
# ビルド成果物のみコピー
COPY --from=builder --chown=nonroot:nonroot /app/dist ./dist
COPY --from=builder --chown=nonroot:nonroot /app/node_modules ./node_modules
# ヘルスチェック
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD ["/nodejs/bin/node", "-e", "require('http').get('http://localhost:3000/health', (r) => { process.exit(r.statusCode === 200 ? 0 : 1) })"]
EXPOSE 3000
# 読み取り専用ファイルシステムで実行可能
CMD ["dist/app.js"]
Trivy によるコンテナスキャン
# GitHub Actions での Trivy スキャン
- name: Trivy vulnerability scan
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
format: table
exit-code: 1
ignore-unfixed: true
vuln-type: os,library
severity: CRITICAL,HIGH
output: trivy-report.txt
# Trivy の設定ファイル
# trivy.yaml
severity:
- CRITICAL
- HIGH
ignore:
unfixed: true
db:
skip-update: false
Kubernetes Pod Security Standards
Pod Security Standards(PSS)の3レベル
| レベル | 説明 | 制限事項 |
|---|---|---|
| Privileged | 制限なし | 開発環境向け |
| Baseline | 基本的な制限 | 特権コンテナ禁止、HostPath禁止 |
| Restricted | 最も厳格 | root実行禁止、読み取り専用FS推奨 |
# Namespace に Pod Security Standards を適用
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
---
# Restricted レベルに準拠した Pod 定義
apiVersion: v1
kind: Pod
metadata:
name: secure-app
namespace: production
spec:
automountServiceAccountToken: false
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: myapp:latest@sha256:abc123... # ダイジェスト指定
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
resources:
limits:
cpu: "500m"
memory: "256Mi"
requests:
cpu: "250m"
memory: "128Mi"
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir:
sizeLimit: 100Mi
イメージの署名と検証
# Cosign でイメージに署名
# cosign sign --key cosign.key myregistry.com/myapp:latest
# Kubernetes での署名検証(Kyverno ポリシー)
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-image-signature
spec:
validationFailureAction: Enforce
rules:
- name: verify-cosign-signature
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "myregistry.com/myapp:*"
attestors:
- entries:
- keys:
publicKeys: |-
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...
-----END PUBLIC KEY-----
まとめ
| ポイント | 内容 |
|---|---|
| DAST | 実行中のアプリケーションに対する動的テスト |
| OWASP ZAP | CI/CDに統合可能なOSSのDASTツール |
| コンテナセキュリティ | ベースイメージ、Dockerfile、ランタイム設定の多層防御 |
| Trivy | コンテナイメージの脆弱性スキャン |
| Pod Security Standards | K8sのPodセキュリティを3レベル(Privileged/Baseline/Restricted)で制御 |
| イメージ署名 | Cosign + Kyverno で改ざん防止 |
チェックリスト
- DASTとSASTの違いと使い分けを説明できる
- OWASP ZAPをCI/CDパイプラインに統合できる
- セキュアなDockerfileを記述できる
- Trivyでコンテナイメージをスキャンできる
- Kubernetes Pod Security Standardsを適用できる
次のステップへ
次は「IaCセキュリティ」を学びます。Terraform、CloudFormation等のIaCコードに対するセキュリティスキャンと、Policy as Codeの考え方を身につけましょう。
推定読了時間: 40分