본문으로 건너뛰기
중급약 28분

XSS·CSRF·세션 관리 직접 확인하기: 화면에서 잡을 수 있는 취약점

SQL 인젝션에 이어 QA가 직접 확인할 수 있는 세 가지다. 스크립트가 실행되는지, 요청이 위조되는지, 로그아웃이 진짜 로그아웃인지 손으로 확인하는 법을 익힌다.

  • #XSS
  • #CSRF
  • #세션관리
  • #토큰노출
  • #보안테스트

개념

전문가가 아니어도 잡을 수 있는 것들

레슨 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를 어디서 어떻게 확인했는지 말할 수 있으면 충분히 구체적이다.

실습 과제

과제 1 — XSS 입력 지점 목록 만들기 (20분)

담당 서비스(또는 연습용 취약 앱)에서 사용자 입력이 들어가는 지점을 10개 이상 찾아 표로 만든다.

입력 지점저장되나어디에 표시되나API로만 가능한가

'어디에 표시되나' 열에 두 곳 이상 적히는 항목 에 표시한다. 그게 우선 확인 대상이다.

해설 보기

'어디에 표시되나'를 채우다 보면 생각보다 많다는 것을 발견한다. 닉네임 하나가 프로필·댓글·알림·관리자 화면·이메일까지 다섯 곳에 나타나는 식이다.

한 곳만 확인하고 통과시키면 안 되는 이유 가 여기 있다. 각 화면이 다른 개발자가 다른 시점에 만들었을 수 있고, 이스케이프 처리가 화면마다 다를 수 있다.

'API로만 가능한가' 열도 중요하다. 화면 폼에는 없지만 API 스펙에는 있는 필드가 있다. 모듈 12(레슨 8)에서 만든 엔드포인트 목록과 대조하면 찾을 수 있다.

과제 2 — 안전한 방식으로 XSS 확인하기 (25분)

허가된 테스트 환경에서만 수행한다. 과제 1의 상위 3개 지점에 대해 단계적으로 확인한다.

지점<b>test</b> 결과판정추가 확인 필요
텍스트로 표시 / 굵은 글씨안전 / 위험

'위험'이 나온 지점에 대해서만 다음 단계로 간다. 발견하면 리포트를 작성하되 공격 문자열은 최소한으로 한다.

해설 보기

<b>test</b> 부터 시작하라고 한 이유가 있다. 처음부터 <script> 를 넣으면 (1) 필터에 걸려 결과를 못 보고, (2) 실제로 실행되면 로그에 공격 흔적이 남아 오해를 살 수 있다.

굵은 글씨로 보이지 않는다고 완전히 안전한 것은 아니다. 태그를 필터링하지만 우회 가능한 구현일 수 있고, 속성 안(value="여기")에 들어가는 경우는 다른 형태가 필요하다. 다만 QA의 최소 점검으로는 여기까지가 적정선 이고, 의심되면 전문 도구(모듈 16 레슨 12의 ZAP)나 전문가에게 넘긴다.

리포트에 "XSS 발견"만 쓰지 말고 입력 지점과 실행 지점을 분리해 적는다. 개발자가 고칠 곳은 대개 실행 지점(출력 이스케이프)이다.

과제 3 — CSRF 방어 확인 (20분)

상태를 바꾸는 요청(생성·수정·삭제) 하나를 골라 확인한다.

  1. 개발자도구에서 그 요청의 헤더·본문에 CSRF 토큰이 있는지 확인
  2. Application 패널에서 인증 쿠키의 SameSite 속성 확인
  3. 프록시로 토큰을 제거하고 전송 → 거부되는가
  4. 인증 방식이 쿠키인지 헤더인지 확인
확인 항목결과판정
CSRF 토큰 존재
SameSite 값
토큰 제거 시 응답
인증 방식쿠키 / Authorization 헤더

네 항목을 종합해 CSRF 위험이 있는지 한 문단으로 판정한다.

과제 4 — 세션 점검 8종 (25분)

본문의 세션 점검표를 실제로 수행하고 결과를 기록한다.

특히 로그아웃 후 토큰 재사용 은 반드시 한다.

① 로그인 → 개발자도구에서 API 요청을 cURL로 복사
② 로그아웃
③ 복사한 cURL 실행 → 응답 코드는?

200이 나오면 결함 리포트를 작성한다. 심각도 판단 근거 도 함께 적는다.

자가 체크리스트

  • XSS가 SQL 인젝션과 원리가 같다는 것을 설명할 수 있다
  • 저장형·반사형·DOM 기반 XSS를 구분하고, 저장형이 왜 가장 위험한지 말할 수 있다
  • XSS 입력 지점 중 놓치기 쉬운 곳을 세 가지 이상 들 수 있다
  • <b>test</b> 부터 시작하는 단계적 확인 방법과 그 이유를 설명할 수 있다
  • 입력한 화면이 아니라 표시되는 모든 곳을 확인해야 하는 이유를 안다
  • XSS 리포트에서 입력 지점과 실행 지점을 분리해 적는 이유를 말할 수 있다
  • CSRF의 동작 원리를 "브라우저가 쿠키를 자동으로 붙인다"로 설명할 수 있다
  • CSRF 방어를 확인하는 세 가지 방법(토큰 존재·토큰 제거·SameSite)을 말할 수 있다
  • SameSite 세 값의 차이와, None 일 때 무엇이 추가로 필요한지 안다
  • 쿠키 인증과 헤더 인증에서 CSRF 위험이 다른 이유를 설명할 수 있다
  • 로그아웃 후 토큰 재사용을 cURL로 확인하는 절차를 말할 수 있다
  • 비밀번호 변경 후 기존 세션이 유지되면 왜 문제인지 설명할 수 있다
  • 토큰이 노출되는 여섯 곳을 열거할 수 있다
  • QA의 최소 점검과 전문 진단의 경계를 설명할 수 있다

흔한 실수

  • 허가 없이 시도한다. 대상과 범위를 문서로 확인하고 시작한다.
  • 처음부터 <script> 를 넣는다. 필터에 걸려 결과를 못 보고, 로그에 공격 흔적만 남는다. <b>test</b> 부터 시작한다.
  • 입력한 화면에서만 확인한다. 목록은 막았는데 알림 메시지는 안 막은 경우가 흔하다. 표시되는 모든 곳을 본다.
  • 화면 폼으로만 입력을 시도한다. API로만 넣을 수 있는 필드와, 폼 검증을 우회한 값에서 문제가 나온다.
  • 태그가 사라지면 안전하다고 결론짓는다. 필터링과 이스케이프는 다르고, 필터는 우회될 수 있다.
  • CSRF 토큰이 있으면 됐다고 본다. 토큰을 제거해도 통과하는지 확인해야 실제 검증 여부를 안다.
  • 프런트엔드 로그아웃만 확인한다. 서버가 세션을 지웠는지는 토큰 재사용으로 확인한다.
  • 토큰을 URL에 담는 구현을 넘긴다. 히스토리·로그·리퍼러로 샌다.
  • 공격 문자열을 이슈에 상세히 남긴다. 최소한으로 쓰고 공개 범위를 제한한다.
  • 취약점을 전부 찾으려 한다. QA의 목표는 명백한 것을 놓치지 않고, 전문 진단의 필요성을 근거로 만드는 것이다.

참고 자료

낯선 말 풀이

이 레슨에 나온 약어입니다. 본문에서도 처음 나올 때 점선으로 표시해 두었습니다 (제목과 링크 글자는 그대로 둡니다).

OWASP — 오왁스프
웹과 앱 보안 자료를 공개로 만드는 비영리 단체. 취약점 목록과 점검 기준이 유명하다.
SQL — 구조화 질의 언어
저장소에 "이런 조건인 줄을 보여 달라"고 묻는 말. 영어 문장에 가까워서 읽기가 비교적 쉽다.
QA — 품질보증
만든 것이 쓸 만한지 확인하고, 애초에 덜 망가지게 만드는 방법을 정하는 일. "테스트"는 그 일의 일부다.
XSS — 크로스 사이트 스크립팅
입력창에 넣은 글자가 화면에서 명령으로 실행되게 만드는 공격.
CSRF — 크로스 사이트 요청 위조
로그인한 사람의 권한을 몰래 빌려 원치 않는 요청을 보내게 만드는 공격.
DB — 데이터베이스
데이터를 넣어 두고 꺼내 쓰는 저장소. 보통 표 여러 개가 서로 연결된 형태다.
URL — 인터넷 주소
웹에서 무언가의 위치를 가리키는 주소 한 줄.
DOM — 문서 객체 모델
웹 화면을 프로그램이 다룰 수 있게 나무 구조로 바꿔 놓은 것. 화면의 현재 상태를 담고 있다.
API — 응용 프로그램 인터페이스
프로그램끼리 주고받는 창구. 사람이 화면 버튼을 누르는 대신, 프로그램이 정해진 주소로 요청을 보내 답을 받는다.
HTML — 하이퍼텍스트 표시 언어
웹 화면의 뼈대를 적는 글자 형식. 제목·문단·버튼 같은 역할을 꺾쇠 괄호로 표시한다.
HAR — 통신 기록 파일
브라우저가 주고받은 요청과 응답을 전부 담아 저장한 파일.
JS — 자바스크립트
웹 화면을 움직이게 만드는 프로그래밍 언어. 버튼을 눌렀을 때 일어나는 일을 적는다.

이 레슨에 나온 나머지 1개를 포함해 용어집 전체 보기