---
name: detecting-auth-session-weaknesses
description: >-
SQIsoft 웹 애플리케이션에서 인증(Authentication)·세션(Session)·JWT 관련 취약점을 검사한다.
프로젝트 스택(Spring Boot 모던 / JSP·Servlet 레거시)을 자동 감지하여 스택별 Semgrep 룰로
1차 탐지하고, AI가 세션 고정·약한 해시·JWT 설정 오류·쿠키 보안속성 누락을 컨텍스트로 검증한다.
탐지 대상: 세션 재발급 누락(세션 고정), SHA-256 단순 해시 비밀번호(솔트 약함/BCrypt 미사용),
JWT 시크릿 하드코딩·서명검증 우회 가능성, 쿠키 Secure/HttpOnly/SameSite 미설정,
Refresh Token 쿠키 보안속성 누락, 세션 타임아웃 미설정.
domain: cybersecurity
subdomain: web-application-security
tags:
- authentication
- session-management
- jwt
- session-fixation
- weak-password-hash
- cookie-security
- cwe-287
- cwe-384
- cwe-916
- cwe-614
- cwe-798
- owasp-a07
- spring-security
- jsp
- servlet
- sqisoft
cwe:
- CWE-287
- CWE-384
- CWE-916
- CWE-614
- CWE-798
owasp:
- A07:2021-Identification-and-Authentication-Failures
stacks:
- spring-modern
- jsp-legacy
version: "0.3.0"
author: sqisoft-security
license: Proprietary
---
# 인증·세션·JWT 취약점 검사
## When to Use
다음 중 하나라도 해당하면 이 스킬을 실행한다.
- 로그인 성공 후 세션 ID가 재발급되는지 확인할 때 (세션 고정 공격 방어 여부)
- 비밀번호 저장 방식이 BCrypt인지, SHA-256 단순 해시인지 확인할 때
- JWT 시크릿 키가 코드·설정 파일에 하드코딩됐는지, 서명 검증이 적절한지 확인할 때
- Refresh Token 쿠키에 `HttpOnly`, `Secure`, `SameSite=Strict` 속성이 있는지 확인할 때
- 세션 타임아웃·동시 세션 제한이 설정됐는지 확인할 때
**이 스킬을 쓰지 않을 때:** 접근통제·IDOR(→ `detecting-broken-access-control`),
CSRF 위조(→ `detecting-csrf-vulnerabilities`), SQL 인젝션·XSS 등 다른 취약점 클래스.
## Prerequisites
- 대상 프로젝트 소스에 대한 읽기 접근
- (선택, 권장) `semgrep` 설치 — 없으면 `scripts/scan_auth.py`가 grep 폴백으로 동작
- 스택 판별 참고: `references/stack-patterns.md`
## Workflow
### 0단계 — 스택 자동 감지
| 스택 | 감지 신호 | 적용 룰 |
|---|---|---|
| `spring-modern` | `build.gradle.kts` / `settings.gradle*`, `src/main/java/**` | `rules/auth-session.yml`의 spring 룰 |
| `jsp-legacy` | `**/WEB-INF/web.xml`, `*.jsp`, `src/main/webapp/**` | `rules/auth-session.yml`의 jsp 룰 + AI 패턴 검사 |
### 1단계 — 스캐너 1차 탐지 (재현 가능)
```bash
python skills/detecting-auth-session-weaknesses/scripts/scan_auth.py "$TARGET" --json
```
스크립트는 스택을 감지해 Semgrep 룰을 실행하고, 없으면 grep 폴백으로 후보를 수집한다.
출력: `{file, line, rule_id, stack, snippet}` 목록.
### 2단계 — AI 컨텍스트 검증 (핵심)
스캐너 후보는 출발점일 뿐이다. 각 후보를 아래 기준으로 직접 검증한다.
**spring-modern 검증 포인트**
1. **JWT 시크릿 키 관리** — `JwtTokenProvider.java` 실사례 확인:
- `@Value("${security.jwt.secret-key}")` + `${JWT_SECRET_KEY}` 환경변수 주입 → 안전
- 코드에 문자열 리터럴로 하드코딩(`private String secretKey = "my-secret"`) → 위험
- 키 길이 검증 로직(`MINIMUM_KEY_LENGTH = 64`) 존재 → 양호
- 개발 환경에서 짧은 키 시 자동 랜덤 키 생성 + 로그 출력 → 운영 환경 실수 방지 확인
2. **JWT 서명 검증** — `validateToken()` 메서드:
- `Jwts.parser().verifyWith(key).build().parseSignedClaims(token)` 패턴 → 정상
- `alg=none` 허용 여부: jjwt 0.12.x 기본은 none 거부. `Jwts.parser()`에 별도 허용 없으면 안전
- `ExpiredJwtException` 처리: 만료 토큰 재사용 방지 확인
3. **Refresh Token 쿠키 보안속성** — `application.yml` / `AuthController`:
- `security.cookie.secure: ${COOKIE_SECURE:false}` — 운영 환경에서 `false` 기본값 위험
- `Refresh Token`이 쿠키에 담기는지, 담긴다면 `HttpOnly=true`, `Secure=true`, `SameSite=Strict` 설정 확인
- `SessionCreationPolicy.STATELESS` 확인 → 서버 세션 미생성 (세션 고정 위험 없음)
4. **비밀번호 인코더** — `WebSecurityConfig.java`:
- `BCryptPasswordEncoder` Bean 등록 → 양호
- 실제로 회원가입·비밀번호 변경 로직에서 `passwordEncoder.encode()` 사용 확인
**jsp-legacy 검증 포인트**
1. **비밀번호 해시 방식** — `context-security.xml` / `EgovFileScrty.java`:
- `hash="plaintext"`, `hashBase64="true"` → **평문 저장** (실제 운영 DB에 Base64만 적용, 해시 없음)
- `EgovFileScrty.encryptPassword(password, id)` — SHA-256 + 사용자 ID를 salt로 사용
- SHA-256은 BCrypt보다 훨씬 빠르므로 대규모 브루트포스에 취약. BCrypt/Argon2 전환 권장
- `@Deprecated` 된 `encryptPassword(String data)` — salt 없는 SHA-256 단순 해시, 더 위험
2. **세션 고정(Session Fixation)** — `MemberController.memberLoginAction()`:
- 로그인 성공 후 `req.getSession().setAttribute("LoginVo", loginVo)` 직전에
`req.getSession().invalidate()` 또는 `changeSessionId()` 호출 여부 확인
- `springSecurity.doFilter(...)` 호출 시 Spring Security가 세션을 재생성하는지 확인
- `context-security.xml`에 `session-fixation-protection` 명시 없으면 기본값(`migrateSession`) 확인
3. **세션 타임아웃** — `web.xml`:
- `30` → 30분 설정. 업무 특성상 적절한지 확인
- 동시 접속 제한: `concurrentMaxSessons="10"` → 10개. 1개로 제한해야 계정 공유 방지
4. **자동로그인 쿠키** — `MemberController` / 세션 쿠키 설정:
- `COOKIE` 확인
- JSESSIONID 쿠키에 `HttpOnly`, `Secure` 속성 여부 → `web.xml cookie-config` 확인
- SameSite는 Servlet 3.x에서 직접 설정 불가 — 서버(Tomcat) 레벨 설정 필요
**공통 — 누락 보강(스캐너가 못 잡는 것)**
- OTP/이메일 인증 우회 가능성 (토큰 만료 미검증)
- 계정 잠금 임계값·잠금 해제 정책 적절성
- 비밀번호 복잡도 정책 서버 검증 유무
### 3단계 — 사업부 표준 리포트
확정 항목만 아래 양식으로 출력. 스캐너 후보 중 검증 탈락은 "오탐 제외"에 남긴다.
## Output Format
```markdown
# 인증·세션·JWT 취약점 점검 리포트
- 대상: <프로젝트/경로> | 감지 스택:
- 스캐너: | 점검일:
## 요약
- 확정 취약: N건 (High n / Medium n / Low n)
- 의도된 예외: M건 | 오탐 제외: K건
## 확정 취약점
### [High] 평문 비밀번호 저장 — context-security.xml:42
- 스택 / 위치: jsp-legacy / context-security.xml:42
- **① 취약한 점(What)**: `hash="plaintext"` + `hashBase64="true"` 설정으로 비밀번호가
Base64 인코딩만 적용된 상태로 DB에 저장된다. Base64는 암호화가 아니며 즉시 복원 가능하다.
- **② 취약한 이유(Why)**: DB가 유출되면 모든 사용자 비밀번호가 노출된다.
해시 없이 Base64만 적용하면 디코딩으로 원문 복원이 가능하다.
- **③ 뚫리는 방법(How · 개념 PoC)**:
DB 덤프에서 `password` 컬럼 값을 Base64 디코딩하면 원문 비밀번호 획득.
`echo "dXNlcjEyMzQ=" | base64 -d` → `user1234`
- **④ 해결방법(Fix)**:
```xml
hash="bcrypt"
```
```java
// 가입/변경 시 BCryptPasswordEncoder 사용
String encoded = new BCryptPasswordEncoder().encode(rawPassword);
```
기존 사용자는 다음 로그인 시 재해시 로직(마이그레이션) 적용 필요.
- 참조: CWE-916, OWASP A07:2021
### [Medium] 세션 고정 취약점 — MemberController.java:331
- 스택 / 위치: jsp-legacy / MemberController.java:331
- **① 취약한 점(What)**: 로그인 성공 직전/직후에 세션 ID 재발급(`invalidate()` /
`changeSessionId()`) 코드가 없다. Spring Security `doFilter` 호출로 처리되는지 불명확.
- **② 취약한 이유(Why)**: 공격자가 피해자에게 미리 알려진 세션 ID를 심어두고
피해자가 로그인하면 공격자가 해당 세션 ID로 인증된 세션을 탈취한다.
- **③ 뚫리는 방법(How · 개념 PoC)**:
공격자가 JSESSIONID 쿠키를 피해자 브라우저에 심은 후, 피해자가 로그인하면
동일 JSESSIONID로 인증 완료 → 공격자가 해당 쿠키로 접근.
- **④ 해결방법(Fix)**:
```java
// MemberController.memberLoginAction — 로그인 성공 후 세션 재생성
HttpSession oldSession = req.getSession(false);
if (oldSession != null) {
oldSession.invalidate();
}
HttpSession newSession = req.getSession(true);
newSession.setAttribute("LoginVo", loginVo);
```
- 참조: CWE-384, OWASP A07:2021
### [Low] Refresh Token 쿠키 Secure 속성 기본값 false — application.yml:121
- 스택 / 위치: spring-modern / application.yml:121
- **① 취약한 점(What)**: `security.cookie.secure: ${COOKIE_SECURE:false}` — 환경변수
미설정 시 운영 환경에서도 `Secure` 속성 없이 쿠키 발행.
- **② 취약한 이유(Why)**: HTTP 평문 채널에서 쿠키가 전송되면 네트워크 도청으로 탈취 가능.
- **③ 뚫리는 방법(How · 개념 PoC)**:
내부망 중간자(ARP 스푸핑 등) 환경에서 HTTP 요청 패킷 캡처 시 Refresh Token 쿠키 노출.
- **④ 해결방법(Fix)**:
```yaml
security:
cookie:
secure: ${COOKIE_SECURE:true} # 기본값을 true로 전환
```
운영 환경 HTTPS 강제 적용 확인 후 변경.
- 참조: CWE-614, OWASP A07:2021
## 의도된 예외 (확인 필요)
- [Info] JwtTokenProvider — 시크릿 키 환경변수(`${JWT_SECRET_KEY}`) 주입, 64바이트 최소 길이 검증 → 안전
## 오탐 제외
- JwtTokenProvider.validateToken() — `verifyWith(key)`로 서명 검증, 만료/폐기 검증 정상.
alg=none 허용 코드 없음.
```
## Verification (검사 자체의 신뢰성 확인)
- [ ] 스택 감지 결과가 실제 프로젝트와 일치하는가
- [ ] `context-security.xml`의 `hash=` 값을 코드로 직접 확인했는가
- [ ] 로그인 핸들러에서 세션 재생성 코드를 확인했는가(추측 금지)
- [ ] JWT `validateToken()` 메서드가 서명·만료·폐기를 모두 검증하는가
- [ ] Refresh Token 쿠키에 `HttpOnly`, `Secure`, `SameSite` 속성이 모두 있는가
- [ ] 시크릿 키가 환경변수·외부 볼트로 주입되는가(코드 내 리터럴 금지)
- [ ] 각 확정 취약점에 재현 근거(파일:라인)가 붙어 있는가
## Key Concepts
| 용어 | 설명 |
|---|---|
| 세션 고정(Session Fixation) | 로그인 전후 세션 ID가 동일 → 미리 심어둔 세션으로 탈취 가능 |
| SHA-256 단순 해시 | 빠른 연산 = 대규모 브루트포스에 취약. BCrypt/Argon2로 전환 필요 |
| BCrypt | 의도적으로 느린 해시 함수 — 솔트 내장, 워크팩터 조정 가능 |
| JWT alg=none | 서명 없는 토큰 허용 취약점. jjwt 0.12.x는 기본 거부. 명시적 허용 코드 확인 |
| Secure 쿠키 | HTTPS 채널에서만 전송 — HTTP 도청 방지 |
| HttpOnly 쿠키 | JavaScript 접근 차단 — XSS를 통한 쿠키 탈취 방지 |
| SameSite=Strict/Lax | 크로스사이트 요청에서 쿠키 전송 제한 — CSRF 보조 방어 |
| COOKIE_SECURE:false | 기본값 false → 운영 환경 실수 위험. 기본값 true 권장 |
## Tools & Systems
- Semgrep (룰: `rules/auth-session.yml`) · grep 폴백
- Spring Security (`BCryptPasswordEncoder`, `SessionCreationPolicy`), eGovFrame Security
- 참고: `references/stack-patterns.md`
- sef-2026 실사례: `JwtTokenProvider.java`(서명 검증), `WebSecurityConfig.java`(BCrypt), `application.yml`(cookie.secure)
- Gseed 실사례: `context-security.xml`(hash=plaintext), `MemberController.java`(세션 재생성), `EgovFileScrty.java`(SHA-256)