# 소스코드 기반 보안통제 증적 정리

작성일: 2026-05-20  
목적: 보안대책서의 `보안통제 매트릭스` 중 소스코드, HTTPS, API, 인증, 측정값 검증 관련 항목을 프로젝트 코드 기준으로 설명한다.

## 1. 결론

아래 항목은 프로젝트 소스코드에서 근거를 제시할 수 있습니다.

| 항목 | 내가 작성/증명 가능한가 | 비고 |
|---|---:|---|
| API 요청 서명 검증 (raw-body) | 가능 | `config.php`, `api/sensor_data.php`, 펌웨어 `sig.c` |
| JSON 센서 API 필수값 검증 | 가능 | `api/sensor_data.php` |
| 측정값 범위 검증 | 가능 | `api/sensor_data.php` (온도 -40~125℃ / 습도 0~100%) |
| 서버 임계 판정·쿨다운 | 가능 | `config.php` `METRIC_*`, `api/sensor_data.php` |
| DB prepared statement 사용 | 가능 | PHP API와 대시보드 주요 DB 처리 |
| 관리자 로그인 기본 보호 | 가능 | `login.php`, `dashboard.php`, `setup_wizard.php`, `monthly_report.php` |
| 관리자 TOTP MFA | 가능 | `login.php`, `setup_mfa.php`, `admin_security.php` |
| 관리자 감사로그 | 가능 | `admin_security.php`, `php/var/admin_audit.log` |
| 비밀값 코드 분리 | 가능 | `config.php`, `config.local.example.php`, 펌웨어 `secrets.h` |
| 보관기간 정리 | 가능 | `retention_cleanup.php` |
| 백업·복구 증빙 | 가능 | `scripts/backup_evidence.php` |
| 보안 헤더 설정 | 가능 | `php/.htaccess` |
| HTTPS 사용 구조 | 일부 가능 | 코드와 문서에는 HTTPS URL 사용. 실제 인증서/리다이렉트는 운영 서버 캡처 필요 |
| 방화벽 정책 | 불가 | 기관 또는 Cafe24/네트워크 담당자 자료 필요 |
| 실제 서버 설치 경로 | 불가 | 운영 서버 캡처 필요 |
| 실제 API 키 일치 여부 | 불가 | 원문 키 없이 운영자가 확인해야 함 |

운영 서버 반영 후에는 `php/security_evidence.php?format=md`에서 소스코드 기반 통제와 운영 점검 결과를 묶은 Markdown 보고서를 내려받을 수 있습니다. 단말(STM32) 측은 서비스 상태와 비밀값 파일 권한, 최근 로그를 운영자가 별도로 수집합니다. 두 결과 모두 API 키나 SMS 키 원문을 출력하지 않는 것을 기준으로 합니다.

## 2. 보안통제 매트릭스에 넣을 코드 기반 설명

### 2.1 전송구간 보호

HWP 반영 문구:

> STM32 단말은 서버 API URL을 HTTPS 주소로 설정하여 온습도 측정값을 전송한다. 서버 측 SMS 연계도 HTTPS 기반 Cafe24 SMS 엔드포인트를 사용한다. 운영 서버에서는 Cafe24 SSL 설정 또는 웹서버 설정을 통해 HTTPS 접속을 적용하며, 제출 시 실제 도메인의 HTTPS 접속 화면과 인증서 정보를 증적으로 첨부한다.

코드 근거:

- 펌웨어 `firmware/common/app_config.h` / `net.c`
  - 서버 API URL을 HTTPS로 설정하여 측정값 전송
- `php/config.php`
  - `SMS_ENDPOINT=https://sslsms.cafe24.com/sms_sender.php`
- `php/.htaccess`
  - HTTPS 강제 리다이렉트는 Cafe24 호스팅 관리자 패널에서 설정하도록 주석 명시

사용자가 제공해야 하는 증적:

- 실제 서비스 URL이 `https://`로 접속되는 화면
- 브라우저 인증서 정보 화면
- Cafe24 SSL 리다이렉트 설정 화면 또는 HTTPS 적용 확인 화면

주의:

> 현재 `.htaccess`는 HTTPS 리다이렉트를 직접 강제하지 않습니다. 리버스 프록시 환경에서 무한 리다이렉트가 발생할 수 있어 Cafe24 관리자 패널의 SSL 리다이렉트 설정을 사용하는 구조입니다. 따라서 HTTPS 적용 증적은 운영 서버 화면이 필요합니다.

### 2.2 API 인증 및 요청 무결성

HWP 반영 문구:

> API 요청은 서버와 STM32 단말이 공유하는 API 키를 기반으로 SHA-256 서명을 생성·검증한다. 단말은 요청 본문 바이트 전체에 대해 `sha256(API_KEY || raw_body)`를 계산하여 `X-Signature` 헤더로 전송하고, 서버는 동일하게 계산한 값과 상수시간 비교한다. 서명이 일치하지 않으면 `403 인증 실패`로 거부하며 DB에 저장하지 않는다. 이를 통해 임의의 외부 요청이 허위 측정값을 위조해 등록하는 위험을 낮춘다.

코드 근거:

- `php/config.php`
  - `verify_signature_raw(string $raw_body): bool`
  - `hash('sha256', API_KEY . $raw_body)` 계산 후 `X-Signature`(`HTTP_X_SIGNATURE`)와 비교
  - `hash_equals()`로 상수시간 서명 비교
- 펌웨어 `firmware/common/sig.c`, `firmware/common/secrets.h`
  - `APP_API_KEY` 기반 raw-body 서명 생성
  - 서버 `API_KEY`와 동일해야 함(`secrets.h.example` 주석 명시)
- `php/api/sensor_data.php`
  - 서명 검증 실패 시 `403 인증 실패`

사용자가 제공해야 하는 증적:

- 정상 API 테스트 결과
- API 키 불일치 또는 서명 불일치 요청이 `403`으로 거부되는 테스트 결과

증적 예시:

```text
정상 요청: 200 OK, {"status":"ok", ...}
비정상 서명 요청: 403, {"status":"error","message":"인증 실패"}
```

### 2.3 센서 데이터 API 입력값 검증

HWP 반영 문구:

> 센서 데이터 API는 POST JSON 요청만 허용하고, 필수 필드가 누락된 요청은 저장하지 않는다. 이벤트 유형은 허용 목록으로 제한하며, 온도·습도 측정값은 서버에서 타입을 변환하고 물리적으로 가능한 범위를 벗어난 값은 무효 처리한다.

코드 근거:

- `php/api/sensor_data.php`
  - `POST` 외 요청은 `405 Method Not Allowed`
  - JSON 파싱 실패 시 `400`
  - 필수 필드: `device_id`, `sensor_id`, `event_type`, `timestamp`
  - 허용 이벤트: `startup`, `periodic`
  - 측정값 범위 검증: 온도 -40~125℃, 습도 0~100% 초과 시 `null` 처리
  - PDO prepared statement로 DB 저장

사용자가 제공해야 하는 증적:

- 정상 측정값 저장 화면 또는 DB 기록(`sensor_metric`)
- 비정상 메소드/필드 누락/범위 초과 요청 차단 테스트 결과

### 2.4 서버 임계 판정 및 SMS 오발송 방지

HWP 반영 문구:

> 온습도 임계 판정은 펌웨어가 아니라 서버에서 수행한다. 서버는 `config.php`의 운영 임계(`METRIC_*`: 고온30/저온10℃, 고습70/저습20%)로 고온·저온·고습·저습을 재판정하여 `sensor_metric.metric_status`에 기록하고, 임계 이탈 시 종류별 SMS를 발송한다. 동일 종류 경보는 30분 쿨다운을 적용하고, 정상복귀 판정은 히스테리시스(온도 1.0℃, 습도 3.0%)를 적용하여 경계 채터링과 SMS 오발송을 방지한다. 임계 안쪽으로 회복하면 정상복귀 SMS를 발송한다.

코드 근거:

- `php/config.php`
  - `METRIC_TEMP_HIGH_C`(30), `METRIC_TEMP_LOW_C`(10), `METRIC_RH_HIGH`(70), `METRIC_RH_LOW`(20)
  - `METRIC_TEMP_HYSTERESIS_C`(1.0), `METRIC_RH_HYSTERESIS`(3.0)
  - `METRIC_ALERT_COOLDOWN_SEC`(1800, 30분)
- `php/api/sensor_data.php`
  - `evaluate_metric_thresholds()`로 서버 재판정 → `sensor_metric`
  - 경보 종류(`high_temp`/`low_temp`/`high_humidity`/`low_humidity`)별 SMS, 쿨다운/복구 판정
  - 임계 이탈 시 `send_metric_alert_sms()`, 회복 시 `send_metric_recovery_sms()`

사용자가 제공해야 하는 증적:

- 임계 초과 시 종류별 경보 SMS 발송 이력(`sms_log`: `[고온경보]`/`[저온경보]`/`[고습경보]`/`[저습경보]`)
- 정상복귀 SMS 발송 이력
- 쿨다운 적용으로 동일 경보가 30분 내 재발송되지 않는 결과

### 2.5 장비 오프라인 감지

HWP 반영 문구:

> 단말은 주기적으로 정상 보고를 전송하며, 서버는 마지막 보고 시각을 기준으로 장비 오프라인 상태를 판정한다. heartbeat 타임아웃을 초과하면 SMS로 담당자에게 장비 이상을 통지하고, 보고가 재개되면 복구 SMS를 발송한다.

코드 근거:

- `php/config.php`
  - `HEARTBEAT_TIMEOUT_SEC`(1200), `HEARTBEAT_CHECK_INTERVAL_SEC`(300)
- `php/cron_heartbeat.php`
  - 마지막 보고 시각 기준 오프라인 판정, `sensor_status.offline_alerted` 관리
- `php/api/sensor_data.php`
  - 오프라인 상태였던 센서가 다시 보고하면 복구 SMS 발송

사용자가 제공해야 하는 증적:

- `sensor_status` 마지막 보고 시각
- `cron_heartbeat.php` 실행 로그
- 오프라인/복구 SMS 발송 이력(`sms_log`)

### 2.6 비밀값 코드 분리

HWP 반영 문구:

> DB 비밀번호, API 키, SMS 인증키, 관리자 비밀번호 해시는 소스코드에 직접 저장하지 않고 운영 환경 설정 파일로 분리한다. 서버는 `config.local.php` 또는 환경변수에서 값을 읽고, STM32 단말은 펌웨어 `secrets.h`(`APP_API_KEY`)에서 API 키를 읽는다. 비밀값 원문은 보안대책서와 저장소에 포함하지 않는다.

코드 근거:

- `php/config.php`
  - `config.local.php` 또는 환경변수에서 운영값 로드
  - `cfg()` 함수 사용
- `php/config.local.example.php`
  - 운영 설정 예시만 제공
- 펌웨어 `firmware/common/secrets.h.example`
  - `APP_API_KEY` 템플릿만 제공, `secrets.h`는 저장소 미커밋(`.gitignore`)
  - 서버 `php/config.php`의 `API_KEY`와 동일해야 함

사용자가 제공해야 하는 증적:

- `config.local.php`가 서버에만 존재한다는 배포 화면
- 펌웨어 `secrets.h`가 저장소에 커밋되지 않았다는 점검 결과
- 비밀값 원문이 문서와 저장소에 없다는 점검 결과

### 2.7 관리자 로그인, TOTP MFA 및 세션 보호

HWP 반영 문구:

> 관리자 페이지는 로그인된 세션에서만 접근 가능하며, 관리자 비밀번호는 평문이 아닌 해시값으로 저장한다. 로그인 요청은 CSRF 토큰을 검증하고, 비밀번호 검증 후 Google Authenticator 등 표준 TOTP 인증 앱의 6자리 일회용 코드를 추가 검증한다. 실패 횟수 제한 및 일정 시간 잠금을 적용하고, 세션 쿠키에는 HttpOnly, Secure, SameSite 속성을 적용한다. 로그인 성공·실패, 로그아웃, MFA 등록 검증은 감사로그로 기록한다.

코드 근거:

- `php/login.php`
  - `ADMIN_PASSWORD_HASH` 기반 `password_verify()`
  - `ADMIN_TOTP_SECRET` 기반 TOTP 6자리 코드 검증
  - CSRF 토큰 검증
  - IP별 로그인 실패 횟수 제한
  - 5회 실패 시 15분 잠금
  - `session_regenerate_id(true)`
  - `session.cookie_httponly=1`
  - `session.cookie_secure=1`
  - `session.cookie_samesite=Strict`
- `php/setup_mfa.php`
  - `MFA_SETUP_TOKEN` 또는 로그인 세션 기반으로 최초 등록 화면 접근 제한
  - Base32 수동 입력 키와 `otpauth://` 등록 URI 제공
  - 외부 Google API 또는 외부 QR 생성 API로 비밀키를 전송하지 않음
- `php/admin_security.php`
  - TOTP 검증, 등록 URI 생성, 감사로그 기록 헬퍼 제공
- `php/dashboard.php`, `php/setup_wizard.php`, `php/security_evidence.php`, `php/monthly_report.php`
  - 로그인 세션 없으면 `login.php`로 리다이렉트

사용자가 제공해야 하는 증적:

- 미로그인 상태에서 대시보드 접근 시 로그인 화면으로 이동하는 화면
- 로그인 화면
- MFA 등록 화면 또는 [docs/evidence/security_plan_mfa_evidence.html](evidence/security_plan_mfa_evidence.html)
- 관리자 비밀번호 해시 생성 결과
- 로그인 실패 잠금 화면
- `php/var/admin_audit.log` 존재 및 로그인 이벤트 일부 마스킹 화면

### 2.8 보안 헤더 및 민감 파일 접근 차단

HWP 반영 문구:

> 웹 서버 설정은 민감 PHP 파일 직접 접근을 차단하고, 보안 헤더를 설정하여 브라우저 기반 공격 위험을 낮춘다. 디렉터리 목록을 비활성화하고 불필요 HTTP 메소드를 차단한다.

코드 근거:

- `php/.htaccess`
  - `config.php`, `config.local.php`, `config.local.example.php`, `ops_checks.php`, `sms_send.php`, `setup_hash.php`, `test_mobile.php` 직접 접근 차단
  - `TRACE`, `DELETE`, `PUT`, `PATCH` 차단
  - `X-Content-Type-Options: nosniff`
  - `X-Frame-Options: DENY`
  - `Content-Security-Policy`
  - `Permissions-Policy`
  - `Options -Indexes`

사용자가 제공해야 하는 증적:

- 민감 파일 직접 URL 접근 차단 화면
- 응답 헤더 확인 결과

응답 헤더 확인 예시:

```bash
curl -I https://example.com/sht30_monitor/login.php
```

## 3. 내가 만들어줄 수 있는 HWP 첨부 문구

아래 문구는 HWP의 “기술적 보안대책”에 그대로 넣을 수 있습니다.

> 본 시스템은 STM32 단말에서 Cafe24 PHP API로 HTTPS 기반 아웃바운드 요청만 수행하도록 구성한다. 센서 데이터 API는 공유 API 키 기반 raw-body SHA-256 서명(`X-Signature`)을 검증하며, 서명이 일치하지 않는 요청은 `403 인증 실패`로 거부한다. 센서 데이터 API는 POST JSON 요청과 필수 필드, 측정값 범위(온도 -40~125℃ / 습도 0~100%)를 검증한다. 온습도 임계 판정은 서버에서 운영 임계(`METRIC_*`)로 수행하고, 동일 종류 경보 30분 쿨다운과 복구 히스테리시스로 SMS 오발송을 방지한다. DB 저장은 PDO prepared statement를 사용하며, 운영 비밀값은 `config.local.php`와 펌웨어 `secrets.h`로 분리하여 소스코드에 직접 저장하지 않는다. 관리자 페이지는 비밀번호 해시, CSRF 토큰, 세션 쿠키 보안 속성, 로그인 실패 제한을 적용한다.

추가 반영 문구:

> 관리자 페이지는 비밀번호 인증 후 TOTP 기반 다중인증을 추가로 요구하며, TOTP 비밀키는 `ADMIN_TOTP_SECRET`으로 관리한다. 최초 등록 또는 담당자 변경 시에는 임시 `MFA_SETUP_TOKEN`으로 `setup_mfa.php`에 접근하여 인증 앱 등록을 검증하고, 등록 완료 후 임시 토큰을 제거한다. 로그인, 로그아웃, 로그인 실패, MFA 등록 검증 이벤트는 관리자 감사로그에 기록한다. 보관기간 경과 데이터는 `retention_cleanup.php --dry-run`으로 대상을 확인한 뒤 백업 완료 후 정리하며, 백업 파일 목록과 복구 테스트 결과는 `scripts/backup_evidence.php`로 증빙한다.

## 4. 그래도 사용자가 제공해야 하는 것

아래는 코드만으로는 증명할 수 없습니다.

| 항목 | 왜 코드로 증명 불가한가 | 사용자가 줄 자료 |
|---|---|---|
| 실제 HTTPS 적용 | 운영 도메인 인증서와 서버 설정 문제 | 브라우저 HTTPS 화면, 인증서 정보, Cafe24 SSL 설정 |
| 실제 방화벽 정책 | 기관/네트워크 장비 설정 | 방화벽 허용 정책 또는 담당자 확인 문구 |
| 실제 단말 인바운드 차단 | 운영 장비 상태 문제 | 단말에서 `ss -lntup` 결과 |
| 실제 서버 파일 권한 | Cafe24 배포 상태 문제 | 파일 관리자 또는 권한 화면 |
| 실제 SMS 수신자 관리 | 운영자 개인정보 관리 문제 | 수신자 현행화 확인표 |
| 실제 백업 수행 | 운영 절차 문제 | 백업 파일 목록, 복구 테스트 결과 |

## 5. 첨부자료를 줄일 수 있는 방식

사용자가 모든 증적을 준비하기 어렵다면, 최소 증적은 아래 5개로 줄일 수 있습니다.

1. HTTPS로 접속되는 대시보드 화면
2. `setup_wizard.php` 점검 화면
3. API 정상/403 테스트 결과
4. 단말 `systemctl status leak-sensor` 화면
5. 측정값 저장(`sensor_metric`) 및 임계 경보 SMS(`sms_log`) 화면

이 5개가 있으면 소스코드 기반 보안대책과 운영 적용 증적을 어느 정도 연결할 수 있습니다.
