From 36293e5ec7232e3053ebbbb5c78e6db087eb2c82 Mon Sep 17 00:00:00 2001 From: posimai Date: Mon, 20 Apr 2026 01:40:48 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20posimai-sc=20=E6=94=AF=E6=8F=B4?= =?UTF-8?q?=E5=A3=AB=E5=AD=A6=E7=BF=92PWA=E3=82=92=E5=90=8C=E6=A2=B1?= =?UTF-8?q?=E3=81=97=E6=9C=AC=E7=95=AA=E7=94=A8=E8=A8=AD=E5=AE=9A=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made-with: Cursor --- STATUS.md | 3 +- docs/design-system.md | 1 + docs/master-architecture.md | 11 +- posimai-sc/.gitignore | 1 + posimai-sc/index.html | 813 +++++++++++++++++++++++++++++++ posimai-sc/js/app.js | 551 +++++++++++++++++++++ posimai-sc/js/data/categories.js | 425 ++++++++++++++++ posimai-sc/js/data/drills.js | 85 ++++ posimai-sc/logo.png | Bin 0 -> 299 bytes posimai-sc/manifest.json | 18 + posimai-sc/package.json | 9 + posimai-sc/sw.js | 59 +++ posimai-sc/vercel.json | 34 ++ 13 files changed, 2006 insertions(+), 4 deletions(-) create mode 100644 posimai-sc/.gitignore create mode 100644 posimai-sc/index.html create mode 100644 posimai-sc/js/app.js create mode 100644 posimai-sc/js/data/categories.js create mode 100644 posimai-sc/js/data/drills.js create mode 100644 posimai-sc/logo.png create mode 100644 posimai-sc/manifest.json create mode 100644 posimai-sc/package.json create mode 100644 posimai-sc/sw.js create mode 100644 posimai-sc/vercel.json diff --git a/STATUS.md b/STATUS.md index bed05798..4fd96d4f 100644 --- a/STATUS.md +++ b/STATUS.md @@ -8,7 +8,8 @@ ## mai のPC から実行待ち -- 特になし(**posimai-boki** は独立リポジトリで Gitea/GitHub へ push 済み。追加分を出したときは `cd posimai-boki && npm run deploy`) +- **posimai-sc(Vercel)**: GitHub 連携後の自動ビルドが正しく `posimai-sc` フォルダだけを出すよう、Vercel の Project Settings > General > **Root Directory** を `posimai-sc` に設定(未設定のままだとリポジトリルートでビルドされる可能性あり)。設定後、空コミットで `posimai-root` を push してデプロイ確認。 +- **posimai-boki** は独立リポジトリで Gitea/GitHub へ push 済み。追加分を出したときは `cd posimai-boki && npm run deploy` ## 次にやること(優先順) diff --git a/docs/design-system.md b/docs/design-system.md index 0dcdf1d0..cd02fc5d 100644 --- a/docs/design-system.md +++ b/docs/design-system.md @@ -56,3 +56,4 @@ | posimai-dev | `#A78BFA` Violet | `#7C3AED` | 開発ポータル — コード・AI・ターミナルの融合。Atlas と差別化 | | ponshu-room | `#D4A574` 琥珀(Amber) | `#D4A574` | **Posimai デザインシステム適用外**。独自テーマ(和紙×墨×琥珀)を使用 | | posimai-analytics | TailwindCSS + ライトテーマ | — | **Posimai デザインシステム適用外**。Kintone 向け BtoBダッシュボード。TailwindCSS / React / light theme で構築。絵文字禁止・Lucide @0.344.0 固定は適用 | +| posimai-sc | `#818CF8` Indigo | `#6366F1` | セキュリティ学習アプリ — 信頼感のあるクールトーン(簿記アプリのネイビー背景と整合) | diff --git a/docs/master-architecture.md b/docs/master-architecture.md index 011bbe8d..9a4ab34c 100644 --- a/docs/master-architecture.md +++ b/docs/master-architecture.md @@ -1,6 +1,6 @@ # Posimai Project — マスターアーキテクチャドキュメント -最終更新: 2026-04-06 +最終更新: 2026-04-20 対象: Claude Code / Cursor / Gemini / 全 AI エージェント **このドキュメントはプロジェクトの現状を一元管理します。実装の前に必ず読んでください。** @@ -36,7 +36,7 @@ ║ フロントエンド(Vercel / CDN) ║ ║ posimai.soar-enrich.com → posimai-dashboard ║ ║ *.posimai.soar-enrich.com → Vercel(ワイルドカード設定済)║ -║ 全 27 本アプリ(全て PWA) ║ +║ 全 30 本前後の PWA(静的/Next 混在) ║ ╚══════════════════════════════════════════════════════════╝ │ https://api.soar-enrich.com/brain/api/... ▼ @@ -169,7 +169,7 @@ posimai-store(LP) --- -## 7. アプリ一覧(全 27 本・2026-04-06 時点) +## 7. アプリ一覧(代表・2026-04-20 追記) | アプリ | バックエンド | 備考 | |--------|------------|------| @@ -200,6 +200,11 @@ posimai-store(LP) | posimai-events | VPS API | イベント情報(モック・Beta) | | posimai-hotels | — | ホテル価格(モック・Beta) | | posimai-analytics | — | Next.js / RFM分析 | +| posimai-guard | Gemini API | AI コードセキュリティスキャン | +| posimai-store | Stripe / VPS | アプリ販売 LP | +| posimai-log | — | 開発ログビューワー(scribe 連携) | +| posimai-boki | — | 簿記2級 学習 PWA(localStorage) | +| posimai-sc | — | 支援士試験 学習 PWA(localStorage・非公式補助) | --- diff --git a/posimai-sc/.gitignore b/posimai-sc/.gitignore new file mode 100644 index 00000000..e985853e --- /dev/null +++ b/posimai-sc/.gitignore @@ -0,0 +1 @@ +.vercel diff --git a/posimai-sc/index.html b/posimai-sc/index.html new file mode 100644 index 00000000..ec6a8a69 --- /dev/null +++ b/posimai-sc/index.html @@ -0,0 +1,813 @@ + + + + + + + + + + + + + + + + + + +SC — 支援士 + + + + + + + + +
+ + +
+
+ + +
+
+ +
+
+ +
+ + +
+ +
+
+

情報処理安全確保支援士

+

セキュリティの基礎から攻撃手法・法規まで、AM2試験に対応した20単元を体系的に学習します。開発者の視点から「なぜそうなのか」を理解しましょう。

+
+
+
+
修了単元
+
+
+
+
全単元
+
+
+
+
クイズ正解
+
+
+
+ + +
+
+ + 今日の学習 +
+ +
+ + +
+ +
+ +
+ +
+
+ + +
+
+
+
+ + 弱点集中特訓 +
+ +
+ + +
+
+
単元クリア
+
+ +
+
+ +
+
+ + +
+
+
+ + +
+
+
+
+
+
+ +
+ + + +
+
+
正解 / 全問
+
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ + +
+
+ + 概念解説 +
+
+
+ +
+ + +
+
+ + 重要ポイント +
+
    + +
+
+ + +
+
+ + 試験対策メモ +
+ +
+ + +
+ + +
+ + +
+
+
+ + 3ステップで学ぶ +
+
+ + +
+
+
+
+
+
+
+ + +
+
+ + Step 1 — 用語確認 +
+
+
+
+
+
タップして全文を表示
+
+
+ +
+
+ + +
+
+ + Step 2 — 二択ドリル +
+ +
+ + +
+
+ + Step 3 — 本番問題 +
+

+ 下の理解度チェックで確認します。途中まで答えた内容はそのまま続きからできます。最初から出し直すときは、全問に一度答えたあとに表示される「やり直し」か、フロー外の「理解度をクリアして学ぶ」を使ってください。 +

+

+ 現在の解答は保持されています。 +

+ +
+
+ + +
+
+
+ + 理解度チェック +
+
+
+ + + + +
+
+
正解 / 全問
+
+
+ + +
+
+
+ + +
+ + + +
+
+
+
+
+ + + + + + + + diff --git a/posimai-sc/js/app.js b/posimai-sc/js/app.js new file mode 100644 index 00000000..5c0ff8d3 --- /dev/null +++ b/posimai-sc/js/app.js @@ -0,0 +1,551 @@ +import { DRILLS } from './data/drills.js'; +import { CATEGORIES } from './data/categories.js'; + +function sanitizeTrustedHtml(dirty) { + if (dirty == null || dirty === '') return ''; + const s = String(dirty); + if (typeof window.DOMPurify === 'undefined') { + const d = document.createElement('div'); + d.textContent = s; + return d.innerHTML; + } + return window.DOMPurify.sanitize(s, { + USE_PROFILES: { html: true, svg: true, svgFilters: true }, + ALLOW_UNKNOWN_PROTOCOLS: false + }); +} + +function sanitizeStoredHtml(html) { + return sanitizeTrustedHtml(html); +} + +document.addEventListener('alpine:init', () => { + Alpine.data('scApp', () => ({ + safeHtml(s) { + return sanitizeTrustedHtml(s); + }, + categories:[], + currentUnit:null, + quizState:{}, + search:'', + sidebarOpen:false, + isDark:true, + progress:{}, + scores:{}, + wrongUnits:{}, + keys:['A','B','C','D','E'], + // concept fold + conceptExpanded:false, + // step mode + stepMode:false, + stepStep:1, + stepKpIdx:0, + stepFlashRevealed:false, + stepDrillIdx:0, + stepDrillAnswered:false, + stepDrillSelected:-1, + // weak drill + weakDrillActive:false, + weakDrillUnits:[], + weakDrillUnitIdx:0, + weakDrillQuizState:{}, + weakDrillResults:[], + weakDrillDone:false, + + init(){ + this.categories = CATEGORIES.map(cat=>({ + ...cat, + units: cat.units.map(u=>({...u, catLabel:cat.label})) + })); + try{ this.progress = JSON.parse(localStorage.getItem('posimai-sc-progress')||'{}'); } + catch{ this.progress = {}; } + try{ this.scores = JSON.parse(localStorage.getItem('posimai-sc-scores')||'{}'); } + catch{ this.scores = {}; } + try{ this.wrongUnits = JSON.parse(localStorage.getItem('posimai-sc-wrong')||'{}'); } + catch{ this.wrongUnits = {}; } + const t = localStorage.getItem('posimai-sc-theme')||'system'; + this.isDark = t==='dark'||(t==='system'&&matchMedia('(prefers-color-scheme:dark)').matches); + const uid=(new URLSearchParams(location.search).get('unit')||'').trim().toLowerCase(); + if(uid){ + const u=this.allUnits().find(x=>x.id===uid); + if(u){ this.currentUnit=u; this.quizState={}; } + else{ try{ const url=new URL(window.location.href); url.searchParams.delete('unit'); const q=url.searchParams.toString(); history.replaceState(null,'',url.pathname+(q?'?'+q:'')+url.hash); }catch(e){} } + } + this.syncUnitToUrl(); + this.$nextTick(()=>{ if(window.lucide) lucide.createIcons(); }); + this.$watch('search', ()=>{ + this.$nextTick(()=>{ if(window.lucide) lucide.createIcons(); }); + }); + if('serviceWorker' in navigator){ + navigator.serviceWorker.register('/sw.js').catch(()=>{}); + } + }, + + syncUnitToUrl(){ + try{ + const url=new URL(window.location.href); + if(this.currentUnit&&this.currentUnit.id) url.searchParams.set('unit',this.currentUnit.id); + else url.searchParams.delete('unit'); + const q=url.searchParams.toString(); + const path=url.pathname+(q?'?'+q:'')+url.hash; + if(path!==window.location.pathname+window.location.search+window.location.hash) + history.replaceState(null,'',path); + }catch(e){} + }, + + goHome(){ + this.currentUnit=null; + this.quizState={}; + this.conceptExpanded=false; + this.stepMode=false; + this.weakDrillActive=false; + this.syncUnitToUrl(); + this.$nextTick(()=>{ if(window.lucide) lucide.createIcons(); }); + }, + + get filteredCats(){ + if(!this.search) return this.categories; + const q=this.search.toLowerCase(); + return this.categories.map(cat=>({ + ...cat, + units:cat.units.filter(u=>u.title.toLowerCase().includes(q)||u.num.toLowerCase().includes(q)) + })).filter(cat=>cat.units.length>0); + }, + + get totalCount(){ return this.categories.reduce((s,c)=>s+c.units.length,0); }, + get doneCount(){ return Object.keys(this.progress).length; }, + get progressPct(){ return this.totalCount ? Math.round(this.doneCount/this.totalCount*100) : 0; }, + + get quizAnswered(){ + let n=0; + Object.values(this.quizState).forEach(s=>{ if(s) n++; }); + return n; + }, + get quizCorrect(){ + let n=0; + Object.values(this.quizState).forEach(s=>{ if(s?.correct) n++; }); + return n; + }, + + catPct(cat){ + if(!cat.units.length) return 0; + return Math.round(cat.units.filter(u=>this.progress[u.id]).length/cat.units.length*100); + }, + + allUnits(){ return this.categories.flatMap(c=>c.units); }, + + isDone(id){ return !!this.progress[id]; }, + markDone(){ + if(!this.currentUnit) return; + this.progress[this.currentUnit.id]=true; + localStorage.setItem('posimai-sc-progress',JSON.stringify(this.progress)); + }, + bestScore(id){ + const s=this.scores[id]; + if(!s) return ''; + return s.best+'/'+s.total; + }, + scoreChipCls(id){ + const s=this.scores[id]; + if(!s) return 'zero'; + if(s.best===s.total) return ''; + if(s.best>=s.total*0.6) return 'partial'; + return 'zero'; + }, + saveScore(id,correct,total){ + const prev=this.scores[id]; + if(!prev||correct>prev.best){ + this.scores[id]={best:correct,total}; + localStorage.setItem('posimai-sc-scores',JSON.stringify(this.scores)); + } + }, + get freqBadgeCls(){ + const f=this.currentUnit?.freq; + if(f==='high') return 'badge badge-high'; + if(f==='mid') return 'badge badge-mid'; + return 'badge badge-base'; + }, + get freqLabel(){ + const f=this.currentUnit?.freq; + if(f==='high') return '頻出'; + if(f==='mid') return '重要'; + return '基礎'; + }, + get diffLabel(){ + const d=this.currentUnit?.diff||1; + return '難易度 '+'★'.repeat(d)+'☆'.repeat(3-d); + }, + + openUnit(unit){ + this.currentUnit=unit; + this.quizState={}; + this.conceptExpanded=false; + this.stepMode=false; + this.syncUnitToUrl(); + this.$nextTick(()=>{ + if(window.lucide) lucide.createIcons(); + const m=document.getElementById('main'); + if(m) m.scrollTo({top:0,behavior:'smooth'}); + }); + }, + + // Weak unit tracking + hasWrong(id){ return !!this.wrongUnits[id]; }, + _onAllAnswered(){ + if(!this.currentUnit) return; + const uid=this.currentUnit.id; + const total=this.currentUnit.quiz?.length||0; + const correct=Object.values(this.quizState).filter(s=>s?.correct).length; + this.saveScore(uid,correct,total); + if(correct>=total){ + delete this.wrongUnits[uid]; + } else { + this.wrongUnits[uid]=(this.wrongUnits[uid]||0)+1; + } + localStorage.setItem('posimai-sc-wrong',JSON.stringify(this.wrongUnits)); + this.wrongUnits={...this.wrongUnits}; + }, + get weakUnits(){ + return this.allUnits().filter(u=>this.wrongUnits[u.id]>0) + .sort((a,b)=>(this.wrongUnits[b.id]||0)-(this.wrongUnits[a.id]||0)); + }, + get todayUnits(){ + const result=[]; + const weak=this.weakUnits; + if(weak.length>0) result.push({...weak[0], todayTag:'苦手'}); + const all=this.allUnits(); + const weakId=result[0]?.id; + const review=all.find(u=>!this.wrongUnits[u.id] && this.scores[u.id] && u.id!==weakId && !this.progress[u.id]); + if(review) result.push({...review, todayTag:'復習'}); + if(result.length<2){ + const next=all.find(u=>!this.progress[u.id] && u.id!==weakId && !result.find(r=>r.id===u.id)); + if(next) result.push({...next, todayTag:'未学習'}); + } + return result; + }, + + // Quiz + answered(qi){ return !!this.quizState[qi]; }, + doAnswer(qi,ci,correct,exp){ + if(this.quizState[qi]) return; + this.quizState[qi]={selected:ci,correct:ci===correct,exp:sanitizeStoredHtml(exp)}; + this.quizState={...this.quizState}; + const total=this.currentUnit?.quiz?.length||0; + if(total>0 && Object.keys(this.quizState).length>=total){ + this._onAllAnswered(); + } + }, + qCardCls(qi){ + const s=this.quizState[qi]; + if(!s) return ''; + return s.correct?'correct':'wrong'; + }, + choiceCls(qi,ci,correct){ + const s=this.quizState[qi]; + if(!s) return ''; + if(ci===s.selected && s.correct) return 'sel-ok'; + if(ci===s.selected && !s.correct) return 'sel-ng'; + if(ci===correct && !s.correct) return 'rev-ok'; + return ''; + }, + get scoreLabel(){ + const total=this.currentUnit?.quiz?.length||0; + const answered=Object.keys(this.quizState).length; + const correct=Object.values(this.quizState).filter(s=>s?.correct).length; + if(!answered) return total+'問'; + return correct+'/'+answered+'問正解'; + }, + get allAnswered(){ + const total=this.currentUnit?.quiz?.length||0; + return total>0 && Object.keys(this.quizState).length>=total; + }, + get resultScore(){ + const correct=Object.values(this.quizState).filter(s=>s?.correct).length; + const total=this.currentUnit?.quiz?.length||0; + return correct+' / '+total; + }, + get resultMsg(){ + const correct=Object.values(this.quizState).filter(s=>s?.correct).length; + const total=this.currentUnit?.quiz?.length||0; + if(correct===total) return '完璧です!次の単元へ進みましょう。'; + if(correct>=total*0.8) return 'よくできました。あと少しで完璧です。'; + if(correct>=total*0.6) return '合格ラインです。間違えた問題の解説を確認しましょう。'; + return '解説をよく読んで、もう一度チャレンジしてみましょう。'; + }, + resetQuiz(){ this.quizState={}; }, + + // Navigation + currentIdx(){ return this.allUnits().findIndex(u=>u.id===this.currentUnit?.id); }, + get hasPrev(){ return this.currentIdx()>0; }, + get hasNext(){ return this.currentIdx()0) this.openUnit(this.allUnits()[i-1]); + }, + nextUnit(){ + const i=this.currentIdx(); + const all=this.allUnits(); + if(i'); + return i===-1 ? c : c.substring(0,i+4); + }, + get conceptRest(){ + const c=this.currentUnit?.concept||''; + const i=c.indexOf('

'); + return i===-1 ? '' : c.substring(i+4); + }, + + // ---- Weak drill ---- + get weakDrillCandidates(){ + return this.allUnits() + .filter(u=>this.hasWrong(u.id)) + .sort((a,b)=>{ + const sa=this.scores[a.id]?.best||0; + const sb=this.scores[b.id]?.best||0; + return sa-sb; + }) + .slice(0,5); + }, + startWeakDrill(){ + this.weakDrillUnits=[...this.weakDrillCandidates]; + this.weakDrillUnitIdx=0; + this.weakDrillQuizState={}; + this.weakDrillResults=[]; + this.weakDrillDone=false; + this.weakDrillActive=true; + this.$nextTick(()=>{ if(window.lucide) lucide.createIcons(); }); + }, + exitWeakDrill(){ + this.weakDrillActive=false; + this.weakDrillDone=false; + this.$nextTick(()=>{ if(window.lucide) lucide.createIcons(); }); + }, + get weakDrillCurrentUnit(){ + return this.weakDrillUnits[this.weakDrillUnitIdx]||null; + }, + wdAnswered(qi){ return !!this.weakDrillQuizState[qi]; }, + wdQCardCls(qi){ + const s=this.weakDrillQuizState[qi]; + if(!s) return ''; + return s.correct?'correct':'wrong'; + }, + wdChoiceCls(qi,ci,correct){ + const s=this.weakDrillQuizState[qi]; + if(!s) return ''; + if(ci===s.selected&&s.correct) return 'sel-ok'; + if(ci===s.selected&&!s.correct) return 'sel-ng'; + if(ci===correct&&!s.correct) return 'rev-ok'; + return ''; + }, + doWeakDrillAnswer(qi,ci,correct,exp){ + if(this.weakDrillQuizState[qi]) return; + this.weakDrillQuizState[qi]={selected:ci,correct:ci===correct,exp:sanitizeStoredHtml(exp)}; + this.weakDrillQuizState={...this.weakDrillQuizState}; + }, + get weakDrillAllAnswered(){ + const total=this.weakDrillCurrentUnit?.quiz?.length||0; + return total>0 && Object.keys(this.weakDrillQuizState).length>=total; + }, + get weakDrillUnitScore(){ + const correct=Object.values(this.weakDrillQuizState).filter(s=>s?.correct).length; + const total=this.weakDrillCurrentUnit?.quiz?.length||0; + return correct+' / '+total; + }, + nextWeakDrillUnit(){ + const unit=this.weakDrillCurrentUnit; + if(!unit) return; + const correct=Object.values(this.weakDrillQuizState).filter(s=>s?.correct).length; + const total=unit.quiz?.length||0; + this.saveScore(unit.id,correct,total); + this.weakDrillResults.push({unitId:unit.id,num:unit.num,unitTitle:unit.title,correct,total}); + if(correct>=total){ + delete this.wrongUnits[unit.id]; + localStorage.setItem('posimai-sc-wrong',JSON.stringify(this.wrongUnits)); + this.wrongUnits={...this.wrongUnits}; + } + if(this.weakDrillUnitIdx{ if(window.lucide) lucide.createIcons(); }); + }, + get weakDrillClearCount(){ + return this.weakDrillResults.filter(r=>r.correct>=r.total).length; + }, + + // ---- Step mode ---- + startStepMode(){ + this.stepMode=true; + this.stepStep=1; + this.stepKpIdx=0; + this.resetFlashRevealState(); + this.stepDrillIdx=0; + this.stepDrillAnswered=false; + this.stepDrillSelected=-1; + this.$nextTick(()=>{ + if(window.lucide) lucide.createIcons(); + const m=document.getElementById('main'); + if(m) m.scrollTo({top:0,behavior:'smooth'}); + }); + }, + startStepModeClearQuiz(){ + this.quizState={}; + this.startStepMode(); + }, + exitStepMode(){ + this.stepMode=false; + this.$nextTick(()=>{ if(window.lucide) lucide.createIcons(); }); + }, + finishStepMode(){ + this.stepMode=false; + this.$nextTick(()=>{ + if(window.lucide) lucide.createIcons(); + const el=document.getElementById('comprehension-quiz'); + if(el) el.scrollIntoView({behavior:'smooth',block:'start'}); + }); + }, + scrollToComprehension(){ + this.$nextTick(()=>{ + const el=document.getElementById('comprehension-quiz'); + if(el) el.scrollIntoView({behavior:'smooth',block:'start'}); + if(window.lucide) lucide.createIcons(); + }); + }, + stepGoBack(){ + if(!this.stepMode) return; + if(this.stepStep===1){ + if(this.stepKpIdx>0){ + this.stepKpIdx--; + this.resetFlashRevealState(); + } else { + this.exitStepMode(); + } + } else if(this.stepStep===2){ + if(this.stepDrillIdx>0){ + this.stepDrillIdx--; + this.stepDrillAnswered=false; + this.stepDrillSelected=-1; + } else { + const kps=this.currentUnit?.keypoints||[]; + this.stepStep=1; + this.stepKpIdx=Math.max(0,kps.length-1); + this.resetFlashRevealState(); + } + } else if(this.stepStep===3){ + const drills=this.unitDrills; + if(drills.length>0){ + this.stepStep=2; + this.stepDrillIdx=drills.length-1; + this.stepDrillAnswered=false; + this.stepDrillSelected=-1; + } else { + const kps=this.currentUnit?.keypoints||[]; + this.stepStep=1; + this.stepKpIdx=Math.max(0,kps.length-1); + this.resetFlashRevealState(); + } + } + this.$nextTick(()=>{ + if(window.lucide) lucide.createIcons(); + const m=document.getElementById('main'); + if(m) m.scrollTo({top:0,behavior:'smooth'}); + }); + }, + stepPipCls(n){ + if(this.stepStep===n) return 's-active'; + if(this.stepStep>n) return 's-done'; + return ''; + }, + _kpDelimiterIndex(kp){ + if(!kp||typeof kp!=='string') return -1; + const ja=kp.indexOf(':'); + if(ja>=0) return ja; + let depth=0; + for(let i=0;i') depth=Math.max(0,depth-1); + else if(ch===':'&&depth===0) return i; + } + return -1; + }, + keypointHasFlip(idx){ + const kp=(this.currentUnit?.keypoints||[])[idx]; + const i=this._kpDelimiterIndex(kp); + return i>0; + }, + keypointTerm(idx){ + const kp=(this.currentUnit?.keypoints||[])[idx]; + const i=this._kpDelimiterIndex(kp); + return i>0 ? kp.slice(0,i).trim() : ''; + }, + resetFlashRevealState(){ + this.stepFlashRevealed=!this.keypointHasFlip(this.stepKpIdx); + }, + advanceStep1Card(){ + if(this.stepStep!==1) return; + if(this.keypointHasFlip(this.stepKpIdx)&&!this.stepFlashRevealed){ + this.stepFlashRevealed=true; + return; + } + this.nextStepFlash(); + }, + nextStepFlash(){ + const kps=this.currentUnit?.keypoints||[]; + if(this.stepKpIdx0 ? 2 : 3; + this.stepDrillIdx=0; + this.stepDrillAnswered=false; + this.stepDrillSelected=-1; + if(this.stepStep===3){ + this.$nextTick(()=>{ + const m=document.getElementById('main'); + if(m) m.scrollTo({top:m.scrollHeight,behavior:'smooth'}); + }); + } + this.$nextTick(()=>{ if(window.lucide) lucide.createIcons(); }); + } + }, + get unitDrills(){ + return DRILLS[this.currentUnit?.id]||[]; + }, + doStepDrill(ci){ + if(this.stepDrillAnswered) return; + this.stepDrillSelected=ci; + this.stepDrillAnswered=true; + }, + nextStepDrill(){ + const drills=this.unitDrills; + if(this.stepDrillIdx{ + if(window.lucide) lucide.createIcons(); + const m=document.getElementById('main'); + if(m) m.scrollTo({top:m.scrollHeight,behavior:'smooth'}); + }); + } + } + })); +}); diff --git a/posimai-sc/js/data/categories.js b/posimai-sc/js/data/categories.js new file mode 100644 index 00000000..a0f92442 --- /dev/null +++ b/posimai-sc/js/data/categories.js @@ -0,0 +1,425 @@ +export const CATEGORIES = [ + { id:'basics', label:'セキュリティ基礎', icon:'shield', units:[ + { id:'s01', num:'S01', title:'CIA三要素', freq:'high', diff:1, + concept:`

情報セキュリティの根幹はCIA三要素です。すべてのセキュリティ対策はこのどれかを守るために存在します。

機密性
Confidentiality
許可された人だけが情報にアクセスできる。暗号化・アクセス制御で実現。
完全性
Integrity
情報が正確で改ざんされていない。ハッシュ・デジタル署名で検証。
可用性
Availability
必要なときに情報を使える。冗長化・バックアップで確保。

開発者視点で言い換えると:機密性は「見せない」、完全性は「改ざんさせない」、可用性は「止めない」です。DoS攻撃は可用性を、SQLインジェクションは機密性・完全性を脅かします。

`, + examtips:[ + '三要素のどれが損なわれているかを問う問題が頻出。攻撃とCIA要素のマッピングを整理すること。', + '真正性(Authenticity)・信頼性(Reliability)・否認防止(Non-repudiation)など追加要素も出題される。CIAだけが全てではない。', + 'DoS攻撃→可用性、盗聴→機密性、改ざん→完全性。この対応を即答できるようにする。' + ], + keypoints:[ + '機密性(C):許可された者だけがアクセスできる。暗号化・認証で守る', + '完全性(I):データが正確で改ざんされていない。ハッシュ・署名で検証', + '可用性(A):必要なときに使える。冗長化・DDoS対策で守る', + '真正性:通信相手が本当に本人かを確認できること(認証で実現)', + '否認防止:後から「やっていない」と言えないようにする(ログ・署名)' + ], + quiz:[ + {q:'DoS攻撃が損なう主なCIA要素はどれか。',choices:['機密性','完全性','可用性','真正性'],answer:2,exp:'DoS攻撃はサービスを停止させ、利用できなくするため可用性を損ないます。'}, + {q:'盗聴によって主に損なわれるCIA要素はどれか。',choices:['可用性','完全性','機密性','信頼性'],answer:2,exp:'盗聴は許可なく情報を取得するため、機密性が損なわれます。'}, + {q:'ハッシュ値を用いてデータが改ざんされていないことを確認する行為は、主にどのCIA要素を守るものか。',choices:['機密性','完全性','可用性','否認防止'],answer:1,exp:'ハッシュによる改ざん検知は完全性(Integrity)を守る手段です。'}, + {q:'情報セキュリティの3要素(CIA)に含まれないものはどれか。',choices:['機密性','可用性','拡張性','完全性'],answer:2,exp:'拡張性(Scalability)はCIA三要素に含まれません。機密性・完全性・可用性が三要素です。'} + ] + }, + { id:'s02', num:'S02', title:'脅威・脆弱性・リスク', freq:'high', diff:1, + concept:`

リスク管理の基礎概念を整理します。脅威は攻撃者や災害など「悪いことが起きる原因」、脆弱性はシステムや運用の「穴」、リスクはそれらが組み合わさって「実際に被害が発生する可能性と影響度」です。

リスクの計算(概念式)
リスク = 脅威 × 脆弱性 × 資産価値

リスク対応の4戦略:
・回避(リスクが生じる活動をやめる)
・低減(対策を実施して確率・影響を下げる)
・移転(保険・外部委託でリスクを移す)
・受容(コスト対効果で許容する)

開発者にとって「脆弱性」とはバグや設定ミスのこと。CVEデータベースに登録される脆弱性情報は、脅威アクターが攻撃に利用する前にパッチを当てるために使います。

`, + examtips:[ + '脅威・脆弱性・リスクの定義の違いを問う問題は毎回出る。「脆弱性」は弱点であり攻撃そのものではない。', + 'リスク対応4戦略(回避・低減・移転・受容)の具体例を各1つ言えるようにする。', + '残留リスク:対策後にも残るリスクのこと。ゼロにはできないことを前提に許容・監視する。' + ], + keypoints:[ + '脅威:資産に損害を与える原因となり得るもの(マルウェア・内部不正・災害)', + '脆弱性:脅威が利用できるシステムや運用の弱点(パッチ未適用・設定ミス)', + 'リスク:脅威が脆弱性を突くことで損害が発生する可能性と影響の組み合わせ', + 'リスク対応:回避・低減・移転・受容の4戦略', + '残留リスク:対策後も残るリスク。経営判断で受容・監視する' + ], + quiz:[ + {q:'「パッチが未適用のため既知の脆弱性が存在するシステム」はリスク管理上、何に該当するか。',choices:['脅威','脆弱性','リスク','インシデント'],answer:1,exp:'未パッチのシステムは攻撃者に利用される「穴」であり、脆弱性に該当します。'}, + {q:'サイバー保険を契約することで損害発生時の費用リスクを保険会社に負わせる対応は、リスク対応の4戦略のうちどれか。',choices:['回避','低減','移転','受容'],answer:2,exp:'保険やアウトソースでリスクを第三者に転嫁することをリスク移転と呼びます。'}, + {q:'リスクアセスメントの手順として正しい順序はどれか。',choices:['リスク特定→リスク評価→リスク分析','リスク特定→リスク分析→リスク評価','リスク評価→リスク特定→リスク分析','リスク分析→リスク評価→リスク特定'],answer:1,exp:'JIS Q 27005に基づくリスクアセスメントは「特定→分析→評価」の順です。'}, + {q:'脅威と脆弱性の説明として正しい組み合わせはどれか。',choices:['脅威:パッチ未適用/脆弱性:マルウェア','脅威:マルウェア/脆弱性:パッチ未適用','脅威:暗号化/脆弱性:認証','脅威:ファイアウォール/脆弱性:DoS'],answer:1,exp:'マルウェアは脅威(攻撃原因)、パッチ未適用は脆弱性(システムの弱点)です。'} + ] + }, + { id:'s03', num:'S03', title:'認証技術', freq:'high', diff:2, + concept:`

認証は「あなたが本当にあなたか」を確認するプロセスです。認証要素は3種類あり、複数組み合わせることで強度が上がります。

認証の3要素
知識要素(Something you know):パスワード・PIN・秘密の質問
所持要素(Something you have):ICカード・スマートフォン・ハードウェアトークン
生体要素(Something you are):指紋・顔認証・虹彩

2要素以上を組み合わせるのが多要素認証(MFA)。SSOはIDPが認証を一元管理し、アプリはそのトークンを信頼するモデルです。開発者にとってJWT(JSON Web Token)はSSO文脈でよく使うものです。

ユーザー
ID/PW入力
IdP
認証・発行
トークン
JWT/SAML
サービス
検証・許可
`, + examtips:[ + '「2段階認証」は同一要素を2回使う場合もある(PW+秘密の質問→どちらも知識要素)。多要素認証は異なる要素を組み合わせる点が重要。', + 'チャレンジレスポンス認証:サーバがランダム値(チャレンジ)を送り、クライアントがハッシュ等で応答(レスポンス)する。パスワードを平文で送らない。', + 'FIDO2 / パスキー:最新の試験でも出始めている。秘密鍵をデバイスに保管し、公開鍵で検証するフィッシング耐性の高い認証方式。' + ], + keypoints:[ + '知識・所持・生体の3要素。異なる要素を2つ以上組み合わせると多要素認証(MFA)', + 'チャレンジレスポンス認証:パスワードを平文で送らずに認証する', + 'SSO(シングルサインオン):一度の認証で複数サービスを利用(SAML/OIDCで実現)', + 'リスクベース認証:ログイン状況(場所・デバイス)が異常なら追加認証', + 'FIDO2/パスキー:公開鍵暗号ベースのフィッシング耐性が高い認証' + ], + quiz:[ + {q:'多要素認証の説明として正しいものはどれか。',choices:['同じ知識要素を2回入力させる','異なる種類の認証要素を2つ以上組み合わせる','生体認証のみを使用する','毎回異なるパスワードを使用する'],answer:1,exp:'多要素認証は知識・所持・生体など異なる種類の認証要素を2つ以上組み合わせます。'}, + {q:'チャレンジレスポンス認証の目的はどれか。',choices:['生体情報の保護','パスワードの平文送信を回避する','SSO実現','セッション固定攻撃の防止'],answer:1,exp:'チャレンジレスポンス認証はパスワード自体を送らず、ハッシュ等の応答で認証します。盗聴対策。'}, + {q:'SSOにおいてIdP(アイデンティティプロバイダ)の役割はどれか。',choices:['各サービスのDBを管理する','ユーザー認証を一元管理しトークンを発行する','ファイアウォールとして機能する','セッションIDを生成する'],answer:1,exp:'IdPはユーザーを認証し、各サービス(SP)が信頼できるトークンを発行する中央認証機関です。'}, + {q:'パスキー(FIDO2)の特徴として正しいものはどれか。',choices:['パスワードをサーバに保存する','秘密鍵をデバイスに保管し公開鍵で検証するためフィッシング耐性が高い','知識要素だけで認証する','SMSによるワンタイムパスワードを使う'],answer:1,exp:'FIDO2/パスキーは公開鍵暗号ベース。秘密鍵はデバイスから外に出ないのでフィッシングが効きません。'} + ] + }, + { id:'s04', num:'S04', title:'アクセス制御', freq:'high', diff:2, + concept:`

アクセス制御は「誰が何にどう操作できるか」を管理する仕組みです。3つのモデルが試験に頻出です。

主なアクセス制御モデル
DAC(任意アクセス制御):所有者が権限を設定(Linuxのchmod等)
MAC(強制アクセス制御):システムがラベルで強制(機密文書管理)
RBAC(役割ベース制御):役割に権限を付与(多くのWebアプリで採用)

最小権限の原則(Least Privilege):業務に必要な最小限の権限のみ与える。開発者でいえばEC2に付けるIAM RoleはReadOnlyで十分な場合にAdminを付けないことです。

Need-to-know:業務上知る必要がある情報だけに絞るデータ分離の原則。MACと組み合わせることが多い。

`, + examtips:[ + 'DAC・MAC・RBACの違いを具体例で説明できること。試験では「誰が権限を設定するか」がポイント。', + '最小権限の原則は選択問題の正答選択肢になることが多い。「できるだけ多くの権限を与える」は誤答。', + '職務分離(Separation of Duties):一人の担当者がすべてを担当しない。承認者と執行者を分ける。よく問われる。' + ], + keypoints:[ + 'DAC:所有者が自分で権限を決める(UNIX パーミッション等)', + 'MAC:ラベルに基づいてシステムが強制(軍・政府系情報管理)', + 'RBAC:役割に権限を紐付け(Webアプリのユーザー・管理者ロール)', + '最小権限の原則:必要最低限の権限だけ付与する', + '職務分離:不正防止のため1人に全権限を集中させない' + ], + quiz:[ + {q:'Linuxのファイルオーナーが chmod でパーミッションを設定するのはどのアクセス制御モデルか。',choices:['MAC','RBAC','DAC','ABAC'],answer:2,exp:'DACは所有者が自分で権限を設定できる任意アクセス制御モデルです。'}, + {q:'「社員の役職(マネージャー・一般社員等)に権限セットを割り当てる」手法はどれか。',choices:['DAC','MAC','RBAC','NAC'],answer:2,exp:'役割(Role)に権限を紐付けるRBACです。Webアプリでは最も広く使われます。'}, + {q:'最小権限の原則の説明として正しいものはどれか。',choices:['管理者には全権限を与える','業務に必要な最小限の権限だけ付与する','権限はデフォルトで全員に開放する','上位職者ほど権限が多くなる'],answer:1,exp:'最小権限(Least Privilege)は必要最小限の権限のみ与え、不正・ミスの影響範囲を最小化します。'}, + {q:'資金送金の「申請」と「承認」を別々の担当者が行うようにするセキュリティ原則はどれか。',choices:['最小権限','職務分離','Need-to-know','MAC'],answer:1,exp:'職務分離(Separation of Duties)は一人が全工程を担わないようにして不正を防ぐ原則です。'} + ] + }, + { id:'s05', num:'S05', title:'ISMS・セキュリティマネジメント', freq:'mid', diff:2, + concept:`

ISMS(Information Security Management System)はJIS Q 27001をベースにした、組織全体で情報セキュリティを継続的に改善する仕組みです。

PDCAサイクル(ISMS)
P(計画):リスクアセスメント→セキュリティポリシー策定
D(実行):管理策の実施・社員教育
C(確認):内部監査・マネジメントレビュー
A(改善):不適合の是正・継続的改善

ISMSの認証取得はISO/IEC 27001に基づきます。プライバシーマーク(PMS)は個人情報保護に特化した日本独自の認証制度です。ゼロトラストアーキテクチャは「境界の内側も信頼しない」という現代的なセキュリティ思想で、ISMSの文脈でも登場します。

`, + examtips:[ + 'ISMSはJIS Q 27001・ISO/IEC 27001が根拠。プライバシーマークとの違い(対象範囲・認定機関)を整理。', + 'PDCAのどのフェーズの話かを問う問題が多い。「内部監査」はC(確認)、「リスクアセスメント」はP(計画)。', + 'ゼロトラスト:「常に検証、決して信頼しない(Never trust, always verify)」が合言葉。境界型セキュリティとの対比で問われる。' + ], + keypoints:[ + 'ISMS:JIS Q 27001に基づく情報セキュリティマネジメントシステム。PDCAで継続改善', + 'リスクアセスメント:リスクの特定→分析→評価。ISMS計画フェーズの中核', + 'プライバシーマーク:個人情報保護専用。日本国内の制度(JIS Q 15001)', + 'ゼロトラスト:内部ネットワークも信頼しない。常に認証・認可を確認', + 'CSIRT:インシデント対応チーム(S06以降で詳述)' + ], + quiz:[ + {q:'ISMSの根拠となる国際規格はどれか。',choices:['ISO/IEC 15408','ISO/IEC 27001','JIS Q 15001','PCI DSS'],answer:1,exp:'ISMSはISO/IEC 27001(日本ではJIS Q 27001)が根拠規格です。'}, + {q:'ISMS認証のPDCAサイクルにおいて「内部監査」はどのフェーズに位置するか。',choices:['Plan','Do','Check','Act'],answer:2,exp:'内部監査は実施内容を確認する「Check」フェーズに位置します。'}, + {q:'ゼロトラストアーキテクチャの考え方として正しいものはどれか。',choices:['内部ネットワークは安全として信頼する','社外からのアクセスだけを検証する','内部・外部を問わずすべてのアクセスを常に検証する','一度認証すれば内部では再認証不要'],answer:2,exp:'ゼロトラストは「Never trust, always verify」として内部も含め常に検証します。'} + ] + } + ]}, + { id:'crypto', label:'暗号・PKI', icon:'key', units:[ + { id:'s06', num:'S06', title:'共通鍵暗号', freq:'high', diff:2, + concept:`

共通鍵暗号(対称暗号)は送受信者が同じ鍵を使って暗号化・復号する方式です。速度が速く大量データの暗号化に向いています。

主要アルゴリズム
AES(Advanced Encryption Standard):現在の標準。128/192/256ビット鍵。ブロック暗号
DES:56ビット鍵。現在は脆弱で使用禁止
3DES:DESを3回適用。AESへ移行中
ChaCha20:ストリーム暗号。モバイル・TLS1.3で採用

鍵配送問題:共通鍵を安全に相手に渡す手段が必要。これを解決したのが公開鍵暗号とDiffie-Hellman鍵交換です。TLSハンドシェイクではDH(ECDH)で共通鍵を合意し、AESで通信を暗号化するという組み合わせが標準です。

`, + examtips:[ + 'AES・DES・3DESのビット長と現在の安全性を整理する。DESは2024年現在使用禁止。', + 'n人で共通鍵を持ち合う場合の鍵の数:n(n-1)/2本。公開鍵暗号ならn本で済む。この違いが問われる。', + 'ブロック暗号のモード(ECB・CBC・GCM等)が出題されることも。ECBは同じ平文→同じ暗号文になる弱点がある。' + ], + keypoints:[ + '共通鍵暗号:暗号化と復号に同じ鍵を使う。高速・大量データ向き', + 'AES:現在の標準暗号。鍵長128/192/256ビット', + 'DES:56ビット鍵で現在は脆弱。使用禁止', + 'n人の鍵数:公開鍵ならn本、共通鍵ならn(n-1)/2本', + '鍵配送問題:共通鍵を安全に渡す手段が課題→DHや公開鍵暗号で解決' + ], + quiz:[ + {q:'現在の標準的な共通鍵暗号アルゴリズムはどれか。',choices:['DES','3DES','AES','RC4'],answer:2,exp:'AESは2001年に米国標準となり、現在最も広く使われる共通鍵暗号です。'}, + {q:'10人のユーザーが互いに安全に通信するために必要な共通鍵の総数はいくつか(1対1通信)。',choices:['10本','20本','45本','100本'],answer:2,exp:'n(n-1)/2 = 10×9/2 = 45本の共通鍵が必要です。'}, + {q:'共通鍵暗号の「鍵配送問題」とは何か。',choices:['鍵が長すぎる問題','暗号化に時間がかかる問題','共通鍵を安全に相手に渡す手段がない問題','鍵の保管場所がない問題'],answer:2,exp:'共通鍵を事前に安全に渡す方法がないことを鍵配送問題と言います。公開鍵暗号やDHで解決します。'}, + {q:'ブロック暗号モードのうち同一ブロックの平文が常に同一の暗号文になるため脆弱とされるものはどれか。',choices:['CBC','GCM','CTR','ECB'],answer:3,exp:'ECB(Electronic Codebook)モードはブロックを独立に暗号化するため、同一平文→同一暗号文になり、パターン漏洩の危険があります。'} + ] + }, + { id:'s07', num:'S07', title:'公開鍵暗号・デジタル署名', freq:'high', diff:2, + concept:`

公開鍵暗号(非対称暗号)は公開鍵と秘密鍵のペアを使います。公開鍵で暗号化→秘密鍵で復号(機密性)、秘密鍵で署名→公開鍵で検証(完全性・否認防止)。

送信者
秘密鍵で署名
署名付き
メッセージ
受信者
公開鍵で検証
主要アルゴリズム
RSA:素因数分解の困難性に基づく。2048ビット以上が現在の最低基準
ECDSA/EdDSA:楕円曲線暗号。RSAより短い鍵で同等の安全性
DH/ECDH:鍵交換アルゴリズム(暗号化ではなく鍵合意)

デジタル署名の手順:①メッセージのハッシュ値を計算②秘密鍵でハッシュを暗号化(=署名)③受信側は公開鍵で署名を復号しハッシュと照合。改ざんと否認を同時に防ぎます。

`, + examtips:[ + '暗号化と署名で使う鍵の向きが逆なことを確実に理解する。暗号化は「受信者の公開鍵」、署名は「送信者の秘密鍵」。', + 'RSAの最低鍵長は2048ビット(2024年現在)。1024ビットは脆弱で不推奨。', + 'デジタル署名≠暗号化。署名は「本人確認・改ざん検知」が目的。機密性(内容を隠す)は公開鍵暗号化が担う。' + ], + keypoints:[ + '暗号化:受信者の公開鍵で暗号化→受信者の秘密鍵で復号(機密性)', + '署名:送信者の秘密鍵で署名→送信者の公開鍵で検証(完全性・否認防止)', + 'RSA:素因数分解の困難性。最低2048ビット', + 'ECDSA:楕円曲線暗号。短い鍵で高い安全性', + 'DH/ECDH:鍵を共有するプロトコル。TLSのforward secrecyに使う' + ], + quiz:[ + {q:'デジタル署名を生成する際に使用する鍵はどれか。',choices:['受信者の公開鍵','送信者の公開鍵','受信者の秘密鍵','送信者の秘密鍵'],answer:3,exp:'署名は「送信者の秘密鍵」で生成し、受信者は「送信者の公開鍵」で検証します。'}, + {q:'公開鍵暗号で暗号化する際に使用する鍵はどれか。',choices:['送信者の秘密鍵','送信者の公開鍵','受信者の秘密鍵','受信者の公開鍵'],answer:3,exp:'「受信者の公開鍵」で暗号化し、受信者だけが持つ「秘密鍵」で復号します。'}, + {q:'デジタル署名が提供するセキュリティ機能として正しいものの組み合わせはどれか。',choices:['機密性・可用性','完全性・否認防止','機密性・完全性','可用性・否認防止'],answer:1,exp:'デジタル署名は改ざん検知(完全性)と「やっていない」と言わせない(否認防止)を提供します。'}, + {q:'RSA暗号の安全性の根拠となる数学的困難性はどれか。',choices:['離散対数問題','素因数分解の困難性','楕円曲線の困難性','ナップサック問題'],answer:1,exp:'RSAは大きな整数の素因数分解が困難であることを安全性の根拠としています。'} + ] + }, + { id:'s08', num:'S08', title:'ハッシュ関数', freq:'high', diff:1, + concept:`

ハッシュ関数は任意のデータから固定長のダイジェスト(ハッシュ値)を生成する一方向関数です。同じ入力から常に同じ出力が得られ、逆算は(計算量的に)不可能です。

主要アルゴリズムと出力長
MD5:128ビット。衝突脆弱性あり。使用禁止(整合性確認のみ残存)
SHA-1:160ビット。衝突脆弱性あり。証明書用途では使用禁止
SHA-256:256ビット。現在の標準。Bitcoinでも使用
SHA-3:Keccakアルゴリズム。SHA-2と並行して使える代替

ハッシュの3性質:①衝突耐性(同じハッシュを持つ別データを作れない)②第二原像耐性(ハッシュ値から元データを求められない)③雪崩効果(入力が1ビット変わると出力が大きく変わる)

パスワード保存にはハッシュにソルト(ランダム値)を加えることでレインボーテーブル攻撃を防ぎます。bcrypt・Argon2が推奨。

`, + examtips:[ + 'MD5・SHA-1は衝突脆弱性が実証されており現在は安全でない。SHA-256以上を使う。', + 'ハッシュは一方向(復号不可)。「ハッシュ値からパスワードを復元」はできない→レインボーテーブルは元の値のハッシュを事前計算して照合するもの。', + 'ソルト:パスワードに加えるランダム値。ユーザーごとに異なるため、同じPWでもハッシュ値が変わりレインボーテーブルを無効化する。' + ], + keypoints:[ + 'ハッシュ関数:一方向変換。固定長ダイジェストを生成', + 'SHA-256:現在の標準。MD5・SHA-1は使用禁止(衝突脆弱性)', + '衝突耐性・第二原像耐性・雪崩効果がセキュアなハッシュの条件', + 'パスワード保存:bcrypt/Argon2+ソルトが推奨。単純SHA-256は不十分', + 'レインボーテーブル攻撃:事前計算ハッシュとの照合。ソルトで無効化' + ], + quiz:[ + {q:'現在セキュリティ用途として推奨されないハッシュアルゴリズムはどれか。',choices:['SHA-256','SHA-3','SHA-512','MD5'],answer:3,exp:'MD5は衝突脆弱性が実証されており、セキュリティ用途での使用は禁止されています。'}, + {q:'ハッシュ関数の性質として正しいものはどれか。',choices:['出力から入力が復元できる','同じ入力から毎回異なる出力が得られる','任意のデータから固定長のダイジェストを生成する','暗号化と復号に使用できる'],answer:2,exp:'ハッシュ関数は任意の入力から固定長ダイジェストを生成する一方向関数です。'}, + {q:'パスワードのハッシュ保存においてソルトを使用する目的はどれか。',choices:['ハッシュ計算を高速化する','レインボーテーブル攻撃を無効化する','パスワードを復元できるようにする','認証速度を向上する'],answer:1,exp:'ソルト(ユーザーごとのランダム値)を加えることで、事前計算済みのレインボーテーブルを無効化します。'}, + {q:'デジタル署名においてハッシュ関数を使う主な理由はどれか。',choices:['メッセージを暗号化するため','メッセージ全体を秘密鍵で処理するコストを削減するため','認証局の検証を省略するため','公開鍵を生成するため'],answer:1,exp:'メッセージ全体を秘密鍵で暗号化すると非常に遅いため、ハッシュ値のみ署名します。'} + ] + }, + { id:'s09', num:'S09', title:'PKI・TLS', freq:'high', diff:2, + concept:`

PKI(Public Key Infrastructure)は公開鍵の正当性を証明する仕組みです。中核となるのが認証局(CA)が発行するデジタル証明書(X.509)です。

CA
認証局
証明書
公開鍵+CAの署名
ブラウザ
CAの公開鍵で検証

TLS 1.3のハンドシェイク:①サーバ証明書を送る②クライアントがCAチェーンを検証③ECDHで共通鍵を合意④AES-GCMで通信開始。開発者にとってHTTPS=TLSは日常的な技術です。

証明書の失効確認
CRL(証明書失効リスト):失効した証明書のリスト。定期更新
OCSP(Online Certificate Status Protocol):リアルタイムで1証明書の状態を確認
OCSP Stapling:サーバ自身が失効情報をキャッシュしてクライアントに渡す
`, + examtips:[ + 'ルートCA→中間CA→サーバ証明書のチェーン(信頼の連鎖)を理解する。ブラウザはルートCAを信頼リストで持つ。', + 'TLS1.2と1.3の違い:TLS1.3はハンドシェイクが1-RTTに短縮、前方秘匿性(PFS)がデフォルト。', + 'CRLとOCSPの違い:CRLは定期ダウンロード式・大きい。OCSPはリアルタイム・1件ずつ。試験でよく比較される。' + ], + keypoints:[ + 'CA(認証局):公開鍵の正当性を証明するデジタル証明書を発行', + 'X.509証明書:公開鍵・所有者情報・CAの署名を含む', + 'TLS 1.3:ECDH鍵交換+AES-GCM暗号。1-RTTハンドシェイク', + 'CRL:失効証明書リスト(定期更新)。OCSP:リアルタイム失効確認', + '信頼の連鎖:ルートCA→中間CA→エンドエンティティ証明書' + ], + quiz:[ + {q:'デジタル証明書においてCAが担う役割はどれか。',choices:['通信を暗号化する','公開鍵の正当性を証明する','秘密鍵を生成する','ファイアウォールとして機能する'],answer:1,exp:'CAは公開鍵が本当にその所有者のものであることをデジタル署名で証明します。'}, + {q:'TLS通信でサーバ証明書の失効をリアルタイムに1件確認するプロトコルはどれか。',choices:['CRL','OCSP','LDAP','PKI'],answer:1,exp:'OCSPは1枚の証明書の失効状態をリアルタイムに確認するプロトコルです。'}, + {q:'TLS 1.3の特徴として正しいものはどれか。',choices:['RC4で通信を暗号化する','1-RTTハンドシェイクで接続が速い','DES鍵交換を使う','前方秘匿性(PFS)はオプション'],answer:1,exp:'TLS1.3は1-RTTハンドシェイクで高速化し、ECDHによる前方秘匿性がデフォルトです。'}, + {q:'証明書の信頼チェーンで「ルートCAの証明書を格納しているのは主にどこか。',choices:['Webサーバ','DNS','OSやブラウザの信頼リスト','OCSP Responder'],answer:2,exp:'ブラウザやOSはルートCAの証明書を信頼リスト(トラストストア)として事前に搭載しています。'} + ] + } + ]}, + { id:'network', label:'ネットワーク防御', icon:'network', units:[ + { id:'s10', num:'S10', title:'ファイアウォール・DMZ', freq:'high', diff:2, + concept:`

ファイアウォールはネットワーク境界でトラフィックをフィルタリングします。DMZ(非武装地帯)はインターネットと内部ネットワークの間に置く中間ゾーンで、Webサーバ等の公開サービスを置きます。

インターネット(外部)不特定多数のアクセス
DMZ(非武装地帯)Webサーバ・DNSサーバ・メールサーバ
内部ネットワーク社内PC・DBサーバ・ファイルサーバ
ファイアウォールの種類
パケットフィルタリング:IPアドレス・ポートで許可/拒否。高速だがアプリ層の攻撃に弱い
SPI(ステートフルパケットインスペクション):コネクション状態を追跡。戻りパケットを自動許可
アプリケーションゲートウェイ:プロキシとして動作。アプリ層まで検査
`, + examtips:[ + 'DMZにはWebサーバ・メールサーバを置く。DBサーバはDMZではなく内部ネットワークに置く(頻出引っかけ)。', + 'デフォルト拒否(ホワイトリスト型)が原則。「明示的に許可されていないものは拒否」。', + 'ステートフルとステートレスの違い:ステートフルはセッション状態を管理するためSYN/ACKなどの戻り方向を自動許可できる。' + ], + keypoints:[ + 'ファイアウォール:パケットをルールに基づき許可/拒否', + 'DMZ:外部と内部の中間ゾーン。公開サービスのみ配置', + 'パケットフィルタリング:IP/ポートベース。高速だがL7攻撃に弱い', + 'SPI:接続状態を追跡。戻りパケットを動的に許可', + 'デフォルト拒否原則:明示的に許可されていないものはすべて遮断' + ], + quiz:[ + {q:'DMZに設置すべきサーバとして適切なものはどれか。',choices:['データベースサーバ','人事管理システム','Webサーバ(公開用)','ファイルサーバ(社内用)'],answer:2,exp:'DMZには外部からアクセスされる公開Webサーバ等を置きます。DBや社内システムは内部ネットワークに置きます。'}, + {q:'ステートフルパケットインスペクション(SPI)の特徴はどれか。',choices:['IPアドレスのみでフィルタリングする','コネクションの状態を追跡し戻りパケットを自動許可する','アプリケーション層のデータを復号して検査する','UDPのみに対応している'],answer:1,exp:'SPIはTCPセッション状態を管理し、正規の戻りパケット(ACK)を動的に許可します。'}, + {q:'ファイアウォールの基本原則「デフォルト拒否」の意味はどれか。',choices:['すべての通信を拒否する','許可ルールに一致しない通信はすべて拒否する','認証なしの通信を拒否する','外部からの通信のみ拒否する'],answer:1,exp:'デフォルト拒否(deny all)は、明示的に許可されていないすべての通信を遮断する原則です。'} + ] + }, + { id:'s11', num:'S11', title:'IDS・IPS・WAF', freq:'high', diff:2, + concept:`

ファイアウォールを補完するセキュリティデバイスです。IDSは検知のみ、IPSは検知して遮断します。WAFはWebアプリケーション専用でHTTPレベルの攻撃を防ぎます。

検知方式
シグネチャ型(不正検知):既知の攻撃パターンと照合。既知攻撃に強い・ゼロデイに弱い
アノマリ型(異常検知):正常な行動からの逸脱を検知。未知攻撃も検知可能・誤検知が多い

WAFの導入パターン:リバースプロキシ型が最も一般的。クラウドWAFはCDNと統合されることも多い。SQLインジェクション・XSS・CSRFを主に防ぎます。誤検知(FP)のチューニングが運用上の課題です。

`, + examtips:[ + 'IDS(検知のみ)とIPS(検知+遮断)の違いは必須。「遮断」のワードがあればIPS。', + 'シグネチャ型はゼロデイ脆弱性の攻撃を検知できない。アノマリ型は未知攻撃を検知できるが誤検知率が高い。', + 'WAFとファイアウォールの違い:FWはL3/4(IP/ポート)、WAFはL7(HTTPのパラメータ・ヘッダ)を検査。' + ], + keypoints:[ + 'IDS:不正侵入の検知のみ(通知・ログ)', + 'IPS:不正侵入の検知と遮断(インライン配置が必要)', + 'WAF:Webアプリケーション専用。SQL・XSS等L7攻撃を防ぐ', + 'シグネチャ型:既知攻撃に強い・ゼロデイに弱い', + 'アノマリ型:未知攻撃も検知可・誤検知率が高い' + ], + quiz:[ + {q:'IPSとIDSの違いとして正しいものはどれか。',choices:['IDSは遮断できるがIPSは検知のみ','IPSは検知と遮断ができるがIDSは検知のみ','IPSはWAFの別名','IDSはファイアウォールの別名'],answer:1,exp:'IDSは検知・通知のみ。IPSはインラインに設置し不正トラフィックを遮断します。'}, + {q:'未知のゼロデイ攻撃の検知に適したIDSの検知方式はどれか。',choices:['シグネチャ型','アノマリ型(異常検知型)','プロトコル解析型','パターンマッチ型'],answer:1,exp:'アノマリ型は正常行動の基準から逸脱を検知するため、シグネチャにない未知の攻撃にも対応できます。'}, + {q:'WAFが主に防ぐ攻撃として正しいものの組み合わせはどれか。',choices:['DoS攻撃・ARP Spoofing','SQLインジェクション・XSS','DDoS攻撃・DNSハイジャック','フィッシング・スミッシング'],answer:1,exp:'WAFはWebアプリケーションへのSQLインジェクション・XSS・CSRFなどL7攻撃を防ぎます。'} + ] + }, + { id:'s12', num:'S12', title:'VPN', freq:'mid', diff:2, + concept:`

VPN(Virtual Private Network)はパブリックネットワーク上に暗号化された仮想トンネルを作り、安全な通信路を提供します。

主なVPN方式
IPsec-VPN:L3でパケット全体を暗号化。ESP/AHプロトコル。拠点間接続に多い
SSL/TLS-VPN:L5/7でHTTPS上にトンネル。ブラウザから利用可能。リモートアクセスに多い
WireGuard:最新の軽量プロトコル。ChaCha20/Poly1305を使用

IPsecの2モード:トランスポートモード(ペイロードのみ暗号化)とトンネルモード(パケット全体を暗号化して新しいIPヘッダを付与)。拠点間VPNではトンネルモードが一般的です。

`, + examtips:[ + 'IPsec-VPNはL3(ネットワーク層)、SSL-VPNはL5以上(セッション/アプリ層)での動作という層の違いが問われる。', + 'IPsecのESP(Encapsulating Security Payload)は暗号化と認証の両方を提供。AH(Authentication Header)は認証のみ(暗号化なし)。', + 'スプリットトンネリング:VPN接続中に社内宛て通信のみVPNを通し、一般ネット通信は直接出る設定。セキュリティリスクとして問われることがある。' + ], + keypoints:[ + 'IPsec-VPN:L3暗号化。拠点間接続向き。ESPは暗号化+認証', + 'SSL/TLS-VPN:HTTPS上のトンネル。リモートアクセス向き。ブラウザ対応', + 'トンネルモード:元パケット全体を暗号化+新IPヘッダ。拠点間に使用', + 'トランスポートモード:ペイロードのみ暗号化。ホスト間通信に使用', + 'WireGuard:現代的な軽量VPN。ChaCha20/Poly1305で高速' + ], + quiz:[ + {q:'IPsecのESPが提供する機能として正しいものはどれか。',choices:['認証のみ','暗号化のみ','暗号化と認証の両方','鍵交換のみ'],answer:2,exp:'ESP(Encapsulating Security Payload)はデータの暗号化と送信元認証の両方を提供します。'}, + {q:'SSL-VPNの説明として正しいものはどれか。',choices:['IPレベルで全パケットを暗号化する','TLS上にトンネルを構築しリモートアクセスに使われる','AHとESPプロトコルを使用する','ネットワーク層で動作する'],answer:1,exp:'SSL/TLS-VPNはHTTPS(TLS)を利用したトンネリングで、ブラウザからアクセスできるリモートアクセスVPNです。'}, + {q:'IPsecのトンネルモードとトランスポートモードの違いはどれか。',choices:['トンネルモードはUDPのみ対応','トンネルモードは元パケット全体を暗号化し新IPヘッダを付与する','トランスポートモードは拠点間接続に使われる','両モードに機能差はない'],answer:1,exp:'トンネルモードは元のIPパケット全体を暗号化し新IPヘッダを付けます。拠点間VPNで使われます。'} + ] + }, + { id:'s13', num:'S13', title:'DNSセキュリティ・メールセキュリティ', freq:'high', diff:2, + concept:`

DNSとメールはインターネットの基盤であり、攻撃の標的になりやすいプロトコルです。

DNS攻撃と対策
DNSキャッシュポイズニング:偽のDNS応答をキャッシュに送り込む→DNSSEC・ランダムポートで対策
DNSハイジャック:DNSサーバ自体を乗っ取る→DNSSEC・監視
DNSSEC:DNSレスポンスにデジタル署名を付与し改ざんを検知
メール認証技術
SPF(Sender Policy Framework):送信元IPをDNSで検証。なりすましドメイン対策
DKIM(DomainKeys Identified Mail):メールヘッダにデジタル署名。改ざん検知
DMARC:SPF/DKIMの結果に基づいて受信側の処理ポリシーを指定(拒否・隔離)
`, + examtips:[ + 'SPF・DKIM・DMARCはセットで覚える。SPFはIP検証、DKIMは署名、DMARCはポリシー(どう処理するか)。', + 'DNSキャッシュポイズニングはカミンスキー攻撃が有名。ランダムポート番号とTXID乱数化が対策。', + 'メールの暗号化(S/MIMEやPGP)とDKIMの違い:DKIMは送信元の正当性確認。S/MIMEは内容の暗号化。目的が違う。' + ], + keypoints:[ + 'DNSキャッシュポイズニング:偽応答でキャッシュを汚染。DNSSEC・ランダムポートで対策', + 'DNSSEC:DNS応答にデジタル署名を付けて改ざんを検知', + 'SPF:送信ドメインのIPアドレスをDNSで公開しなりすましを検証', + 'DKIM:メールヘッダの署名で改ざん・なりすましを検知', + 'DMARC:SPF/DKIMに基づくポリシーを定義(none/quarantine/reject)' + ], + quiz:[ + {q:'DNSキャッシュポイズニング攻撃の目的はどれか。',choices:['DNSサーバを過負荷にする','偽のDNS応答をキャッシュに記録させ利用者を偽サイトに誘導する','メールサーバを乗っ取る','TLS証明書を偽造する'],answer:1,exp:'DNSキャッシュポイズニングは偽の名前解決情報をキャッシュさせ、フィッシングサイト等に誘導します。'}, + {q:'メール送信ドメインの正当性をIPアドレスで検証する仕組みはどれか。',choices:['DKIM','DMARC','SPF','DNSSEC'],answer:2,exp:'SPF(Sender Policy Framework)は送信元IPをDNSのTXTレコードで検証しなりすましを防ぎます。'}, + {q:'DMARCが提供する主な機能はどれか。',choices:['メール本文を暗号化する','送信元IPを検証する','SPF/DKIMの結果に基づく受信ポリシーを定義する','メールのデジタル署名を行う'],answer:2,exp:'DMARCはSPF/DKIM両方の検証結果に基づいて、受信側が「none/quarantine/reject」などのポリシーを適用します。'}, + {q:'DKIMが提供する機能はどれか。',choices:['送信元IPアドレスの検証','メールヘッダへのデジタル署名による改ざん・なりすましの検知','メール本文の暗号化','SPFポリシーの実施'],answer:1,exp:'DKIMはメールヘッダに秘密鍵で署名し、受信側が公開鍵で署名を検証することで改ざんを検知します。'} + ] + } + ]}, + { id:'attacks', label:'攻撃手法と対策', icon:'sword', units:[ + { id:'s14', num:'S14', title:'マルウェア', freq:'high', diff:1, + concept:`

マルウェアはMalicious Softwareの略で、悪意のあるプログラムの総称です。種類と感染経路・対策を整理します。

主なマルウェアの種類
ウイルス:他のプログラムに寄生して感染拡大
ワーム:ネットワークを自力で伝播(ホスト不要)
トロイの木馬:正常なソフトに見せかけて侵入
ランサムウェア:ファイルを暗号化し身代金を要求
スパイウェア:情報を窃取して送信
ルートキット:管理者権限を奪い存在を隠蔽
ボット:C&Cサーバの指令でDDoS等に悪用

検知方式:シグネチャ型(パターン照合)・ビヘイビア型(振る舞い検知)・サンドボックス(隔離環境で動作確認)。ゼロデイマルウェアにはシグネチャが効かないためビヘイビア型が重要です。

`, + examtips:[ + 'ランサムウェア対策の3-2-1ルール:3つのバックアップ・2種類のメディア・1つはオフライン。試験でも対策として正答になりやすい。', + 'ルートキットは感染後にOS自体を改ざんして自分を隠す。通常のアンチウイルスで検知しにくい。', + 'C&Cサーバ(Command and Control):ボットを制御するサーバ。通信を遮断することが対策の一つ。' + ], + keypoints:[ + 'ウイルス:宿主ファイルに寄生。ワーム:自力でネット感染', + 'ランサムウェア:暗号化→身代金。対策はオフラインバックアップ', + 'トロイの木馬:正規ソフトに偽装。ルートキット:自身を隠蔽', + 'ビヘイビア検知:振る舞いから未知マルウェアを検出', + 'サンドボックス:隔離環境でマルウェアを動作させ分析' + ], + quiz:[ + {q:'ネットワークを通じて自己複製しホストプログラムなしに拡散するマルウェアはどれか。',choices:['ウイルス','ワーム','スパイウェア','ルートキット'],answer:1,exp:'ワームは宿主プログラムを必要とせず、ネットワーク経由で自力に拡散します。'}, + {q:'ランサムウェア対策として最も有効なものはどれか。',choices:['パスワードを強化する','定期的なオフラインバックアップを取得する','ファイアウォールを設置する','アカウントの2段階認証を導入する'],answer:1,exp:'ランサムウェアでファイルが暗号化されても、オフラインバックアップから復元できます。オンラインバックアップは暗号化される場合があります。'}, + {q:'シグネチャ型の検知が無効な攻撃手法はどれか。',choices:['既知マルウェアの変種','ゼロデイマルウェア(未知の攻撃)','添付ファイルウイルス','既知の脆弱性を狙った攻撃'],answer:1,exp:'シグネチャ型はパターン照合のため、まだシグネチャがないゼロデイマルウェアを検知できません。'}, + {q:'C&Cサーバ(Command and Control)の役割はどれか。',choices:['バックアップデータを保管する','感染したボットに指令を出し統制する','ファイアウォールのルールを管理する','証明書を発行する'],answer:1,exp:'C&Cサーバはボットネットの司令塔で、感染端末(ボット)に攻撃命令を送ります。'} + ] + }, + { id:'s15', num:'S15', title:'Webアプリケーション攻撃', freq:'high', diff:2, + concept:`

Webアプリ開発者が必ず知るべき攻撃パターンです。OWASP Top 10の常連が試験でも頻出です。

主要な攻撃と対策
SQLインジェクション:DB操作を注入。→プリペアドステートメント・入力検証
XSS(クロスサイトスクリプティング):悪意のあるスクリプトを挿入→エスケープ・CSP
CSRF(クロスサイトリクエストフォージェリ):ログイン中ユーザーに意図しないリクエスト→CSRFトークン・Same-Site Cookie
パストラバーサル:../でディレクトリを遡り任意ファイルを読む→パス正規化・バリデーション
コマンドインジェクション:OSコマンドを注入→外部コマンド使用禁止

XSSの種類:反射型(URLパラメータ経由)・格納型(DBに保存されたスクリプト)・DOMベース型(クライアントサイドJS処理)。試験では格納型が最も危険とされる。

`, + examtips:[ + 'SQLインジェクションの対策は「プリペアドステートメント(バインド変数)」が正答になる。エスケープだけでは不十分な場合がある。', + 'XSS対策:出力時のHTMLエスケープ(< > 等)+Content Security Policy(CSP)ヘッダ。', + 'CSRF対策:CSRFトークン(セッションに紐づくランダム値をフォームに埋める)+Same-Site Cookie属性。' + ], + keypoints:[ + 'SQLインジェクション:プリペアドステートメントで対策', + 'XSS:HTMLエスケープ+CSPヘッダで対策。Cookieには HttpOnly 属性', + 'CSRF:CSRFトークン+Same-Site Cookie(Strict/Lax)で対策', + 'パストラバーサル:../によるファイルパス操作。正規化・ホワイトリストで対策', + 'セキュリティヘッダ:CSP・X-Frame-Options・HSTS等を必ず設定' + ], + quiz:[ + {q:'SQLインジェクションの最も効果的な対策はどれか。',choices:['入力文字数を制限する','プリペアドステートメント(バインド変数)を使用する','WAFを設置する','ファイアウォールでDBポートを塞ぐ'],answer:1,exp:'プリペアドステートメントはSQLとデータを分離するため、SQLインジェクションを根本的に防ぎます。'}, + {q:'XSS(クロスサイトスクリプティング)攻撃の目的として最も適切なものはどれか。',choices:['DBのデータを削除する','サービスを停止させる','セッションCookieを盗みセッションハイジャックを行う','管理者権限を奪取する'],answer:2,exp:'XSS攻撃の典型的な目的はCookieからセッションIDを盗み、被害者として操作することです。'}, + {q:'CSRF攻撃への対策として有効なものはどれか。',choices:['パラメータのSQLエスケープ','CSRFトークンをフォームに埋め込む','パスワードを強化する','HTTPSを使用する'],answer:1,exp:'CSRFトークンはセッションに紐づくランダム値で、正規フォームからのリクエストであることを検証します。'}, + {q:'格納型(persistent)XSSとはどのような攻撃か。',choices:['URLパラメータに悪意のあるスクリプトを含める','悪意のあるスクリプトをDBに保存しアクセスした全ユーザーに実行させる','クライアントサイドのJS処理でスクリプトを注入する','セッションクッキーを改ざんする'],answer:1,exp:'格納型XSSはDBに保存したスクリプトがページ表示のたびに全ユーザーで実行されるため最も危険です。'} + ] + }, + { id:'s16', num:'S16', title:'パスワード攻撃・フィッシング', freq:'high', diff:1, + concept:`

パスワードとフィッシングはエンドユーザーを標的にした攻撃であり、技術的対策と人的対策の両方が必要です。

パスワード攻撃の種類
ブルートフォース:全パターンを試す。アカウントロックで緩和
辞書攻撃:よく使われる単語リストを試す
パスワードスプレー:多アカウントに対し少数のPWを試す(ロック回避)
クレデンシャルスタッフィング:他サービスの漏洩リストを転用
レインボーテーブル:事前計算ハッシュとの照合。ソルトで無効化

フィッシング:正規サービスを偽った詐欺メールやサイトで認証情報を奪う。スピアフィッシング(特定の個人・組織を狙う)はターゲットを調査して巧妙な文面を作成します。ビジネスメール詐欺(BEC)はCEOや取引先を名乗り送金指示をするものです。

`, + examtips:[ + 'クレデンシャルスタッフィングは「他サービスで漏洩したIDとPWをそのまま別サービスに使う」。パスワードの使い回しが被害を広げる。', + 'パスワードスプレー攻撃:少数のPW("Spring2024!"等)を多数のアカウントに試す。アカウントロックを回避するための手法。', + 'スピアフィッシング対策:多要素認証・送信元確認(DMARC)・セキュリティ教育。技術だけでは防げない。' + ], + keypoints:[ + 'ブルートフォース:全試行。アカウントロックとレート制限で対策', + 'クレデンシャルスタッフィング:流出リストの転用。パスワード使い回しが最大の原因', + 'パスワードスプレー:少数PWを多数アカウントに試す。ロック閾値をずらす攻撃', + 'フィッシング:偽メール・偽サイトで認証情報を奪う', + 'スピアフィッシング:特定個人を調査した標的型。BECはその一種' + ], + quiz:[ + {q:'他のサービスで漏洩したID・パスワードのリストを使って別サービスにログインを試みる攻撃はどれか。',choices:['パスワードスプレー攻撃','ブルートフォース攻撃','クレデンシャルスタッフィング','辞書攻撃'],answer:2,exp:'クレデンシャルスタッフィングはパスワードの使い回しを悪用して漏洩リストを他サービスに転用する攻撃です。'}, + {q:'アカウントロックアウト機能を回避しながらパスワードを試みる攻撃手法はどれか。',choices:['ブルートフォース攻撃','パスワードスプレー攻撃','レインボーテーブル攻撃','クレデンシャルスタッフィング'],answer:1,exp:'パスワードスプレーは少数の一般的なパスワードを多数のアカウントに分散して試すため、ロックアウト閾値を超えません。'}, + {q:'スピアフィッシングの説明として正しいものはどれか。',choices:['不特定多数に大量配信するフィッシング','特定の個人・組織を調査した標的型フィッシング','電話を使ったソーシャルエンジニアリング','SMSを使ったフィッシング'],answer:1,exp:'スピアフィッシングは標的を事前に調査し、信頼性の高い偽メール・偽サイトで情報を盗む標的型攻撃です。'} + ] + }, + { id:'s17', num:'S17', title:'DoS/DDoS・APT', freq:'high', diff:2, + concept:`

DoS/DDoSはサービスを停止させる攻撃(可用性の侵害)、APT(Advanced Persistent Threat)は国家レベルの高度な標的型持続攻撃です。

DDoS攻撃の種類
Volumetric(帯域消費型):大量トラフィックで帯域を埋める(UDP flood・ICMP flood)
Protocol(プロトコル型):SYN floodでサーバリソースを枯渇させる
Application(アプリ型):HTTP floodでWebサーバのCPUを使い切る
Amplification(増幅型):DNSやNTPを踏み台にして大量レスポンスを標的に向ける

APTはKill Chain(偵察→侵入→水平移動→持続化→情報窃取)で進む多段階攻撃です。MITRE ATT&CK フレームワークはATTのTTPを体系化したもので、防御側が攻撃者の行動を予測するために使います。

`, + examtips:[ + 'SYN flood:TCPのSYNパケットだけ大量送信しACKを返さない。サーバはhalf-open接続を大量保持しリソース枯渇。SYN Cookie/Proxyで対策。', + 'DNSアンプ攻撃:小さいDNSクエリに大きいレスポンスが返ることを悪用。送信元IPを詐称して標的に大量レスポンスを向ける。', + 'APT対策:多層防御(Defense in Depth)・SIEM・EDR・ゼロトラスト。侵入を前提にした「検知・対応」が重要。' + ], + keypoints:[ + 'DoS:単一ホストからの攻撃。DDoS:多数のボットからの分散型攻撃', + 'SYN flood:half-open接続でサーバリソースを枯渇させる', + 'DNSアンプ:小クエリ→大レスポンス増幅。送信元偽装で標的に向ける', + 'APT:偵察→侵入→永続化→情報窃取の多段階攻撃', + 'MITRE ATT&CK:攻撃者のTTPを体系化したフレームワーク' + ], + quiz:[ + {q:'SYN flood攻撃の仕組みはどれか。',choices:['大量のICMPパケットを送りつける','TCPのSYNパケットを大量送信しハンドシェイクを完了させずサーバリソースを枯渇させる','DNSの大量レスポンスを標的に向ける','HTTPリクエストを大量送信する'],answer:1,exp:'SYN floodはTCPの3ウェイハンドシェイクを悪用し、half-open接続を大量に作成してサーバのリソース(メモリ・接続テーブル)を枯渇させます。'}, + {q:'DNSアンプ攻撃で攻撃者が悪用する特性はどれか。',choices:['DNSサーバの脆弱性を直接攻撃する','小さいクエリに対して大きいレスポンスが返るDNSの増幅特性','DNSキャッシュに偽情報を注入する','DNSサーバ自体をダウンさせる'],answer:1,exp:'DNSアンプ攻撃は小さいDNSクエリ(数十バイト)に対して何倍もの大きいDNSレスポンス(数千バイト)が返る増幅特性を利用します。'}, + {q:'APT(高度標的型攻撃)の特徴として正しいものはどれか。',choices:['不特定多数を短時間で攻撃する','標的を定め長期にわたって持続的に侵入・情報窃取を行う','パスワードを総当たりで試す','ランサムウェアを展開して即座に金銭を要求する'],answer:1,exp:'APTは特定の標的(政府・重要インフラ等)に対して長期間潜伏しながら情報を収集・窃取する国家レベルの攻撃です。'} + ] + }, + { id:'s18', num:'S18', title:'脆弱性管理', freq:'mid', diff:2, + concept:`

脆弱性管理はソフトウェアの欠陥を発見・評価・修正するサイクルです。ゼロデイ脆弱性は特に対応が難しく、多層防御が重要です。

脆弱性評価・管理の仕組み
CVE(Common Vulnerabilities and Exposures):脆弱性の識別番号(例:CVE-2021-44228)
CVSS(Common Vulnerability Scoring System):脆弱性の深刻度スコア(0.0〜10.0)
NVD(National Vulnerability Database):米NISTが管理するCVEデータベース
JVN(Japan Vulnerability Notes):日本のIPAが運用する脆弱性情報データベース

脆弱性の種類:設計上の欠陥(CSRF等の仕様設計)・実装上のバグ(バッファオーバーフロー等)・設定ミス(デフォルトパスワード・不要サービスの放置)。設定ミスは最もよくある脆弱性の源です。

`, + examtips:[ + 'CVSS v3の基本スコアは0.0〜10.0。Critical(9.0〜10.0)・High(7.0〜8.9)・Medium・Low・None。', + 'ゼロデイ脆弱性:パッチが存在しない状態での攻撃。ワークアラウンド(回避策)で暫定対応するしかない。', + 'ペネトレーションテスト:攻撃者視点でシステムを試験的に攻撃し脆弱性を発見する手法。バグバウンティプログラムも関連。' + ], + keypoints:[ + 'CVE:脆弱性の共通識別番号。CVSSでスコアリング(0.0〜10.0)', + 'ゼロデイ:パッチ未存在の脆弱性。WAF・IPS・行動監視で暫定対応', + 'ペネトレーションテスト:倫理的ハッカーが攻撃してセキュリティを評価', + '脆弱性の3種類:設計欠陥・実装バグ・設定ミス', + 'JVN:日本の脆弱性情報データベース。IPAが運用' + ], + quiz:[ + {q:'CVSSの説明として正しいものはどれか。',choices:['脆弱性の識別番号(ID)を付与する仕組み','脆弱性の深刻度を0.0〜10.0でスコアリングする共通評価システム','脆弱性情報を公開するデータベース','侵入テストの手法の標準'],answer:1,exp:'CVSS(Common Vulnerability Scoring System)は脆弱性の深刻度を標準化されたスコアで評価するフレームワークです。'}, + {q:'ゼロデイ脆弱性の説明として正しいものはどれか。',choices:['脆弱性が発見されてから0日以内にパッチが提供されたもの','パッチや修正が存在しない状態で悪用されている脆弱性','CVSSスコアが0点の軽微な脆弱性','すでに修正済みの脆弱性'],answer:1,exp:'ゼロデイは発見(または公開)からパッチが出るまでの間に悪用されるか、パッチが存在しない状態の脆弱性です。'}, + {q:'ペネトレーションテストの目的はどれか。',choices:['マルウェアを駆除する','攻撃者の視点でシステムを試験し脆弱性を発見する','ログを分析して不正アクセスを検知する','従業員にセキュリティ教育を行う'],answer:1,exp:'ペネトレーションテストは倫理的ハッカー(ペネトレーター)が実際の攻撃手法でシステムを試験し、悪用可能な脆弱性を特定します。'} + ] + } + ]}, + { id:'management', label:'管理・法規', icon:'scale', units:[ + { id:'s19', num:'S19', title:'インシデント対応・CSIRT', freq:'high', diff:2, + concept:`

インシデント対応はPDCAではなくPICERFサイクル(準備→識別→封じ込め→根絶→復旧→事後レビュー)で考えます。CSIRT(Computer Security Incident Response Team)は組織内外のインシデントを専門に扱うチームです。

インシデント対応フェーズ(NIST SP 800-61)
1. 準備(Preparation):体制・ツール・手順書の整備
2. 検知・分析(Detection & Analysis):インシデントの識別と影響範囲確認
3. 封じ込め(Containment):被害の拡大防止(ネットワーク切断等)
4. 根絶(Eradication):マルウェア除去・脆弱性修正
5. 復旧(Recovery):サービス再開・モニタリング
6. 事後レビュー(Post-incident Activity):再発防止策の策定

SIEM(Security Information and Event Management):複数のログを統合して異常を検知するプラットフォーム。SOC(Security Operations Center)はSIEMを監視する専門チームです。

`, + examtips:[ + '封じ込め(Containment)は根絶より先に行う。まず被害を止め、その後マルウェアを除去する。', + 'フォレンジックス:デジタル証拠の収集・保全・分析。証拠の連鎖(Chain of Custody)を保つことが重要。', + 'JPCERT/CC:日本のCSIRTの調整機関。脆弱性情報・インシデント情報を収集・共有する。' + ], + keypoints:[ + 'インシデント対応6フェーズ:準備→検知→封じ込め→根絶→復旧→事後レビュー', + '封じ込めが根絶の前:まず被害拡大を止めてからマルウェアを除去', + 'CSIRT:インシデント対応の専門チーム。JPCERT/CCが日本の調整機関', + 'SIEM:ログを統合して異常検知。SOCが監視を担う', + 'デジタルフォレンジック:証拠保全・Chain of Custodyが重要' + ], + quiz:[ + {q:'インシデント対応において「封じ込め」フェーズの目的はどれか。',choices:['マルウェアを完全に除去する','インシデントの被害拡大を防ぐ','サービスを復旧させる','事後レビューを実施する'],answer:1,exp:'封じ込めはまず被害の拡大を止めることが目的。ネットワーク隔離などを行います。マルウェア除去は「根絶」フェーズです。'}, + {q:'CSIRTの役割として正しいものはどれか。',choices:['ソフトウェアの開発','セキュリティインシデントの検知・対応・調整','ネットワーク機器の保守','社員の人事管理'],answer:1,exp:'CSIRTはセキュリティインシデントを専門に検知・分析・対応し、関係機関との情報共有・調整を行うチームです。'}, + {q:'SIEMの機能として正しいものはどれか。',choices:['ファイアウォールとして通信を遮断する','複数システムのログを集約して脅威を相関分析する','マルウェアをサンドボックスで実行する','脆弱性スキャンを行う'],answer:1,exp:'SIEMは複数のログソースを統合し、相関分析によって個別ログでは見えない攻撃パターンを検知します。'}, + {q:'デジタルフォレンジックにおける「Chain of Custody(証拠の連鎖)」の目的はどれか。',choices:['マルウェアを削除する','証拠が収集から法廷提出まで改ざんされていないことを証明する','バックアップを取得する','インシデントを封じ込める'],answer:1,exp:'Chain of Custodyは証拠の取り扱い履歴を記録し、証拠の完全性と法的証拠能力を維持するための手続きです。'} + ] + }, + { id:'s20', num:'S20', title:'法規・評価基準', freq:'high', diff:2, + concept:`

情報セキュリティに関わる法律・制度・評価基準を整理します。試験では法律の名前と対象・罰則の組み合わせが問われます。

主な法律・制度
不正アクセス禁止法:不正アクセス行為そのものを禁止(クラッキング・フィッシング等)
個人情報保護法:個人情報の取得・利用・保管・第三者提供の規制。漏洩時の報告義務
サイバーセキュリティ基本法:国のサイバーセキュリティ戦略の基盤。NISC設置の根拠法
不正競争防止法:営業秘密の保護。漏洩・横領に刑事罰
電子署名法:電子署名の法的効力を定める
評価基準・フレームワーク
ISO/IEC 15408(CC:Common Criteria):製品のセキュリティ評価基準。EALレベルで評価
PCI DSS:クレジットカード業界のセキュリティ基準(加盟店・決済事業者向け)
NIST SP 800シリーズ:米国のセキュリティガイドライン群
NIST Cybersecurity Framework(CSF):識別・防御・検知・対応・回復の5機能
`, + examtips:[ + '不正アクセス禁止法:アクセス権限のないコンピュータへの不正ログインを禁止。ポートスキャンだけでは違反にならないが、脆弱性を突いた侵入は違反。', + '個人情報保護法改正(2022年〜):漏洩報告の義務化(個人に報告義務あり)・越境移転の規制強化・クッキー等の「個人関連情報」が追加。', + 'NIST CSFの5機能:Identify(識別)→Protect(防護)→Detect(検知)→Respond(対応)→Recover(回復)。頭文字IPDRRで覚える。' + ], + keypoints:[ + '不正アクセス禁止法:権限なしのアクセス行為を禁止。IDのなりすましも対象', + '個人情報保護法:個人情報の適切な取扱い。漏洩時は本人・個人情報保護委員会への報告義務', + 'ISO/IEC 15408(CC):製品セキュリティの国際評価基準。EAL1〜7のレベル', + 'PCI DSS:カード決済のセキュリティ基準。12要件で構成', + 'NIST CSF:識別・防護・検知・対応・回復(IPDRR)の5機能フレームワーク' + ], + quiz:[ + {q:'不正アクセス禁止法で禁じられる行為として正しいものはどれか。',choices:['セキュリティ調査のための公開Webサーバのポートスキャン','他人のIDとパスワードを使って無断でログインすること','脆弱性情報をセキュリティ機関に報告すること','自社システムへのペネトレーションテスト'],answer:1,exp:'不正アクセス禁止法は権限なしに他人のIDなどを使ってコンピュータにアクセスする行為を禁じます。'}, + {q:'2022年施行の個人情報保護法改正で新たに義務化された主な事項はどれか。',choices:['全企業のISMS認証取得','個人情報漏洩時の個人および当局への報告義務','生体情報の収集禁止','クッキーの使用禁止'],answer:1,exp:'2022年改正で個人情報の漏洩・侵害が発生した場合、個人と個人情報保護委員会への報告が義務化されました。'}, + {q:'NIST Cybersecurity Framework(CSF)の5機能として正しい組み合わせはどれか。',choices:['計画・実行・確認・改善・廃棄','識別・防護・検知・対応・回復','認証・暗号・監視・対応・復旧','設計・構築・テスト・展開・監視'],answer:1,exp:'NIST CSFはIdentify・Protect・Detect・Respond・Recover(IPDRR)の5機能で構成されます。'}, + {q:'ISO/IEC 15408(CC:Common Criteria)の用途はどれか。',choices:['組織のISMS認証','IT製品・システムのセキュリティ機能の評価・認証','個人情報の保護規制','クレジットカードの安全基準'],answer:1,exp:'Common Criteriaは情報技術製品のセキュリティ機能をEAL(Evaluation Assurance Level)1〜7で評価・認証する国際規格です。'} + ] + } + ]} +]; diff --git a/posimai-sc/js/data/drills.js b/posimai-sc/js/data/drills.js new file mode 100644 index 00000000..80794bdd --- /dev/null +++ b/posimai-sc/js/data/drills.js @@ -0,0 +1,85 @@ +/** + * Step 2 二択ドリル(単元 id ごと)。choices は常に2件、answer は 0 または 1。 + */ +export const DRILLS = { + s01: [ + { q: 'DoS 攻撃が主に損なう CIA 要素はどちらか。', choices: ['機密性', '可用性'], answer: 1, exp: 'DoS はサービス利用を妨げるため、主に可用性を損ないます。' }, + { q: 'デジタル署名が主に提供するのはどちらか。', choices: ['機密性と可用性', '完全性と否認防止'], answer: 1, exp: '署名は改ざん検知(完全性)と「やっていない」と言えない状態(否認防止)を支えます。' } + ], + s02: [ + { q: '「パッチ未適用の既知の穴」は用語として何と呼ぶか。', choices: ['脅威', '脆弱性'], answer: 1, exp: 'システム側の弱点は脆弱性。脅威は攻撃者やマルウェアなどの原因側です。' }, + { q: '保険で金銭的損失を第三者に移すのは4戦略のどれか。', choices: ['低減', '移転'], answer: 1, exp: '保険・アウトソースでリスクを第三者に移すのはリスク移転です。' } + ], + s03: [ + { q: 'パスワードと SMS の OTP のように「異なる種類の要素」を2つ以上使うのは。', choices: ['多要素認証(MFA)', 'シングルサインオン(SSO)'], answer: 0, exp: '知識・所持・生体など異なる要素の組み合わせが多要素認証です。' }, + { q: 'SSO でユーザーを認証しトークンを発行する中央の役割は。', choices: ['SP(サービスプロバイダ)', 'IdP(アイデンティティプロバイダ)'], answer: 1, exp: 'IdP が認証を一元化し、各 SP がトークンを検証します。' } + ], + s04: [ + { q: 'Linux のファイル所有者が chmod で権限を決めるモデルは。', choices: ['MAC', 'DAC'], answer: 1, exp: '所有者が任意に権限を設定できるのは DAC(任意アクセス制御)です。' }, + { q: '業務に必要な最小限の権限だけを与える原則は。', choices: ['職務分離', '最小権限'], answer: 1, exp: 'Least Privilege は権限の過剰付与を避ける原則です。' } + ], + s05: [ + { q: 'ISMS の国際的な枠組みとして根拠になりやすい規格は。', choices: ['ISO 9001', 'ISO/IEC 27001'], answer: 1, exp: 'ISMS は主に ISO/IEC 27001(日本では JIS Q 27001)が根拠です。' }, + { q: '「内部も含め常に検証する」現代的な境界を前提にしない思想は。', choices: ['境界型セキュリティ', 'ゼロトラスト'], answer: 1, exp: 'ゼロトラストは Never trust, always verify として内外を区別しません。' } + ], + s06: [ + { q: '現在の標準的な共通鍵ブロック暗号として広く使われるのは。', choices: ['DES', 'AES'], answer: 1, exp: 'AES が現行の標準です。DES は鍵長が短く非推奨です。' }, + { q: 'n 人が1対1で共通鍵だけで安全に通信する場合、必要な鍵の本数は公開鍵方式より。', choices: ['少ない', '多い'], answer: 1, exp: '共通鍵は n(n-1)/2 本。公開鍵方式は鍵ペア管理で n 程度に抑えやすいです。' } + ], + s07: [ + { q: 'デジタル署名を作るときに使うのは送信者のどちらの鍵か。', choices: ['公開鍵', '秘密鍵'], answer: 1, exp: '署名は送信者の秘密鍵で生成し、受信者は公開鍵で検証します。' }, + { q: 'メッセージの機密性を確保する「公開鍵で暗号化」に使うのは受信者の。', choices: ['秘密鍵', '公開鍵'], answer: 1, exp: '誰でも暗号化できるのは受信者の公開鍵。復号は受信者の秘密鍵だけです。' } + ], + s08: [ + { q: '衝突耐性が破られ実証されているためセキュリティ用途で避けるべきなのは。', choices: ['SHA-256', 'MD5'], answer: 1, exp: 'MD5 は衝突攻撃が実証されており、整合性保証用途でも避けるべきです。' }, + { q: 'レインボーテーブル対策としてパスワードに混ぜるランダム値は。', choices: ['ペッパー', 'ソルト'], answer: 1, exp: 'ユーザーごとに異なるソルトで同一パスワードでもハッシュが変わり、事前計算表を無効化します。' } + ], + s09: [ + { q: '公開鍵の正当性を証明するために CA が発行するのは。', choices: ['CRL', 'X.509 証明書'], answer: 1, exp: 'CA は公開鍵と所有者情報に署名した証明書を発行します。' }, + { q: '1枚の証明書の失効をリアルタイムに問い合わせるプロトコルは。', choices: ['CRL', 'OCSP'], answer: 1, exp: 'OCSP はオンラインで失効状態を返します。CRL はリスト取得型です。' } + ], + s10: [ + { q: 'DMZ に置くのが一般的なのはどちらか。', choices: ['社内 DB サーバ', '公開 Web サーバ'], answer: 1, exp: '外部公開サービスを DMZ に置き、DB は内部ネットワークに隔離します。' }, + { q: 'ファイアウォールの基本方針として推奨されるのは。', choices: ['デフォルト許可', 'デフォルト拒否'], answer: 1, exp: '明示許可以外を拒否するホワイトリスト型が原則です。' } + ], + s11: [ + { q: '検知のみで遮断しないのはどちらか。', choices: ['IPS', 'IDS'], answer: 1, exp: 'IDS は検知・通知が中心。IPS はインラインで遮断も行います。' }, + { q: 'HTTP のパラメータやボディを検査して SQLi 等を防ぐのは主に。', choices: ['パケットフィルタ FW', 'WAF'], answer: 1, exp: 'WAF は L7 で Web 攻撃を検査します。FW は主に L3/L4 です。' } + ], + s12: [ + { q: 'ブラウザからリモートアクセスに使われやすい VPN は。', choices: ['IPsec 拠点間', 'SSL/TLS VPN'], answer: 1, exp: 'SSL/TLS VPN は HTTPS 上のトンネルでクライアント導入が容易です。' }, + { q: 'IPsec の ESP が提供するのは主に。', choices: ['認証のみ', '暗号化と認証'], answer: 1, exp: 'ESP はペイロードの暗号化と認証を提供します(AH は認証中心)。' } + ], + s13: [ + { q: '送信ドメインの送信許可 IP を DNS の TXT で示すのは。', choices: ['DKIM', 'SPF'], answer: 1, exp: 'SPF は送信元 IP の正当性を DNS で宣言します。' }, + { q: 'SPF/DKIM の結果に基づき受信側の処理方針を宣言するのは。', choices: ['S/MIME', 'DMARC'], answer: 1, exp: 'DMARC はポリシー(拒否・隔離等)をドメイン側が公開します。' } + ], + s14: [ + { q: '宿主を介さずネットワークで自己伝播するのは主に。', choices: ['ウイルス', 'ワーム'], answer: 1, exp: 'ワームは単体で伝播します。ウイルスは多くの場合宿主ファイルに寄生します。' }, + { q: 'ランサムウェア被害からの復旧で特に強調されるバックアップは。', choices: ['クラウド同期のみ', 'オフラインのバックアップ'], answer: 1, exp: 'オンライン接続のバックアップも暗号化されることがあるため、オフライン保管が有効です。' } + ], + s15: [ + { q: 'SQL インジェクション対策として根本的なのは。', choices: ['文字数制限のみ', 'プリペアドステートメント'], answer: 1, exp: 'SQL とデータを分離するプリペアドが第一選択です。' }, + { q: 'ログイン済みユーザーの意図しない状態変更リクエストを悪用するのは。', choices: ['XSS', 'CSRF'], answer: 1, exp: 'CSRF は別サイトから正規セッションでリクエストを送らせます。' } + ], + s16: [ + { q: '流出した ID/PW のリストを別サービスに流用して試す攻撃は。', choices: ['辞書攻撃', 'クレデンシャルスタッフィング'], answer: 1, exp: '他サービス漏洩の資格情報を転用するのがクレデンシャルスタッフィングです。' }, + { q: '多数アカウントに少数の共通パスワードを分散試行するのは。', choices: ['ブルートフォース', 'パスワードスプレー'], answer: 1, exp: 'ロックアウトを避けるため少数 PW を広く試します。' } + ], + s17: [ + { q: 'TCP の 3 ウェイハンドシェイクを悪用し接続テーブルを枯渇させるのは。', choices: ['DNS アンプ', 'SYN flood'], answer: 1, exp: 'SYN を大量に送り ACK を返さず half-open を増やします。' }, + { q: '長期間にわたり標的組織に潜伏して情報窃取を続ける攻撃は。', choices: ['DDoS', 'APT'], answer: 1, exp: 'APT は高度かつ持続的な標的型攻撃です。' } + ], + s18: [ + { q: '脆弱性の深刻度を 0.0〜10.0 で表す仕組みは。', choices: ['CVE', 'CVSS'], answer: 1, exp: 'CVSS がスコアリング。CVE は識別子です。' }, + { q: 'パッチが無い状態で悪用が始まっている脆弱性は一般に。', choices: ['既知の脆弱性', 'ゼロデイ'], answer: 1, exp: '修正前に悪用される脆弱性をゼロデイと呼びます。' } + ], + s19: [ + { q: 'マルウェア除去の前に被害拡大を止めるフェーズは。', choices: ['根絶', '封じ込め'], answer: 1, exp: 'まず封じ込めで影響範囲を限定し、その後根絶します。' }, + { q: '複数システムのログを集約し相関分析するプラットフォームは。', choices: ['WAF', 'SIEM'], answer: 1, exp: 'SIEM はログ統合と検知・調査を支援します。' } + ], + s20: [ + { q: '権限なく他人の ID でコンピュータにアクセスすることを禁止する法律は。', choices: ['個人情報保護法', '不正アクセス禁止法'], answer: 1, exp: '不正アクセス行為の禁止が中心です。' }, + { q: 'NIST CSF の 5 機能に「防護」に相当する英語は。', choices: ['Detect', 'Protect'], answer: 1, exp: 'Identify / Protect / Detect / Respond / Recover が 5 機能です。' } + ] +}; diff --git a/posimai-sc/logo.png b/posimai-sc/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..53987d00d40e49134966a78995fdb39a1d92039f GIT binary patch literal 299 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSpFCY0Ln>~)y=2JC;J|UglE*d|cyiiCE?59?T(s^47|WdH(C LS3j3^P6 { + e.waitUntil( + Promise.all([ + caches.open(CACHE).then(c => + c.addAll( + STATIC.filter(u => { + try { + new URL(u, self.location.origin); + return true; + } catch { + return false; + } + }) + ) + ), + self.skipWaiting() + ]) + ); +}); + +self.addEventListener('activate', e => { + e.waitUntil( + caches + .keys() + .then(keys => Promise.all(keys.filter(k => k !== CACHE).map(k => caches.delete(k)))) + .then(() => self.clients.claim()) + ); +}); + +self.addEventListener('fetch', e => { + if (e.request.method !== 'GET') return; + if (!e.request.url.startsWith(self.location.origin)) return; + + e.respondWith( + caches.open(CACHE).then(cache => + cache.match(e.request).then(cached => { + const network = fetch(e.request) + .then(res => { + if (res.ok && res.type === 'basic') cache.put(e.request, res.clone()); + return res; + }) + .catch(() => cached || new Response('Offline', { status: 503, statusText: 'Service Unavailable' })); + return cached || network; + }) + ) + ); +}); diff --git a/posimai-sc/vercel.json b/posimai-sc/vercel.json new file mode 100644 index 00000000..4058d28b --- /dev/null +++ b/posimai-sc/vercel.json @@ -0,0 +1,34 @@ +{ + "headers": [ + { + "source": "/sw.js", + "headers": [ + { + "key": "Cache-Control", + "value": "public, max-age=0, must-revalidate" + } + ] + }, + { + "source": "/(.*)", + "headers": [ + { + "key": "X-Content-Type-Options", + "value": "nosniff" + }, + { + "key": "X-Frame-Options", + "value": "DENY" + }, + { + "key": "X-XSS-Protection", + "value": "1; mode=block" + }, + { + "key": "Referrer-Policy", + "value": "strict-origin-when-cross-origin" + } + ] + } + ] +}