수집 거부 카운터는 사유별 숫자만 남겨, "우리 노드 오설정 vs 외부 스캐너"를 구분할 수 없었다. 서명불일치 2건이 우리 노드의 키 불일치인지 인터넷 프로브인지 대시보드만으론 판별 불가했다. - config.php: 최근 거부 이벤트 링버퍼(var/ingest_reject_events.json, 상한 INGEST_REJECT_EVENTS_MAX=50, flock 기반 DoS 안전) 추가. record_ingest_rejection() 이 모든 거부에서 출처 IP(REMOTE_ADDR, 형식검증)를 자동 기록한다. 본문 유래 값(device_id 등)은 신뢰불가 → ingest_sanitize_token() 으로 제어문자 제거·절단 (로그 인젝션 방어). var/ 는 .htaccess 로 웹 접근 차단됨. - api/sensor_data.php: 서명 거부 시 서명모드(raw=키불일치/none=헤더스트립)와 device_id 를 컨텍스트로 함께 남겨 출처 판별을 돕는다. - dashboard.php: '수집 거부' 위젯 아래 '최근 거부 출처' 표(시각·IP·사유·모드· device_id) 노출. 모든 필드 htmlspecialchars. - tests/ingest_reject_log_test.php: 로그 인젝션 제거·IP 형식검증 회귀 추가, run_tests.sh 등록. security_hardening_test.php 소스 회귀 5종 추가. 주: config.php 에는 작업 트리에 있던 선행 변경(METRIC_SENSOR_IDS — 대시보드 온습도 센서카드 목록)이 함께 포함된다.
55 lines
2.8 KiB
PHP
55 lines
2.8 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
// 수집 거부 출처 로그 헬퍼 회귀 (DB/파일 없이 순수 로직만).
|
|
// ingest_sanitize_token(): 로그 인젝션 방지(제어문자 제거·절단)
|
|
// ingest_client_ip(): REMOTE_ADDR 형식검증(부재/위조형식 → '?')
|
|
// 함수/상수 존재: record_ingest_reject_event / read_ingest_reject_events / INGEST_REJECT_EVENTS_MAX
|
|
|
|
$root = dirname(__DIR__);
|
|
require_once $root . '/php/config.php';
|
|
|
|
$failures = [];
|
|
function check(bool $cond, string $msg): void { global $failures; if (!$cond) { $failures[] = $msg; } }
|
|
function eq($a, $b, string $msg): void {
|
|
check($a === $b, $msg . ' (expected ' . var_export($b, true) . ', got ' . var_export($a, true) . ')');
|
|
}
|
|
|
|
// ── 함수/상수 존재 ──────────────────────────────────────────
|
|
check(function_exists('ingest_sanitize_token'), 'ingest_sanitize_token() should exist.');
|
|
check(function_exists('ingest_client_ip'), 'ingest_client_ip() should exist.');
|
|
check(function_exists('record_ingest_reject_event'), 'record_ingest_reject_event() should exist.');
|
|
check(function_exists('read_ingest_reject_events'), 'read_ingest_reject_events() should exist.');
|
|
check(defined('INGEST_REJECT_EVENTS_MAX'), 'INGEST_REJECT_EVENTS_MAX should be defined.');
|
|
|
|
// ── ingest_sanitize_token: 로그 인젝션/제어문자 방지 ────────
|
|
eq(ingest_sanitize_token('rpi4-leak-02', 32), 'rpi4-leak-02', 'plain token unchanged');
|
|
// 개행/캐리지리턴 → 위조 로그줄 주입 차단(제어문자는 공백 처리)
|
|
check(strpos(ingest_sanitize_token("rpi\n[API] 서명 불일치 from evil", 64), "\n") === false,
|
|
'newline stripped (log injection blocked)');
|
|
check(strpos(ingest_sanitize_token("a\r\nb", 64), "\r") === false, 'carriage return stripped');
|
|
check(strpos(ingest_sanitize_token("a\tb", 64), "\t") === false, 'tab stripped');
|
|
// 길이 절단
|
|
check(mb_strlen(ingest_sanitize_token(str_repeat('x', 100), 32)) === 32, 'truncated to max length');
|
|
eq(ingest_sanitize_token(' spaced ', 32), 'spaced', 'trimmed');
|
|
eq(ingest_sanitize_token('abc', 0), 'abc', 'max 0 -> no truncation');
|
|
|
|
// ── ingest_client_ip: REMOTE_ADDR 형식검증 ──────────────────
|
|
$_SERVER['REMOTE_ADDR'] = '203.0.113.9';
|
|
eq(ingest_client_ip(), '203.0.113.9', 'valid IPv4 returned');
|
|
$_SERVER['REMOTE_ADDR'] = '2001:db8::1';
|
|
eq(ingest_client_ip(), '2001:db8::1', 'valid IPv6 returned');
|
|
$_SERVER['REMOTE_ADDR'] = 'not-an-ip';
|
|
eq(ingest_client_ip(), '?', 'invalid IP format -> ?');
|
|
unset($_SERVER['REMOTE_ADDR']);
|
|
eq(ingest_client_ip(), '?', 'missing REMOTE_ADDR -> ?');
|
|
|
|
if ($failures) {
|
|
fwrite(STDERR, "Ingest reject-log test failures:\n");
|
|
foreach ($failures as $f) {
|
|
fwrite(STDERR, "- {$f}\n");
|
|
}
|
|
exit(1);
|
|
}
|
|
|
|
echo "Ingest reject-log checks passed.\n";
|