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 제외.
307 lines
12 KiB
HTML
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 입력] --> B[비밀번호 bcrypt 검증]
|
|
B --> C{TOTP MFA 코드 검증}
|
|
C -- 성공 --> D[관리자 세션 발급]
|
|
C -- 실패 --> E[로그인 차단 및 감사 로그 기록]
|
|
|
|
F[MFA 최초 등록] -- MFA_SETUP_TOKEN 임시 접근 --> G[setup_mfa.php]
|
|
G -- Base32 키 / otpauth URI 제공 --> H[Google Authenticator 등록]
|
|
H -- 6자리 코드 검증 --> I[ADMIN_TOTP_SECRET 운영 설정 반영]
|
|
I -- 등록 후 --> J[MFA_SETUP_TOKEN 삭제 또는 빈 값 처리]
|
|
|
|
D -- 운영 이력 --> M[관리자 감사 로그]
|
|
D -- 보존기간 정책 --> 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>
|