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 제외.
273 lines
15 KiB
Markdown
273 lines
15 KiB
Markdown
# 소스코드 기반 보안통제 증적 정리
|
|
|
|
작성일: 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개가 있으면 소스코드 기반 보안대책과 운영 적용 증적을 어느 정도 연결할 수 있습니다.
|