본문 바로가기
운영체제 (LNX,WIN)

WSL2 + netsh portproxy 구조에서 클라이언트 IP 보존: 보안 로그 표준

by 날으는물고기 2025. 12. 9.

WSL2 + netsh portproxy 구조에서 클라이언트 IP 보존: 보안 로그 표준

728x90

현재 구조는 대략 이렇게 되어 있습니다.

[클라이언트 PC]  ──(인터넷/사내망)──>  [Windows 호스트]  ──(netsh portproxy)──>  [WSL2]
                                                   80/tcp                        80/tcp

Windows에서 이런 식으로 설정

netsh interface portproxy add v4tov4 `
  listenaddress=0.0.0.0 listenport=80 `
  connectaddress=<WSL_IP> connectport=80

WSL에서 웹서버(예: Nginx, Apache, Node 앱 등)를 80 포트에 띄워놓았고, 외부에서 접속하면 서비스는 잘 되지만 WSL에서 보이는 클라이언트 IP가 전부 Windows 게이트웨이 IP로만 보이는 문제가 있습니다.

300x250

예를 들면

  • WSL에서 ss -tn 해보면:결과가 전부 src = 172.19.144.1 같은 WSL 게이트웨이 IP로 보임
    ss -tn sport = :80
  • Nginx access.log에도:처럼 실제 클라이언트가 아닌 WSL 게이트웨이 IP만 찍힘
    172.19.144.1 - - [08/Dec/2025:10:00:00 +0900] "GET / HTTP/1.1" 200 ...

원인: netsh portproxy + WSL2 네트워크 구조

이 문제는 단순 설정 실수나 옵션 부족이 아니라, 구조적·설계적 한계에 가깝습니다.

1. WSL2 네트워크 구조

WSL2는 사실상 작은 VM이라서

  • Windows 호스트와는 가상 스위치 (vSwitch) 로 연결된 별도 네트워크
  • Windows 호스트에서 보면 WSL은 내부 사설망(172.x.x.x 등)에 있는 별도 머신

이때 외부에서 들어오는 트래픽은

  1. 먼저 Windows 호스트가 받음
  2. Windows가 내부에서 WSL VM으로 NAT 또는 포트 프록시(netsh)로 재전달

2. netsh interface portproxy 동작 방식

netsh interface portproxy커널 레벨의 포트 포워딩이라기보다는 “소켓 기반 간접 프록시”에 가깝습니다.

  • Windows가 listenaddress:listenport 로 소켓을 열고
  • 연결이 들어오면 새로운 outbound 연결connectaddress:connectport로 생성
  • 이 outbound 연결의 소스 IP는 항상 Windows 호스트(혹은 vNIC) IP

따라서

  • 원본 클라이언트 IP는 Windows에서 이미 끊겨버리고
  • WSL로 갈 때는 “Windows → WSL” 연결만 남습니다.
  • WSL 입장에서는 항상 Windows IP에서 오는 TCP 세션으로만 보게 됩니다.

👉 결론

netsh 포트프록시를 사용하는 한, WSL 내부에서 커널/소켓 레벨(REMOTE_ADDR)에서 “실제 클라이언트 IP”를 그대로 보는 것은 구조적으로 불가능합니다.

이건 설정 잘못이 아니라 원리 자체의 한계입니다.

현실적인 해결 전략의 방향

그렇다면 “실제 IP를 남기고 싶다”는 요구사항을 어떻게 충족시킬 수 있느냐?

핵심 아이디어

L4 레벨(IP/포트)에서 해결하려 하지 말고,
L7(HTTP 레벨)에서 프록시를 두고 “헤더”로 IP를 전달하게 만들자.

  1. Windows에서 HTTP 리버스 프록시 (Nginx, IIS+ARR 등)를 사용
  2. 이 프록시가 클라이언트의 실제 IP를 알고 있으므로
  3. WSL로 요청을 넘길 때 X-Forwarded-For, X-Real-IP 같은 헤더에 원본 IP를 넣어줌
  4. WSL 내부 웹서버/애플리케이션은 이 헤더를 기반으로 로그를 남김

추천 구조: Windows 리버스 프록시 + WSL 서비스

기존 구조

클라이언트 → Windows(netsh portproxy:80) → WSL:80

권장 구조

클라이언트 → Windows(Nginx or IIS 80) → WSL:80

그리고 netsh portproxy는 아예 제거하거나 최소한 외부 접속용 HTTP 포트(80/443)에는 쓰지 않는 것을 추천합니다.

예시 A: Windows Nginx를 리버스 프록시로 사용

(1) 구성 개요

[클라이언트] ───> [Windows:80 - Nginx] ───> [WSL:80 - Web Server(Nginx/Apache/App)]
                 (실제 IP를 알고 있음)         (X-Forwarded-For 기반으로 IP 인식)

(2) Windows Nginx 설치 (요약)

  1. 공식 Nginx for Windows zip 다운로드 후 압축 해제
  2. 예: C:\nginx 위치에 배치
  3. 테스트 실행
    cd C:\nginx
    start nginx.exe
  4. Windows 방화벽에서 80 포트 inbound 허용

(운영용이면 nssm 같은 도구로 Windows 서비스로 등록해서 자동 시작하게 구성 가능)

(3) Windows Nginx 설정 예시

C:\nginx\conf\nginx.conf (핵심 부분만 예시)

worker_processes  1;

events {
    worker_connections  1024;
}

http {
    # 로그 형식 정의
    log_format main '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent"';

    access_log logs/access.log main;

    # WSL 서비스 주소 (WSL IP는 ifconfig/ipconfig / wsl hostname -I 등으로 확인)
    upstream wsl_service {
        server 172.21.23.5:80;  # 예: WSL IP
    }

    server {
        listen 80;
        server_name _;

        location / {
            proxy_pass http://wsl_service;

            # ===== 여기서 실제 클라이언트 IP 전달 =====
            proxy_set_header X-Real-IP       $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header Host $host;
        }
    }
}

이제

  • 클라이언트 → Windows:80 (Nginx)
  • Nginx가 클라이언트의 실제 IP를 $remote_addr로 알고 있음
  • 이를 X-Real-IP, X-Forwarded-For에 담아서 WSL로 전달

여기까지 하면 Windows 측 로그(access.log) 에는 이미 실제 IP가 찍힙니다.

(4) WSL 내부 Nginx에서 X-Forwarded-For 기반으로 IP 인식

WSL 내부 Nginx 설정 (/etc/nginx/nginx.conf 예시)

http {
    # 프록시(Nginx for Windows)의 IP를 신뢰
    # 실제 환경에 맞는 IP/대역 입력 (예: 172.21.23.1/32 또는 사내 대역)
    set_real_ip_from  172.21.23.0/24;
    real_ip_header    X-Forwarded-For;
    real_ip_recursive on;

    log_format main '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent"';

    access_log /var/log/nginx/access.log main;

    server {
        listen 80;
        server_name _;

        location / {
            proxy_pass http://127.0.0.1:8080;  # 뒤에 애플리케이션이 있으면
        }
    }
}

이제 /var/log/nginx/access.log를 보면

203.0.113.45 - - [08/Dec/2025:11:00:00 +0900] "GET / HTTP/1.1" 200 ...

처럼 실제 외부 클라이언트 IP$remote_addr로 찍힙니다.

예시 B: IIS + ARR로 리버스 프록시 구성

IIS를 선호하거나 이미 쓰고 있다면 아래 구조도 가능합니다.

클라이언트 → Windows:80(IIS+ARR) → WSL:80

(1) 기본 단계

  1. IIS 설치 (Web Server 역할)
  2. Application Request Routing(ARR) 및 URL Rewrite 모듈 설치
  3. IIS 관리자에서
    • “서버” 선택 → “Application Request Routing Cache” → “Server Proxy Settings”
    • “Enable proxy” 체크
  4. “사이트”에서 Default Web Site 또는 별도 사이트를 생성 후 “Reverse Proxy…” 메뉴 선택
    • Back-end 서버로 http://<WSL_IP>:80 입력

(2) X-Forwarded-For 헤더 설정

ARR는 보통 자동으로 X-Forwarded-For를 넣도록 옵션이 있습니다.

  • “Server Proxy Settings(서버 프록시 설정)”에서
    • “X-Forwarded-For 헤더 사용” 체크
  • 또는 URL Rewrite 규칙에서 직접 헤더 추가

(3) WSL 내부 웹서버 설정

역시 동일하게

  • Nginx → real_ip_header X-Forwarded-For;
  • Apache → mod_remoteip 설정

예: Apache (httpd.conf)

LoadModule remoteip_module modules/mod_remoteip.so

RemoteIPHeader X-Forwarded-For
RemoteIPTrustedProxy 172.21.23.1   # IIS 서버 IP
LogFormat "%a %l %u %t \"%r\" %>s %b" common
CustomLog logs/access_log common

이렇게 하면 %a 에 실제 클라이언트 IP가 기록됩니다.

netsh portproxy를 꼭 유지해야 한다면?

정직하게 말하면

“netsh portproxy를 유지하면서도 WSL 내부에서 REMOTE_ADDR를 실제 IP로 만드는 방법은 없다”
(IP 레벨에서 NAT/TCP 재연결이 이미 끝난 상태)

현실적인 타협은 다음 두 가지 정도입니다.

  1. Windows에서 별도 로그를 먼저 남기는 방식
    • netsh portproxy 앞단에 HTTP 프록시를 하나 더 두고, 그 프록시에서 실IP 로그
    • 또는 Windows 방화벽 고급 로깅, 커스텀 서비스 등으로 IP 기록
      → 하지만 이 경우 “실제 서비스 로그(WSL)와 IP 로그(Windows)가 분리”되어 SIEM 통합이 번거롭고,
      WSL만 보고는 클라이언트 식별이 어려움
  2. netsh는 개발/테스트 용도로만 사용하고
    실제 서비스/보안 관점에서는 리버스 프록시 구조를 표준으로 가져가는 것

보안·운영 측면에서 보면 두 번째가 훨씬 건전한 구조입니다.

보안 관점에서의 가이드 & 점검 포인트

보안 입장에서 내부 사용자/개발팀에 제공할 수 있는 “정책/가이드” 형태로 정리해보면

1. 아키텍처/설계 정책

  1. WSL2 + netsh portproxy 구조에서는 원본 IP가 WSL에 보이지 않는다는 점을 공식화
    • “이는 시스템 구조 특성으로 인한 제한이며, 보안 로그 기준을 L7(프록시) 레벨로 전환해야 한다”는 내용을 문서화
  2. 표준 접속 구조 정의
    • 외부/사내 클라이언트 → Windows 리버스 프록시(Nginx or IIS) → WSL 서비스
    • HTTP/HTTPS 포트(80/443)에 대해서는 netsh portproxy 사용 금지(권고 수준이 아니라 policy 수준으로)
  3. 표준 헤더 규칙
    • 리버스 프록시는 항상 다음 헤더를 삽입/관리
      • X-Forwarded-For: 클라이언트 IP(프록시 체인 포함)
      • X-Real-IP: 바로 앞단 클라이언트 IP
      • X-Forwarded-Proto, X-Forwarded-Host 등 필요시
    • 외부에서 들어오는 임의의 X-Forwarded-For는 걷어내고, 프록시가 새로 생성하도록 설정
      (IP 스푸핑 방지)

2. 애플리케이션/웹서버 설정 가이드

  1. WSL 내부 웹서버는 REMOTE_ADDR 대신 헤더 기반으로 IP 사용
    • Nginx
      • set_real_ip_from <프록시IP>
      • real_ip_header X-Forwarded-For
      • real_ip_recursive on
    • Apache
      • mod_remoteipRemoteIPHeader X-Forwarded-For 설정
    • Node.js (Express)
      app.set('trust proxy', true);  // 또는 'loopback', '172.21.23.1' 등 프록시 한정
      
      app.use((req, res, next) => {
        console.log('client ip:', req.ip);  // X-Forwarded-For 기반
        next();
      });
    • Flask
      from flask import request
      
      @app.route("/")
      def index():
          real_ip = request.headers.get("X-Forwarded-For", request.remote_addr)
          print("client ip:", real_ip)
          return "ok"
  2. 신뢰 범위 제한
    • trust proxy 혹은 set_real_ip_from내부 프록시의 IP만 지정
      아무 IP나 X-Forwarded-For를 보낸다고 믿지 않도록

3. 로그 포맷 & SIEM 연계

  1. 웹서버 로그 포맷 표준화
    • 예: Nginx
      log_format security '$remote_addr [$time_local] '
                          'xff="$http_x_forwarded_for" '
                          'host="$host" request="$request" '
                          'status=$status bytes=$body_bytes_sent '
                          'ua="$http_user_agent" ref="$http_referer"';
      access_log /var/log/nginx/security.log security;
    • $remote_addr → 최종적으로 실제 클라이언트 IP (XFF 기반)
    • $http_x_forwarded_for → 전체 프록시 체인
    • 필요하다면 WSL이 실제로 받은 REMOTE_ADDR(게이트웨이 IP)도 별도 필드로 로그에 남길 수 있음
      (이건 변조 위험이 없으니 forensic 용도로 쓸 수 있음)
  2. SIEM/EDR 필드 매핑
    • source.ip / client.ip = $remote_addr (실제 클라이언트 IP)
    • proxy.ip / gateway.ip = WSL의 REMOTE_ADDR (게이트웨이 IP)
    • 탐지 룰, 대시보드는 client.ip 기준으로 설계
  3. 운영 체크리스트
    • Windows 리버스 프록시가 정상 동작 중인가?
    • WSL 내 웹서버가 X-Forwarded-For 기반으로 IP를 인식하고 있는가?
    • 무작위 접속 테스트 시, WSL 로그에 실제 클라이언트 IP가 찍히는지 확인
    • SIEM에서 source.ip 필드가 실제 IP로 들어오는지 검증
    • 외부에서 임의의 X-Forwarded-For 값을 넣어도 로그에 반영되지 않는지(프록시에서 필터링하는지) 테스트

최종 정리

  • 문제의 본질
    • WSL2 + netsh interface portproxy 구조에서는 Windows가 TCP를 다시 열어 전달하기 때문에
      WSL에서는 항상 Windows/게이트웨이 IP만 보입니다.
    • 이건 옵션으로 해결 가능한 문제가 아니라 구조 자체의 한계입니다.
  • 실제 클라이언트 IP를 로그에 남기는 현실적인 방법
    1. Windows에 리버스 프록시(Nginx, IIS+ARR 등)를 두고
    2. 이 프록시가 X-Forwarded-For/X-Real-IP 헤더로 원본 IP를 WSL로 전달
    3. WSL 내부 웹서버/앱은 이 헤더를 기반으로 로그를 찍도록 설정
  • 보안/운영 관점
    • HTTP/HTTPS 서비스 표준 구조를 “리버스 프록시 → WSL”로 정하고
    • netsh portproxy는 개발용·임시용 수준으로 제한하는 것이 바람직
    • 헤더 신뢰 범위, 로그 포맷, SIEM 매핑을 사내 표준으로 문서화
728x90
그리드형(광고전용)

댓글