개념
전문가가 아니어도 잡을 수 있는 것들
레슨 5에서 OWASP Top 10을 개괄했고, 레슨 10에서 SQL 인젝션을 직접 확인해봤다. 이 레슨은 그 연장이다. QA가 특별한 도구 없이, 브라우저와 개발자도구만으로 확인할 수 있는 세 가지를 다룬다.
- XSS — 내가 입력한 스크립트가 남의 화면에서 실행되는가
- CSRF — 사용자가 모르는 사이에 요청이 위조되는가
- 세션 관리 — 로그아웃·만료가 실제로 동작하는가
이 셋을 고른 이유가 있다. 발견 난이도는 낮은데 피해는 크고, 자동 스캐너가 놓치는 경우가 많기 때문이다. 특히 저장형 XSS와 권한이 얽힌 세션 문제는 사람이 실제 흐름을 밟아야 드러난다.
⚠️ 반드시 허가된 환경에서만 한다. 우리 서비스의 테스트 환경이거나 명시적으로 승인된 범위여야 한다. 운영 환경이나 남의 서비스에 시도하는 것은 법적 문제가 된다. 레슨 5·10에서 반복한 원칙이다.
XSS — 내 입력이 코드가 되는 순간
XSS(Cross-Site Scripting) 는 공격자가 넣은 스크립트가 다른 사용자의 브라우저에서 실행되는 취약점이다.
원리는 SQL 인젝션과 같다. 레슨 10에서 배운 그것 — 데이터로 들어가야 할 것이 코드로 해석되는 것. 다만 실행되는 곳이 DB가 아니라 브라우저다.
[ 정상 ]
사용자가 닉네임에 "홍길동" 입력
→ 화면에 "홍길동" 표시
[ XSS ]
공격자가 닉네임에 <script>알림창</script> 입력
→ 저장됨
→ 다른 사용자가 그 닉네임을 보는 순간
그 사용자의 브라우저에서 스크립트가 실행됨세 가지 유형 을 구분해두면 어디를 봐야 할지 정해진다.
| 유형 | 어떻게 전달되나 | QA가 볼 곳 | 위험도 |
|---|---|---|---|
| 저장형(Stored) | 서버에 저장돼 다른 사용자에게 노출 | 댓글, 닉네임, 게시글, 프로필, 파일명 | 가장 높다 |
| 반사형(Reflected) | URL 파라미터가 화면에 그대로 표시 | 검색어, 오류 메시지, 필터 값 | 높다 |
| DOM 기반 | 서버를 거치지 않고 클라이언트 코드가 처리 | URL 해시, 프런트에서 조립하는 화면 | 탐지가 어렵다 |
저장형이 가장 위험한 이유 는 공격자가 아무것도 안 해도 피해자가 스스로 찾아오기 때문이다. 반사형은 피해자에게 조작된 링크를 클릭하게 만들어야 한다.
XSS를 확인하는 절차
1단계 — 입력 지점 목록을 만든다.
사용자 입력이 들어가는 곳을 전부 적는다. 놓치기 쉬운 곳들이 있다.
| 흔히 보는 곳 | 놓치기 쉬운 곳 |
|---|---|
| 댓글·게시글 본문 | 파일 업로드의 파일명 |
| 닉네임·프로필 | 이미지 alt 텍스트 |
| 검색어 | 오류 메시지에 반사되는 입력값 |
| 주소·메모 | API로만 넣을 수 있는 필드 |
| 게시글 제목 | 관리자 화면에만 표시되는 값 |
오른쪽 열이 실제로 문제가 나는 곳이다. 화면 폼에는 길이 제한과 검증이 걸려 있어도, API로 직접 보내면 그 검증을 지나칠 수 있다. 모듈 19(레슨 4)에서 배운 프록시 브레이크포인트가 여기서 쓰인다.
2단계 — 탐지용 문자열을 넣는다.
처음부터 스크립트를 넣기보다, 특수문자가 어떻게 처리되는지 부터 본다.
① 먼저 이걸 넣어본다
<b>test</b>
→ 화면에 "<b>test</b>" 라고 그대로 보이면 → 이스케이프됨(안전 신호)
→ 화면에 굵은 글씨 "test" 로 보이면 → HTML로 해석됨(위험 신호)
② ①에서 위험 신호가 나오면 확인 문자열
"><img src=x onerror=alert(1)>
③ 속성 안에 들어가는 경우
" onmouseover="alert(1)①만으로 대부분 판별된다. 굵은 글씨로 보인다면 그 자리는 HTML을 해석하고 있고, 스크립트도 실행될 가능성이 높다.
3단계 — 저장 후 다른 곳에서 확인한다.
이게 QA가 스캐너보다 잘하는 부분이다. 입력한 화면이 아니라 그 값이 표시되는 모든 곳 을 확인해야 한다.
닉네임에 넣었다면 → 프로필 화면
댓글 목록의 작성자 이름
알림 메시지 ("OOO님이 댓글을 남겼습니다")
관리자 화면의 회원 목록
이메일 알림 본문한 곳에서 막혔다고 안전한 게 아니다. 목록 화면은 이스케이프하는데 알림 메시지는 안 하는 경우가 실제로 흔하다.
4단계 — 결과를 판정한다.
| 결과 | 의미 |
|---|---|
| 문자열이 그대로 텍스트로 보인다 | 정상 처리 |
| 태그가 사라졌다 | 필터링됨 — 우회 가능성은 별도 확인 |
| 태그가 렌더링됐다 | 취약 |
| 스크립트가 실행됐다 | 취약 — 즉시 보고 |
XSS 리포트를 쓸 때
발견하면 리포트 작성에도 요령이 있다. 모듈 6(레슨 2)의 원칙에 몇 가지가 더해진다.
- 어디에 넣어서 어디서 실행됐는지 를 분리해 적는다 (입력 지점 ≠ 실행 지점)
- 실제 피해 시나리오를 한 줄 적는다 — "다른 사용자의 세션 쿠키를 탈취할 수
있습니다" (
HttpOnly가 없다면) - 공격 문자열은 최소한으로.
alert(1)이면 충분하고, 실제 공격 코드를 이슈에 남기지 않는다 - 이슈의 공개 범위를 제한 한다(모듈 22 레슨 6의 프로젝트 액세스 수준)
- 심각도는 대개 최상 이다 — 저장형이고 인증 없이 노출되는 화면이라면 특히
CSRF — 사용자가 모르는 사이에 요청이 나간다
CSRF(Cross-Site Request Forgery) 는 사용자가 로그인한 상태를 이용해, 사용자가 의도하지 않은 요청을 보내게 만드는 공격이다.
① 사용자가 우리 서비스에 로그인한 상태 (쿠키 보유)
② 사용자가 공격자가 만든 페이지를 방문
③ 그 페이지가 우리 서비스로 요청을 보냄
<form action="https://ourservice.com/api/transfer" method="POST">
<input name="to" value="공격자계좌">
</form>
(자동 제출)
④ 브라우저가 쿠키를 자동으로 붙여 보냄
→ 서버는 정상 요청으로 처리핵심은 브라우저가 쿠키를 자동으로 붙인다는 것 이다. 사용자는 아무것도 안 했는데 요청은 인증된 상태로 나간다.
CSRF를 확인하는 방법
QA가 확인할 수 있는 지점은 셋이다.
1) 상태 변경 요청에 CSRF 토큰이 있는가
개발자도구 Network 탭(모듈 9 레슨 12)에서 POST·PUT·DELETE 요청을 열어 본다.
확인할 것
· 요청 헤더나 본문에 CSRF 토큰이 있는가
(X-CSRF-Token, _csrf, authenticity_token 등 이름은 다양)
· 그 값이 매번 같은가 다른가 (같으면 의미가 약하다)2) 토큰을 빼거나 바꿔서 보내면 거부되는가
모듈 19(레슨 4)의 프록시 브레이크포인트로 확인한다.
| 시도 | 기대 결과 |
|---|---|
| 토큰 헤더를 삭제하고 전송 | 403 등 거부 |
| 토큰 값을 임의 문자열로 변경 | 거부 |
| 다른 사용자의 토큰으로 전송 | 거부 |
여기서 200이 나오면 CSRF 방어가 없는 것이다.
3) 쿠키의 SameSite 속성 확인
토큰이 없어도 쿠키의 SameSite 속성으로 상당 부분 방어된다. Application
패널에서 확인한다.
| 값 | 의미 |
|---|---|
Strict | 외부 사이트에서 온 요청에는 쿠키를 안 붙인다 — 가장 강함 |
Lax | 대부분의 교차 사이트 POST에 안 붙는다 — 현대 브라우저 기본값 |
None | 항상 붙는다 — Secure 와 함께 쓰여야 하고, CSRF 방어가 별도로 필요 |
SameSite=None 인데 CSRF 토큰도 없다면 그건 보고 대상이다.
API 기반 서비스라면 조금 다르다. 인증을 쿠키가 아니라
Authorization헤더로 한다면 브라우저가 자동으로 붙이지 않으므로 CSRF 위험이 크게 낮다. 다만 쿠키와 헤더를 섞어 쓰는 구조 에서는 여전히 확인이 필요하다.
세션 관리 — 로그아웃이 진짜 로그아웃인가
가장 확인하기 쉬운데 문제가 자주 나오는 영역이다. 점검 항목을 순서대로 정리한다.
| 점검 | 확인 방법 | 문제 신호 |
|---|---|---|
| 로그아웃 후 뒤로가기 | 로그아웃 → 브라우저 뒤로가기 | 이전 화면의 개인정보가 보인다 |
| 로그아웃 후 토큰 재사용 | 로그아웃 전 토큰을 복사 → 로그아웃 후 그 토큰으로 API 호출 | 200이 나오면 서버가 세션을 안 지웠다 |
| 세션 만료 | 만료 시간까지 방치 후 조작 | 만료 안내 없이 오류만 뜨거나, 계속 동작 |
| 동시 로그인 | 다른 기기에서 로그인 | 정책대로 동작하는가(둘 다 유지? 앞 세션 종료?) |
| 비밀번호 변경 후 | 변경 후 기존 세션 | 기존 세션이 유지되면 문제 |
| 권한 변경 후 | 관리자가 권한을 낮춤 | 기존 세션에 즉시 반영되는가 |
| 토큰 저장 위치 | Application 패널 | 로컬스토리지에 있으면 XSS에 노출 |
| 토큰 노출 | Network·URL·콘솔 로그 | URL 쿼리에 토큰이 있으면 로그·리퍼러로 샌다 |
두 번째 줄이 실무에서 가장 자주 걸린다. 프런트엔드에서 토큰을 지우고 로그인 화면으로 보내는 것으로 "로그아웃"을 구현한 경우, 서버는 그 토큰을 여전히 유효하게 본다. 화면상으로는 로그아웃됐는데 API는 그대로 열려 있다.
확인 절차는 간단하다.
① 로그인 후 개발자도구에서 요청 하나를 cURL로 복사 (토큰 포함)
② 화면에서 로그아웃
③ 복사한 cURL을 터미널에서 실행
→ 200이 나오면 서버 세션이 안 지워진 것'비밀번호 변경 후 기존 세션' 도 중요하다. 계정이 탈취됐다고 판단해 비밀번호를 바꿨는데 공격자의 세션이 그대로 살아 있다면, 비밀번호 변경의 의미가 없다.
토큰 노출 — 어디에 흘리고 있나
세션 관리와 짝을 이루는 점검이다. 모듈 9(레슨 12)의 DevTools로 확인한다.
| 확인할 곳 | 문제 |
|---|---|
| URL 쿼리 스트링 | 브라우저 히스토리·서버 로그·리퍼러 헤더에 남는다 |
| 로컬스토리지 | XSS로 읽을 수 있다 |
| 콘솔 로그 | 개발용 로그가 운영에 남아 있는 경우 |
| 오류 메시지 | 스택 트레이스에 토큰이 섞이는 경우 |
| HAR·캡처 파일 | 우리가 만드는 증거 자료에 그대로 담긴다 |
| 프런트엔드 소스 | API 키·시크릿이 번들에 박혀 있는 경우 |
마지막 두 줄은 QA 자신에게도 해당한다. 모듈 9(레슨 12)와 모듈 19 (레슨 4·5)에서 반복한 원칙 — 증거 파일을 공유하기 전에 무엇이 들어 있는지 본다.
프런트엔드 번들에서 키를 찾는 것도 어렵지 않다. 개발자도구 Sources
패널에서 apiKey, secret, token 같은 문자열로 검색하면 된다.
공개 저장소나 배포된 JS에 키가 있으면 그건 이미 노출된 것이다.
어디까지가 QA의 일인가
이 레슨의 항목들은 전부 전문 도구 없이 확인 가능한 것들 이다. 그리고 여기서 걸린다면 더 깊은 문제가 있을 가능성이 높다는 신호다.
QA의 최소 점검 → 여기서 걸리면
(이 레슨의 항목들) 전문 진단이 필요하다는 근거가 된다
↓
전문 도구 (모듈 16 레슨 12·13, 모듈 23)
↓
침투 테스트 / 외부 진단 (모듈 7 레슨 9의 아웃소싱 판단)QA의 목표는 취약점을 전부 찾는 것이 아니라, 명백한 것을 놓치지 않는 것 이다. 그리고 그 결과로 "우리 서비스에 전문 진단이 필요하다"는 논거를 만드는 것이다.
실무에서 왜 필요한가
보안은 흔히 "전문가의 영역"으로 미뤄지고, 그래서 연 1회 외부 진단으로만 처리되는 경우가 많다. 그런데 그 사이에 릴리즈는 수십 번 나간다. QA의 최소 점검이 그 공백을 메운다.
그리고 이 레슨의 항목들은 비용이 거의 들지 않는다. XSS 확인은
<b>test</b> 를 입력해보는 것으로 시작하고, 로그아웃 세션 확인은 cURL
한 번이다. 5분짜리 점검으로 심각도 최상 결함을 잡을 수 있다면, 안 할
이유가 없다.
채용 관점에서도 신호가 된다. "보안 테스트 경험"이라고 적힌 공고가 실제로 요구하는 것은 침투 테스트 능력이 아니라 기본 취약점을 알아보고 재현할 수 있는지 인 경우가 많다. 저장형 XSS를 어디서 어떻게 확인했는지 말할 수 있으면 충분히 구체적이다.