kintex/docs/security/mobile-security-audit.md
zio 214b6df1ef feat(public,security,data): visitor public site + AI guide, security hardening, V42-V45 migrations
- frontend: visitor public home (AiAssistant, AiPlanningBriefing, VisitorTrackPage),
  3-track public routing (PublicShell/App), i18n ko/en/zh/ja, favicon
- backend: public AI visitor-assistant + event calendar API (publicsite/*),
  profile avatar API (auth/profile/*), SecurityConfig CORS whitelist,
  SecretStartupValidator (B12 fail-fast, prod only), application-prod.yml,
  AppIntegrity, /api/auth/me expansion (MeResponse)
- db: V42 bulk demo seed, V43 visitor_guide/transport + event calendar view,
  V44 performance indexes, V45 app_user profile photo columns (all idempotent)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 19:05:00 +09:00

139 lines
13 KiB
Markdown

# KINTEX AI 모바일 앱 + 백엔드 보안 감사 결과
> 감사일: 2026-07-12 · 감사자: 통합 QA(reviewer) · 방식: 정적 소스 감사(코드 미수정, 읽기전용)
> 체크리스트: `mobile-security-checklist.md` · 개선 백로그: 본 문서 §하단
> 증거는 `파일:라인` 표기. `mobile/`는 다른 에이전트 편집 중이라 스냅샷 기준.
## 종합 판정
| 영역 | PASS | FAIL | N-A | 총평 |
|------|:----:|:----:|:---:|------|
| 1. 위변조 방지 | 1 | 5 | 0 | **최대 약점** — 루트/디버거/무결성 탐지·난독화 선언 부재 |
| 2. 데이터 보호 | 5 | 2 | 0 | SecureStore·로그 청결 우수 / FLAG_SECURE·백그라운드 마스킹 부재 |
| 3. 통신 보안 | 2 | 2 | 0 | HTTPS 강제 / cleartext 차단 선언·피닝 부재 |
| 4. 인증·세션 | 4 | 1 | 0 | 2FA·잠금·RBAC 견고 / 생체 로컬잠금 부재 |
| 5. 시큐어 코딩 | 7 | 0 | 0 | **모범** — SQLi 가드·CSPRNG·스택트레이스 차단 완비 |
| 6. 빌드·배포 | 4 | 2 | 0 | env 시크릿·오류응답 청결 / 권한 최소화·CORS 명시 보강 |
전반: **백엔드·시큐어코딩은 공공 기준 대비 견고**. **모바일 클라이언트 측 위변조 방지(anti-tampering)와 화면보호가 최대 갭** — 공공 대민 앱 기준(KISA 위변조 항목군)에서 필수 보강 대상.
---
## 1. 앱 위변조 방지
| ID | 판정 | 위험도 | 증거 / 근거 |
|----|------|--------|-------------|
| AT-1 루트/탈옥 탐지 | **FAIL** | High | package.json 의존성에 `jail-monkey`·`freerasp`·root 탐지 라이브러리 없음(mobile/package.json 13~33). 코드 내 탐지 로직 0건. |
| AT-2 서명/무결성 | **FAIL** | High | Play Integrity/App Attest·서명 지문 검증 코드 없음. 리패키징 탐지 부재. |
| AT-3 코드 난독화 | **FAIL** | Med | app.json에 `jsEngine: "hermes"` 미선언(mobile/app.json 2~53) — RN 0.74 기본 Hermes지만 명시·R8 설정 없음. `expo-build-properties`(R8/ProGuard) 미설치. |
| AT-4 디버거/에뮬레이터 | **FAIL** | Med | Frida/디버거/에뮬레이터 탐지 없음. |
| AT-5 디버그 비활성 | **부분 PASS** | Low | eas.json production=`app-bundle`(비디버그 기본, eas.json 18~22). 단 `android:debuggable=false` 명시·`__DEV__` 스트립 확인 필요. |
| AT-6 개발자모드/USB | **FAIL** | Med | 개발자옵션·USB 디버깅 감지 없음. |
## 2. 데이터 보호
| ID | 판정 | 위험도 | 증거 / 근거 |
|----|------|--------|-------------|
| DP-1 토큰 안전저장 | **PASS** | — | JWT를 expo-secure-store(Keychain/Keystore)에 저장(mobile/lib/secureStore.ts 10~25, mobile/context/AuthContext.tsx 62~66). web만 AsyncStorage 폴백(비네이티브). |
| DP-2 하드코딩 비밀 0 | **PASS** | — | 시크릿/키/토큰 grep 0건(mobile 전체). config.ts는 base URL만(공개, mobile/lib/config.ts 10~11). |
| DP-3 비밀번호 미저장 | **PASS** | — | 비밀번호는 요청 body로만 전송, 저장 로직 없음(mobile/lib/auth.ts 26~33, login.tsx 주석 4). |
| DP-4 화면캡처/오버레이 | **FAIL** | Med | 2FA/티켓QR 등 민감화면에 FLAG_SECURE 미적용(grep 0건). `expo-screen-capture` 미설치. |
| DP-5 백그라운드 스냅샷 | **FAIL** | Med | 앱 전환 시 민감화면 마스킹 로직 없음. |
| DP-6 클립보드 보호 | **PASS(경미)** | Low | 클립보드 강제복사 코드 없음. 티켓/개인정보 필드 클립보드 제어는 별도 검토 권장. |
| DP-7 로그 민감정보 0 | **PASS** | — | `console.log/warn/error` grep 0건(mobile 전체). 토큰·비밀번호 로깅 없음. |
## 3. 통신 보안
| ID | 판정 | 위험도 | 증거 / 근거 |
|----|------|--------|-------------|
| NW-1 TLS 강제 | **부분 PASS** | Med | base URL `https://kintex.zioinfo.co.kr`(mobile/app.json 45, config.ts 11). cleartext `http://` grep 0건. 단 Android `usesCleartextTraffic=false`·networkSecurityConfig 미선언(기본 SDK34 false지만 명시 권장). |
| NW-2 인증서 검증 | **PASS** | — | 표준 fetch(트러스트스토어 검증), 검증 우회 코드 없음(mobile/lib/api.ts 56~61). |
| NW-3 인증서 피닝 | **FAIL** | Med | 인증서/공개키 피닝 미적용. 공공 대민 앱 권고사항. |
| NW-4 API 키 미노출 | **PASS** | — | 통신에 클라이언트 API 키 없음. Authorization은 런타임 발급 JWT만(mobile/lib/api.ts 52). |
## 4. 인증·세션
| ID | 판정 | 위험도 | 증거 / 근거 |
|----|------|--------|-------------|
| AU-1 2FA(TOTP) | **PASS** | — | OTP_REQUIRED→재제출 흐름(mobile/lib/auth.ts 26~38, login.tsx 62~84). 서버 TotpService(HS 라이브러리 codeVerifier, backend security/TotpService.java 46~57). |
| AU-2 생체·로컬잠금 | **FAIL** | Med | `expo-local-authentication` 미설치, 로컬 재잠금 없음(진행 중 내정보 트랙과 연계). |
| AU-3 세션 만료·재발급 | **PASS** | — | 401 시 메모리 토큰 정리·재로그인 유도(mobile/lib/api.ts 67~70). 서버 TTL env(backend application.yml 67, JwtService 만료 66). |
| AU-4 로그인 실패 잠금 | **PASS** | — | 임계 초과 시 locked_until 설정, 계정열거 방지 일반화 메시지(backend security/LoginAttemptService.java 34~57). 클라 ACCOUNT_LOCKED 처리(login.tsx 74~75). |
| AU-5 서버측 RBAC | **PASS** | — | 행사 단위 RBAC를 JWT roles 클레임+컨트롤러 가드로 서버 강제(backend JwtService.java 47~69, SecurityConfig.java 48). |
## 5. 시큐어 코딩
| ID | 판정 | 위험도 | 증거 / 근거 |
|----|------|--------|-------------|
| SC-1 입력검증 | **PASS** | — | 클라 검증(login.tsx 53~60, register.tsx 27~30) + 서버 @Valid(GlobalExceptionHandler MethodArgumentNotValid 39~47). |
| SC-2 SQLi 방지 | **PASS** | — | MyBatis 동적 `${}`는 NlQueryReadMapper.xml `${sql}` 단 1곳, 결정론적 화이트리스트 가드 통과분만 실행(backend NlQuerySqlGuard.java: 단일SELECT·테이블 화이트리스트·주석/다중문/PII/LIMIT 검증 56~130). 그 외 전부 `#{}` 바인딩. |
| SC-3 안전한 난수 | **PASS** | — | OTP IV SecureRandom(backend SecretCipher.java 31,44), OTP secret은 totp SecretGenerator(CSPRNG), 웹훅 HmacSHA256. |
| SC-4 웹뷰 안전설정 | **N-A→PASS** | — | WebView 미사용(mobile grep 0건). 공격면 없음. |
| SC-5 딥링크 검증 | **부분 PASS** | Low | scheme `kintex`(app.json 6). expo-router 라우팅. 딥링크 파라미터 명시 검증 로직 추가 권장(현재 인증가드로 보호). |
| SC-6 스택트레이스 미노출 | **PASS** | — | 서버 include-stacktrace/message: never(application.yml 13~14), GlobalExceptionHandler 요약만 반환·스택 미저장(51~62), 401 봉투(SecurityConfig 49~57). 클라 요약 메시지만 노출(api.ts 82~89). |
| SC-7 암호화 저장 | **PASS** | — | OTP secret AES-256-GCM 봉인(backend SecretCipher.java seal/open 39~77, `enc:v1:` 포맷). |
## 6. 빌드·배포
| ID | 판정 | 위험도 | 증거 / 근거 |
|----|------|--------|-------------|
| BD-1 릴리스 로그/소스맵 | **PASS(경미)** | Low | console 로그 0건. 소스맵 배포 제외·`__DEV__` 스트립은 빌드시 확인 권장. |
| BD-2 최소 권한 | **부분 PASS** | Med | app.json에 명시 권한 선언 없음(기본 Expo 권한만). expo-image-picker 사용(package.json 19) → 카메라/사진 권한 발생. `blockedPermissions`로 불필요 권한 제거 권장. |
| BD-3 서드파티 취약점 | **미검증** | Med | `npm audit` 미실행(감사 범위상 코드 실행 배제). 의존성 버전은 SDK51 정렬 최신(package.json). CI에 audit 게이트 권장. |
| BD-4 서버 오류응답 | **PASS** | — | IP/SSH/비밀번호/스택트레이스 응답 미노출(GlobalExceptionHandler, ServerOut 스키마 규칙 준수). error_log 요약 1줄만(51~59). |
| BD-5 CORS/헤더 | **부분 PASS** | Med | `.cors(cors -> {})`에 CorsConfigurationSource 빈 미정의(backend SecurityConfig.java 35) → 명시 허용출처 없음(와일드카드 `*`도 없음). 운영 도메인 명시 권장. |
| BD-6 시크릿 주입 | **PASS(주의)** | Med | JWT_SECRET·KINTEX_OTP_ENC_KEY·DB/SMTP/ADMIN 비번 전부 env 주입(application.yml 27,43,66,76,83). **주의**: JWT_SECRET 개발 폴백 `CHANGE_ME_DEV_ONLY...`(66), OTP 키 미주입 시 개발 파생키 경고(SecretCipher 88) → **운영 env 주입 필수**. GEMINI_API_KEY는 워커 전용, 백엔드 미사용(9). |
---
## 추가 관찰(비차단, 운영 하드닝)
- **로그 레벨**: `logging.level.com.zioinfo.kintex: DEBUG`(application.yml 106) — 운영 프로파일에서 INFO로 하향 권장(민감 파라미터 verbose 노출 위험).
- **JWT 저장 위치**: 웹 타깃만 AsyncStorage 폴백(secureStore.ts 10) — 관람객 B2C 웹뷰/PWA 배포 시 XSS 토큰 탈취면 존재. 네이티브 앱은 무관.
- **웹훅 인증**: `/api/webhooks/in/**` permitAll이나 토큰+HMAC-SHA256 이중검증·상수시간 비교(WebhookSignature.java `MessageDigest.isEqual` 58) — 양호.
---
## 개선 백로그 (FAIL/취약 → 구현 작업 전환)
> 담당: 위변조·SecureStore·화면보호·network config = **kintex-mobile-dev** / 백엔드 = **kintex-backend-dev** / EAS·빌드설정 = **kintex-devops-dev**
> 코드 편집은 각 담당 에이전트가 수행(본 QA는 문서·감사만). SDK51 호환 주의 병기.
### P0 (High · 공공 대민 필수)
| # | 항목 | 담당 | 권장 구현 | SDK51 주의 |
|---|------|------|-----------|-----------|
| B1 | AT-1 루트/탈옥 탐지 | mobile-dev | `jail-monkey`(isJailBroken/canMockLocation) 또는 `freerasp-react-native` 도입 → 루팅 단말 경고·민감기능 제한. config plugin으로 prebuild 반영 | jail-monkey는 config plugin 필요(bare/prebuild). Expo 관리형은 `expo prebuild` 또는 dev-client 필요 — devops와 협의 |
| B2 | AT-2 앱 무결성 검증 | mobile-dev + backend-dev | Android **Play Integrity API**·iOS **App Attest** 토큰을 로그인/민감 API에 첨부 → backend에서 검증. 서명 지문 확인 | Expo 관리형은 네이티브 모듈 → dev-client/prebuild. 서버 검증 엔드포인트 신설 |
| B3 | AT-3 난독화 선언 | devops-dev | app.json `android.jsEngine:"hermes"`(또는 최상위) 명시 + `expo-build-properties``android.enableProguardInReleaseBuilds:true`·`enableShrinkResources:true` | expo-build-properties 플러그인 추가·prebuild 필요. Hermes는 RN0.74 기본이나 명시로 회귀 방지 |
| B4 | DP-4 화면캡처 방지 | mobile-dev | `expo-screen-capture` `preventScreenCaptureAsync()`를 2FA·티켓QR·개인정보 화면 focus 시 적용 | expo-screen-capture SDK51 호환. iOS는 캡처 감지만·Android는 FLAG_SECURE 차단 |
### P1 (Med)
| # | 항목 | 담당 | 권장 구현 | SDK51 주의 |
|---|------|------|-----------|-----------|
| B5 | NW-1 cleartext 차단 명시 | devops-dev | `expo-build-properties``android.usesCleartextTraffic:false` 명시 + iOS ATS 유지 확인 | prebuild 반영 |
| B6 | NW-3 인증서 피닝 | mobile-dev + devops-dev | `react-native-ssl-pinning` 또는 build-properties networkSecurityConfig `pin-set`(SPKI 해시). 백엔드 인증서 갱신 로테이션 절차 병행 | 피닝 만료/로테이션 운영 리스크 — 백업 핀 2개 이상 |
| B7 | DP-5 백그라운드 마스킹 | mobile-dev | AppState 'inactive/background' 시 오버레이 블러/스플래시 표시(민감 화면) | RN AppState + 조건부 렌더 |
| B8 | AU-2 생체 로컬잠금 | mobile-dev | `expo-local-authentication`으로 앱 재진입·민감작업 시 생체 재인증(내정보 트랙과 통합) | SDK51 호환. 폴백(PIN) 설계 |
| B9 | AT-4/AT-6 디버거·개발자모드 탐지 | mobile-dev | freerasp의 debugger/emulator/hooking 탐지 콜백 활용(B1과 통합) | B1과 동일 도입 경로 |
| B10 | BD-2 권한 최소화 | devops-dev | app.json `android.permissions` 명시 + `blockedPermissions`로 불필요 권한 제거. image-picker 권한 문자열(카메라/사진) 최소화 | image-picker는 권한 필요 — 사용처만 요청 |
| B11 | BD-5 CORS 명시 | backend-dev | `CorsConfigurationSource` 빈으로 운영 도메인(`kintex.zioinfo.co.kr`·`kintex.wise.ai.kr`)만 allowedOrigins 명시, 와일드카드 금지 | Spring Security 6 lambda DSL |
| B12 | BD-6 운영 시크릿 강제 | devops-dev + backend-dev | 운영 배포 시 JWT_SECRET·KINTEX_OTP_ENC_KEY env 주입 검증(부재 시 기동 실패 fail-fast 검토), DEBUG 로그 INFO 하향 | 개발 폴백 값 운영 유입 차단 |
| B13 | BD-3 의존성 CVE | devops-dev | CI에 `npm audit --audit-level=high`(mobile) + gradle 의존성 스캔 게이트 추가 | 정기 스캔 |
### P2 (Low · 하드닝)
| # | 항목 | 담당 | 권장 구현 |
|---|------|------|-----------|
| B14 | AT-5 debuggable 검증 | devops-dev | 릴리스 빌드 `android:debuggable=false`·소스맵 미배포·`__DEV__` 스트립 CI 검증 |
| B15 | SC-5 딥링크 검증 | mobile-dev | `kintex://` 딥링크 진입 파라미터 스키마 검증·화이트리스트 라우팅 |
| B16 | DP-6 클립보드 정책 | mobile-dev | 티켓QR·개인정보 필드 `contextMenuHidden`·민감 복사 자동클리어 |
---
## 재검증 게이트(구현 후)
1. `mobile/`: jail-monkey/freerasp 도입·screen-capture 적용·build-properties(hermes/R8/cleartext) 반영 → 본 문서 AT-*/DP-4/NW-1 재판정.
2. `backend`: CorsConfigurationSource 빈·운영 로그레벨·시크릿 fail-fast → BD-5/6 재판정.
3. 빌드 회귀: `mobile tsc --noEmit` EXIT 0, `./gradlew build` 통과 확인.