Compare commits

..

2 Commits

Author SHA1 Message Date
posimai 548c4fcca2 security: 認証・レート制限の5箇所を修正
- /journal/posts/public: 任意ユーザー列挙を廃止、SITE_PUBLIC_USER 固定
- /site/config/public: IDOR 修正 + 公開キーをホワイトリスト制限
- POST /together/groups: IP 単位 5回/時間 のレート制限を追加
- POST /together/join: IP 単位 10回/時間 のレート制限を追加
- POST /together/share: Gemini archive を shared_by 単位 20回/時間 に制限
- POST /ponshu/license/validate: IP 単位 10回/分 のブルートフォース防止

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 13:30:10 +09:00
posimai 10071901e1 feat(posimai-sc): S31 compliance intro, beginner glossaries, S16 Q4, SW v2; dashboard timeline and projects
Made-with: Cursor
2026-04-20 13:08:49 +09:00
6 changed files with 82 additions and 32 deletions

View File

@ -412,7 +412,7 @@ header{display:flex;align-items:center;justify-content:space-between;padding:0 1
<div x-show="!currentUnit && !weakDrillActive"> <div x-show="!currentUnit && !weakDrillActive">
<div class="home-hero"> <div class="home-hero">
<h1>情報処理<span>安全確保支援士</span></h1> <h1>情報処理<span>安全確保支援士</span></h1>
<p>セキュリティの基礎から攻撃手法・法規まで、AM2試験に対応した30単元を体系的に学習します。開発者の視点から「なぜそうなのか」を理解しましょう。</p> <p>セキュリティの基礎から攻撃手法・法規まで、AM2試験に対応した31単元を体系的に学習します。開発者の視点から「なぜそうなのか」を理解しましょう。</p>
<div class="stats-row"> <div class="stats-row">
<div class="stat-card"> <div class="stat-card">
<div class="stat-val" x-text="doneCount"></div> <div class="stat-val" x-text="doneCount"></div>

View File

@ -1,7 +1,7 @@
export const CATEGORIES = [ export const CATEGORIES = [
{ id:'basics', label:'セキュリティ基礎', icon:'shield', units:[ { id:'basics', label:'セキュリティ基礎', icon:'shield', units:[
{ id:'s01', num:'S01', title:'CIA三要素', freq:'high', diff:1, { id:'s01', num:'S01', title:'CIA三要素', freq:'high', diff:1,
concept:`<p>情報セキュリティの根幹は<strong>CIA三要素</strong>です。すべてのセキュリティ対策はこのどれかを守るために存在します。</p><div class="viz-cia"><div class="cia-card cia-c"><div class="cia-name">機密性</div><div class="cia-en">Confidentiality</div><div class="cia-desc">許可された人だけが情報にアクセスできる。暗号化・アクセス制御で実現。</div></div><div class="cia-card cia-i"><div class="cia-name">完全性</div><div class="cia-en">Integrity</div><div class="cia-desc">情報が正確で改ざんされていない。ハッシュ・デジタル署名で検証。</div></div><div class="cia-card cia-a"><div class="cia-name">可用性</div><div class="cia-en">Availability</div><div class="cia-desc">必要なときに情報を使える。冗長化・バックアップで確保。</div></div></div><p>開発者視点で言い換えると機密性は「見せない」、完全性は「改ざんさせない」、可用性は「止めない」です。DoS攻撃は可用性を、SQLインジェクションは機密性・完全性を脅かします。</p>`, concept:`<p>情報セキュリティの根幹は<strong>CIA三要素</strong>です。すべてのセキュリティ対策はこのどれかを守るために存在します。</p><div class="formula-box"><div class="formula-label">初学者向け:用語のイメージ</div><strong>機密性</strong>:秘密にしたい内容を、許可のない人に見られないようにすること。<br><strong>完全性</strong>:データが途中で改ざんされていないかを確認できること。<br><strong>可用性</strong>:サービスやファイルを、必要なときにいつでも使える状態に保つこと。</div><div class="viz-cia"><div class="cia-card cia-c"><div class="cia-name">機密性</div><div class="cia-en">Confidentiality</div><div class="cia-desc">許可された人だけが情報にアクセスできる。暗号化・アクセス制御で実現。</div></div><div class="cia-card cia-i"><div class="cia-name">完全性</div><div class="cia-en">Integrity</div><div class="cia-desc">情報が正確で改ざんされていない。ハッシュ・デジタル署名で検証。</div></div><div class="cia-card cia-a"><div class="cia-name">可用性</div><div class="cia-en">Availability</div><div class="cia-desc">必要なときに情報を使える。冗長化・バックアップで確保。</div></div></div><p>開発者視点で言い換えると機密性は「見せない」、完全性は「改ざんさせない」、可用性は「止めない」です。DoS攻撃は可用性を、SQLインジェクションは機密性・完全性を脅かします。</p>`,
examtips:[ examtips:[
'三要素のどれが損なわれているかを問う問題が頻出。攻撃とCIA要素のマッピングを整理すること。', '三要素のどれが損なわれているかを問う問題が頻出。攻撃とCIA要素のマッピングを整理すること。',
'<strong>真正性Authenticity・信頼性Reliability・否認防止Non-repudiation</strong>など追加要素も出題される。CIAだけが全てではない。', '<strong>真正性Authenticity・信頼性Reliability・否認防止Non-repudiation</strong>など追加要素も出題される。CIAだけが全てではない。',
@ -22,7 +22,7 @@ export const CATEGORIES = [
] ]
}, },
{ id:'s02', num:'S02', title:'脅威・脆弱性・リスク', freq:'high', diff:1, { id:'s02', num:'S02', title:'脅威・脆弱性・リスク', freq:'high', diff:1,
concept:`<p>リスク管理の基礎概念を整理します。<strong>脅威</strong>は攻撃者や災害など「悪いことが起きる原因」、<strong>脆弱性</strong>はシステムや運用の「穴」、<strong>リスク</strong>はそれらが組み合わさって「実際に被害が発生する可能性と影響度」です。</p><div class="formula-box"><div class="formula-label">リスクの計算(概念式)</div>リスク = 脅威 × 脆弱性 × 資産価値<br><br>リスク対応の4戦略<br>・回避(リスクが生じる活動をやめる)<br>・低減(対策を実施して確率・影響を下げる)<br>・移転(保険・外部委託でリスクを移す)<br>・受容(コスト対効果で許容する)</div><p>開発者にとって「脆弱性」とはバグや設定ミスのこと。CVEデータベースに登録される脆弱性情報は、脅威アクターが攻撃に利用する前にパッチを当てるために使います。</p>`, concept:`<p>リスク管理の基礎概念を整理します。<strong>脅威</strong>は攻撃者や災害など「悪いことが起きる原因」、<strong>脆弱性</strong>はシステムや運用の「穴」、<strong>リスク</strong>はそれらが組み合わさって「実際に被害が発生する可能性と影響度」です。</p><div class="formula-box"><div class="formula-label">初学者向け3語の違い</div><strong>脅威</strong>:被害をもたらし得る「原因側」(マルウェア、災害、内部不正など)。<br><strong>脆弱性</strong>:攻撃や事故に利用されやすい「弱点」(未パッチ、弱いパスワード設定など)。<br><strong>リスク</strong>:脅威が脆弱性を突いたときに、どれだけ損失が起きうるかという見込み。</div><div class="formula-box"><div class="formula-label">リスクの計算(概念式)</div>リスク = 脅威 × 脆弱性 × 資産価値<br><br>リスク対応の4戦略<br>・回避(リスクが生じる活動をやめる)<br>・低減(対策を実施して確率・影響を下げる)<br>・移転(保険・外部委託でリスクを移す)<br>・受容(コスト対効果で許容する)</div><p>開発者にとって「脆弱性」とはバグや設定ミスのこと。CVEデータベースに登録される脆弱性情報は、脅威アクターが攻撃に利用する前にパッチを当てるために使います。</p>`,
examtips:[ examtips:[
'脅威・脆弱性・リスクの定義の違いを問う問題は毎回出る。「脆弱性」は弱点であり攻撃そのものではない。', '脅威・脆弱性・リスクの定義の違いを問う問題は毎回出る。「脆弱性」は弱点であり攻撃そのものではない。',
'リスク対応4戦略回避・低減・移転・受容の具体例を各1つ言えるようにする。', 'リスク対応4戦略回避・低減・移転・受容の具体例を各1つ言えるようにする。',
@ -43,7 +43,7 @@ export const CATEGORIES = [
] ]
}, },
{ id:'s03', num:'S03', title:'認証技術', freq:'high', diff:2, { id:'s03', num:'S03', title:'認証技術', freq:'high', diff:2,
concept:`<p>認証は「あなたが本当にあなたか」を確認するプロセスです。認証要素は3種類あり、複数組み合わせることで強度が上がります。</p><div class="formula-box"><div class="formula-label">認証の3要素</div>知識要素Something you knowパスワード・PIN・秘密の質問<br>所持要素Something you haveICカード・スマートフォン・ハードウェアトークン<br>生体要素Something you are指紋・顔認証・虹彩</div><p>2要素以上を組み合わせるのが<strong>多要素認証MFA</strong>。SSOはIDPが認証を一元管理し、アプリはそのトークンを信頼するモデルです。開発者にとってJWTJSON Web TokenはSSO文脈でよく使うものです。</p><div class="viz-flow"><div class="vf-node">ユーザー<div class="vf-sub">ID/PW入力</div></div><div class="vf-arrow">→</div><div class="vf-node vf-hl">IdP<div class="vf-sub">認証・発行</div></div><div class="vf-arrow">→</div><div class="vf-node">トークン<div class="vf-sub">JWT/SAML</div></div><div class="vf-arrow">→</div><div class="vf-node">サービス<div class="vf-sub">検証・許可</div></div></div>`, concept:`<p>認証は「あなたが本当にあなたか」を確認するプロセスです。認証要素は3種類あり、複数組み合わせることで強度が上がります。</p><div class="formula-box"><div class="formula-label">初学者向け:よく出る言葉</div><strong>認証Authentication</strong>:利用者が本人かどうかを確認すること(ログイン)。<br><strong>認可Authorization</strong>:本人でも「どのデータや操作を許すか」を決めること(権限)。<br><strong>多要素認証MFA</strong>:知識・所持・生体など<strong>種類の異なる</strong>要素を2つ以上使うこと同じ種類を2回は多要素にならない場合がある。</div><div class="formula-box"><div class="formula-label">認証の3要素</div>知識要素Something you knowパスワード・PIN・秘密の質問<br>所持要素Something you haveICカード・スマートフォン・ハードウェアトークン<br>生体要素Something you are指紋・顔認証・虹彩</div><p>2要素以上を組み合わせるのが<strong>多要素認証MFA</strong>。SSOはIDPが認証を一元管理し、アプリはそのトークンを信頼するモデルです。開発者にとってJWTJSON Web TokenはSSO文脈でよく使うものです。</p><div class="viz-flow"><div class="vf-node">ユーザー<div class="vf-sub">ID/PW入力</div></div><div class="vf-arrow">→</div><div class="vf-node vf-hl">IdP<div class="vf-sub">認証・発行</div></div><div class="vf-arrow">→</div><div class="vf-node">トークン<div class="vf-sub">JWT/SAML</div></div><div class="vf-arrow">→</div><div class="vf-node">サービス<div class="vf-sub">検証・許可</div></div></div>`,
examtips:[ examtips:[
'「2段階認証」は同一要素を2回使う場合もあるPW+秘密の質問→どちらも知識要素)。<strong>多要素認証は異なる要素を組み合わせる</strong>点が重要。', '「2段階認証」は同一要素を2回使う場合もあるPW+秘密の質問→どちらも知識要素)。<strong>多要素認証は異なる要素を組み合わせる</strong>点が重要。',
'チャレンジレスポンス認証:サーバがランダム値(チャレンジ)を送り、クライアントがハッシュ等で応答(レスポンス)する。パスワードを平文で送らない。', 'チャレンジレスポンス認証:サーバがランダム値(チャレンジ)を送り、クライアントがハッシュ等で応答(レスポンス)する。パスワードを平文で送らない。',
@ -64,7 +64,7 @@ export const CATEGORIES = [
] ]
}, },
{ id:'s04', num:'S04', title:'アクセス制御', freq:'high', diff:2, { id:'s04', num:'S04', title:'アクセス制御', freq:'high', diff:2,
concept:`<p>アクセス制御は「誰が何にどう操作できるか」を管理する仕組みです。3つのモデルが試験に頻出です。</p><div class="formula-box"><div class="formula-label">主なアクセス制御モデル</div>DAC任意アクセス制御所有者が権限を設定Linuxのchmod等<br>MAC強制アクセス制御システムがラベルで強制機密文書管理<br>RBAC役割ベース制御役割に権限を付与多くのWebアプリで採用</div><p>最小権限の原則Least Privilege業務に必要な最小限の権限のみ与える。開発者でいえばEC2に付けるIAM RoleはReadOnlyで十分な場合にAdminを付けないことです。</p><p>Need-to-know業務上知る必要がある情報だけに絞るデータ分離の原則。MACと組み合わせることが多い。</p>`, concept:`<p>アクセス制御は「誰が何にどう操作できるか」を管理する仕組みです。3つのモデルが試験に頻出です。</p><div class="formula-box"><div class="formula-label">初学者向け:この単元の入口</div><strong>アクセス制御</strong>ユーザーやプロセスが、どのリソースファイル・DB・機能をどこまで使えるかを決める仕組み。<br><strong>最小権限</strong>:業務に必要な範囲だけ権限を付け、余計な操作や閲覧をさせない原則。<br><strong>職務分離</strong>:申請・承認・実行などを別の人に分け、一人で不正を完結できないようにすること。</div><div class="formula-box"><div class="formula-label">主なアクセス制御モデル</div>DAC任意アクセス制御所有者が権限を設定Linuxのchmod等<br>MAC強制アクセス制御システムがラベルで強制機密文書管理<br>RBAC役割ベース制御役割に権限を付与多くのWebアプリで採用</div><p>最小権限の原則Least Privilege業務に必要な最小限の権限のみ与える。開発者でいえばEC2に付けるIAM RoleはReadOnlyで十分な場合にAdminを付けないことです。</p><p>Need-to-know業務上知る必要がある情報だけに絞るデータ分離の原則。MACと組み合わせることが多い。</p>`,
examtips:[ examtips:[
'DAC・MAC・RBACの違いを具体例で説明できること。試験では「誰が権限を設定するか」がポイント。', 'DAC・MAC・RBACの違いを具体例で説明できること。試験では「誰が権限を設定するか」がポイント。',
'最小権限の原則は選択問題の正答選択肢になることが多い。「できるだけ多くの権限を与える」は誤答。', '最小権限の原則は選択問題の正答選択肢になることが多い。「できるだけ多くの権限を与える」は誤答。',
@ -85,7 +85,7 @@ export const CATEGORIES = [
] ]
}, },
{ id:'s05', num:'S05', title:'ISMS・セキュリティマネジメント', freq:'mid', diff:2, { id:'s05', num:'S05', title:'ISMS・セキュリティマネジメント', freq:'mid', diff:2,
concept:`<p><strong>ISMS</strong>Information Security Management SystemはJIS Q 27001をベースにした、組織全体で情報セキュリティを継続的に改善する仕組みです。</p><div class="formula-box"><div class="formula-label">PDCAサイクルISMS</div>P計画リスクアセスメント→セキュリティポリシー策定<br>D実行管理策の実施・社員教育<br>C確認内部監査・マネジメントレビュー<br>A改善不適合の是正・継続的改善</div><p>ISMSの認証取得はISO/IEC 27001に基づきます。プライバシーマークPMSは個人情報保護に特化した日本独自の認証制度です。ゼロトラストアーキテクチャは「境界の内側も信頼しない」という現代的なセキュリティ思想で、ISMSの文脈でも登場します。</p>`, concept:`<p><strong>ISMS</strong>Information Security Management SystemはJIS Q 27001をベースにした、組織全体で情報セキュリティを継続的に改善する仕組みです。</p><div class="formula-box"><div class="formula-label">初学者向けISMS とゼロトラスト</div><strong>ISMS</strong>:方針を決め、リスクを評価し、規程・教育・点検・改善を続ける「組織のセキュリティの仕組み」そのもの。<br><strong>PDCA</strong>Plan計画→Do実行→Check評価→Act改善の繰り返しでレベルを上げる考え方。<br><strong>ゼロトラスト</strong>:社内ネットワークにいるから安全、とはせず、内外を問わず毎回アクセスを検証する設計思想。</div><div class="formula-box"><div class="formula-label">PDCAサイクルISMS</div>P計画リスクアセスメント→セキュリティポリシー策定<br>D実行管理策の実施・社員教育<br>C確認内部監査・マネジメントレビュー<br>A改善不適合の是正・継続的改善</div><p>ISMSの認証取得はISO/IEC 27001に基づきます。プライバシーマークPMSは個人情報保護に特化した日本独自の認証制度です。ゼロトラストアーキテクチャは「境界の内側も信頼しない」という現代的なセキュリティ思想で、ISMSの文脈でも登場します。</p>`,
examtips:[ examtips:[
'ISMSはJIS Q 27001・ISO/IEC 27001が根拠。プライバシーマークとの違い対象範囲・認定機関を整理。', 'ISMSはJIS Q 27001・ISO/IEC 27001が根拠。プライバシーマークとの違い対象範囲・認定機関を整理。',
'PDCAのどのフェーズの話かを問う問題が多い。「内部監査」はC確認、「リスクアセスメント」はP計画。', 'PDCAのどのフェーズの話かを問う問題が多い。「内部監査」はC確認、「リスクアセスメント」はP計画。',
@ -107,7 +107,7 @@ export const CATEGORIES = [
]}, ]},
{ id:'crypto', label:'暗号・PKI', icon:'key', units:[ { id:'crypto', label:'暗号・PKI', icon:'key', units:[
{ id:'s06', num:'S06', title:'共通鍵暗号', freq:'high', diff:2, { id:'s06', num:'S06', title:'共通鍵暗号', freq:'high', diff:2,
concept:`<p><strong>共通鍵暗号(対称暗号)</strong>は送受信者が同じ鍵を使って暗号化・復号する方式です。速度が速く大量データの暗号化に向いています。</p><div class="formula-box"><div class="formula-label">主要アルゴリズム</div>AESAdvanced Encryption Standard現在の標準。128/192/256ビット鍵。ブロック暗号<br>DES56ビット鍵。現在は脆弱で使用禁止<br>3DESDESを3回適用。AESへ移行中<br>ChaCha20ストリーム暗号。モバイル・TLS1.3で採用</div><p>鍵配送問題共通鍵を安全に相手に渡す手段が必要。これを解決したのが公開鍵暗号とDiffie-Hellman鍵交換です。TLSハンドシェイクではDHECDHで共通鍵を合意し、AESで通信を暗号化するという組み合わせが標準です。</p>`, concept:`<p><strong>共通鍵暗号(対称暗号)</strong>は送受信者が同じ鍵を使って暗号化・復号する方式です。速度が速く大量データの暗号化に向いています。</p><div class="formula-box"><div class="formula-label">初学者向け:暗号の役割(あとで詳しく)</div>この単元の<strong>共通鍵</strong>は「同じ秘密の鍵で暗号化・復号する」方式です。次の単元の<strong>公開鍵</strong>とセットで「誰でも送れるが本人だけ読める」「本人だけが署名できる」など別の役割を担います。<strong>ハッシュ</strong>(別単元)は「要約フィンガープリント」で改ざん検知に使います。</div><div class="formula-box"><div class="formula-label">主要アルゴリズム</div>AESAdvanced Encryption Standard現在の標準。128/192/256ビット鍵。ブロック暗号<br>DES56ビット鍵。現在は脆弱で使用禁止<br>3DESDESを3回適用。AESへ移行中<br>ChaCha20ストリーム暗号。モバイル・TLS1.3で採用</div><p>鍵配送問題共通鍵を安全に相手に渡す手段が必要。これを解決したのが公開鍵暗号とDiffie-Hellman鍵交換です。TLSハンドシェイクではDHECDHで共通鍵を合意し、AESで通信を暗号化するという組み合わせが標準です。</p>`,
examtips:[ examtips:[
'AES・DES・3DESのビット長と現在の安全性を整理する。DESは2024年現在使用禁止。', 'AES・DES・3DESのビット長と現在の安全性を整理する。DESは2024年現在使用禁止。',
'n人で共通鍵を持ち合う場合の鍵の数n(n-1)/2本。公開鍵暗号ならn本で済む。この違いが問われる。', 'n人で共通鍵を持ち合う場合の鍵の数n(n-1)/2本。公開鍵暗号ならn本で済む。この違いが問われる。',
@ -357,7 +357,8 @@ export const CATEGORIES = [
quiz:[ quiz:[
{q:'他のサービスで漏洩したID・パスワードのリストを使って別サービスにログインを試みる攻撃はどれか。',choices:['パスワードスプレー攻撃','ブルートフォース攻撃','クレデンシャルスタッフィング','辞書攻撃'],answer:2,exp:'クレデンシャルスタッフィングはパスワードの使い回しを悪用して漏洩リストを他サービスに転用する攻撃です。'}, {q:'他のサービスで漏洩したID・パスワードのリストを使って別サービスにログインを試みる攻撃はどれか。',choices:['パスワードスプレー攻撃','ブルートフォース攻撃','クレデンシャルスタッフィング','辞書攻撃'],answer:2,exp:'クレデンシャルスタッフィングはパスワードの使い回しを悪用して漏洩リストを他サービスに転用する攻撃です。'},
{q:'アカウントロックアウト機能を回避しながらパスワードを試みる攻撃手法はどれか。',choices:['ブルートフォース攻撃','パスワードスプレー攻撃','レインボーテーブル攻撃','クレデンシャルスタッフィング'],answer:1,exp:'パスワードスプレーは少数の一般的なパスワードを多数のアカウントに分散して試すため、ロックアウト閾値を超えません。'}, {q:'アカウントロックアウト機能を回避しながらパスワードを試みる攻撃手法はどれか。',choices:['ブルートフォース攻撃','パスワードスプレー攻撃','レインボーテーブル攻撃','クレデンシャルスタッフィング'],answer:1,exp:'パスワードスプレーは少数の一般的なパスワードを多数のアカウントに分散して試すため、ロックアウト閾値を超えません。'},
{q:'スピアフィッシングの説明として正しいものはどれか。',choices:['不特定多数に大量配信するフィッシング','特定の個人・組織を調査した標的型フィッシング','電話を使ったソーシャルエンジニアリング','SMSを使ったフィッシング'],answer:1,exp:'スピアフィッシングは標的を事前に調査し、信頼性の高い偽メール・偽サイトで情報を盗む標的型攻撃です。'} {q:'スピアフィッシングの説明として正しいものはどれか。',choices:['不特定多数に大量配信するフィッシング','特定の個人・組織を調査した標的型フィッシング','電話を使ったソーシャルエンジニアリング','SMSを使ったフィッシング'],answer:1,exp:'スピアフィッシングは標的を事前に調査し、信頼性の高い偽メール・偽サイトで情報を盗む標的型攻撃です。'},
{q:'パスワードをハッシュで保存する際にソルトsaltを加える主な目的はどれか。',choices:['ハッシュ計算を高速化する','レインボーテーブル攻撃を困難にする','パスワードを平文のまま保存する','ブルートフォース攻撃を速くする'],answer:1,exp:'ソルトはユーザーごとに異なるランダム値をパスワードに付加してからハッシュ化するため、同一パスワードでもハッシュ値が変わり、事前計算したレインボーテーブルがそのまま使えなくなります。'}
] ]
}, },
{ id:'s17', num:'S17', title:'DoS/DDoS・APT', freq:'high', diff:2, { id:'s17', num:'S17', title:'DoS/DDoS・APT', freq:'high', diff:2,
@ -510,7 +511,7 @@ export const CATEGORIES = [
] ]
}, },
{ id:'s20', num:'S20', title:'法規・評価基準', freq:'high', diff:2, { id:'s20', num:'S20', title:'法規・評価基準', freq:'high', diff:2,
concept:`<p>情報セキュリティに関わる法律・制度・評価基準を整理します。試験では法律の名前と対象・罰則の組み合わせが問われます。</p><div class="formula-box"><div class="formula-label">主な法律・制度</div>不正アクセス禁止法:不正アクセス行為そのものを禁止(クラッキング・フィッシング等)<br>個人情報保護法:個人情報の取得・利用・保管・第三者提供の規制。漏洩時の報告義務<br>サイバーセキュリティ基本法国のサイバーセキュリティ戦略の基盤。NISC設置の根拠法<br>不正競争防止法:営業秘密の保護。漏洩・横領に刑事罰<br>電子署名法:電子署名の法的効力を定める</div><div class="formula-box"><div class="formula-label">評価基準・フレームワーク</div>ISO/IEC 15408CCCommon Criteria製品のセキュリティ評価基準。EALレベルで評価<br>PCI DSSクレジットカード業界のセキュリティ基準加盟店・決済事業者向け<br>NIST SP 800シリーズ米国のセキュリティガイドライン群<br>NIST CSF 2.02024年2月Govern統治を追加した6機能G-IPDRR<br> Govern→Identify→Protect→Detect→Respond→Recover<br>NIST SP 800シリーズ米国のセキュリティガイドライン群<br>PCI DSSクレジットカード業界のセキュリティ基準。12要件で構成</div>`, concept:`<p>情報セキュリティに関わる法律・制度・評価基準を整理します。試験では法律の名前と対象・罰則の組み合わせが問われます。</p><div class="formula-box"><div class="formula-label">主な法律・制度</div>不正アクセス禁止法:不正アクセス行為そのものを禁止(クラッキング・フィッシング等)<br>個人情報保護法:個人情報の取得・利用・保管・第三者提供の規制。漏洩時の報告義務<br>サイバーセキュリティ基本法国のサイバーセキュリティ戦略の基盤。NISC設置の根拠法<br>不正競争防止法:営業秘密の保護。漏洩・横領に刑事罰<br>電子署名法:電子署名の法的効力を定める</div><div class="formula-box"><div class="formula-label">評価基準・フレームワーク</div>ISO/IEC 15408CCCommon Criteria製品のセキュリティ評価基準。EALレベルで評価<br>PCI DSSクレジットカード業界のセキュリティ基準加盟店・決済事業者向け、12要件で構成<br>NIST SP 800シリーズ米国のセキュリティガイドライン群<br>NIST CSF 2.02024年2月Govern統治を追加した6機能G-IPDRR<br> Govern→Identify→Protect→Detect→Respond→Recover<br>ISO/IEC 27001ISMS の国際規格Annex A に管理策を列挙)</div>`,
examtips:[ examtips:[
'不正アクセス禁止法:権限なしのコンピュータへの不正ログインを禁止。ポートスキャンだけでは違反にならないが、脆弱性を突いた侵入は違反。', '不正アクセス禁止法:権限なしのコンピュータへの不正ログインを禁止。ポートスキャンだけでは違反にならないが、脆弱性を突いた侵入は違反。',
'個人情報保護法令和4年2022年施行で漏洩報告義務化・個人関連情報追加。令和8年2026年4月改正法案で<strong>課徴金制度</strong>が新設予定(違法利得への制裁強化)。', '個人情報保護法令和4年2022年施行で漏洩報告義務化・個人関連情報追加。令和8年2026年4月改正法案で<strong>課徴金制度</strong>が新設予定(違法利得への制裁強化)。',
@ -637,6 +638,27 @@ export const CATEGORIES = [
{q:'脅威情報を機械可読形式で記述するための標準仕様はどれか。',choices:['CVSS','STIXStructured Threat Information eXpression'],answer:1,exp:'STIX は脅威アクター・攻撃キャンペーン・IoC などを構造化して記述するための標準形式です。TAXII プロトコルと組み合わせて組織間で自動共有されます。'}, {q:'脅威情報を機械可読形式で記述するための標準仕様はどれか。',choices:['CVSS','STIXStructured Threat Information eXpression'],answer:1,exp:'STIX は脅威アクター・攻撃キャンペーン・IoC などを構造化して記述するための標準形式です。TAXII プロトコルと組み合わせて組織間で自動共有されます。'},
{q:'ハニーポットを設置する主な目的はどれか。',choices:['本番システムの負荷を分散する','攻撃者を引き付けて行動を観察し脅威インテリジェンスを収集する'],answer:1,exp:'ハニーポットは重要システムを守る囮。攻撃者がどのような手法を使うかを安全に観察でき、TTPsの把握や早期警告システムとして機能します。'} {q:'ハニーポットを設置する主な目的はどれか。',choices:['本番システムの負荷を分散する','攻撃者を引き付けて行動を観察し脅威インテリジェンスを収集する'],answer:1,exp:'ハニーポットは重要システムを守る囮。攻撃者がどのような手法を使うかを安全に観察でき、TTPsの把握や早期警告システムとして機能します。'}
] ]
},
{ id:'s31', num:'S31', title:'コンプライアンス・証跡(ログ)入門', freq:'mid', diff:1,
concept:`<p><strong>コンプライアンス</strong>は、法令・業界ガイドライン・社内規程に沿って業務を運営することです。情報セキュリティ分野では、個人情報保護やISMSの文脈で「記録」「監査」「証跡」がセットで問われます。</p><div class="formula-box"><div class="formula-label">初学者向け:証跡とログ</div><strong>証跡</strong>:いつ・誰が・何をしたかを後から追跡できる記録。インシデント調査や監査で事実関係を説明する根拠になる。<br><strong>システムログ</strong>:ログイン成否、設定変更、特権操作などを機械的に残したもの。<br><strong>ログの保護</strong>攻撃者に消されたら意味がないため、権限分離・改ざん検知・外部SIEMへの転送などで<strong>改ざん耐性</strong>を高める。</div><div class="formula-box"><div class="formula-label">内部統制との関係</div>重要な処理は<strong>職務分離</strong>や承認フローと組み合わせ、ログはその実行事実を残す。保存期間・閲覧権限・マスキングは個人情報保護と両立して設計する。</div><p>クラウドではログの有効化や保管先の設計も利用者責任に含まれる場合がある(責任共有モデル)。</p>`,
examtips:[
'「コンプライアンス=法令遵守だけ」と捉えない。社内規程・契約・業界基準も含む。',
'監査証跡は「後から説明できること」が目的。ログを取らないより、取り方と保護が問われる。',
'クラウドの監査ログはデフォルトで無効のサービスもある。必要なイベントを有効化し、長期保管は別ストレージやSIEM連携を検討。'
],
keypoints:[
'コンプライアンス:法令・規程・契約に適合した運用',
'証跡・ログ:操作の事実を記録し調査・説明責任を果たす',
'ログ保護権限分離と改ざん耐性外部集約・WORM等',
'内部統制:職務分離・承認とログを組み合わせて不正を抑止',
'クラウド:ログ設計・保管も利用者側の責任になり得る'
],
quiz:[
{q:'コンプライアンスの説明として最も適切なものはどれか。',choices:['利益だけを最大化すること','法令・規程・契約等に適合するよう業務を運営すること','セキュリティ対策を一切行わないこと','技術的脆弱性をゼロにすること'],answer:1,exp:'コンプライアンスは法令だけでなく、業界ガイドライン・社内規程・取引先契約など、組織が従うべきルール全体への適合を指します。'},
{q:'監査証跡(ログ)の主な目的として適切なものはどれか。',choices:['ディスク使用量を減らすこと','いつ誰が何をしたかを後から検証できるようにすること','パスワードを平文で保存すること','通信を暗号化しないこと'],answer:1,exp:'証跡はインシデント調査・監査・説明責任のために、操作の事実を追える状態にすることを目的とします。'},
{q:'ログの改ざん耐性を高める取り組みとして適切なものはどれか。',choices:['管理者が自由に削除できる1台のサーバのみに保存','権限分離し、改ざん検知や外部SIEMへの集約を行う','ログを取らない','全員に同一の管理者権限を付与'],answer:1,exp:'ログが攻撃者や内部不正で改ざん・削除されると証跡として機能しません。権限分離と外部集約が有効です。'},
{q:'クラウド利用におけるログ・監査の考え方として適切なものはどれか。',choices:['すべてのログはクラウド事業者が自動で完全に代替する','必要な監査ログを有効化し、保管期間とアクセス権を設計するのは利用者責任になり得る','ログは個人情報ではないので無制限に公開してよい','ログは開発環境のみでよい'],answer:1,exp:'責任共有モデルでは、設定可能な監査ログの有効化や保管設計が利用者側の責任になる場合があります。'}
]
} }
]} ]}
]; ];

View File

@ -183,5 +183,11 @@ export const DRILLS = {
{ q: 'IoCIndicators of Compromiseの具体例として正しいのはどれか。', choices: ['攻撃者グループの動機と目的', 'マルウェアのハッシュ値・悪意ある通信先の IP アドレス'], answer: 1, exp: 'IoC は侵害が発生した「証拠」となる具体的な技術的指標。SIEM にフィードすることでインシデントの検知・調査に活用されます。' }, { q: 'IoCIndicators of Compromiseの具体例として正しいのはどれか。', choices: ['攻撃者グループの動機と目的', 'マルウェアのハッシュ値・悪意ある通信先の IP アドレス'], answer: 1, exp: 'IoC は侵害が発生した「証拠」となる具体的な技術的指標。SIEM にフィードすることでインシデントの検知・調査に活用されます。' },
{ q: '脅威情報を機械可読形式で記述する標準仕様はどれか。', choices: ['CVSS', 'STIXStructured Threat Information eXpression'], answer: 1, exp: 'STIX は脅威アクター・攻撃キャンペーン・IoC などを構造化して記述するための標準形式。TAXII プロトコルと組み合わせて組織間で自動共有されます。' }, { q: '脅威情報を機械可読形式で記述する標準仕様はどれか。', choices: ['CVSS', 'STIXStructured Threat Information eXpression'], answer: 1, exp: 'STIX は脅威アクター・攻撃キャンペーン・IoC などを構造化して記述するための標準形式。TAXII プロトコルと組み合わせて組織間で自動共有されます。' },
{ q: 'ハニーポットを設置する主な目的はどれか。', choices: ['本番システムの負荷を分散する', '攻撃者を引き付けて行動を観察し脅威インテリジェンスを収集する'], answer: 1, exp: 'ハニーポットは重要システムを守る囮。攻撃者の TTP を安全に観察でき、早期警告システムとしても機能します。' } { q: 'ハニーポットを設置する主な目的はどれか。', choices: ['本番システムの負荷を分散する', '攻撃者を引き付けて行動を観察し脅威インテリジェンスを収集する'], answer: 1, exp: 'ハニーポットは重要システムを守る囮。攻撃者の TTP を安全に観察でき、早期警告システムとしても機能します。' }
],
s31: [
{ q: 'コンプライアンスの意味に最も近いのはどれか。', choices: ['法令・規程に適合した運用をすること', '脆弱性スキャンを週1回だけ行うこと'], answer: 0, exp: 'コンプライアンスは法令・業界ルール・社内規程・契約など、従うべき要件への適合を指します。' },
{ q: '監査証跡(ログ)の主な目的はどれか。', choices: ['ストレージコストを下げること', 'いつ誰が何をしたかを後から検証できるようにすること'], answer: 1, exp: '証跡は説明責任・調査・監査のために、操作の事実を追える記録を残すことが目的です。' },
{ q: 'ログの保護で重視すべきことはどれか。', choices: ['全員が同じ管理者で削除できること', '権限分離と改ざん検知・外部集約などで改ざん耐性を高めること'], answer: 1, exp: 'ログが改ざん・削除されると証跡として機能しません。権限分離と外部SIEM等が有効です。' },
{ q: 'クラウド利用時の監査ログの考え方として適切なのはどれか。', choices: ['必要なイベントの有効化や保管設計は利用者責任になり得る', 'すべてクラウド事業者が自動で完備する'], answer: 0, exp: '責任共有モデルでは、監査ログの有効化や保持ポリシーが利用者側の作業になる場合があります。' }
] ]
}; };

View File

@ -1,5 +1,5 @@
// posimai-sc SW — same-origin の静的資産のみキャッシュCDN は対象外) // posimai-sc SW — same-origin の静的資産のみキャッシュCDN は対象外)
const CACHE = 'posimai-sc-v1'; const CACHE = 'posimai-sc-v2';
const STATIC = [ const STATIC = [
'/', '/',
'/index.html', '/index.html',

View File

@ -8,7 +8,7 @@ const express = require('express');
* POST /ponshu/license/validate (認証不要 モバイルアプリから直接呼ぶ) * POST /ponshu/license/validate (認証不要 モバイルアプリから直接呼ぶ)
* POST /ponshu/admin/license/revoke (認証必須) * POST /ponshu/admin/license/revoke (認証必須)
*/ */
module.exports = function createPonshuRouter(pool, authMiddleware) { module.exports = function createPonshuRouter(pool, authMiddleware, checkRateLimit) {
const router = express.Router(); const router = express.Router();
const APP_SUPPORT_EMAIL = process.env.APP_SUPPORT_EMAIL || 'support@posimai.soar-enrich.com'; const APP_SUPPORT_EMAIL = process.env.APP_SUPPORT_EMAIL || 'support@posimai.soar-enrich.com';
@ -20,6 +20,11 @@ module.exports = function createPonshuRouter(pool, authMiddleware) {
return res.status(400).json({ valid: false, error: 'Missing parameters' }); return res.status(400).json({ valid: false, error: 'Missing parameters' });
} }
// IP ごとに 10回/分 の制限(ブルートフォース防止)
if (checkRateLimit && !checkRateLimit('ponshu_validate', req.ip || 'unknown', 10, 60 * 1000)) {
return res.status(429).json({ valid: false, error: 'リクエストが多すぎます。しばらく待ってから再試行してください' });
}
try { try {
const result = await pool.query( const result = await pool.query(
`SELECT license_key, plan, status, device_id FROM ponshu_licenses WHERE license_key = $1`, `SELECT license_key, plan, status, device_id FROM ponshu_licenses WHERE license_key = $1`,

View File

@ -1762,23 +1762,16 @@ function buildRouter() {
// ── Journal API ────────────────────────── // ── Journal API ──────────────────────────
// GET /journal/posts/public — 認証不要・published=true のみposimai-site 用) // GET /journal/posts/public — 認証不要・published=true のみposimai-site 用)
// ?user=maita でユーザー指定可能(将来の独立サイト対応 // SITE_PUBLIC_USER 環境変数で許可ユーザーを固定(任意ユーザー列挙を防止
r.get('/journal/posts/public', async (req, res) => { r.get('/journal/posts/public', async (req, res) => {
try { try {
const limit = Math.min(parseInt(req.query.limit || '50') || 50, 100); const limit = Math.min(parseInt(req.query.limit || '50') || 50, 100);
const userId = req.query.user || null; const allowedUser = process.env.SITE_PUBLIC_USER || 'maita';
const { rows } = userId const { rows } = await pool.query(
? await pool.query(
`SELECT id, title, body, tags, created_at, updated_at `SELECT id, title, body, tags, created_at, updated_at
FROM journal_posts WHERE published=TRUE AND user_id=$1 FROM journal_posts WHERE published=TRUE AND user_id=$1
ORDER BY updated_at DESC LIMIT $2`, ORDER BY updated_at DESC LIMIT $2`,
[userId, limit] [allowedUser, limit]
)
: await pool.query(
`SELECT id, title, body, tags, created_at, updated_at
FROM journal_posts WHERE published=TRUE
ORDER BY updated_at DESC LIMIT $1`,
[limit]
); );
res.json({ posts: rows }); res.json({ posts: rows });
} catch (e) { console.error(e); res.status(500).json({ error: 'DB error' }); } } catch (e) { console.error(e); res.status(500).json({ error: 'DB error' }); }
@ -1835,16 +1828,27 @@ function buildRouter() {
// ── Site Config API ─────────────────────── // ── Site Config API ───────────────────────
// GET /site/config/public — 認証不要・posimai-site 用 // GET /site/config/public — 認証不要・posimai-site 用
// ?user=maita でユーザー指定可能(将来の独立サイト対応) // SITE_PUBLIC_USER 環境変数で許可ユーザーを固定(任意ユーザー列挙を防止)
// SITE_CONFIG_PUBLIC_KEYS 環境変数でカンマ区切りの公開キーを指定可(未設定時はデフォルト一覧)
const DEFAULT_SITE_CONFIG_PUBLIC_KEYS = new Set([
'atlas_data', 'diary_content', 'site_title', 'site_description',
'profile_name', 'profile_bio', 'avatar_url', 'accent_color',
'header_image', 'social_links', 'theme',
]);
r.get('/site/config/public', async (req, res) => { r.get('/site/config/public', async (req, res) => {
try { try {
const userId = req.query.user || 'maita'; const allowedUser = process.env.SITE_PUBLIC_USER || 'maita';
const allowedKeys = process.env.SITE_CONFIG_PUBLIC_KEYS
? new Set(process.env.SITE_CONFIG_PUBLIC_KEYS.split(',').map(k => k.trim()))
: DEFAULT_SITE_CONFIG_PUBLIC_KEYS;
const { rows } = await pool.query( const { rows } = await pool.query(
'SELECT key, value FROM site_config WHERE user_id=$1', 'SELECT key, value FROM site_config WHERE user_id=$1',
[userId] [allowedUser]
); );
const config = {}; const config = {};
rows.forEach(row => { config[row.key] = row.value; }); rows.forEach(row => {
if (allowedKeys.has(row.key)) config[row.key] = row.value;
});
res.json({ config }); res.json({ config });
} catch (e) { console.error(e); res.status(500).json({ error: 'DB error' }); } } catch (e) { console.error(e); res.status(500).json({ error: 'DB error' }); }
}); });
@ -2535,6 +2539,9 @@ ${excerpt}
r.post('/together/groups', async (req, res) => { r.post('/together/groups', async (req, res) => {
const { name, username } = req.body || {}; const { name, username } = req.body || {};
if (!name || !username) return res.status(400).json({ error: 'name と username は必須です' }); if (!name || !username) return res.status(400).json({ error: 'name と username は必須です' });
if (!checkRateLimit('together_create', req.ip || 'unknown', 5, 60 * 60 * 1000)) {
return res.status(429).json({ error: 'グループ作成の上限に達しました。1時間後に再試行してください' });
}
try { try {
let invite_code, attempt = 0; let invite_code, attempt = 0;
while (attempt < 5) { while (attempt < 5) {
@ -2562,6 +2569,9 @@ ${excerpt}
r.post('/together/join', async (req, res) => { r.post('/together/join', async (req, res) => {
const { invite_code, username } = req.body || {}; const { invite_code, username } = req.body || {};
if (!invite_code || !username) return res.status(400).json({ error: 'invite_code と username は必須です' }); if (!invite_code || !username) return res.status(400).json({ error: 'invite_code と username は必須です' });
if (!checkRateLimit('together_join', req.ip || 'unknown', 10, 60 * 60 * 1000)) {
return res.status(429).json({ error: '参加試行回数が上限に達しました。1時間後に再試行してください' });
}
try { try {
const group = await pool.query( const group = await pool.query(
'SELECT * FROM together_groups WHERE invite_code=$1', 'SELECT * FROM together_groups WHERE invite_code=$1',
@ -2636,7 +2646,14 @@ ${excerpt}
res.json({ ok: true, share }); res.json({ ok: true, share });
// URL がある場合のみ非同期アーカイブ(ユーザーを待たせない) // URL がある場合のみ非同期アーカイブ(ユーザーを待たせない)
if (url) archiveShare(share.id, url); // shared_by ごとに 20回/時間 の Gemini 呼び出し制限
if (url) {
if (checkRateLimit('together_archive', shared_by, 20, 60 * 60 * 1000)) {
archiveShare(share.id, url);
} else {
pool.query(`UPDATE together_shares SET archive_status='skipped' WHERE id=$1`, [share.id]).catch(() => {});
}
}
} catch (e) { } catch (e) {
console.error('[together/share]', e.message); console.error('[together/share]', e.message);
res.status(500).json({ error: 'Internal server error' }); res.status(500).json({ error: 'Internal server error' });
@ -3102,7 +3119,7 @@ app.post('/api/stripe/webhook',
); );
// ── Ponshu Room ライセンス (routes/ponshu.js) ───────────────────────── // ── Ponshu Room ライセンス (routes/ponshu.js) ─────────────────────────
const ponshuRouter = require('./routes/ponshu')(pool, authMiddleware); const ponshuRouter = require('./routes/ponshu')(pool, authMiddleware, checkRateLimit);
app.use('/brain/api', ponshuRouter); app.use('/brain/api', ponshuRouter);
app.use('/api', ponshuRouter); app.use('/api', ponshuRouter);