펌웨어 속에 숨겨진 GitHub 토큰 — 보안 카메라의 실수
보안 카메라 제조사 Hanwha(Vision)의 펌웨어를 분석하다가 로그인 페이지에서 GitHub 관리자 토큰이 노출된 사례가 공개됐다. 단순한 설정 실수처럼 보일 수 있지만, 이 사례는 임베디드 장비와 웹 자산 전반에 걸친 시크릿 관리의 허점을 잘 드러낸다.
연구자는 Hanwha 카메라의 공개 펌웨어 이미지를 binwalk로 분석하면서 시작했다. 내부에는 암호화된 fwimage.tgz가 중첩 구조로 존재했고, 외부 레이어의 복호화 패스프레이즈는 HTW + 모델번호 형태였다. 내부 레이어는 다른 방식으로 암호화되어 있었는데, 펌웨어 업그레이더 바이너리(fwupgrader)를 Ghidra로 역분석해 보니 AES 키가 바이너리 내 정적 키 테이블과 XOR 연산으로 난독화되어 있었다. 즉, 보안이 있는 척하는 구조였을 뿐 실질적인 키 보호는 없었다. 결국 루트 파일시스템을 추출하는 데 성공했고, 거기서 GitHub 관리자 토큰이 발견됐다.
왜 이런 일이 반복되는가
이 사례의 핵심 문제는 두 가지다.
- 시크릿의 코드/아티팩트 동봉: GitHub 토큰 같은 자격증명이 빌드 산출물에 포함된 채 배포됐다. 개발 편의를 위해 하드코딩한 값이 그대로 프로덕션 펌웨어에 남은 전형적인 패턴이다.
- 보안을 가장한 난독화(Security by Obscurity): XOR 기반 키 숨기기, 고정 패스프레이즈 등은 분석자를 잠깐 늦출 수는 있어도 실질적인 보호를 제공하지 못한다. 바이너리를 한 번 역분석하면 모든 동일 모델에 동일한 취약점이 적용된다.
Java 백엔드 개발자 관점에서도 이는 낯설지 않은 문제다. Spring Boot 애플리케이션의 application.properties나 Docker 이미지 레이어에 DB 패스워드나 API 키가 포함된 채 레지스트리에 올라가는 일은 실무에서 드물지 않다.
실무에서 시크릿을 다루는 원칙
올바른 시크릿 관리는 "코드나 아티팩트와 시크릿을 철저히 분리"하는 것에서 시작한다.
# ❌ 잘못된 예: application.properties에 직접 기입
spring.datasource.password=mypassword123
# ✅ 올바른 예: 환경변수 또는 외부 시크릿 매니저 참조
spring.datasource.password=${DB_PASSWORD}
빌드 파이프라인에서도 마찬가지다. GitHub Actions나 Jenkins에서 시크릿은 반드시 Secret Store(GitHub Secrets, AWS Secrets Manager, HashiCorp Vault 등)를 통해 런타임에 주입해야 하며, 절대로 Dockerfile이나 JAR 내부에 포함되어선 안 된다. 추가로 git-secrets, truffleHog, gitleaks 같은 도구를 CI 파이프라인에 통합하면 실수로 커밋된 시크릿을 조기에 차단할 수 있다.
엔터프라이즈 환경에서 IoT·임베디드 장비를 도입할 때도 마찬가지 기준을 적용해야 한다. 펌웨어 업데이트가 가능한 장비라면 공개된 펌웨어 이미지에서 하드코딩된 자격증명이 없는지 검토하는 것이 공급망 보안(Supply Chain Security)의 일부다.
정리
- 시크릿(API 키, 토큰, 패스워드)은 코드·빌드 산출물·컨테이너 이미지 어디에도 포함되어서는 안 되며, 런타임 주입 방식을 사용해야 한다.
- XOR 난독화나 고정 패스프레이즈 같은 "보안처럼 보이는 구조"는 실질적인 보호가 아니므로 실무에서 보안 수단으로 신뢰해선 안 된다.
- CI/CD 파이프라인에 시크릿 스캐닝 도구를 통합해 사전 차단 체계를 갖추는 것이 현실적인 방어선이다.