본문 바로가기
IT/보안

[보안] OWASP ZAP으로 XSS 취약점 진단 및 해결: 실제 웹 애플리케이션 디버깅 사례

by 수누다 2026. 8. 3.
반응형

[웹보안] OWASP ZAP으로 XSS 취약점 진단 및 해결

OWASP ZAP XSS 진단은 웹 서비스를 운영하는 분이라면 한 번쯤 꼭 부딪히는 주제입니다. 기능 테스트는 다 끝났는데, 막상 보안 점검 단계에서 경고가 쏟아지면 머리가 좀 아파지거든요. 저도 홈랩에서 돌리던 작은 웹 애플리케이션이랑 사내 업무성 화면을 비슷한 방식으로 점검해보면서, "이거 사용자 입력만 잘 받으면 되는 거 아닌가?" 했다가 삽질 좀 했습니다 ㅎㅎ 특히 XSS(Cross-Site Scripting, 교차 사이트 스크립팅) 쪽은 화면이 멀쩡해 보여도 실제로는 브라우저에서 위험한 스크립트가 실행될 여지가 숨어 있는 경우가 많더라고요.

이번 글에서는 OWASP ZAP XSS 점검을 어떻게 시작하면 좋은지, 어떤 식으로 취약점을 재현하고, 실제 웹 애플리케이션 디버깅 과정에서 무엇을 확인해야 하는지 제 경험 기준으로 정리해보겠습니다. 단순히 도구만 돌리는 글이 아니라, 웹 보안 취약점을 어떻게 읽고 해석할지까지 같이 보실 수 있게 구성했습니다.

OWASP ZAP을 이용한 XSS 진단 전체 흐름 다이어그램

브라우저, 프록시, 대상 웹 애플리케이션, 취약점 탐지 흐름을 한눈에 보여주는 개요 이미지입니다.

1. 왜 XSS 취약점이 아직도 자주 나오냐고요?

생각보다 단순하거든요. 입력값을 받는 위치는 많은데, 출력할 때 문맥(context)을 제대로 구분하지 않아서 그렇습니다. 쉽게 말해, 사용자가 넣은 값을 화면에 다시 보여줄 때 HTML 문맥인지, 속성(attribute) 문맥인지, JavaScript 문맥인지 구분해야 하는데, 여기서 하나만 어긋나도 XSS 공격으로 이어질 수 있습니다.

예를 들어 검색창, 게시판 제목, 프로필 소개, 관리자 메모, 에러 메시지, 심지어 URL 파라미터를 그대로 렌더링하는 부분까지 전부 후보입니다. 실제로 써보니까 프론트엔드 프레임워크가 자동 이스케이프를 해주는 경우도 많지만, 예외가 있거든요. 템플릿 엔진에서 raw 출력이 들어가거나, 서버에서 만든 HTML 조각을 그대로 내려보내거나, 클라이언트에서 innerHTML 같은 식으로 꽂아 넣는 순간 위험해집니다.

  • Reflected XSS(반사형 XSS): 요청에 포함된 값이 응답에 바로 반영될 때 주로 발생합니다.
  • Stored XSS(저장형 XSS): DB 등에 저장된 악성 스크립트가 다른 사용자 화면에서 실행됩니다.
  • DOM-based XSS(DOM 기반 XSS): 서버 응답보다 브라우저 내 스크립트 처리 과정에서 발생합니다.

여기서 중요한 포인트! OWASP Top 10을 공부할 때 XSS를 그냥 "스크립트 막기" 정도로만 보면 놓치는 게 많습니다. 핵심은 신뢰하지 않는 입력(untrusted input)을 어떤 문맥에서 출력하는가입니다.

2. OWASP ZAP이 정확히 뭘 해주나요?

OWASP ZAP(Zed Attack Proxy, 웹 애플리케이션 보안 점검 프록시)은 브라우저와 서버 사이에 끼워 넣어서 요청/응답을 관찰하고, 취약점 탐지까지 도와주는 보안 감사 도구라고 보시면 돼요. Burp Suite를 먼저 접해보신 분이라면 비슷한 감각으로 이해하셔도 됩니다. 저는 처음엔 프록시 개념이 낯설었는데, 한 번 구조를 잡고 나니까 디버깅할 때 엄청 편하더라고요.

OWASP ZAP에서 XSS를 볼 때 주로 쓰는 기능은 아래 네 가지입니다.

기능 설명 언제 유용한가
Intercept 요청을 중간에서 잡아 수정 파라미터 변조, 재현 테스트
Spider 사이트 구조를 수집 숨은 엔드포인트 탐색
Passive Scan 트래픽을 보며 수동 분석 부담 적은 초기 점검
Active Scan 공격 페이로드를 보내며 점검 실제 취약점 확인

운영 서비스에 바로 Active Scan(액티브 스캔, 실제 공격성 요청 포함)을 넣는 건 조심하셔야 합니다. 테스트 환경이나 스테이징에서 먼저 하시는 게 맞습니다. 이건 진짜 중요해요. 의도치 않게 데이터가 바뀌거나, WAF(Web Application Firewall, 웹 애플리케이션 방화벽) 경보를 울릴 수도 있거든요.

3. 실전 환경 구성: 브라우저 프록시부터 맞춥니다

제가 보통 하는 첫 단계는 단순해요. 브라우저 트래픽이 ZAP을 지나가도록 프록시를 맞추고, 대상 애플리케이션을 평소처럼 직접 조작해봅니다. 자동 스캔부터 돌리기보다, 먼저 정상 흐름을 타보는 게 훨씬 좋습니다. 어느 파라미터가 어디서 어떻게 반영되는지 감이 오거든요.

  1. OWASP ZAP을 실행합니다.
  2. 브라우저 프록시를 ZAP 로컬 프록시에 맞춥니다.
  3. 테스트 대상 URL을 브라우저로 직접 접속합니다.
  4. 로그인, 검색, 게시글 작성, 프로필 수정처럼 입력이 있는 화면을 우선 탐색합니다.
  5. ZAP의 Sites 트리와 History에서 요청/응답을 확인합니다.
# 예시: 로컬 개발 서버 실행
npm run dev

# 또는 Python 예시
python app.py

애플리케이션이 로컬에서 돌아간다면 더 편해요. 수정하고, 다시 요청 보내고, 결과 확인하는 루프가 빠르거든요. 저는 이 단계에서 먼저 검색창, 에러 메시지, 상세 화면 제목 같은 곳을 체크합니다. 왜냐하면 reflected XSS가 생각보다 이런 데서 잘 보이기 때문입니다.

브라우저 프록시 설정과 ZAP 사이트 트리 화면 개요

브라우저 프록시가 ZAP을 거쳐 사이트 트리와 요청 히스토리가 채워지는 흐름을 설명하는 이미지입니다.

4. 제가 실제로 많이 보는 XSS 의심 패턴

처음엔 이게 뭔가 싶었는데, 몇 번 보다 보면 냄새가 나는 지점이 있어요. 예를 들어 아래 같은 코드요.

from flask import Flask, request, render_template_string

app = Flask(__name__)

@app.route('/search')
def search():
    q = request.args.get('q', '')
    return render_template_string("""
        <h1>Search</h1>
        <div>Result for: %s</div>
    """ % q)

이 코드는 사용자 입력값 <code>q를 그대로 HTML에 끼워 넣고 있어요. 겉보기에는 단순하지만, 여기서 <script> 같은 값이 들어가면 브라우저가 코드로 해석할 수 있습니다. 실제 서비스 코드에서는 이렇게 대놓고 문자열 포매팅하는 경우는 드물어도, 템플릿 조합이나 HTML fragment 생성 로직에서 비슷한 문제가 숨어 있더라고요.

프런트엔드에서도 마찬가지입니다.

const keyword = new URLSearchParams(location.search).get('q');
document.getElementById('result').innerHTML = 'Result: ' + keyword;

이건 전형적인 DOM 기반 XSS 위험 패턴이거든요. textContent를 써야 할 자리에 innerHTML을 쓰고 있어요. 저도 예전에 테스트용 관리자 페이지에서 이런 식으로 빨리 만들었다가 나중에 보안 점검에서 걸린 적이 있습니다. 기능은 잘 되는데 보안은 별개더라고요.

5. OWASP ZAP으로 XSS 재현하는 방법

이제 본격적으로 OWASP ZAP XSS 진단을 해보겠습니다. 제가 많이 쓰는 흐름은 Passive Scan으로 냄새를 맡고, 직접 재현 페이로드를 넣어본 다음, 필요할 때만 Active Scan을 거는 방식이거든요.

  1. 입력값이 들어가는 요청을 하나 고릅니다.
  2. ZAP History에서 해당 요청을 찾습니다.
  3. Request를 열고 파라미터 값을 XSS 테스트 문자열로 바꿉니다.
  4. 응답 HTML과 브라우저 렌더링 결과를 같이 봅니다.
  5. ZAP Alerts에서 XSS 관련 경고를 확인합니다.
# 예시 페이로드
<script>alert(1)</script>

# 속성 문맥 점검용 예시
"><img src=x onerror=alert(1)>

# 단순 반영 여부 확인용 예시
zap-test-123

여기서 팁 하나. 무조건 공격적인 문자열부터 넣지 말고, 먼저 zap-test-123 같은 무해한 문자열이 응답 어디에 반영되는지 확인하세요. 그 다음 문맥에 맞는 페이로드로 좁혀가는 게 좋아요. 이 과정을 건너뛰면 "경고는 떴는데 왜 안 터지지?" 하면서 시간 많이 써요. 제가 딱 그랬거든요.

만약 로그인 후 화면만 점검해야 한다면, 세션 쿠키가 살아 있는 상태로 브라우저를 통해 먼저 정상 요청을 만들고, 그 요청을 재사용하는 방식이 편합니다. ZAP이 세션 흐름을 계속 보고 있으니까 재현이 빨라집니다.

ZAP에서 요청 파라미터를 수정해 XSS 페이로드를 넣는 장면

히스토리에서 요청을 선택하고 파라미터를 수정해 테스트 문자열을 넣는 과정을 보여주는 이미지입니다.

6. 취약점이 확인되면 어디를 고쳐야 하나요?

이 부분이 가장 중요하거든요. XSS는 단순 필터링으로 끝내면 다시 터집니다. 제가 직접 해보니, 결국은 출력 시점의 문맥별 인코딩(output encoding)안전한 DOM 조작으로 가야 재발이 줄었어요.

상황 문제 패턴 권장 대응
HTML 본문 출력 사용자 문자열 직접 삽입 템플릿 자동 이스케이프 사용
HTML 속성값 따옴표 포함 값 삽입 속성 문맥 인코딩 적용
JavaScript 내 문자열 스크립트에 직접 변수 주입 JSON 직렬화 후 안전하게 전달
DOM 조작 innerHTML 사용 textContent, createElement 사용

서버 템플릿 기준으로는 raw 출력이나 safe 플래그 남용을 줄이는 게 먼저고요. 프런트엔드 기준으로는 사용자 입력을 HTML로 만들지 않는 습관이 정말 중요하거든요.

from flask import Flask, request, render_template

app = Flask(__name__)

@app.route('/search')
def search():
    q = request.args.get('q', '')
    return render_template('search.html', q=q)
<h1>Search</h1>
<div>Result for: {{ q }}</div>

프런트엔드는 이렇게 바꾸는 편이 안전해요.

const keyword = new URLSearchParams(location.search).get('q') || '';
document.getElementById('result').textContent = 'Result: ' + keyword;

추가로 Content Security Policy(CSP, 콘텐츠 보안 정책)도 같이 검토해보세요. CSP는 근본 해결책을 대체하진 못하지만, 인라인 스크립트 실행을 제한하는 보조 방어선으로 꽤 도움이 돼요.

Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'

7. ⚠️ 제가 실제로 겪은 트러블슈팅 포인트

여기서부터가 진짜 현업 감각이 들어가는 부분입니다. ZAP 경고가 떴다고 무조건 exploitable(실제 악용 가능)한 건 아니고, 반대로 화면에서 팝업이 안 떴다고 안전한 것도 아니에요.

  • 경고는 떴는데 브라우저에서 실행이 안 됨
    응답에 값이 반영되긴 했지만 HTML 이스케이프가 일부 적용돼 있거나, CSP 때문에 실행이 막힌 경우였어요. 이때는 응답 원문과 개발자도구 DOM을 같이 봐야 합니다.
  • 테스트 문자열이 보이지 않음
    서버가 아니라 클라이언트 렌더링 구간에서 값이 처리되는 경우가 있어요. DOM 기반 XSS 가능성을 봐야 해요. ZAP만 보지 말고 브라우저 콘솔과 Elements 탭도 같이 보세요.
  • WAF가 중간에서 차단함
    공격 문자열이 차단돼서 정상 재현이 안 되는 경우가 있었어요. 이럴 땐 무해한 문자열로 먼저 반영 위치를 찾고, 스테이징 환경에서 재확인하는 게 좋습니다.
  • 입력값 검증만 추가했는데 다른 화면에서 다시 발생
    한 화면만 막고 공통 렌더링 유틸은 그대로 둔 경우였어요. 출력 레이어 전체를 봐야 해요.

저는 특히 "검색 페이지 하나만 고치면 끝"이라고 생각했다가, 같은 값을 상세 팝오버와 관리자 로그 화면이 또 다른 방식으로 출력해서 다시 잡힌 적이 있어요. 이런 게 은근 많거든요. 그래서 한 번 취약점이 나오면, 입력의 출발점이 아니라 출력의 종착점 전체를 추적하는 게 맞습니다.

트러블슈팅 체크리스트

  1. 반영 위치가 서버 응답인지, DOM 렌더링 이후인지 구분합니다.
  2. 문자열이 어떤 문맥에 들어가는지 확인합니다.
  3. 템플릿 자동 이스케이프가 비활성화된 구간이 있는지 봅니다.
  4. innerHTML, dangerouslySetInnerHTML 같은 위험 API 사용 여부를 찾습니다.
  5. CSP 적용 상태와 브라우저 차단 로그를 확인합니다.
  6. ZAP Alert를 근거로 동일 패턴이 다른 화면에도 있는지 확장 점검합니다.

8. 수정 후 검증은 이렇게 했습니다

수정이 끝났다고 바로 안심하시면 안 돼요. 보안 이슈는 회귀(regression, 수정 후 다른 경로에서 재발) 확인이 중요하거든요. 저는 보통 세 단계로 확인합니다.

  1. 처음 문제를 재현했던 동일 요청을 다시 보냅니다.
  2. 유사한 입력 필드에도 같은 테스트를 반복합니다.
  3. ZAP Passive/Active Scan 결과에서 관련 경고 변화를 봅니다.
# 애플리케이션 로그 확인 예시
tail -f app.log

# 프런트 정적 분석/테스트 예시
npm test

이때 "팝업이 안 뜬다"만 보지 말고, 응답 HTML이 안전하게 이스케이프되는지, DOM에 텍스트로 들어가는지까지 확인하는 게 좋아요. 가능하면 보안 관련 회귀 테스트도 같이 넣어두세요. 예를 들어 검색 결과에 특수문자가 들어왔을 때 HTML이 아니라 텍스트로 렌더링되는지 확인하는 테스트요.

검증이 끝나면 ZAP Alerts에서 해당 경고가 사라졌는지, 또는 위험도가 낮아졌는지 확인해요. 환경에 따라 경고가 남을 수도 있는데, 그 경우는 false positive(오탐)인지 근거를 남겨두는 게 좋습니다.

취약점 수정 전후를 비교하고, 검증 포인트를 시각적으로 정리한 이미지입니다.

9. 정리: OWASP ZAP XSS 점검은 도구보다 해석이 중요합니다

오늘 정리한 내용을 한 줄로 줄이면 이렇습니다. OWASP ZAP XSS 진단은 시작점일 뿐이고, 진짜 실력 차이는 경고를 어떻게 읽고 코드까지 추적해서 고치느냐에서 나타나요. 저도 처음엔 도구가 다 잡아줄 줄 알았는데, 실제로 써보니까 결국 사람이 문맥을 이해해야 하더라고요.

특히 XSS 공격은 눈에 안 띄게 숨어 있다가, 관리자 화면이나 내부 도구처럼 방심한 곳에서 터지는 경우가 많아요. 그래서 개발 편의성 때문에 넣어둔 raw 렌더링, 빠른 화면 조합용 HTML 삽입 같은 부분을 다시 보는 습관이 중요합니다. 혹시 이런 경험 있으신가요? 기능은 멀쩡한데 보안 점검에서 갑자기 발목 잡는 순간이요. 그럴 때 당황하지 마시고, 입력값 반영 위치부터 차근차근 좁혀가시면 됩니다.

다음 글에서는 CSP 설정을 조금 더 깊게 다뤄보거나, DOM 기반 XSS를 브라우저 개발자도구 중심으로 추적하는 방법도 정리해볼 예정입니다. 이전 글에서 다뤘던 리버스 프록시와 로그 분석 글이 있다면 같이 보셔도 흐름 잡는 데 도움이 됩니다.

XSS 진단부터 수정, 재검증까지 요약한 인포그래픽

OWASP ZAP XSS 진단, 수정, 재검증 흐름을 한 장으로 요약한 마무리 이미지입니다.

빠른 요약

  • OWASP ZAP은 프록시 기반으로 요청/응답과 취약점 탐지를 도와주는 강력한 도구입니다.
  • XSS는 입력 필터보다 출력 문맥별 인코딩과 안전한 DOM 조작이 핵심이에요.
  • 경고 자체보다 실제 반영 위치와 실행 가능성을 해석하는 과정이 더 중요합니다.
  • 수정 후에는 동일 요청 재현, 유사 화면 확장 점검, 회귀 테스트까지 확인해야 해요.

자주 묻는 질문

Q. OWASP ZAP 경고가 뜨면 바로 취약점이라고 봐도 되나요?
A. 아니에요. 오탐 가능성이 있어서 응답 원문, DOM 반영 방식, 브라우저 동작을 같이 확인해야 해요.

Q. 입력값에서 script 태그만 막으면 충분한가요?
A. 보통은 부족해요. 속성 문맥, JavaScript 문맥, DOM 조작까지 고려해야 해서 출력 시점 방어가 더 중요합니다.

Q. 운영 환경에서도 Active Scan을 돌려도 되나요?
A. 권장하지 않아요. 스테이징이나 테스트 환경에서 먼저 검증하는 편이 훨씬 안전합니다.

반응형