mcpField
D
dotnet-security-owasp

dotnet-security-owasp

github.com/wshaddixセキュリティ86146 回インストール35 週間前に更新
リポジトリ

dotnet-security-owasp について

dotnet-security-owasp は、.NET 開発者向けのセキュリティガイダンススキルです。OWASP Top 10(2021)に沿って、.NET 固有のリスク、緩和例、よくある落とし穴をまとめています。

主な機能

  1. 01OWASP Top 10(2021)の網羅
  2. 02.NET 固有の脆弱性リスク
  3. 03緩和策のコード例
  4. 04よくある落とし穴の注意
  5. 05非推奨のセキュリティパターンの警告

活用シーン

  1. 01.NET コードの OWASP 関連リスクの確認
  2. 02一般的なアプリ脆弱性への緩和策の選定
  3. 03危険または非推奨の .NET セキュリティパターンの特定

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/wshaddix/dotnet-skills --skill dotnet-security-owasp

インストール後、Claude Code で/dotnet-security-owaspを入力して読み込まれたか確認します。

詳細

dotnet-security-owasp

.NET アプリケーション向けの OWASP Top 10(2021)セキュリティガイダンスです。各カテゴリには、脆弱性の説明、.NET 固有のリスク、緩和策のコード例、よくある落とし穴が含まれます。このスキルは、非推奨のセキュリティパターン(CAS、APTCA、.NET Remoting、DCOM、BinaryFormatter)に関する警告を一元的に管理します。

対象外: 認証/認可の実装 — API レベルの認証パターン(Identity、OAuth/OIDC、JWT、パスキー、CORS)については、[skill:dotnet-api-security] を参照してください。Blazor の認証 UI(AuthorizeView、CascadingAuthenticationState)については、[skill:dotnet-blazor-auth] を参照してください。クラウド固有のセキュリティサービス(Azure Key Vault、AWS Secrets Manager)については、クラウドエピックを参照してください。暗号アルゴリズムの選択と鍵管理については、[skill:dotnet-cryptography] を参照してください。構成のバインドと Options パターンについては、[skill:dotnet-csharp-configuration] を参照してください。

相互参照:[skill:dotnet-secrets-management] はシークレットの取り扱い、[skill:dotnet-cryptography] は暗号化のベストプラクティス、[skill:dotnet-csharp-coding-standards] はセキュアコーディング規約を参照してください。


A01: アクセス制御の不備

脆弱性: ユーザーが意図された権限の範囲を超えて行動し、他のユーザーのデータにアクセスしたり、権限を昇格したり、アクセスチェックを回避したりします。

.NET におけるリスク: コントローラーやエンドポイントに [Authorize] 属性がないこと、所有権の検証を行わずにルートパラメーターからユーザー ID を取得する安全でない直接オブジェクト参照(IDOR)、意図しないオリジンを許可する CORS の設定ミスなどがあります。

緩和策
// 1. Apply authorization globally, then opt out explicitly
builder.Services.AddAuthorizationBuilder()
    .SetFallbackPolicy(new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .Build());

var app = builder.Build();
app.MapControllers(); // All endpoints require auth by default

// 2. Resource-based authorization to prevent IDOR
public sealed class DocumentAuthorizationHandler
    : AuthorizationHandler<EditRequirement, Document>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        EditRequirement requirement,
        Document resource)
    {
        if (resource.OwnerId == context.User.FindFirstValue(ClaimTypes.NameIdentifier))
        {
            context.Succeed(requirement);
        }
        return Task.CompletedTask;
    }
}

// In the endpoint:
app.MapPut("/documents/{id}", async (
    int id,
    DocumentDto dto,
    IAuthorizationService authService,
    ClaimsPrincipal user,
    AppDbContext db) =>
{
    var document = await db.Documents.FindAsync(id);
    if (document is null) return Results.NotFound();

    var authResult = await authService.AuthorizeAsync(user, document, "Edit");
    if (!authResult.Succeeded) return Results.Forbid();

    document.Title = dto.Title;
    await db.SaveChangesAsync();
    return Results.NoContent();
});
// 3. Restrict CORS to known origins
builder.Services.AddCors(options =>
{
    options.AddPolicy("Strict", policy =>
    {
        policy.WithOrigins("https://app.example.com")
              .WithMethods("GET", "POST")
              .WithHeaders("Content-Type", "Authorization");
    });
});

落とし穴: AllowAnyOrigin() と AllowCredentials() の併用は ASP.NET Core の実行時に拒否されますが、SetIsOriginAllowed(_ => true) と AllowCredentials() を併用すると、すべてのオリジンが警告なしに許可されます。このパターンは決して使用しないでください。


A02: 暗号化の失敗

脆弱性: 暗号化が弱い、または存在しないために、機密データが漏えいします。平文での保存、非推奨のアルゴリズム、不適切な鍵管理などが該当します。

.NET におけるリスク: パスワードのハッシュ化に MD5/SHA1 を使用すること、平文のパスワードを含む接続文字列を appsettings.json に保存すること、機密データを HTTP 経由で送信すること、暗号化に DES/RC2 を使用することなどがあります。

緩和策
// Enforce HTTPS and HSTS
builder.Services.AddHttpsRedirection(options =>
{
    options.HttpsPort = 443;
});

var app = builder.Build();
app.UseHsts(); // Strict-Transport-Security header
app.UseHttpsRedirection();

// Never store secrets in appsettings.json -- use user secrets or env vars
// See [skill:dotnet-secrets-management] for proper secrets handling
// Use Data Protection API for symmetric encryption of application data
public sealed class TokenProtector(IDataProtectionProvider provider)
{
    private readonly IDataProtector _protector =
        provider.CreateProtector("Tokens.V1");

    public string Protect(string plaintext) => _protector.Protect(plaintext);

    public string Unprotect(string ciphertext) => _protector.Unprotect(ciphertext);
}

アルゴリズムの選択(AES-GCM、RSA、ECDSA)と鍵導出については、[skill:dotnet-cryptography] を参照してください。


A03: インジェクション

脆弱性: 信頼できないデータがコマンドやクエリの一部としてインタープリターに送られます。SQL インジェクション、コマンドインジェクション、LDAP インジェクション、クロスサイトスクリプティング(XSS)などがあります。

.NET におけるリスク: SQL クエリで文字列を連結すること、サニタイズされていない入力を使って Process.Start を呼び出すこと、Razor ページでユーザー入力を未加工の HTML としてレンダリングすることなどがあります。

緩和策
// SQL injection prevention: always use parameterized queries
// EF Core is parameterized by default via LINQ
var orders = await db.Orders
    .Where(o => o.CustomerId == customerId)
    .ToListAsync();

// When raw SQL is needed, use parameterized interpolation
var results = await db.Orders
    .FromSqlInterpolated($"SELECT * FROM Orders WHERE Status = {status}")
    .ToListAsync();

// NEVER concatenate user input into SQL:
// var bad = db.Orders.FromSqlRaw("SELECT * FROM Orders WHERE Status = '" + status + "'");
// XSS prevention: Razor encodes output by default.
// Use @Html.Raw() ONLY for trusted, pre-sanitized HTML.
// In Minimal APIs, return typed results -- not raw strings:
app.MapGet("/greeting", (string name) =>
    Results.Content($"<p>Hello, {HtmlEncoder.Default.Encode(name)}</p>",
        "text/html"));

// Command injection prevention: avoid Process.Start with user input.
// If unavoidable, validate against an allowlist:
public static bool IsAllowedTool(string toolName) =>
    toolName is "dotnet" or "git" or "nuget";

落とし穴: FromSqlRaw で文字列を連結すると、パラメーター化が回避されます。必ず FromSqlInterpolated を使用するか、SqlParameter オブジェクトを FromSqlRaw に渡してください。


A04: 安全でない設計

脆弱性: 実装だけでは修正できない設計パターンの欠陥です。レート制限の欠如、多層防御の不足、リソース消費の制限がないことなどが該当します。

.NET におけるリスク: レート制限のない API、上限のないファイルアップロード、状態を変更する操作に対する偽造防止トークンの欠如などがあります。

緩和策
// Rate limiting with built-in middleware (.NET 7+)
builder.Services.AddRateLimiter(options =>
{
    options.AddFixedWindowLimiter("api", limiterOptions =>
    {
        limiterOptions.PermitLimit = 100;
        limiterOptions.Window = TimeSpan.FromMinutes(1);
        limiterOptions.QueueLimit = 0;
    });
    options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
});

var app = builder.Build();
app.UseRateLimiter();

app.MapGet("/api/data", () => Results.Ok("data"))
    .RequireRateLimiting("api");
// Anti-forgery for Minimal APIs (.NET 8+)
builder.Services.AddAntiforgery();

var app = builder.Build();
app.UseAntiforgery();

// Form-bound endpoint: antiforgery validated automatically
app.MapPost("/orders", async ([FromForm] string productId, AppDbContext db) =>
{
    var order = new Order { ProductId = productId };
    db.Orders.Add(order);
    await db.SaveChangesAsync();
    return Results.Created($"/orders/{order.Id}", order);
});

// JSON endpoint: opt in explicitly with RequireAntiforgery()
app.MapPost("/api/orders", async (CreateOrderDto dto, AppDbContext db) =>
{
    var order = new Order { ProductId = dto.ProductId };
    db.Orders.Add(order);
    await db.SaveChangesAsync();
    return Results.Created($"/api/orders/{order.Id}", order);
}).RequireAntiforgery();

落とし穴: 正しく適用するには、UseRateLimiter() を UseRouting() の後、MapControllers()/MapGet() の前に呼び出す必要があります。


A05: セキュリティ設定の不備

脆弱性: 安全でないデフォルト設定、不完全な設定、公開状態のクラウドストレージ、不要な機能の有効化、詳細すぎるエラーメッセージなどがあります。

.NET におけるリスク: 本番環境での詳細な例外ページ(UseDeveloperExceptionPage)、サーバーヘッダーを公開する Kestrel のデフォルト設定、有効なまま残されたデバッグエンドポイント、セキュリティヘッダーの欠如などがあります。

緩和策
// Remove server identity headers (configure BEFORE Build)
builder.WebHost.ConfigureKestrel(options =>
{
    options.AddServerHeader = false;
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    // Generic error handler in production -- no stack traces
    app.UseExceptionHandler("/error");
    app.UseHsts();
}

// Add security headers via middleware
app.Use(async (context, next) =>
{
    context.Response.Headers.Append("X-Content-Type-Options", "nosniff");
    context.Response.Headers.Append("X-Frame-Options", "DENY");
    context.Response.Headers.Append("Referrer-Policy", "strict-origin-when-cross-origin");
    context.Response.Headers.Append(
        "Content-Security-Policy",
        "default-src 'self'; script-src 'self'; style-src 'self'");
    await next();
});
// Constrain request body size to prevent resource exhaustion (configure BEFORE Build)
builder.WebHost.ConfigureKestrel(options =>
{
    options.Limits.MaxRequestBodySize = 10 * 1024 * 1024; // 10 MB
    options.Limits.MaxRequestHeadersTotalSize = 32 * 1024; // 32 KB
    options.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds(30);
});

// Then: var app = builder.Build();

落とし穴: UseDeveloperExceptionPage() はソースコードのパスとスタックトレースを漏えいさせます。IsDevelopment() による条件分岐の下でのみ有効にし、本番環境やステージング環境では決して有効にしないでください。


A06:脆弱で古くなったコンポーネント

脆弱性: 既知の脆弱性があるコンポーネント、サポートが終了したフレームワーク、またはパッチが適用されていない依存関係を使用することです。

.NET におけるリスク: サポート対象外の .NET バージョンでの実行、既知の CVE がある NuGet パッケージ、監査されていない推移的依存関係の脆弱性です。

緩和策

<PropertyGroup>
  <NuGetAudit>true</NuGetAudit>
  <NuGetAuditLevel>low</NuGetAuditLevel>
  <NuGetAuditMode>all</NuGetAuditMode> 
</PropertyGroup>
# Audit NuGet packages for known vulnerabilities
dotnet list package --vulnerable --include-transitive

# Keep packages up to date
dotnet outdated  # requires dotnet-outdated-tool

# Check .NET SDK/runtime support status
dotnet --info

注意点: NuGetAuditMode の既定値は direct です。all に設定しない限り、推移的な脆弱性は検出されません。深い依存関係の問題を見つけるため、CI では必ず all を使用してください。


A07:識別および認証の失敗

脆弱性: 認証メカニズムが弱いこと、クレデンシャルスタッフィング、セッション固定攻撃、多要素認証の不備です。

.NET におけるリスク: 既定の Identity パスワードポリシーが弱すぎること、Secure/SameSite 属性のないセッション Cookie、アカウントロックアウト設定の不備です。

緩和策
// Configure strong Identity password and lockout policies
builder.Services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
    // Password requirements
    options.Password.RequireDigit = true;
    options.Password.RequiredLength = 12;
    options.Password.RequireNonAlphanumeric = true;
    options.Password.RequireUppercase = true;
    options.Password.RequireLowercase = true;

    // Account lockout
    options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15);
    options.Lockout.MaxFailedAccessAttempts = 5;
    options.Lockout.AllowedForNewUsers = true;

    // User settings
    options.User.RequireUniqueEmail = true;
})
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultTokenProviders();
// Secure cookie configuration
builder.Services.ConfigureApplicationCookie(options =>
{
    options.Cookie.HttpOnly = true;
    options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
    options.Cookie.SameSite = SameSiteMode.Strict;
    options.ExpireTimeSpan = TimeSpan.FromHours(2);
    options.SlidingExpiration = true;
});

注意点: CookieSecurePolicy.SameAsRequest では、開発環境で HTTP 経由の Cookie が許可されます。これは問題ありません。しかし、本番環境で TLS を終端するリバースプロキシの背後にある場合、アプリからは HTTP に見えるため、Cookie が安全でない形で送信されます。本番環境では必ず CookieSecurePolicy.Always を使用し、転送ヘッダーを設定してください。


A08:ソフトウェアおよびデータの完全性の失敗

脆弱性: 完全性の侵害を防げないコードやインフラストラクチャです。署名されていないパッケージ、安全でない CI/CD パイプライン、信頼できないデータのデシリアライズなどが含まれます。

.NET におけるリスク: デシリアライズに BinaryFormatter を使用すること(任意コード実行につながります)、信頼できないフィードから署名されていない NuGet パッケージを受け入れること、パッケージソースマッピングの不備です。

緩和策


<configuration>
  <packageSources>
    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
    <add key="internal" value="https://pkgs.example.com/nuget/v3/index.json" />
  </packageSources>
  <packageSourceMapping>
    <packageSource key="nuget.org">
      <package pattern="*" />
    </packageSource>
    <packageSource key="internal">
      <package pattern="MyCompany.*" />
    </packageSource>
  </packageSourceMapping>
</configuration>
// NEVER use BinaryFormatter -- it is a critical deserialization vulnerability.
// BinaryFormatter is obsolete as error (SYSLIB0011) in .NET 8+ and removed in .NET 9+.
// Use System.Text.Json instead:
var data = JsonSerializer.Deserialize<OrderDto>(jsonString);

// For binary serialization needs, use MessagePack or Protobuf:
// <PackageReference Include="MessagePack" Version="3.*" />
var bytes = MessagePackSerializer.Serialize(order);
var restored = MessagePackSerializer.Deserialize<Order>(bytes);

注意点: パッケージソースマッピングでは、最も具体的なパターンが優先されます。MyCompany.* は * ワイルドカードより優先されます。依存関係の混乱攻撃を防ぐため、内部パッケージには必ず具体的なパターンを定義してください。


A09:セキュリティログおよび監視の失敗

脆弱性: セキュリティ関連イベントのログが不十分であること、侵害を監視していないこと、進行中の攻撃を検知して対応できないことです。

.NET におけるリスク: 認証失敗を記録しないこと、機密性の高い操作の監査証跡がないこと、機密データ(パスワード、トークン)を平文でログに記録することです。

緩和策
// Log security events with structured logging
public sealed class AuditMiddleware(RequestDelegate next, ILogger<AuditMiddleware> logger)
{
    public async Task InvokeAsync(HttpContext context)
    {
        var userId = context.User.FindFirstValue(ClaimTypes.NameIdentifier) ?? "anonymous";
        var path = context.Request.Path.Value;

        using (logger.BeginScope(new Dictionary<string, object?>
        {
            ["UserId"] = userId,
            ["RequestPath"] = path,
            ["RemoteIp"] = context.Connection.RemoteIpAddress?.ToString()
        }))
        {
            await next(context);

            // Log failed authentication attempts
            if (context.Response.StatusCode == StatusCodes.Status401Unauthorized)
            {
                logger.LogWarning("Authentication failed for {Path}", path);
            }

            // Log authorization failures
            if (context.Response.StatusCode == StatusCodes.Status403Forbidden)
            {
                logger.LogWarning("Authorization denied for {Path}", path);
            }
        }
    }
}
// NEVER log sensitive data -- redact credentials and PII
// Configure log filtering to exclude sensitive paths
builder.Logging.AddFilter("Microsoft.AspNetCore.Authentication", LogLevel.Warning);

// Use IHttpLoggingInterceptor (.NET 8+) to redact request/response headers
builder.Services.AddHttpLogging(options =>
{
    options.LoggingFields = HttpLoggingFields.RequestPath
        | HttpLoggingFields.RequestMethod
        | HttpLoggingFields.ResponseStatusCode
        | HttpLoggingFields.Duration;
    // Explicitly exclude request/response bodies and auth headers
});

注意点: {Placeholder} 構文を使った構造化ログは安全ですが、ログ呼び出しで文字列補間($"User {userId}")を使うと構造化ログが回避され、マスキングに対応していないログの出力先に個人識別情報(PII)が漏れる可能性があります。


A10:サーバーサイドリクエストフォージェリ(SSRF)

脆弱性: ユーザーが指定した URL に基づいて、検証せずにアプリケーションがリモートリソースを取得することで、攻撃者が内部サービスやメタデータエンドポイントにアクセスできることです。

.NET におけるリスク: ユーザー指定の URL を使った HttpClient の呼び出し、内部ネットワークへの URL リダイレクトの追従、クラウドメタデータエンドポイント(169.254.169.254)へのアクセスです。

緩和策
// Validate and restrict outbound URLs
public static class UrlValidator
{
    private static readonly HashSet<string> AllowedHosts = new(StringComparer.OrdinalIgnoreCase)
    {
        "api.example.com",
        "cdn.example.com"
    };

    public static bool IsAllowed(string url)
    {
        if (!Uri.TryCreate(url, UriKind.Absolute, out var uri))
            return false;

        // Block non-HTTPS
        if (uri.Scheme != Uri.UriSchemeHttps)
            return false;

        // Block private/internal IPs
        if (IPAddress.TryParse(uri.Host, out var ip))
        {
            if (IsPrivateOrReserved(ip))
                return false;
        }

        // Allowlist hosts
        return AllowedHosts.Contains(uri.Host);
    }

    private static bool IsPrivateOrReserved(IPAddress ip)
    {
        byte[] bytes = ip.GetAddressBytes();
        return bytes[0] switch
        {
            10 => true,                                         // 10.0.0.0/8
            127 => true,                                        // 127.0.0.0/8
            169 when bytes[1] == 254 => true,                   // 169.254.0.0/16 (link-local / cloud metadata)
            172 when bytes[1] >= 16 && bytes[1] <= 31 => true,  // 172.16.0.0/12
            192 when bytes[1] == 168 => true,                   // 192.168.0.0/16
            _ => false
        };
    }
}

// Usage in an endpoint
app.MapPost("/fetch", async (FetchRequest request, IHttpClientFactory factory) =>
{
    if (!UrlValidator.IsAllowed(request.Url))
        return Results.BadRequest("URL not allowed");

    var client = factory.CreateClient();
    var response = await client.GetStringAsync(request.Url);
    return Results.Ok(response);
});
// Configure HttpClient to disable automatic redirect following
builder.Services.AddHttpClient("external", client =>
{
    client.BaseAddress = new Uri("https://api.example.com");
})
.ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
{
    AllowAutoRedirect = false // Prevent redirect-based SSRF
});

注意点: DNS リバインディングは IP 許可リストを回避できます。攻撃者のドメインは検証時にパブリック IP に解決され、実際のリクエスト時には内部 IP に解決されます。DNS の解決結果を固定するか、接続後に再検証してください。


非推奨のセキュリティパターン

このスキルは、非推奨のセキュリティパターンに関する警告の正式な管理元です。他のスキルでは警告を重複させず、ここを参照してください。

コードアクセスセキュリティ(CAS)

CAS は .NET Core/.NET 5 以降ではサポートされていません。CAS を目的として System.Security.Permissions、SecurityPermission、または [SecurityCritical]/[SecuritySafeCritical] 属性を参照するコードは、削除するか、OS レベルのセキュリティ境界(コンテナー、プロセス分離)に置き換える必要があります。

部分的に信頼された呼び出し元を許可(APTCA)

[AllowPartiallyTrustedCallers] 属性は、.NET Core/.NET 5 以降では効果がありません。部分的信頼のモデルは廃止されています。移行時に APTCA 属性を削除してください。代わりに標準的な認可と入力検証を使用してください。

.NET Remoting

.NET Remoting は .NET Core/.NET 5 以降では利用できません。リモートオブジェクトのデシリアライズに制限がなかったため、本質的に安全ではありませんでした。次のものに置き換えてください。

  • プロセス間/マシン間の RPC には gRPC(トランスポートのセキュリティについては [skill:dotnet-cryptography] を参照)
  • 同一マシン内の IPC には名前付きパイプ
  • サービス間通信には HTTP API
DCOM

Distributed COM(DCOM)はWindows 専用で、.NET Core/.NET 5 以降ではサポートされていません。分散通信には gRPC、REST API、またはメッセージキューに置き換えてください。

BinaryFormatter

BinaryFormatter は .NET 8 でエラー扱いで非推奨(SYSLIB0011)となり、.NET 9 以降では削除されています。デシリアライズ攻撃によって任意のコードが実行されるおそれがあります。次のものに置き換えてください。

  • JSON のシリアル化には System.Text.Json
  • バイナリ形式には MessagePack または Protocol Buffers
  • XML の用途には、許可する型を厳密に限定した XmlSerializer

回避策として System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization を true に設定しないでください。


エージェントに関する注意点

  1. 明確な理由なく [AllowAnonymous] を使用しないでください。これはグローバルなフォールバックポリシーを上書きします。匿名アクセスを許可する各エンドポイントには、その理由を説明するコメントを付けてください。
  2. 手軽さを理由に HTTPS リダイレクトを無効にしないでください。ローカル開発では代わりに dotnet dev-certs https --trust を使用してください。
  3. リクエストボディをそのままログに記録しないでください。認証情報、トークン、個人識別情報(PII)が含まれている可能性があります。安全なフィールドを選択するには HttpLoggingFields を使用してください。
  4. クライアント側の検証だけに頼らないでください。必ずサーバー側でも検証してください。Razor のフォーム検証は UX のためのものであり、セキュリティ対策ではありません。
  5. 文字列補間を使って FromSqlRaw を使用しないでください。自動でパラメーター化される FromSqlInterpolated を使用してください。
  6. シークレットを appsettings.json に保存しないでください。開発ではユーザーシークレットを、本番環境では環境変数またはマネージド ID を使用してください。[skill:dotnet-secrets-management] を参照してください。
  7. 非推奨のパターンを使ってセキュリティ上重要なコードを生成しないでください。CAS、APTCA、.NET Remoting、DCOM、BinaryFormatter はすべて、最新の .NET ではサポートされていません。上記の「非推奨のセキュリティパターン」セクションを参照してください。

前提条件

  • .NET 8.0 以降(LTS の基準バージョン)
  • セキュリティミドルウェア、CSRF 対策、レート制限を利用する場合は ASP.NET Core 8.0 以降
  • 認証/ID 管理に Microsoft.AspNetCore.Identity を使用する場合(A07 パターンを使用する場合)

参考資料

コメント