Zero Trust 時代の「認証・特権の継ぎ目」を徹底解剖
企業のDXが加速する中、SSOとPAMの連携が当たり前となった現在。しかし、その「継ぎ目」こそが最大の攻撃対象となっています。
この記事では、攻撃者の視点から見た最新の攻撃手法と、それに対する実践的な防御策を、実装コードとともに解説します。
SSO や PAM については、以下の記事で詳しく説明しています。
令和のアイデンティティ地図:IdP ↔ PAM ↔ ワークロード
現代のエンタープライズ環境では、Identity Provider(IdP)とPrivileged Access Management(PAM)が密接に連携し、ユーザーのアクセス制御を行っています。しかし、この連携部分にこそ脆弱性が潜んでいます。
この図で重要なのは、3つの境界です。
- User ↔ IdP:初期認証の境界
- IdP ↔ PAM:トークン検証の境界
- PAM ↔ Target:特権セッションの境界
攻撃者は、これらの境界の「継ぎ目」を狙って侵入を試みます。
攻撃者の常套手段 2025 — Token Theft → 横移動の実際
CryptoChameleon攻撃の分析
2024年3月に初確認されたCryptoChameleon攻撃は、FCCの職員を標的としたOktaを模倣したフィッシング攻撃として報告され、従来の手法を大幅に進化させています。
攻撃の流れ
- 巧妙なフィッシングサイト:完全にOktaのUIを模倣
- MFA バイパス:リアルタイムでプロキシ経由でMFAコードを中継
- セッションハイジャック:取得したトークンを即座に悪用
- PAM侵入:有効なトークンでPAMゲートウェイを突破
長寿命トークン悪用の実態
最も危険なのは、Refresh Tokenや長寿命アクセストークンの悪用です。
// 攻撃者が取得したトークンの例
const stolenToken = {
access_token: "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
expires_in: 3600,
refresh_token: "AQABAAAA...", // 90日間有効
scope: "openid profile email"
};
// 攻撃者は refresh_token を使って長期間アクセス可能
[ 対策ポイント ]
- Refresh Tokenの有効期限を最短に設定
- Token Binding(デバイス固有の暗号化)を実装
- 異常なトークン使用パターンの検知
SAML / OIDC フロー徹底分解
SAML Assertion Manipulation攻撃
SAMLの仕組みを悪用した攻撃は、特に Assertion Replay と Signature Wrapping が危険です。これらの攻撃は、正規のSAMLアサーションを改竄・再利用することで認証を突破しようとします。
以下は、正常なSAMLアサーションの構造例です。
<!-- 正常なSAMLアサーション -->
<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="_12345" IssueInstant="2025-01-15T10:00:00Z">
<saml:Subject>
<saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">
user@company.com
</saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:SubjectConfirmationData NotOnOrAfter="2025-01-15T10:05:00Z"/>
</saml:SubjectConfirmation>
</saml:Subject>
</saml:Assertion>
攻撃手法
- Replay Attack:過去のアサーションを再送信
- Signature Wrapping:署名を残したまま内容を改竄
- XML Signature Exclusion:署名対象外の要素を操作
実装レベルでの対策
PAM側でJWTトークンを検証する際は、単純な署名チェックだけでなく、厳格な検証パラメータ を設定することが重要です。以下は.NET 8を使用した実装例で、Azure Entra IDとの連携を想定しています。
// .NET 8 での堅牢なJWT検証
public class TokenValidator
{
private readonly JwtSecurityTokenHandler _handler = new();
private readonly HttpClient _httpClient;
public TokenValidator(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<bool> ValidateTokenAsync(string token)
{
try
{
// 1. JWKSエンドポイントから公開鍵を取得
var jwks = await GetJwksAsync();
// 2. 検証パラメータを厳格に設定
var parameters = new TokenValidationParameters
{
IssuerSigningKeys = jwks,
ValidateIssuer = true,
ValidIssuer = "https://login.microsoftonline.com/<tenant>/v2.0",
ValidateAudience = true,
ValidAudience = "api://your-pam-gateway",
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(1), // 時刻のズレを最小限に
RequireSignedTokens = true,
RequireExpirationTime = true
};
// 3. トークンの検証実行
var principal = _handler.ValidateToken(token, parameters, out var validatedToken);
// 4. 追加の検証(Nonceチェックなど)
return await AdditionalValidationAsync(validatedToken);
}
catch (SecurityTokenException)
{
return false;
}
}
private async Task<bool> AdditionalValidationAsync(SecurityToken token)
{
// Nonce の重複チェック
// IP アドレス制限チェック
// デバイス認証チェック
return true;
}
}
PAM の "継ぎ目" を突く:Vault 完全回避ルート
Legacy認証の残存問題
多くの企業でPAMを導入していても、以下のような バイパス経路 が残存しています。攻撃者はこれらの抜け道を見つけて、PAMの制御を回避しようとします。
# 攻撃者が発見する典型的なバイパス経路
# 1. 古いSSHキーが残存
~/.ssh/id_rsa_old # PAM管理外
# 2. ローカルadminアカウントのキャッシュ
net user administrator /active:yes
# 3. サービスアカウントの平文パスワード
# config.xml 内に <password>PlainTextPassword123</password>
# 4. 共有アカウントの直接アクセス
# PAM を経由せずに直接 DB サーバーに接続
重要
既存システムの棚卸しとレガシー認証の段階的廃止が必要です
RDP Clipboard攻撃の実態
PAM経由のRDPセッションでも、クリップボード共有が有効だと機密情報が漏洩します。攻撃者は正規のRDPセッション内で、クリップボードにアクセスして機密データを外部に持ち出そうとします。
以下は、攻撃者がRDPセッション内で実行する可能性があるコマンド例です。
# 攻撃者がRDPセッション内で実行
Get-Clipboard | Out-File -Path "C:\temp\stolen_data.txt"
# クリップボードの内容をローカルに保存
[ 対策ポイント ]
- RDPクリップボード共有を無効化
- セッション内でのファイル転送を制限
- 画面録画とキーロガー検知
防御パターン:SSO 強化 + PAM JIT 化
Azure Entra PIM でのJust-In-Time実装
Azure Entra Privileged Identity Management (PIM) を使用すると、管理者権限を「必要な時だけ」有効化できます。これにより、常時管理者権限を持つリスクを大幅に軽減できます。
以下は、PIMの設定例をYAML形式で表現したものです(実際の設定はAzureポータル経由で行います)。
📚 参考: Azure Entra PIM 公式ドキュメント
# Azure Entra PIM 設定例
privileged_role_assignments:
- role: "Global Administrator"
assignment_type: "Eligible"
max_duration: "PT2H" # 最大2時間
approval_required: true
mfa_required: true
justification_required: true
conditional_access:
- name: "PAM Gateway Access"
conditions:
applications: ["PAM-Gateway-App"]
locations: ["Corporate Network"]
device_platforms: ["Windows", "macOS"]
access_controls:
require_mfa: true
require_compliant_device: true
require_approved_client_app: true
KeeperPAM OIDC 連携の実装
KeeperPAMは、OpenID ConnectプロトコルでIdPと連携し、シームレスなSSO体験と強固なアクセス制御を実現できます。以下の設定例では、Azure Entra IDとの連携を行い、JIT(Just-In-Time)アクセス制御を実装しています。
特に重要なのは、TTL(Time To Live) の設定で、特権アクセスの時間を最小限に制限することです。
📚 参考: KeeperPAM 公式ドキュメント
# KeeperPAM 設定ファイル
oidc_configuration:
issuer: "https://login.microsoftonline.com/<tenant>/v2.0"
client_id: "pam-gateway-client"
client_secret: "${KPR_OIDC_SECRET}"
scopes:
- "openid"
- "profile"
- "email"
- "groups"
access_policies:
- name: "Database Administrators"
role: "JustInTimeAdmin"
ttl: 300 # 5分間のみ有効
approval_workflow:
- approver: "security-team@company.com"
- emergency_access: false
- name: "Emergency Access"
role: "EmergencyAdmin"
ttl: 900 # 15分間
approval_workflow:
- approver: "ciso@company.com"
- emergency_access: true
- audit_log: "high_priority"
session_recording:
enabled: true
storage: "azure_blob"
retention: "90_days"
real_time_monitoring: true
BeyondTrust Entitle でのクラウドJIT設定
2024年4月16日にBeyondTrustがEntitleを買収して実現されたクラウドJIT機能を活用した設定例です。この機能により、クラウドリソースへの特権アクセスも時間制限付きで管理できるようになりました。
以下のJavaScriptコードは、BeyondTrust APIを使用して一時的なアクセス権限を付与する例です。
📚 参考: BeyondTrust プレスリリース
// BeyondTrust API を使用したJITアクセス制御
const grantTemporaryAccess = async (userId, resourceId, duration) => {
const accessRequest = {
user_id: userId,
resource_id: resourceId,
duration_minutes: duration,
justification: "Production incident response",
auto_revoke: true
};
try {
const response = await fetch('/api/v1/access/grant', {
method: 'POST',
headers: {
'Authorization': `Bearer ${await getAccessToken()}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(accessRequest)
});
return await response.json();
} catch (error) {
console.error('JIT access grant failed:', error);
throw error;
}
};
実装ハンズオン
JWT署名検証の実装
PAMゲートウェイでは、IdPから受け取ったJWTトークンの署名検証が最重要です。単純な文字列チェックではなく、暗号学的な署名検証を行うことで、トークンの改竄を確実に検知できます。
以下は、ASP.NET Core 8でDI(依存性注入)パターンを使用した実装例です。
// Program.cs (ASP.NET Core 8)
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
// HttpClient と TokenValidator を DI に登録
builder.Services.AddHttpClient<TokenValidator>();
// JWT 認証の設定
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["JwtSettings:Issuer"],
ValidAudience = builder.Configuration["JwtSettings:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(builder.Configuration["JwtSettings:Key"]!))
};
});
var app = builder.Build();
// PAM エンドポイントの保護
app.MapGet("/pam/access", async (HttpContext context, TokenValidator validator) =>
{
var token = context.Request.Headers["Authorization"]
.FirstOrDefault()?.Split(" ").Last();
if (string.IsNullOrEmpty(token))
{
return Results.Unauthorized();
}
// カスタム検証ロジック
var isValid = await validator.ValidateTokenAsync(token);
if (!isValid)
{
return Results.Unauthorized();
}
return Results.Ok(new { access = "granted", duration = 300 });
}).RequireAuthorization();
app.Run();
KeeperPAM コンテナ設定
KeeperPAMをコンテナ環境で運用する場合の設定例です。Dockerを使用することで、環境の一貫性と運用の自動化を実現できます。特に、機密情報は環境変数で管理し、設定ファイルとセッションログは永続化ボリュームに保存することが重要です。
📚 参考: Docker 公式ドキュメント
# Dockerfile
FROM keepersecurity/keeperpam:latest
COPY config/pam-config.yaml /etc/keeperpam/
COPY certificates/ /etc/ssl/certs/
EXPOSE 443 22
CMD ["keeperpam", "--config=/etc/keeperpam/pam-config.yaml"]
# docker-compose.yml
version: '3.8'
services:
keeperpam:
image: keepersecurity/keeperpam:latest
ports:
- "443:443"
- "2222:22"
environment:
- KPR_OIDC_SECRET=${OIDC_SECRET}
- KPR_DB_PASSWORD=${DB_PASSWORD}
volumes:
- ./config:/etc/keeperpam
- ./sessions:/var/log/keeperpam
depends_on:
- postgres
postgres:
image: postgres:15
environment:
- POSTGRES_DB=keeperpam
- POSTGRES_USER=pam_user
- POSTGRES_PASSWORD=${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
監査 & シグナル設計
セッション録画の実装
PAMシステムにおいて、セッション録画は監査とインシデント調査の要となります。ただし、録画データは改竄されないよう、各イベントにハッシュ値を付与して整合性を保証することが重要です。
以下のPythonクラスは、セッションイベントをハッシュ化して記録し、AWS S3に暗号化して保存する実装例です。
# セッション録画ハッシュ化保存
import hashlib
import json
from datetime import datetime
class SessionRecorder:
def __init__(self, session_id, user_id):
self.session_id = session_id
self.user_id = user_id
self.events = []
def record_event(self, event_type, data):
event = {
"timestamp": datetime.utcnow().isoformat(),
"event_type": event_type,
"data": data,
"hash": self._calculate_hash(data)
}
self.events.append(event)
def _calculate_hash(self, data):
"""イベントの整合性確保用ハッシュ"""
return hashlib.sha256(
json.dumps(data, sort_keys=True).encode()
).hexdigest()
def save_session(self):
"""セッションをS3に保存"""
session_data = {
"session_id": self.session_id,
"user_id": self.user_id,
"events": self.events,
"session_hash": self._calculate_session_hash()
}
# S3に暗号化して保存
# 実装省略
def _calculate_session_hash(self):
"""セッション全体の整合性確保"""
session_string = json.dumps(self.events, sort_keys=True)
return hashlib.sha256(session_string.encode()).hexdigest()
SIEM連携クエリ例
SSOとPAMのログを SIEM(Security Information and Event Management) で統合分析することで、高度な脅威を検知できます。以下は、Azure SentinelとAWS Athenaでの実装例です。
[ Azure Sentinelでの異常検知クエリ ]
長寿命トークンの使用や異常なアクセス頻度を検知します。
-- Azure Sentinel (KQL) でのSSOトークン異常検知
SecurityEvent
| where TimeGenerated > ago(1h)
| where EventID == 4648 // ログオンイベント
| where ProcessName contains "PAM"
| extend TokenLifetime = datetime_diff('minute', TokenExpiry, TimeGenerated)
// TokenExpiry は独自に拡張したカラム例。
// Sentinel に取り込む際は AAD トークンの有効期限を計算して追加してください
| where TokenLifetime > 60 // 1時間以上の長寿命トークン
| summarize Count = count() by Account, IpAddress
| where Count > 10 // 異常な頻度
| order by Count desc
[ AWS Athenaでの長期トレンド分析 ]
PAMアクセスログを分析して、異常なセッション時間やアクセスパターンを特定します。
📚 参考: AWS Athena SQL リファレンス
-- AWS Athena でのPAMアクセス分析
-- date は Glue テーブルで yyyy-MM-dd 形式の partition_key として登録している前提
SELECT
user_id,
source_ip,
target_resource,
session_duration,
COUNT(*) as access_count
FROM pam_access_logs
WHERE
date >= current_date - interval '7' day
AND session_duration > 3600 -- 1時間以上のセッション
GROUP BY user_id, source_ip, target_resource, session_duration
HAVING access_count > 5
ORDER BY access_count DESC;
令和のセキュリティチェックリスト
SSO側の対策
- トークンの有効期限を最短化(15分以内)
- Device Bindingの実装
- 異常なIPアドレスからのアクセス検知
- MFA の強制適用
PAM側の対策
- Just-In-Time アクセスの実装
- セッション録画の暗号化保存
- Legacy認証経路の完全除去
- 緊急アクセス手順の文書化
監査・運用
- SIEM連携による異常検知
- 定期的なアクセス権限レビュー
- インシデント対応手順の整備
- 従業員向けセキュリティトレーニング
さいごに
SSOとPAMの連携は、現代のエンタープライズセキュリティにおいて不可欠です。しかし、その「継ぎ目」に潜む脆弱性を理解し、適切な対策を講じることがとても重要です。
特に重要なのは!!
- 最小権限の原則:必要最小限の権限を、必要最小限の時間だけ付与
- 多層防御:単一の防御策に依存せず、複数の防御層を構築
- 継続的監視:異常を早期発見し、迅速に対応
攻撃者は常に進化し続けています。私たちも、最新の脅威に対応できる防御策を継続的に実装し、運用していく必要があります。