POSA_LEAKSMS/docs/evidence/security_plan_mfa_evidence.html
유창욱 90f121e14c chore: import codebase with security hardening
SHT30 온습도 모니터링 시스템 전체 소스(서버 PHP, STM32 펌웨어, SQL, 테스트).
전체 코드리뷰에서 도출된 보안 하드닝 10건 반영:
- 요청 서명 HMAC-SHA256 전환(펌웨어 sig.c/서버 config.php/호스트 패리티 동시)
- 재전송 방어 + 기본 API_KEY fail-closed + 디바이스 문자열 정제(api/sensor_data.php)
- 오프라인 SMS 중복 발송 경합 제거(cron_heartbeat.php, 원자적 선점)
- CSV 수식 주입 방지(monthly_report.php), 감사로그 회전 락(retention_cleanup.php)
- 브루트포스 카운터 원자화(login.php), 예시 TOTP 비밀키 무효화, 마이그레이션 멱등화

_backup/(하드코딩 실 비밀값 포함)·config.local.php·런타임 상태는 .gitignore 제외.
2026-06-20 09:37:40 +09:00

307 lines
12 KiB
HTML

<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>관리자 MFA 및 운영 보안 보완 증빙</title>
<style>
body {
margin: 0;
font-family: "Noto Sans KR", "Malgun Gothic", Arial, sans-serif;
color: #172033;
background: #f5f7fb;
line-height: 1.65;
}
main {
max-width: 1080px;
margin: 0 auto;
padding: 32px 24px 64px;
background: #fff;
min-height: 100vh;
}
h1 {
margin: 0 0 8px;
font-size: 26px;
color: #123f8c;
}
h2 {
margin: 34px 0 12px;
padding-bottom: 6px;
border-bottom: 2px solid #dbe7f8;
font-size: 20px;
color: #1d3557;
}
h3 {
margin: 22px 0 8px;
font-size: 16px;
color: #20324d;
}
p, li, td, th {
font-size: 14px;
}
.meta {
margin: 0 0 24px;
color: #526175;
font-size: 13px;
}
.copy-block {
border: 1px solid #cbd8ea;
background: #fbfdff;
border-radius: 8px;
padding: 18px 20px;
}
.evidence-image {
width: 100%;
max-width: 980px;
border: 1px solid #d5dde8;
border-radius: 8px;
box-shadow: 0 4px 14px rgba(15, 23, 42, .12);
display: block;
margin: 12px 0;
}
table {
width: 100%;
border-collapse: collapse;
margin: 12px 0;
}
th, td {
border: 1px solid #ccd7e5;
padding: 10px 12px;
vertical-align: top;
}
th {
background: #eef4fb;
font-weight: 700;
text-align: left;
}
code, pre {
font-family: Consolas, "Courier New", monospace;
}
pre {
white-space: pre-wrap;
word-break: break-word;
background: #0f172a;
color: #e2e8f0;
padding: 16px;
border-radius: 8px;
overflow: auto;
font-size: 13px;
line-height: 1.5;
}
.diagram {
border: 1px solid #ccd7e5;
border-radius: 8px;
padding: 18px;
background: #fff;
overflow-x: auto;
}
.diagram svg {
max-width: 100%;
height: auto;
display: block;
margin: 0 auto;
}
.note {
padding: 12px 14px;
border-left: 4px solid #2563eb;
background: #eff6ff;
color: #1e3a8a;
font-size: 13px;
}
@media print {
body { background: #fff; }
main { padding: 0; }
.copy-block, .diagram, pre { break-inside: avoid; }
.evidence-image { max-width: 100%; box-shadow: none; }
}
</style>
</head>
<body>
<main>
<h1>관리자 MFA 및 운영 보안 보완 증빙</h1>
<p class="meta">작성일: 2026-05-28 / 대상: SHT30 온습도 모니터링 시스템 관리자 웹 기능</p>
<h2>1. 보안대책서 삽입 문구</h2>
<div class="copy-block">
<h3>관리자 접근통제 보완</h3>
<p>
본 시스템은 관리자 페이지 접근 시 단일 비밀번호 인증에 의존하지 않도록
TOTP(Time-based One-Time Password) 기반의 다중인증(MFA)을 적용하였다.
관리자 계정은 비밀번호 검증 후 Google Authenticator, Microsoft Authenticator, Authy 등
표준 TOTP 인증 앱에서 생성한 6자리 일회용 인증코드를 추가로 검증해야 한다.
TOTP 검증은 서버 내부에서 수행되며, 비밀키는 외부 Google API 또는 외부 QR 생성 서비스로 전송하지 않는다.
</p>
<p>
최초 등록 또는 담당자 변경 시에는 임시 설정 토큰인 <code>MFA_SETUP_TOKEN</code>을 사용하여
등록 화면에 접근한다. 등록 화면은 Base32 수동 입력 키와 <code>otpauth://</code> 표준 등록 URI를 제공하고,
운영자는 인증 앱에 등록한 뒤 6자리 코드를 검증한다. 검증 완료 후 발급된
<code>ADMIN_TOTP_SECRET</code> 값을 서버 설정에 반영하며, 등록 완료 즉시
<code>MFA_SETUP_TOKEN</code>은 삭제 또는 빈 값으로 변경한다.
</p>
<p>
관리자 인증 성공, 실패 및 MFA 등록 검증 이벤트는 감사 로그에 기록되도록 하여
비인가 접근 시도 및 계정 운영 이력을 사후 확인할 수 있도록 하였다.
운영 로그는 보존기간 정책에 따라 정리할 수 있도록 정리 스크립트와
백업 증빙 생성 절차를 마련하였다.
</p>
</div>
<h2>2. 구현 항목 요약</h2>
<table>
<thead>
<tr>
<th>구분</th>
<th>구현 내용</th>
<th>보안 효과</th>
</tr>
</thead>
<tbody>
<tr>
<td>관리자 MFA</td>
<td><code>ADMIN_TOTP_SECRET</code> 기반 TOTP 6자리 코드 검증</td>
<td>관리자 비밀번호 유출 시에도 추가 인증 없이는 관리자 페이지 접근 차단</td>
</tr>
<tr>
<td>MFA 등록 절차</td>
<td><code>setup_mfa.php</code>에서 Base32 키와 <code>otpauth://</code> URI 제공</td>
<td>Cafe24 웹호스팅 환경에서도 별도 Google API 없이 인증 앱 등록 가능</td>
</tr>
<tr>
<td>임시 등록 토큰</td>
<td><code>MFA_SETUP_TOKEN</code>으로 최초 등록 화면 접근 제한</td>
<td>MFA 설정 페이지의 임의 접근 및 비인가 등록 시도 방지</td>
</tr>
<tr>
<td>감사 로그</td>
<td>로그인 성공/실패 및 MFA 등록 검증 이벤트 기록</td>
<td>비인가 접근 시도 추적 및 운영 이력 증빙 가능</td>
</tr>
<tr>
<td>보존기간/백업 증빙</td>
<td>보존기간 정리 스크립트와 백업 증빙 생성 스크립트 추가</td>
<td>운영 로그 최소 보관, 복구 가능성 및 운영 증빙 확보</td>
</tr>
</tbody>
</table>
<h2>3. 실제 화면 캡처</h2>
<p>
아래 화면은 로컬 증빙용 임시 토큰과 임시 TOTP 키를 사용하여 렌더링한
Google Authenticator 호환 MFA 등록 화면이다. 운영 환경에서는 실제 운영 비밀키를 별도로 생성하여 사용한다.
</p>
<img class="evidence-image" src="mfa_setup_page_capture.png" alt="Google Authenticator MFA 등록 화면 캡처">
<p class="note">
캡처 파일: <code>docs/evidence/mfa_setup_page_capture.png</code><br>
증빙용 URL: <code>/setup_mfa.php?token=증빙용임시토큰</code>
</p>
<h2>4. 보안 흐름도</h2>
<div class="diagram" aria-label="관리자 MFA 보안 흐름도">
<svg viewBox="0 0 920 420" xmlns="http://www.w3.org/2000/svg" role="img">
<defs>
<marker id="arrow" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto" markerUnits="strokeWidth">
<path d="M0,0 L0,6 L9,3 z" fill="#2f5597"/>
</marker>
<style>
.box { fill: #f8fbff; stroke: #2f5597; stroke-width: 1.5; rx: 8; }
.secure { fill: #eefaf1; stroke: #1f7a3f; stroke-width: 1.5; rx: 8; }
.deny { fill: #fff1f2; stroke: #be123c; stroke-width: 1.5; rx: 8; }
.text { font: 14px "Malgun Gothic", Arial, sans-serif; fill: #172033; }
.title { font: 700 15px "Malgun Gothic", Arial, sans-serif; fill: #172033; }
.line { stroke: #2f5597; stroke-width: 1.7; marker-end: url(#arrow); fill: none; }
</style>
</defs>
<rect class="box" x="36" y="42" width="160" height="78"/>
<text class="title" x="116" y="74" text-anchor="middle">관리자</text>
<text class="text" x="116" y="98" text-anchor="middle">ID/PW 입력</text>
<rect class="box" x="260" y="42" width="170" height="78"/>
<text class="title" x="345" y="72" text-anchor="middle">비밀번호 검증</text>
<text class="text" x="345" y="96" text-anchor="middle">bcrypt 해시 확인</text>
<rect class="secure" x="494" y="42" width="190" height="78"/>
<text class="title" x="589" y="72" text-anchor="middle">TOTP MFA 검증</text>
<text class="text" x="589" y="96" text-anchor="middle">6자리 일회용 코드</text>
<rect class="secure" x="748" y="42" width="142" height="78"/>
<text class="title" x="819" y="72" text-anchor="middle">관리자 세션</text>
<text class="text" x="819" y="96" text-anchor="middle">접근 허용</text>
<rect class="box" x="36" y="208" width="200" height="86"/>
<text class="title" x="136" y="238" text-anchor="middle">MFA 최초 등록</text>
<text class="text" x="136" y="263" text-anchor="middle">MFA_SETUP_TOKEN</text>
<text class="text" x="136" y="283" text-anchor="middle">임시 접근</text>
<rect class="secure" x="320" y="208" width="220" height="86"/>
<text class="title" x="430" y="238" text-anchor="middle">인증 앱 등록</text>
<text class="text" x="430" y="263" text-anchor="middle">Base32 키 / otpauth URI</text>
<text class="text" x="430" y="283" text-anchor="middle">외부 API 전송 없음</text>
<rect class="secure" x="624" y="208" width="206" height="86"/>
<text class="title" x="727" y="238" text-anchor="middle">운영 설정 반영</text>
<text class="text" x="727" y="263" text-anchor="middle">ADMIN_TOTP_SECRET</text>
<text class="text" x="727" y="283" text-anchor="middle">등록 토큰 제거</text>
<rect class="deny" x="178" y="342" width="240" height="52"/>
<text class="title" x="298" y="373" text-anchor="middle">비인가 접근 차단</text>
<rect class="secure" x="504" y="342" width="240" height="52"/>
<text class="title" x="624" y="373" text-anchor="middle">감사 로그 및 보존기간 관리</text>
<path class="line" d="M196 81 H260"/>
<path class="line" d="M430 81 H494"/>
<path class="line" d="M684 81 H748"/>
<path class="line" d="M236 251 H320"/>
<path class="line" d="M540 251 H624"/>
<path class="line" d="M819 120 C819 170 760 178 727 208"/>
<path class="line" d="M819 120 C819 324 700 326 624 342"/>
<path class="line" d="M819 120 C819 324 410 326 298 342"/>
</svg>
</div>
<h2>5. Mermaid 원문</h2>
<p>보안대책서 또는 별도 Markdown 문서에서 재렌더링할 수 있도록 Mermaid 원문을 함께 첨부한다.</p>
<pre>flowchart LR
A[관리자 ID/PW 입력] --&gt; B[비밀번호 bcrypt 검증]
B --&gt; C{TOTP MFA 코드 검증}
C -- 성공 --&gt; D[관리자 세션 발급]
C -- 실패 --&gt; E[로그인 차단 및 감사 로그 기록]
F[MFA 최초 등록] -- MFA_SETUP_TOKEN 임시 접근 --&gt; G[setup_mfa.php]
G -- Base32 키 / otpauth URI 제공 --&gt; H[Google Authenticator 등록]
H -- 6자리 코드 검증 --&gt; I[ADMIN_TOTP_SECRET 운영 설정 반영]
I -- 등록 후 --&gt; J[MFA_SETUP_TOKEN 삭제 또는 빈 값 처리]
D -- 운영 이력 --&gt; M[관리자 감사 로그]
D -- 보존기간 정책 --&gt; N[로그 정리 및 백업 증빙]</pre>
<h2>6. 검증 결과</h2>
<table>
<thead>
<tr>
<th>검증 항목</th>
<th>결과</th>
</tr>
</thead>
<tbody>
<tr>
<td>MFA 등록 화면 실제 렌더링</td>
<td>Chrome headless 캡처 완료: <code>mfa_setup_page_capture.png</code></td>
</tr>
<tr>
<td>보안 하드닝 회귀 테스트</td>
<td><code>php tests\security_hardening_test.php</code> 통과</td>
</tr>
<tr>
<td>PHP 문법 검사</td>
<td><code>admin_security.php</code>, <code>setup_mfa.php</code>, <code>config.php</code>, <code>config.local.example.php</code> 통과</td>
</tr>
<tr>
<td>외부 비밀키 전송 여부</td>
<td>외부 Google API 또는 외부 QR 생성 API 미사용. 서버 내부 TOTP 검증만 수행</td>
</tr>
</tbody>
</table>
</main>
</body>
</html>