【実務・中級編】 JWTの署名アルゴリズムをnoneに設定する攻撃手法と検証回避 – オフェンシブセキュリティ & リバースエンジニアリング防御ガイド

おい、ちょっといいか?

最近、チームでWeb APIの設計について話す機会があったよな。その時に「JWT(JSON Web Token)を使っています」って答えてくれたメンバーが何人かいたけど、正直なところ、ちょっと胸騒ぎがしたんだ。

JWTは便利だ。ステートレスでスケーラブルな認証・認可を実現する上で、これほど強力なツールはない。でもな、その便利さの裏には、巧妙に仕組まれた落とし穴がいくつも潜んでいる。今日の話は、その中でも特に根深く、未だに多くのシステムで見かける「alg=none攻撃」についてだ。

「え、alg=none?それって古い脆弱性じゃないですか?」って思った奴、いるだろ?
残念ながら、その認識が一番危ない。この手の脆弱性は、古くても有効な場面が山ほどある。そして、その「まさかうちのシステムでは」という油断こそが、サイバー攻撃者が最も狙いやすい盲点なんだ。

俺たちが何百と見てきたインシデント事例の中で、このalg=noneに起因する認証バイパスや特権昇格は決して少なくない。それは、ライブラリの「親切心」と、開発者の「ちょっとした見落とし」が重なった時に、静かに、そして致命的に発生するんだ。

今日は、この攻撃がどういう原理で、どうやってシステムを欺き、そして君たちの手でどうすれば完全に防御できるのかを、具体的なコードを交えながら徹底的に解説していく。コーヒーでも淹れて、じっくりと読んでくれ。

—

JWTの「顔」を理解する:ヘッダー、ペイロード、署名

まず、基本の「き」からおさらいしておこう。JWTは大きく3つの部分から構成されている。これらはすべてBase64 URLエンコードされており、ピリオド(.)で区切られているんだ。

1. ヘッダー (Header): トークンのタイプ(typ)と、使用されている署名アルゴリズム(alg)を記述するJSONオブジェクト。
例: {"alg": "HS256", "typ": "JWT"}
2. ペイロード (Payload): ユーザーID、ロール、有効期限など、実際の情報(クレーム)を記述するJSONオブジェクト。
例: {"sub": "1234567890", "name": "John Doe", "admin": true, "iat": 1516239022}
3. 署名 (Signature): ヘッダーとペイロードの内容が改ざんされていないことを検証するための部分。algで指定されたアルゴリズムと秘密鍵を使って計算される。

これらの要素が結合され、最終的に以下のような形式のJWTが生成されるわけだ。

Base64Url(Header).Base64Url(Payload).Base64Url(Signature)

ここで今日の主役となるのが、ヘッダー内の"alg"フィールドだ。こいつが、今回の攻撃の全ての入り口になる。

—

悪夢のシナリオ:alg=none攻撃のメカニズム

さて、本題に入ろう。alg=none攻撃とは一体何なのか。

JWTの仕様では、algフィールドに"none"という値が存在する。これは文字通り「署名なし」を意味する。本来、これは特殊なケースや、JWTが署名なしで利用されることを意図した設計上の選択肢だった。

しかし、多くのJWTライブラリがこの"none"を「有効なアルゴリズム」として解釈してしまう、という実装上の盲点があったんだ。

どういうことか?

攻撃者は、正当なJWTを入手した後、そのヘッダー部分を以下のように書き換える。

// オリジナルのヘッダー例
{
  "alg": "HS256", // HS256アルゴリズムで署名されている
  "typ": "JWT"
}

// 攻撃者が改変するヘッダー例
{
  "alg": "none", // 署名なしと宣言
  "typ": "JWT"
}

そして、この改変されたヘッダーと、自由に改ざんしたペイロード(例えば、"admin": falseを"admin": trueに書き換えるなど)を組み合わせて、署名部分を空にするか、全く無効な値をセットする。

この改変されたJWTをサーバーに送信すると、サーバー側のJWT検証ライブラリがどう反応するか、これが問題なんだ。

多くの脆弱なライブラリは、alg: "none"という宣言を見ると、「ああ、このトークンは署名がないんだな。それなら署名検証はスキップして、ペイロードの内容をそのまま信用しよう」と判断してしまうんだ。

つまり、署名検証を無効化し、攻撃者が意図した通りのペイロードをサーバーに解釈させてしまう。これがalg=none攻撃の恐ろしさだ。認証情報や権限を自由に書き換え、システムを騙すことができる。

現場の泥臭いインシデント:開発者の心理とライブラリの罠

「そんな単純なミス、うちのシステムではありえない」って思うかもしれないが、俺はこういうケースを何度も見てきた。

  • 開発者の「とりあえず動かせばいいや」: 「なんかJWTの検証がうまくいかないな…そうだ、ライブラリのオプションでalgorithmsを指定しなかったら、全部受け入れるようになるんじゃないか?」という安易な発想。
  • ライブラリのデフォルト挙動: 古いライブラリや、特定のオプションを指定しない限りnoneを許容してしまうライブラリが存在する。
  • フレームワークの抽象化: フレームワークがJWTの検証処理をラップしてしまい、中の詳細なアルゴリズム指定が隠蔽されてしまうケース。開発者は「フレームワークに任せているから安全」と誤解してしまう。
  • 「最新版を使っているから大丈夫」という幻想: ライブラリのバージョンが新しくても、正しい使い方をしなければ脆弱性は生まれる。重要なのは、その機能がどう動くのかを理解し、セキュアな設定を適用することだ。

この手の脆弱性は、大抵「急いで実装した」「よく分からないけど、ネットのサンプルコードをコピペした」といった状況で生まれる。そして、一度システムに組み込まれてしまうと、なかなか表面化しない厄介な特性を持っているんだ。

—

攻撃の実演:PoC (Proof of Concept)

実際にどうやって攻撃者がJWTを改ざんするのか、具体的なステップを見てみよう。

ステップ1: ターゲットとなるJWTの取得

まず、攻撃者は何らかの方法で正当なJWTを取得する。これは、例えばログイン後のCookieやAuthorizationヘッダーから盗み出すのが一般的だ。

例として、以下のようなJWTをターゲットとする。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOmZhbHNlLCJpYXQiOjE1MTYyMzkwMjJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

これをjwt.ioのようなツールでデコードすると、以下の情報が得られる。

ヘッダー:

{
  "alg": "HS256",
  "typ": "JWT"
}

ペイロード:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": false,
  "iat": 1516239022
}

このユーザーはadmin: falseで、管理者権限を持っていない。

ステップ2: ヘッダーとペイロードの改変

次に、攻撃者はこのJWTのヘッダーとペイロードを改変する。目標は、admin: trueの権限を得ることだ。

1. ヘッダーの改変: "alg": "HS256" を "alg": "none" に書き換える。

{
      "alg": "none",
      "typ": "JWT"
    }

これをBase64 URLエンコードすると: eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0

2. ペイロードの改変: "admin": false を "admin": true に書き換える。

{
      "sub": "1234567890",
      "name": "John Doe",
      "admin": true,
      "iat": 1516239022
    }

これをBase64 URLエンコードすると: eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0

ステップ3: 署名部分の削除(または無効化)と送信

最後に、改変したヘッダーとペイロードを結合し、署名部分を空にする(あるいは、適当な無効な値を付与する)。

eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.

この改変されたJWTを、認証が必要なAPIリクエストのAuthorizationヘッダーなどに含めてサーバーに送信する。

脆弱なシステムでは、このトークンを受信すると、"alg": "none"を見て署名検証をスキップし、ペイロードの"admin": trueを信用してしまう。結果として、攻撃者は管理者権限を取得してしまうわけだ。恐ろしいだろ?

—

完全防御策:コピペで動くセキュアな実装と設定

この攻撃に対する防御策は、基本に忠実かつ厳格であることだ。最も重要なのは、アプリケーション側で許可する署名アルゴリズムを明示的に指定すること。これが最も効果的な防御策であり、あらゆるJWTライブラリで共通して実践すべきことだ。

そして、多層防御の観点から、WAFなどのインフラ層でも対策を講じることで、より堅牢なシステムを構築できる。

1. アプリケーション層での防御(最も重要!)

JWTライブラリを使用する際、検証時に許可するアルゴリズムのリストを厳格に指定する。"none"がそのリストに含まれていなければ、脆弱なライブラリであってもこの攻撃は防げる。

PHP (Firebase/PHP-JWT) の例

<?php
require_once 'vendor/autoload.php'; // Composerでインストールした場合

use Firebase\JWT\JWT;
use Firebase\JWT\Key;

/**
 * JWTの検証とデコードを行う関数
 * @param string $jwt_token 検証するJWT文字列
 * @param string $secret_key JWT署名用の秘密鍵
 * @return object|false デコードされたペイロードオブジェクト、または検証失敗時はfalse
 */
function verifyAndDecodeJwt(string $jwt_token, string $secret_key)
{
    // JWTの有効期限検証時の許容誤差(秒)
    // 例えば、ネットワーク遅延やサーバー間の時刻同期のズレを吸収するため
    $leeway = 60; 

    // 🚨 ここが最重要ポイント 🚨
    // 許可するアルゴリズムを明示的に配列で指定する。
    // HS256以外のアルゴリズムを意図的に受け入れる場合は、ここに追加する。
    // "none" は絶対に含めない。
    $allowed_algorithms = ['HS256']; 

    try {
        JWT::$leeway = $leeway; // 誤差を設定
        
        // Keyオブジェクトを使って、秘密鍵とアルゴリズムを指定する
        // 第3引数で許可するアルゴリズムの配列を渡すことで、"alg": "none"攻撃を防ぐ
        $decoded = JWT::decode($jwt_token, new Key($secret_key, 'HS256'), $allowed_algorithms);
        
        // デコード成功
        return $decoded;
    } catch (\Exception $e) {
        // 検証失敗(署名不正、期限切れ、無効なアルゴリズムなど)
        error_log("JWT検証失敗: " . $e->getMessage());
        return false;
    }
}

// 使用例
$secret = 'your_super_secret_key_here_for_HS256'; // 本番環境では環境変数などから安全に取得する
$valid_jwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOmZhbHNlLCJpYXQiOjE1MTYyMzkwMjJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';

$decoded_payload = verifyAndDecodeJwt($valid_jwt, $secret);

if ($decoded_payload) {
    echo "JWT検証成功!\n";
    print_r($decoded_payload);
    // 認証・認可処理に進む
} else {
    echo "JWT検証失敗。\n";
    // エラーレスポンスを返す
}

// 🚨 攻撃者が改変したJWTの例 (alg=none) 🚨
$attack_jwt = 'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.'; 

echo "\n--- alg=none攻撃の検証 ---\n";
$decoded_attack_payload = verifyAndDecodeJwt($attack_jwt, $secret);

if ($decoded_attack_payload) {
    echo "JWT検証成功 (alg=none)!これは危険!\n"; // このメッセージは表示されないはず
} else {
    echo "JWT検証失敗 (alg=none)!防御成功!\n"; // こちらが表示されることを確認
}

Python (PyJWT) の例

import jwt
from jwt.exceptions import InvalidTokenError, DecodeError, ExpiredSignatureError, InvalidAlgorithmError

def verify_and_decode_jwt(jwt_token: str, secret_key: str) -> dict | None:
    """
    JWTの検証とデコードを行う関数
    :param jwt_token: 検証するJWT文字列
    :param secret_key: JWT署名用の秘密鍵
    :return: デコードされたペイロード辞書、または検証失敗時はNone
    """
    # 🚨 ここが最重要ポイント 🚨
    # 許可するアルゴリズムを明示的にリストで指定する。
    # HS256以外のアルゴリズムを意図的に受け入れる場合は、ここに追加する。
    # "none" は絶対に含めない。
    allowed_algorithms = ["HS256"] 
    
    try:
        # algorithms引数で許可するアルゴリズムのリストを渡すことで、"alg": "none"攻撃を防ぐ
        decoded_payload = jwt.decode(
            jwt_token, 
            secret_key, 
            algorithms=allowed_algorithms,
            # optional: verify_exp=True (有効期限の検証), verify_signature=True (署名の検証)
            # これらはデフォルトでTrueなので明示的に指定しなくても良いが、意図を明確にするために記述することもある
        )
        return decoded_payload
    except InvalidAlgorithmError:
        print("JWT検証失敗: 許可されていないアルゴリズムが使用されました。")
        return None
    except ExpiredSignatureError:
        print("JWT検証失敗: トークンの有効期限が切れています。")
        return None
    except InvalidTokenError as e:
        print(f"JWT検証失敗: 無効なトークンです。詳細: {e}")
        return None
    except Exception as e:
        print(f"予期せぬエラーが発生しました: {e}")
        return None

# 使用例
secret = 'your_super_secret_key_here_for_HS256' # 本番環境では環境変数などから安全に取得する
valid_jwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOmZhbHNlLCJpYXQiOjE1MTYyMzkwMjJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'

decoded_payload = verify_and_decode_jwt(valid_jwt, secret)

if decoded_payload:
    print("JWT検証成功!")
    print(decoded_payload)
    # 認証・認可処理に進む
else:
    print("JWT検証失敗。")
    # エラーレスポンスを返す

# 🚨 攻撃者が改変したJWTの例 (alg=none) 🚨
attack_jwt = 'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.' 

print("\n--- alg=none攻撃の検証 ---")
decoded_attack_payload = verify_and_decode_jwt(attack_jwt, secret)

if decoded_attack_payload:
    print("JWT検証成功 (alg=none)!これは危険!") # このメッセージは表示されないはず
else:
    print("JWT検証失敗 (alg=none)!防御成功!") # こちらが表示されることを確認

Node.js (jsonwebtoken) の例

const jwt = require('jsonwebtoken');

/**
 * JWTの検証とデコードを行う関数
 * @param {string} jwtToken 検証するJWT文字列
 * @param {string} secretKey JWT署名用の秘密鍵
 * @returns {object|null} デコードされたペイロードオブジェクト、または検証失敗時はnull
 */
function verifyAndDecodeJwt(jwtToken, secretKey) {
    // 🚨 ここが最重要ポイント 🚨
    // 許可するアルゴリズムを明示的に配列で指定する。
    // HS256以外のアルゴリズムを意図的に受け入れる場合は、ここに追加する。
    // "none" は絶対に含めない。
    const allowedAlgorithms = ['HS256'];

    try {
        // algorithmsオプションで許可するアルゴリズムの配列を渡すことで、"alg": "none"攻撃を防ぐ
        const decoded = jwt.verify(jwtToken, secretKey, {
            algorithms: allowedAlgorithms,
            // optional: ignoreExpiration: false (有効期限の検証), ignoreNotBefore: false (nbfクレームの検証)
            // これらはデフォルトでfalseなので明示的に指定しなくても良いが、意図を明確にするために記述することもある
        });
        return decoded;
    } catch (error) {
        console.error(`JWT検証失敗: ${error.message}`);
        return null;
    }
}

// 使用例
const secret = 'your_super_secret_key_here_for_HS256'; // 本番環境では環境変数などから安全に取得する
const validJwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOmZhbHNlLCJpYXQiOjE1MTYyMzkwMjJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';

let decodedPayload = verifyAndDecodeJwt(validJwt, secret);

if (decodedPayload) {
    console.log("JWT検証成功!");
    console.log(decodedPayload);
    // 認証・認可処理に進む
} else {
    console.log("JWT検証失敗。");
    // エラーレスポンスを返す
}

// 🚨 攻撃者が改変したJWTの例 (alg=none) 🚨
const attackJwt = 'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.'; 

console.log("\n--- alg=none攻撃の検証 ---");
let decodedAttackPayload = verifyAndDecodeJwt(attackJwt, secret);

if (decodedAttackPayload) {
    console.log("JWT検証成功 (alg=none)!これは危険!"); // このメッセージは表示されないはず
} else {
    console.log("JWT検証失敗 (alg=none)!防御成功!"); // こちらが表示されることを確認
}

—

補足:非対称鍵(RS256など)の利用も検討する

今回のalg=none攻撃は、対称鍵(HS256など)と非対称鍵(RS256など)のどちらを使っているかに関わらず発生しうる。しかし、鍵管理の観点から言えば、非対称鍵の利用はセキュリティを高める。

  • 対称鍵 (HS256): 署名と検証に同じ秘密鍵を使用する。サーバー側でこの秘密鍵が漏洩すると、攻撃者は任意のJWTを署名・検証できるようになる。
  • 非対称鍵 (RS256): 署名には秘密鍵を、検証には公開鍵を使用する。公開鍵が漏洩しても、攻撃者は新しいJWTを署名することはできない。秘密鍵はサーバーサイドでのみ厳重に管理し、公開鍵はクライアントや他のサービスに配布する。

可能であれば、公開鍵暗号方式であるRS256などのアルゴリズムへの移行も検討してほしい。もちろん、その際にも許可するアルゴリズムは厳格に指定するのを忘れるな。

2. サーバー・インフラ層での防御(多層防御)

アプリケーション層での対策が最も重要だが、WAF(Web Application Firewall)やAPI Gatewayを使って、不正なリクエストをさらに手前でブロックする「多層防御」の考え方も重要だ。WAFは、アプリケーションが脆弱性を抱えていた場合の最後の砦となり得る。

WAFでの防御(ModSecurity + Nginx の例)

ModSecurityのようなWAFでは、HTTPリクエストのボディやヘッダーを検査し、特定のパターンにマッチした場合にブロックするルールを設定できる。

Nginx + ModSecurity 設定例:

# Nginxのhttpブロックまたはserverブロック内に記述

http {
    # ModSecurityモジュールをロード
    modsecurity on;
    modsecurity_rules_file /etc/nginx/modsec/modsecurity.conf; # ModSecurityのメイン設定ファイルへのパス

    server {
        listen 80;
        server_name example.com;

        location /api/ {
            # 🚨 ここにJWT alg=noneを検知するModSecurityルールを追加 🚨
            # Authorizationヘッダーまたはリクエストボディ内のJWT文字列を検査
            # "alg":"none" のパターンを正規表現で検知し、ブロックする
            # 大文字小文字を区別しないように `i` フラグを使用
            # SecRule REQUEST_HEADERS:Authorization|REQUEST_BODY "@rx \"alg\"\\s*:\\s*\"none\"" \
            #     "id:100001,phase:2,block,msg:'JWT alg=none attack detected',log,auditlog"
            
            # より厳密に、Base64デコードされた部分を検査する例(複雑になるため、アプリケーション層での対策が基本)
            # ここではシンプルに生のJWT文字列内のパターンを検知するルールを提示
            #
            # REQUEST_HEADERS:Authorization: Authorizationヘッダーを検査
            # REQUEST_BODY: POST/PUTリクエストのボディを検査 (JWTがボディに含まれる場合)
            # "@rx": 正規表現マッチング
            # \"alg\"\\s*:\\s*\"none\": "alg":"none" または "alg" : "none" のようなパターンを検知
            # id: ルールID (ユニークな値)
            # phase: 2 (リクエストボディの検査フェーズ)
            # block: マッチした場合にリクエストをブロック
            # msg: ログに出力されるメッセージ
            # log, auditlog: イベントをログに記録
            SecRule REQUEST_HEADERS:Authorization|REQUEST_BODY "@rx \"alg\"\\s*:\\s*\"none\"" \
                "id:9000001,\
                phase:2,\
                block,\
                msg:'JWT alg=none detected - Possible Tampering Attempt',\
                log,\
                auditlog,\
                severity:'CRITICAL'"
            
            # proxy_passの設定など、通常のAPI処理
            proxy_pass http://backend_server;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }
}

ModSecurityの注意点:

  • WAFは強力だが、あくまで補助的な防御策だ。最も信頼できるのはアプリケーションコードでの厳格な検証だ。
  • 正規表現の誤設定は、正当なリクエストをブロックしたり、逆に攻撃を見逃したりするリスクがある。テストを十分に行うこと。
  • Base64エンコードされたJWTの内部をWAFで完全に検査するには、より高度な設定やカスタムモジュールが必要になる場合がある。

クラウドWAF(AWS WAFなど)での防御

AWS WAFのようなクラウドWAFでも、カスタムルールを作成してalg=none攻撃を防ぐことができる。

AWS WAF カスタムルール設定例:

1. ルールタイプ: String match rule または Regex match rule を選択。
2. Part of the request to filter on:

  • HTTP header を選択し、Header field name に Authorization を指定。
  • JWTがリクエストボディに含まれる場合は、Body も対象に含める。

3. Match type:

  • Contains string を選択し、String to match に alg":"none を指定する。
  • または Regex match を選択し、Regex pattern に \"alg\"\\s*:\\s*\"none\" を指定する。

4. Text transformation: NONE または LOWERCASE (正規表現によっては必要)
5. Action: BLOCK

これで、Authorizationヘッダーやリクエストボディ内に"alg":"none"パターンを含むリクエストをブロックできる。

—

まとめと次のステップ

今日の話、どうだった? JWTのalg=none攻撃は、古参の攻撃手法でありながら、未だにシステムに大きなダメージを与える可能性を秘めている。それは、ライブラリのデフォルト設定や開発者の見落としという、ソフトウェア開発につきものの「人間的な隙」を突くからだ。

改めて、この攻撃からシステムを守るための最も重要なポイントは、JWTライブラリで許可する署名アルゴリズムを明示的に、かつ厳格に指定することだ。そして、WAFなどのインフラ層での対策は、アプリケーション層の防御を補完する多層防御の一環として非常に有効だ。

君たちの手で、今一度、既存のシステムでJWTがどのように検証されているか、徹底的に見直してほしい。そして、新しいAPIを開発する際には、今日の話を思い出して、このセキュアな実装を最初から組み込んでくれ。

セキュリティは「言われたからやる」ものではない。「なぜ必要なのか」を理解し、自らの手で堅牢なシステムを築き上げる、クリエイティブな仕事だ。頼むぞ、未来のシステムは君たちにかかっているんだからな。

もし何か疑問があれば、いつでも俺に聞いてくれ。一緒に、よりセキュアなシステムを創っていこうじゃないか。

コメント

タイトルとURLをコピーしました