「あなたのWebサイト、本当に安全ですか?」
突然ですが、そう聞かれて自信を持って「はい」と答えられるでしょうか。インターネットが社会インフラとなった現代、Webサイトのセキュリティは決して他人事ではありません。特に、SQLインジェクション(SQL Injection) は、古くから存在するにもかかわらず、依然として猛威を振るう深刻なサイバー攻撃の一つです。
たった一行の不正な文字列によって、データベースに保存された顧客情報や機密情報が盗まれたり、Webサイトが改ざんされたりする可能性があるのです。
この記事を読めば、以下のことが分かります。
- SQLインジェクションがどのような攻撃なのか、その基本的な仕組み
- なぜSQLインジェクションがこれほど危険なのか
- ハッカーが実際に使う攻撃コードの具体例とその解説(
' OR '1'='1'--
やUNION SELECT
など) - あなたのWebサイトをSQLインジェクションから守るための具体的な対策方法
この記事は、Web開発者の方はもちろん、Webサイト運営者、セキュリティに関心のあるすべての方に向けて、SQLインジェクションの脅威とその対策を、わかりやすく解説することを目指しています。
記事の概要
- SQLインジェクションとは? – 攻撃の基本原理と危険性を易しく解説
- なぜ起こる?SQLインジェクションの仕組み – データベースとプログラムの連携部分に潜む脆弱性を図解
- SQLインジェクションの種類 – 代表的な攻撃パターンを紹介
- 【深掘り】攻撃コード徹底解説 – 実際の攻撃で使われるコードの意味をステップバイステップで解き明かす
- 認証回避:
' OR '1'='1'--
- 情報窃取 (UNION SELECT): テーブル構造の調査からデータ抜き取りまで
- 認証回避:
- 被害の深刻さ – SQLインジェクションが引き起こす具体的な損害
- 【最重要】SQLインジェクション対策 – 今すぐできる具体的な防御策
- プリペアドステートメント (Prepared Statements)
- エスケープ処理
- 入力値の検証 (バリデーション)
- WAF (Web Application Firewall) の導入
- まとめ – 継続的なセキュリティ対策の重要性
SQLインジェクションとは?
SQLインジェクションとは、Webアプリケーションの脆弱性を利用して、不正なSQL文(データベースへの命令文)を実行させる攻撃のことです。
多くのWebサイトでは、ユーザーが入力した情報(ID、パスワード、検索キーワードなど)をもとに、データベースから必要な情報を取り出したり、情報を書き換えたりしています。このとき、Webアプリケーションがユーザーからの入力を適切に処理していないと、攻撃者は入力フォームなどに不正なSQL文の断片を「注入(インジェクション)」し、本来意図されていないデータベース操作を実行できてしまうのです。
例えるなら…
レストランであなたが「いつもの(メニュー名)」と注文したとします。通常、店員さんは厨房に「(メニュー名)を一つ」と伝えます。しかし、もし悪意のある人があなたの注文に紛れて「いつもの、それとレジのお金全部」というメモを店員さんに渡したらどうでしょう?もし店員さんがメモの内容を疑わずにそのまま厨房(データベース)に伝えてしまったら、大変なことになりますよね。SQLインジェクションは、これと似たような原理で起こる攻撃なのです。
なぜ危険なのか?
SQLインジェクションが成功すると、攻撃者はデータベースに対して以下のような操作が可能になります。
- 情報の窃取: 個人情報、クレジットカード情報、ログイン情報などの機密データを盗み出す。
- データの改ざん: データベースの内容を不正に書き換える。
- データの削除: 重要なデータを消去する。
- サービス停止: データベースを破壊し、Webサイトの機能を停止させる。
- 不正操作: 他のユーザーになりすましてログインしたり、不正な操作を行ったりする。
- OSコマンド実行: 場合によっては、データベースサーバーを通じてOSコマンドを実行し、サーバー自体を乗っ取る。
このように、SQLインジェクションは非常に深刻な被害を引き起こす可能性がある、極めて危険な脆弱性なのです。独立行政法人情報処理推進機構(IPA)が発表する「情報セキュリティ10大脅威」でも、組織向け・個人向けともに長年ランクインしており、その脅威は継続しています。
なぜ起こる?SQLインジェクションの仕組み
SQLインジェクションは、主にWebアプリケーションがユーザーからの入力を検証せずに、そのままSQL文の組み立てに使用してしまうことが原因で発生します。
通常の処理:
- ユーザーがWebフォームにIDとパスワードを入力する。
- Webアプリケーションは入力されたIDとパスワードを受け取る。
- Webアプリケーションは、受け取ったIDとパスワードを使って、データベースに問い合わせるためのSQL文を動的に生成する。(例:
SELECT * FROM users WHERE user_id = '入力されたID' AND password = '入力されたパスワード'
) - データベースはSQL文を実行し、該当するユーザー情報があればWebアプリケーションに返す。
- Webアプリケーションは結果に基づき、ログイン成功または失敗の画面を表示する。
SQLインジェクション攻撃の場合:
問題はステップ3のSQL文の動的生成にあります。もし、入力値をチェックせずにそのままSQL文に埋め込んでいると…
- 攻撃者がパスワード入力欄に
pass' OR '1'='1'--
という文字列を入力する。 - Webアプリケーションはこれを受け取る。
- Webアプリケーションは、入力値をそのまま埋め込んでSQL文を生成する。 元のSQL文:
SELECT * FROM users WHERE user_id = 'ユーザーID' AND password = '
入力されたパスワード
'
生成されるSQL文:SELECT * FROM users WHERE user_id = 'ユーザーID' AND password = '
pass' OR '1'='1'--
'
この不正なSQL文がデータベースで実行されるとどうなるでしょうか? これについては、後の「【深掘り】攻撃コード徹底解説」で詳しく見ていきましょう。
ポイント: ユーザーが入力する値は、常に「悪意のあるものである可能性」を考慮し、そのまま信用してSQL文の部品にしてはいけない、ということです。
SQLインジェクションの種類
SQLインジェクションには、攻撃手法や目的によっていくつかの種類があります。代表的なものをいくつか紹介します。
- エラーベースSQLインジェクション: データベースのエラーメッセージを利用して情報を得る手法。わざとエラーを引き起こすSQL文を注入し、表示されるエラーメッセージからデータベースの構造やバージョンなどの情報を推測します。
- UNIONベースSQLインジェクション:
UNION
演算子を使って、本来のクエリ結果に加えて、攻撃者が指定した別のテーブルの情報を結合させて表示させる手法。情報の窃取によく使われます。 - ブラインドSQLインジェクション: データベースからの直接的な応答(エラーメッセージやデータ)が得られない場合に用いられる手法。条件分岐(
WHERE
句など)と、データベースの応答の違い(表示される内容や応答時間など)を利用して、情報を少しずつ(1文字ずつなど)抜き出します。時間差攻撃(Time-based Blind SQLi)もこの一種です。 - セカンドオーダーSQLインジェクション: 注入された不正なSQL文がすぐには実行されず、一度データベースに保存された後、別の処理で呼び出された際に実行される手法。対策が見落とされやすいケースがあります。
【深掘り】攻撃コード徹底解説
ここでは、冒頭で触れた具体的な攻撃コード(ペイロード)が、どのようにしてSQLインジェクションを引き起こすのか、その仕組みを詳しく見ていきましょう。
認証回避: pass' OR '1'='1'--
これは、ログイン認証などを不正に突破するためによく使われる古典的なペイロードです。
状況: ユーザーIDとパスワードを入力してログインするWebサイトがあるとします。パスワード入力欄にこのペイロードが入力された場合を考えます。
元のSQL文 (想定): SELECT * FROM users WHERE user_id = '入力されたID' AND password = '
入力されたパスワード
';
注入後のSQL文: SELECT * FROM users WHERE user_id = 'ユーザーID' AND password = '
pass' OR '1'='1'--
';
解説:
password = 'pass'
: まず、パスワードが ‘pass’ であるかどうかが評価されます。これは通常、偽(False)になるでしょう。'
:pass'
の後ろのシングルクォートは、password = '...'
の部分の閉じ引用符として解釈されます。これにより、password
カラムの条件指定はここまでで一旦終了します。OR '1'='1'
: 次にOR
演算子が来ます。'1'='1'
は常に**真(True)となる条件です。OR
演算子は、左右どちらかの条件が真であれば、全体の条件式を真と評価します。つまり、password = 'pass'
が偽(False)であっても、'1'='1'
が真(True)なので、password = 'pass' OR '1'='1'
全体としては常に真(True)**になります。--
: これはSQLにおけるコメントアウト記号です。この記号以降の文字列は、データベースによって無視されます。これにより、元のSQL文にあった最後のシングルクォート(';
の''
)が無効化され、SQL文法エラーを防いでいます。(※データベースの種類によっては#
や/* ... */
が使われることもあります。)
結果: このSQL文は、user_id
が存在しさえすれば、password = 'pass' OR '1'='1'
の部分が常に真(True)になるため、パスワードが間違っていても、あるいは入力されていなくても、認証が成功してしまうのです。
図解:
コード スニペット
graph TD
A[パスワード入力欄に<br>`pass' OR '1'='1'--` を入力] --> B(Webアプリケーション);
B --> C{SQL文生成<br>`SELECT * FROM users WHERE user_id = '...' AND password = 'pass' OR '1'='1'--'`};
C --> D[データベース];
D --> E{SQL実行<br>WHERE句の条件:<br>password = 'pass' (偽) <br>OR<br>'1'='1' (真)<br>→ 全体が真に!};
E --> F[認証成功!<br>(パスワード不一致でも)];
情報窃取 (UNION SELECT)
UNION
演算子は、複数のSELECT
文の結果を一つにまとめるためのものです。攻撃者はこれを利用して、本来のクエリ結果に加えて、データベース内の他のテーブルから情報を盗み出そうとします。
ここでは、SQLiteデータベースを例に、sqlite_master
という特別なテーブル(データベース内のテーブル定義などが格納されている)から情報を抜き出す手順を見ていきましょう。(※MySQLやPostgreSQLなど他のデータベースでは information_schema
というテーブルが同様の役割を果たします。)
前提:
- Webサイトには、何かを検索する機能があり、その結果を表示しているとします。(例: 商品検索、記事検索など)
- 検索結果を表示するSQL文が、例えば
SELECT name, description, price FROM products WHERE category = '入力されたカテゴリ'
のようになっていると仮定します。このクエリは3つのカラム(name
,description
,price
)を返します。 - 攻撃者は、カテゴリ入力欄に不正な文字列を注入します。
ステップ1: テーブル構造の調査 ' OR '1'='1' UNION SELECT 1,2,3 FROM sqlite_master --
注入後のSQL文 (簡略化): SELECT name, description, price FROM products WHERE category = '' OR '1'='1' UNION SELECT 1,2,3 FROM sqlite_master --'
解説:
' OR '1'='1'
: まず、WHERE
句の条件を常に真(True)にして、products
テーブルの何らかのデータを取得しようとします。(場合によっては、元のSELECT
文が何も返さないように条件を操作することもあります。)UNION SELECT 1,2,3 FROM sqlite_master
: ここが重要です。UNION
を使って、sqlite_master
テーブルの内容を追加で取得しようとしています。sqlite_master
: SQLiteのシステムテーブル。テーブル名やカラム定義などが格納されています。1,2,3
:UNION
を使う際の重要なルールは、結合するSELECT文のカラム数が同じである必要があることです。元のクエリが3つのカラム (name
,description
,price
) を返しているので、UNION
の後も3つのカラムを指定する必要があります。ここでは、まずカラム数を合わせるために、仮のデータとして数値の1
,2
,3
を指定しています。このクエリが成功すれば、Webページ上に1
,2
,3
といったデータが表示されるはずです。これにより、攻撃者は「UNION SELECT攻撃が可能なこと」と「元のクエリのカラム数が3であること」を知ることができます。
--
: コメントアウトで、元のSQL文の残りを無効化します。
ステップ2: テーブル名の取得 ' OR '1'='1' UNION SELECT name, 2, 3 FROM sqlite_master WHERE type='table' --
注入後のSQL文 (簡略化): SELECT name, description, price FROM products WHERE category = '' OR '1'='1' UNION SELECT name, 2, 3 FROM sqlite_master WHERE type='table' --'
解説:
- ステップ1でカラム数が3であることが分かったので、今度は
sqlite_master
テーブルから具体的な情報を取得します。 SELECT name, 2, 3
: 1番目のカラムとしてsqlite_master
のname
カラム(テーブル名やインデックス名などが格納されている)を指定します。2番目と3番目はカラム数を合わせるためのダミーデータです。FROM sqlite_master WHERE type='table'
:sqlite_master
テーブルの中から、type
が ‘table’ であるもの(つまりテーブル定義)のname
(テーブル名)を取得します。- 結果: Webページ上に、データベース内に存在するテーブル名の一覧(例えば
users
,products
,credit_cards
など)が表示される可能性があります。
ステップ3: テーブル定義 (カラム名) の取得 ' OR '1'='1' UNION SELECT sql, 2, 3 FROM sqlite_master WHERE type='table' AND name='テーブル名' --
(※ テーブル名
にはステップ2で取得した、目的のテーブル名が入る。例えば users
など)
注入後のSQL文 (簡略化): SELECT name, description, price FROM products WHERE category = '' OR '1'='1' UNION SELECT sql, 2, 3 FROM sqlite_master WHERE type='table' AND name='users' --'
解説:
SELECT sql, 2, 3
:sqlite_master
テーブルのsql
カラムには、テーブルを作成した際のSQL文(CREATE TABLE ...
文)が格納されています。これを取得します。WHERE type='table' AND name='users'
:users
テーブルの定義情報を指定します。- 結果: Webページ上に
users
テーブルのCREATE TABLE
文が表示され、攻撃者はusers
テーブルに含まれるカラム名(例:user_id
,password
,email
など)を知ることができます。
ステップ4: 機密データの窃取 ' OR '1'='1' UNION SELECT user_id, password, email FROM users --
(※ カラム名はステップ3で取得した情報に基づき、盗みたい情報を指定する)
注入後のSQL文 (簡略化): SELECT name, description, price FROM products WHERE category = '' OR '1'='1' UNION SELECT user_id, password, email FROM users --'
解説:
UNION SELECT user_id, password, email FROM users
: いよいよ目的のテーブル(ここではusers
)から、欲しいカラム(user_id
,password
,email
)のデータを直接抜き出します。ここでも、元のクエリのカラム数(3つ)に合わせて指定しています。- 結果: Webページ上に、
users
テーブルに格納されている全ユーザーのID、パスワード、メールアドレスが表示されてしまい、大規模な情報漏洩につながります。
UNION SELECT のポイント:
- 元のSQL文のカラム数を特定する必要がある。
- データベースの構造(テーブル名、カラム名)を段階的に調査できる。
- 最終的に任意のテーブルからデータを窃取できる。
図解 (UNION SELECTによる情報窃取の流れ):
コード スニペット
graph TD
subgraph "ステップ1: カラム数特定"
A[入力欄に<br>`' OR '1'='1' UNION SELECT 1,2,3...` を注入] --> B{Webアプリ経由でDBへ};
B --> C{UNION成功?<br>ページに1,2,3が表示?};
C -- Yes --> D(カラム数を特定!);
end
subgraph "ステップ2: テーブル名取得"
D --> E[入力欄に<br>`UNION SELECT name,2,3 FROM sys_table` を注入];
E --> F{Webアプリ経由でDBへ};
F --> G{ページにテーブル名<br>(users, products等)が表示?};
G -- Yes --> H(テーブル名を特定!);
end
subgraph "ステップ3: カラム名取得"
H --> I[入力欄に<br>`UNION SELECT sql,2,3 FROM sys_table WHERE name='users'` を注入];
I --> J{Webアプリ経由でDBへ};
J --> K{ページにCREATE TABLE文<br>(user_id, password等)が表示?};
K -- Yes --> L(カラム名を特定!);
end
subgraph "ステップ4: データ窃取"
L --> M[入力欄に<br>`UNION SELECT user_id,password,email FROM users` を注入];
M --> N{Webアプリ経由でDBへ};
N --> O[ページに<br>ID, パスワード, メール等が<br>大量に表示される!];
O --> P(情報漏洩!);
end
%% sys_table は sqlite_master や information_schema を意味する
免責事項: 上記のペイロード例は、SQLインジェクションの仕組みを理解するための教育目的でのみ解説しています。許可なく他者のシステムに対してこれらのコードを試すことは、法律で禁止された不正アクセス行為にあたります。絶対に悪用しないでください。
被害の深刻さ – SQLインジェクションがもたらすもの
SQLインジェクション攻撃が成功した場合、その被害は甚大なものになる可能性があります。
- 顧客情報・個人情報の漏洩: 氏名、住所、電話番号、メールアドレス、クレジットカード情報などが流出し、悪用されるリスクがあります。これは顧客からの信頼を失墜させ、企業の評判に深刻なダメージを与えます。損害賠償請求に発展するケースも少なくありません。
- 機密情報の漏洩: 企業の内部情報、技術情報、取引情報などが盗まれ、競合他社に渡るなどの被害が考えられます。
- Webサイトの改ざん: Webサイトの内容が書き換えられたり、不正なスクリプトが埋め込まれたりして、フィッシングサイトへ誘導されたり、マルウェア配布の踏み台にされたりする可能性があります。
- 金銭的被害: 不正送金、ECサイトでの不正購入など、直接的な金銭被害が発生することがあります。
- サービス停止: データベースが破壊されたり、サーバーがダウンしたりして、Webサイトや関連サービスが利用できなくなり、ビジネス機会の損失につながります。
- 法的責任・社会的信用の失墜: 情報漏洩やサービス停止により、法的な責任を問われたり、社会的な信用を大きく損なったりする可能性があります。
【最重要】SQLインジェクション対策 – あなたのサイトを守る盾
幸いなことに、SQLインジェクションは適切な対策を講じることで防ぐことができる脆弱性です。ここでは、主要な対策方法をいくつか紹介します。
1. プリペアドステートメント (Prepared Statements) の利用
これが最も重要かつ効果的な対策の一つです。
プリペアドステートメントは、SQL文の「テンプレート」をあらかじめデータベースに送信しておき、後からユーザーが入力した値を「パラメータ」として渡す仕組みです。
仕組み:
- テンプレート準備: まず、SQL文の骨格(テンプレート)を定義します。このとき、ユーザー入力が入る部分は
?
や:name
などのプレースホルダにしておきます。 例:SELECT * FROM users WHERE user_id = ? AND password = ?
- テンプレート送信: このテンプレートをデータベースに送信し、「コンパイル(解釈・実行計画作成)」してもらいます。この段階では、まだ実際の値は含まれていません。
- パラメータ送信: 次に、ユーザーが入力した実際の値(例:
ユーザーID
,パスワード
)を、プレースホルダに対応する「パラメータ」として別途データベースに送信します。 - 安全な実行: データベースは、コンパイル済みのテンプレートに、送られてきたパラメータを安全な方法で埋め込み、SQL文を実行します。
なぜ安全なのか? テンプレートとパラメータを分離して扱うため、後から送られてきたパラメータ(ユーザー入力値)は、単なる値(データ)として扱われ、SQL文の構文(命令)の一部として解釈されることがありません。たとえ入力値に ' OR '1'='1'--
のような文字列が含まれていても、それは単なる「そういう文字列のパスワード」として扱われるだけで、SQL文のロジックを変えることはできないのです。
例 (PHP PDOの場合):
PHP
<?php
// データベース接続 (省略)
// ユーザーからの入力
$userId = $_POST['user_id'];
$password = $_POST['password'];
// ★ 1. テンプレート準備 (プレースホルダを使用)
$sql = "SELECT * FROM users WHERE user_id = :userId AND password = :password";
$stmt = $pdo->prepare($sql); // ★ 2. テンプレート送信・コンパイル
// ★ 3. パラメータ送信 (値をバインド)
$stmt->bindParam(':userId', $userId, PDO::PARAM_STR);
$stmt->bindParam(':password', $password, PDO::PARAM_STR);
// ★ 4. 安全な実行
$stmt->execute();
// 結果の取得 (省略)
?>
多くのプログラミング言語やデータベースフレームワークで、プリペアドステートメント(またはバインド機構)を利用する機能が提供されています。原則として、SQL文を動的に生成する場合は、必ずプリペアドステートメントを使用してください。
2. エスケープ処理
プリペアドステートメントが利用できない場合や、補完的な対策として、SQL文中で特別な意味を持つ文字(例: シングルクォート '
, ダブルクォート "
, バックスラッシュ \
など)を、無害な別の文字列表現に変換(エスケープ)する方法があります。
例えば、シングルクォート '
を ''
(シングルクォート2つ)や \'
(バックスラッシュとシングルクォート)に変換することで、文字列リテラルを閉じる記号として解釈されるのを防ぎます。
注意点:
- 適切なエスケープ処理は複雑: データベースの種類や文字コードによって、エスケープすべき文字や方法が異なります。不完全なエスケープ処理は脆弱性を残す可能性があります。
- プリペアドステートメントの方が安全: 可能であればプリペアドステートメントの使用を優先すべきです。エスケープ処理は、それが使えない場合の次善策、または併用する対策と考えるのが良いでしょう。
3. 入力値の検証 (バリデーション)
ユーザーからの入力値が、想定される形式や範囲に収まっているかをチェックすることも重要です。
- 型チェック: 数値であるべき箇所に文字列が入っていないか?
- 文字種チェック: 特定の文字(例: メールアドレスに許可されない文字)が含まれていないか?
- 長さチェック: 入力値が異常に長くないか?
- ホワイトリスト方式: 許可する文字やパターンを定義し、それ以外は受け付けないようにする。(例: ユーザーIDは半角英数字のみ許可)
入力値の検証は、SQLインジェクションだけでなく、クロスサイトスクリプティング(XSS)など他の脆弱性対策にも有効です。ただし、バリデーションだけではSQLインジェクションを完全に防ぐことは難しいため、必ずプリペアドステートメントやエスケープ処理と組み合わせて実施してください。
4. WAF (Web Application Firewall) の導入
WAFは、Webアプリケーションの前段に設置され、送受信されるHTTPリクエスト/レスポンスを監視し、不正な通信(攻撃パターンに合致する通信)を検知・遮断するセキュリティ対策製品です。
SQLインジェクションの典型的な攻撃パターン(例: ' OR '1'='1'
や UNION SELECT
などを含むリクエスト)を検知し、ブロックすることができます。
WAFのメリット:
- 既存のWebアプリケーションのコードを改修せずに、比較的容易に導入できる。
- SQLインジェクション以外の様々なWebアプリケーション脆弱性(XSS、OSコマンドインジェクションなど)にも対応できる。
WAFの注意点:
- 万能ではない: 未知の攻撃パターンや巧妙に偽装された攻撃は検知できない可能性があります(誤検知・検知漏れ)。
- 根本対策ではない: WAFはあくまで保険のようなものであり、アプリケーション自体の脆弱性を修正する(セキュアコーディングを行う)ことが最も重要です。プリペアドステートメントなどの対策は必須です。
専門家の意見: 多くのセキュリティ専門家は、「多層防御」の重要性を指摘しています。つまり、プリペアドステートメント、入力値検証、そしてWAFといった複数の対策を組み合わせることで、より堅牢なセキュリティ体制を築くことができるのです。
まとめ – 絶え間ない警戒と対策を
SQLインジェクションは、Webアプリケーション開発における基本的なセキュリティ対策を怠ると容易に発生してしまう、深刻な脆弱性です。その攻撃手法は巧妙化し続けており、一度対策したからといって安心はできません。
この記事で解説した内容をまとめます。
- SQLインジェクションは不正なSQL文を注入する攻撃であり、情報漏洩やデータ改ざんなどの深刻な被害を引き起こす。
- ユーザー入力を検証せずにSQL文を組み立てることが主な原因。
' OR '1'='1'--
やUNION SELECT
などのペイロードで、認証回避や情報窃取が可能になる。- 対策の基本は「プリペアドステートメント」の利用。
- 入力値の検証、エスケープ処理、WAFの導入も有効な対策。
Webサイトやサービスを開発・運用する上で、セキュリティ対策は必要不可欠なコストであり、責任です。常に最新の脆弱性情報に関心を持ち、セキュアコーディングの原則を学び、実践し続けることが重要です。
この記事が、SQLインジェクションへの理解を深め、あなたの貴重な情報資産を守るための一助となれば幸いです。安全なインターネット環境の実現に向けて、共に取り組みましょう。
参考文献・引用元:
- 独立行政法人情報処理推進機構 (IPA) – 安全なウェブサイトの作り方: https://www.ipa.go.jp/security/vuln/websecurity.html
- OWASP (Open Web Application Security Project) – SQL Injection: https://owasp.org/www-community/attacks/SQL_Injection
- OWASP Top 10: https://owasp.org/www-project-top-ten/
たび友|サイトマップ
関連webアプリ
たび友|サイトマップ:https://tabui-tomo.com/sitemap
索友:https://kentomo.tabui-tomo.com
ピー友:https://pdftomo.tabui-tomo.com
パス友:https://passtomo.tabui-tomo.com
クリプ友:https://cryptomo.tabui-tomo.com
進数友:https://shinsutomo.tabui-tomo.com