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

Charles·mitmproxy 실전: 값을 바꿔 보내고 응답을 갈아끼우기

프록시는 트래픽을 보는 도구가 아니라 조작하는 도구다. 브레이크포인트·Map Local·Rewrite로 서버 없이 예외 상황을 만들어내는 법을 익힌다.

  • #Charles
  • #프록시
  • #mitmproxy
  • #MapLocal
  • #브레이크포인트
  • #모바일테스트

개념

보는 것에서 바꾸는 것으로

모듈 9(레슨 10)에서 Charles·Wireshark·tcpdump가 각각 어떤 도구인지 개념을 봤다. 그리고 레슨 12에서 브라우저 DevTools로 요청을 확인하는 법을 익혔다.

이 레슨은 그다음이다. 프록시의 진짜 값어치는 보는 것이 아니라 바꾸는 것 에 있기 때문이다.

서버 팀에 부탁한다. "결제 API가 500을 반환하는 상황을 만들어주세요. 그때 화면이 어떻게 되는지 봐야 합니다."

답이 온다. "그 코드는 지금 못 건드립니다. 스테이징에서 장애를 일부러 낼 수는 없어요."

이 대화가 QA에게 익숙하다면 이 레슨이 필요하다. 프록시로는 서버를 건드리지 않고 그 상황을 만들 수 있다. 응답을 가로채서 500으로 바꿔 내 화면에만 전달하면 되기 때문이다.

이 레슨은 Charles를 기준으로 설명하지만, 개념은 도구가 달라도 같다 — Proxyman, mitmproxy, Fiddler 모두 같은 기능을 다른 이름으로 제공한다.

프록시가 중간에 서는 원리

[ 평소 ]
   앱/브라우저  ────────────────▶  서버
 
[ 프록시를 켜면 ]
   앱/브라우저  ───▶  프록시  ───▶  서버
                      │  │
                      │  └── 요청을 보거나 바꿔서 전달
                      └───── 응답을 보거나 바꿔서 전달

HTTP는 그냥 보인다. 문제는 HTTPS 다. 암호화돼 있으니 중간에서 내용을 볼 수 없다. 그래서 프록시는 자기 인증서를 기기에 설치 하게 하고, 그 인증서로 트래픽을 복호화한 뒤 다시 암호화해 서버로 보낸다.

이게 중간자 공격(MITM)과 기술적으로 같은 구조 라는 점이 중요하다. 그래서 아래 원칙이 따라온다.

⚠️ 테스트가 끝나면 인증서를 지우고 프록시 설정을 해제한다. 인증서가 설치된 기기는 그 프록시를 신뢰 한다. 공용 와이파이에서 켜둔 채 돌아다니면 그 자체가 위험이고, 개인 기기에 남겨두면 잊는다.

설정 — 여기서 대부분 막힌다

처음 쓰는 사람이 막히는 지점이 정해져 있어서 순서대로 정리한다.

1) PC 브라우저 트래픽 보기

Charles를 켜면 대개 시스템 프록시가 자동으로 잡힌다. HTTPS를 보려면 두 가지를 해야 한다.

SSL 은 통신을 암호로 감싸는 기술의 옛 이름이다(지금은 TLS 라고 부르지만 도구 메뉴에는 옛 이름이 그대로 남아 있다). 암호로 감싸져 있으니 그냥은 안을 못 보고, 내 컴퓨터가 믿는 증명서를 하나 깔아 두어야 도구가 중간에서 풀어 보여 줄 수 있다. 아래 "루트 인증서 설치" 가 그 작업이다.

  • 루트 인증서 설치 (Help → SSL Proxying → Install Charles Root Certificate)
  • SSL Proxying 대상 지정 (Proxy → SSL Proxying Settings)

두 번째를 빠뜨리는 경우가 많다. 인증서를 설치해도 어떤 호스트를 복호화할지 지정하지 않으면 여전히 암호문만 보인다.

SSL Proxying Settings 예시
   *.example.com : 443      ← 우리 서비스만
   api.example.com : 443

*:* 로 전부 지정할 수도 있지만 권하지 않는다. 무관한 트래픽까지 쏟아져 목록에서 우리 요청을 찾기 어려워지고, 관계없는 서비스의 개인정보까지 캡처에 담긴다.

2) 모바일 기기 트래픽 보기

여기가 프록시를 쓰는 가장 큰 이유다. 앱에는 DevTools가 없다.

① PC와 모바일 기기를 같은 네트워크에 둔다
② 모바일 Wi-Fi 설정에서 프록시를 수동으로 지정
     호스트: PC의 IP        포트: 8888 (Charles 기본)
③ 모바일 브라우저에서 chls.pro/ssl 접속 → 인증서 다운로드
④ 기기 설정에서 인증서를 신뢰하도록 활성화
     · iOS: 설정 → 일반 → 정보 → 인증서 신뢰 설정
     · Android: 설정에서 CA 인증서 설치

안 보일 때 확인할 것 두 가지.

  • 인증서 피닝(certificate pinning) — 앱이 서버 인증서를 코드에 박아두고 검증하는 방식이다. 이러면 프록시 인증서를 거부해 아예 통신이 안 된다. 금융·결제 앱에서 흔하다. 이건 설정 문제가 아니라 의도된 보안 조치 이므로, 개발팀에 디버그 빌드를 요청하는 것이 정답이다.
  • 안드로이드 7 이상의 사용자 인증서 제한 — 앱이 명시적으로 허용하지 않으면 사용자가 설치한 CA를 신뢰하지 않는다. 역시 디버그 빌드가 필요하다.

기능 1 — 브레이크포인트: 요청·응답을 멈춰 세운다

가장 강력한 기능이다. 특정 요청에 브레이크포인트 를 걸면 그 요청이 프록시에 도착했을 때 멈추고, 내용을 편집한 뒤 보낼지 물어본다.

요청 브레이크포인트
   앱이 보낸 것:  {"quantity": 1, "couponId": "SUMMER"}
   내가 바꾼 것:  {"quantity": -1, "couponId": "SUMMER"}
                                ↑ 클라이언트 검증을 우회한 값
   → 서버가 이걸 어떻게 처리하는지 본다
 
응답 브레이크포인트
   서버가 준 것:  200 {"orders": [ ...20건 ]}
   내가 바꾼 것:  200 {"orders": []}
   → 빈 목록일 때 화면이 어떻게 되는지 본다

요청 브레이크포인트가 열어주는 테스트 영역 이 넓다. 화면에서는 만들 수 없는 입력을 서버에 보낼 수 있기 때문이다.

시도확인하는 것
수량을 음수·0·매우 큰 값으로서버 측 검증이 있는가
필수 필드를 빼고 전송400을 주는가, 아니면 500이 나는가
다른 사용자의 리소스 ID로 변경권한 검증(모듈 12 레슨 8의 403)
가격·할인율을 조작서버가 값을 재계산하는가, 클라이언트를 믿는가
만료된 토큰으로 전송401 처리와 재로그인 유도

네 번째가 실무에서 실제로 사고가 나는 지점이다. 화면에서 계산한 최종 금액을 그대로 서버가 받아 저장하는 구현이 드물지 않다. 모듈 16 (레슨 5)에서 배운 클라이언트를 신뢰하면 안 된다 는 원칙을 직접 확인하는 방법이다.

이건 허가받은 환경에서만 한다. 우리 서비스의 테스트 환경이거나 명시적으로 승인된 범위여야 한다. 모듈 16에서 다룬 원칙과 같다.

기능 2 — Map Local: 응답을 파일로 갈아끼운다

특정 요청의 응답을 로컬 파일 내용으로 대체 한다. 브레이크포인트가 매번 손으로 고치는 방식이라면, Map Local은 자동이다.

   GET /api/orders   →   ~/test-data/orders-empty.json
   GET /api/user     →   ~/test-data/user-vip.json

서버 없이 프런트엔드를 테스트하는 가장 빠른 방법 이다. 실무에서 쓰는 상황들이다.

상황준비할 파일
빈 상태(주문 0건) 화면빈 배열 응답
대량 데이터(1,000건) 렌더링큰 배열 응답
긴 텍스트·이모지·특수문자경계값이 담긴 응답
등급별 화면(VIP·휴면·정지)상태별 사용자 응답
아직 개발 안 된 신규 API스펙대로 만든 가짜 응답

마지막 줄이 특히 값을 한다. 백엔드가 아직 없어도 OpenAPI 스펙(모듈 12 레슨 4)만 있으면 프런트엔드 테스트를 시작할 수 있다. 그리고 나중에 실제 API가 나왔을 때 내 가짜 응답과 다르면 그게 계약 위반 이다.

Map Remote는 비슷하지만 요청을 다른 서버로 돌린다. 운영 앱을 스테이징 API에 붙여 보는 식으로 쓴다.

기능 3 — Rewrite: 규칙으로 자동 치환

브레이크포인트와 Map Local 사이에 있는 기능이다. 정규식 규칙 으로 헤더나 본문을 자동으로 바꾼다.

규칙 예시
  · 모든 요청의 User-Agent 헤더를 특정 값으로 → 기기별 분기 테스트
  · 응답의 "status":"ACTIVE" → "status":"SUSPENDED"  → 정지 계정 화면
  · 요청 헤더에 실험 그룹 지정 추가 → A/B 테스트 특정 그룹 강제
  · 응답 상태 코드 200 → 503                      → 점검 중 화면

세 번째가 유용하다. 기능 플래그나 A/B 테스트로 나뉜 화면을 확인할 때, 개발팀에 요청하지 않고 헤더 하나로 특정 그룹에 들어갈 수 있는 경우가 많다.

기능 4 — Throttling: 느린 네트워크를 진짜로 재현

DevTools에도 쓰로틀링이 있지만(레슨 12), 프록시 쓰로틀링은 모바일 앱에도 적용된다 는 것이 다르다. 그리고 대역폭·지연·패킷 손실을 더 세밀하게 설정할 수 있다.

설정만들어내는 상황
낮은 대역폭이미지·영상 로딩 지연, 타임아웃
높은 지연(latency)응답 대기 중 중복 조작, 순서 역전
패킷 손실재시도 로직이 동작하는가
대역폭 제한 + 큰 응답부분 로딩 처리

모듈 17(모바일 테스트)에서 다루는 네트워크 전환(Wi-Fi ↔ LTE ↔ 지하철 음영)의 상당 부분을 여기서 재현할 수 있다.

기능 5 — Repeat / Compose: 요청을 다시, 또는 새로

  • Repeat — 잡힌 요청을 그대로 다시 보낸다. 중복 요청 처리(멱등성) 를 확인하는 가장 빠른 방법이다. 결제 요청을 두 번 보내면 두 번 결제되는가?
  • Repeat Advanced — 같은 요청을 N회, 동시에 보낸다. 가벼운 동시성 테스트가 된다(재고 차감 경쟁 상태 등).
  • Compose — 잡힌 요청을 편집해 새로 만들어 보낸다. Postman과 비슷하지만 실제 앱이 보낸 요청을 출발점으로 삼는다 는 것이 다르다. 헤더·토큰이 이미 채워져 있으므로 훨씬 빠르다.

실무 팁: 모바일 앱의 API를 Postman으로 테스트하려 할 때, 헤더와 토큰을 손으로 재현하느라 시간을 쓴다. Charles에서 잡은 요청을 Compose로 열거나 cURL로 복사 하면 그 과정이 생략된다.

mitmproxy — 터미널과 자동화가 필요할 때

Charles가 GUI 도구라면 mitmproxy 는 터미널 기반이고 오픈소스다. 같은 일을 하지만 성격이 다르다.

Charles / Proxymanmitmproxy
인터페이스GUI터미널(+ 웹 UI)
비용상용(체험 제공)무료·오픈소스
자동화제한적파이썬 스크립트로 확장
CI 연동어렵다가능
학습 곡선낮다중간

mitmproxy의 결정적 장점은 스크립트 다. 파이썬으로 요청·응답을 프로그래밍적으로 조작할 수 있어, 반복 테스트를 자동화 할 수 있다.

# 모든 주문 API 응답을 빈 목록으로 바꾸는 스크립트
def response(flow):
    if "/api/orders" in flow.request.pretty_url:
        flow.response.text = '{"orders": []}'

Charles에서 손으로 하던 Map Local이 코드 몇 줄이 된다. 그리고 이걸 CI에 넣으면 "빈 상태 화면"을 매 릴리즈마다 자동으로 확인 할 수 있다. 모듈 13의 UI 자동화와 결합하는 지점이다.

캡처 파일을 다룰 때의 원칙

프록시 세션에는 모든 것이 들어 있다. 토큰, 쿠키, 개인정보, 결제 정보.

하지 말 것대신
세션 파일을 이슈에 그대로 첨부필요한 요청만 골라 내보내기
캡처 화면을 자르지 않고 스크린샷토큰·개인정보 부분 가리기
캡처 내용을 AI에 붙여 넣기민감 정보 제거 후, 또는 하지 않기
공용 네트워크에서 프록시 켜두기테스트 후 즉시 해제
개인 기기에 인증서 남기기테스트 종료 시 삭제

모듈 18b(레슨 9)에서 다룬 원칙 그대로다. 편의를 위해 통째로 붙여 넣는 순간 사고가 난다.

실무에서 왜 필요한가

QA가 프록시를 다루면 개발팀에 부탁해야 하던 일의 상당수를 스스로 할 수 있게 된다. 에러 응답 재현, 빈 상태 확인, 등급별 화면, 느린 네트워크, 중복 요청. 이것들을 서버 코드 수정 없이 만들어낼 수 있다는 것은 테스트 범위와 속도를 동시에 바꾼다.

특히 모바일 앱 QA에서는 사실상 필수 다. 앱에는 DevTools가 없으므로 "이 화면이 어떤 API를 부르는지"조차 프록시 없이는 알 수 없다. 결함을 프런트엔드/백엔드로 가르는 판단도 여기서 시작한다.

보안 관점의 확인도 QA의 일이 된다. 요청 값을 조작해 서버가 검증하는지 보는 것은 모듈 16(레슨 5)의 취약점을 실제로 확인하는 방법이고, "클라이언트에서 막았으니 괜찮다"는 주장이 사실인지 5분이면 알 수 있다.

채용 관점에서도 신호가 된다. "Charles 사용 경험"은 공고에 흔히 적히는데, 실제로 요구되는 것은 트래픽을 보는 것이 아니라 조작해서 상황을 만들어낸 경험 이다. Map Local로 백엔드 없이 프런트를 검증했다거나, 요청 변조로 권한 검증 누락을 찾았다는 이야기는 구체적이다.

실습 과제

과제 1 — 설정하고 첫 트래픽 잡기 (25분)

Charles(또는 Proxyman·mitmproxy)를 설치하고 다음을 순서대로 한다.

  1. PC 브라우저의 HTTPS 트래픽이 내용까지 보이게 만든다
  2. SSL Proxying 대상을 특정 도메인만 으로 좁힌다
  3. 가능하면 모바일 기기를 연결해 앱 트래픽을 잡는다
  4. 안 잡히는 앱이 있다면 왜인지 추정해 적는다
해설 보기

2번을 강조한 이유가 있다. *:* 로 두면 목록에 수백 개의 요청이 쏟아져 우리 요청을 찾을 수 없고, 무관한 서비스의 개인정보까지 캡처에 남는다. 필요한 도메인만 지정하는 것이 기본 이다.

4번에서 "인증서를 설치했는데도 안 보인다"면 대개 셋 중 하나다 — (a) SSL Proxying 대상에 그 호스트를 안 넣었다, (b) 앱이 인증서 피닝을 쓴다, (c) 안드로이드에서 사용자 CA를 신뢰하지 않는 앱이다.

(b)와 (c)는 설정으로 뚫는 문제가 아니다. 개발팀에 디버그 빌드를 요청하는 것이 정답이고, 이것을 우회하려는 시도는 조직 정책과 법적 문제에 걸릴 수 있다.

과제 2 — 요청을 조작해 서버 검증 확인하기 (25분)

허가된 테스트 환경에서만 수행한다. 요청 브레이크포인트로 다음을 시도하고 결과를 기록한다.

조작기대(서버가 막아야 함)실제결함인가
수량을 음수로400 등 거부
필수 필드 제거400
다른 사용자의 리소스 ID403
금액·할인 값 변경서버가 재계산

발견한 것이 있으면 결함 리포트로 작성한다. 재현 방법에 프록시 조작 내용을 정확히 적는다.

해설 보기

'금액 변경' 행에서 문제가 발견되는 경우가 실제로 있다. 프런트에서 계산한 최종 결제 금액을 그대로 서버가 받는 구현이다. 이건 심각도가 높은 결함이고, 모듈 6(레슨 1)의 기준으로 심각도 최상 에 해당한다.

리포트를 쓸 때 주의할 점 — 공격 방법을 상세히 쓰되 대상은 테스트 환경으로 명시 한다. 그리고 이슈의 공개 범위를 제한하는 것이 좋다. 모듈 22(레슨 6)에서 다룬 프로젝트 액세스 수준이 여기서 쓰인다.

'다른 사용자 리소스 ID' 항목이 403이 아니라 200을 반환하면, 그건 접근 통제 실패이고 모듈 16(레슨 5)에서 다룬 OWASP 계열 취약점이다.

과제 3 — Map Local로 화면 상태 만들기 (25분)

응답 파일 3개를 만들어 각각 적용하고 화면을 확인한다.

파일내용화면이 어떻게 되나결함 있나
빈 목록{"items": []}
대량 데이터항목 500개
경계값매우 긴 이름·이모지·특수문자

빈 상태와 경계값에서 결함이 자주 나온다. 발견한 것을 적는다.

과제 4 — 멱등성과 쓰로틀링 (20분)

  1. 생성 계열 요청(주문·댓글·좋아요)을 잡아 Repeat 로 다시 보낸다 → 두 번 생성되는가?
  2. Repeat Advanced로 동시에 5회 보낸다 → 재고·포인트가 어떻게 되는가?
  3. Throttling을 걸고 앱에서 같은 동작을 한다 → 중복 조작이 가능한가?

발견한 것 중 하나를 골라, 네트워크 조건과 조작 방법을 포함한 리포트를 작성한다.

자가 체크리스트

  • 프록시가 중간에 서는 원리와, HTTPS를 보기 위해 인증서가 필요한 이유를 설명할 수 있다
  • 인증서 설치가 중간자 공격과 같은 구조라는 점과, 그래서 지켜야 할 원칙을 안다
  • SSL Proxying 대상을 지정하지 않으면 왜 여전히 안 보이는지 설명할 수 있다
  • SSL 대상을 전체(*:*)로 두면 안 되는 이유를 두 가지 들 수 있다
  • 모바일 기기를 프록시에 연결하는 순서를 말할 수 있다
  • 인증서 피닝이 무엇이고, 그때의 올바른 대응이 무엇인지 안다
  • 요청 브레이크포인트로 확인할 수 있는 검증 항목을 다섯 가지 들 수 있다
  • Map Local이 백엔드 없이 프런트를 테스트하게 해주는 원리를 설명할 수 있다
  • Rewrite로 A/B 그룹이나 계정 상태를 강제하는 방법을 안다
  • 프록시 쓰로틀링이 DevTools 쓰로틀링과 무엇이 다른지 말할 수 있다
  • Repeat으로 멱등성을 확인하는 방법을 설명할 수 있다
  • Compose가 Postman보다 빠른 이유를 말할 수 있다
  • mitmproxy가 Charles와 다른 지점(스크립트·CI)을 설명할 수 있다
  • 캡처 파일과 스크린샷을 공유할 때의 원칙을 말할 수 있다

흔한 실수

  • 인증서만 설치하고 SSL Proxying 대상을 지정하지 않는다. 여전히 암호문만 보이고, 도구가 고장 났다고 생각하게 된다.
  • SSL 대상을 전체로 둔다. 목록이 쏟아져 우리 요청을 못 찾고, 무관한 서비스의 개인정보까지 캡처된다.
  • 인증서 피닝을 설정 문제로 오해한다. 의도된 보안 조치다. 디버그 빌드를 요청하는 것이 정답이다.
  • 테스트 후 프록시와 인증서를 그대로 둔다. 공용 네트워크에서 위험하고, 개인 기기에서는 잊힌다.
  • 요청 조작을 허가 없는 환경에서 한다. 반드시 승인된 테스트 환경에서만.
  • 금액·권한 조작 결과를 공개 이슈에 상세히 올린다. 심각도가 높은 만큼 공개 범위를 제한한다.
  • Map Local 파일을 실제 스펙과 다르게 만든다. 있지도 않은 필드로 테스트하면 통과해도 의미가 없다. 스펙(모듈 12 레슨 4)을 근거로 만든다.
  • 캡처 세션을 통째로 첨부하거나 AI에 붙여 넣는다. 토큰·개인정보가 그대로 들어 있다.
  • 프록시를 보는 용도로만 쓴다. 조작이 이 도구의 절반이다.

참고 자료

낯선 말 풀이

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

API — 응용 프로그램 인터페이스
프로그램끼리 주고받는 창구. 사람이 화면 버튼을 누르는 대신, 프로그램이 정해진 주소로 요청을 보내 답을 받는다.
QA — 품질보증
만든 것이 쓸 만한지 확인하고, 애초에 덜 망가지게 만드는 방법을 정하는 일. "테스트"는 그 일의 일부다.
HTTP — 하이퍼텍스트 전송 규약
웹에서 요청과 응답을 주고받는 약속. "무엇을 달라"고 보내면 "여기 있다" 또는 "그런 건 없다"가 돌아온다.
HTTPS — 암호화된 웹 전송
웹 통신을 암호로 감싼 것. 중간에서 누가 들여다봐도 내용을 못 읽는다.
TLS — 전송 계층 보안
통신을 암호로 감싸는 기술 그 자체. 웹에서 쓰면 주소가 https로 시작한다.
ID — 식별자
무엇 하나를 다른 것과 구별하기 위해 붙인 이름이나 번호.
OpenAPI — 오픈에이피아이 명세
창구가 어떤 주소로 무엇을 받고 무엇을 돌려주는지 정해진 형식으로 적어 둔 문서.
LTE — 엘티이
휴대전화 통신 방식의 한 세대. 흔히 말하는 4G 다.
GUI — 그래픽 사용자 인터페이스
글자만 있는 화면이 아니라 그림·버튼·창으로 조작하는 화면. 보통 말하는 "화면"이 이것이다.
UI — 사용자 인터페이스
사람이 직접 보고 누르는 부분 — 화면, 버튼, 입력창.
CI — 지속적 통합
코드를 올릴 때마다 자동으로 합쳐서 빌드하고 시험까지 돌리는 방식.
AI — 인공지능
사람이 규칙을 일일이 적지 않아도 데이터에서 규칙을 찾아 판단하는 소프트웨어.

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

이 레슨의 실습

실습은 별도 저장소에서 진행합니다. 새 탭으로 열립니다.