이전 단계

이전글에서는 Cloudflare의 고유한 HTTP 헤더에 관해 간략하게 알아보았다. 혹시 놓치신 분이 있다면 바로 돌아갈 수 있다.

로그 설정하기

log_format에 선언하기

위 헤더를 Nginx에서 사용하는 것은 간단하다. 다음과 같은 내용을 http / server 절 적당한 곳 상단에 입력해주면 된다.

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

$http_ 헤더 소문자, 대쉬는 언더바로 치환하여 바로 변수가 된다. 여기선 $http_cf_ipcountry, $http_cf_ray가 추가되어 방문자의 국가 코드와 Ray 일련번호도 동시에 기록될 것이다.

여기서 왜 $http_cf_connecting_ip를 쓰지 않는지 의문스럽게 여기는 분들도 있을 듯 하다. 이는 다다음 단락에서 해결하도록 하자.

(번외) 이 로그의 효과

123.*.*.218 - - [17/May/2020:19:26:18 +0900] "GET / HTTP/1.1" 304 0 0.000 -- "https://www.google.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36" KR "594ca3b0fd091252-HKG"
40.*.*.149 - - [17/May/2020:19:32:57 +0900] "GET / HTTP/1.1" 304 0 0.000 -- "-" "Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)" US "594cad6abf37cf10-IAD"
175.*.*.202 - - [17/May/2020:20:00:33 +0900] "GET / HTTP/1.1" 200 22593 0.000 -- "https://www.google.com/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.113 Safari/537.36" KR "594cd5da2d350a68-KIX"

단편적으로 로그를 따왔는데, CF-Ray에 서버 지역 정보가 포함되어 있음을 알 수 있다. KR 이라는 똑같은 방문자 국가에 대해서 HKG (홍콩), KIX (일본서부) 같은 다양한 경로로 Cloudflare 서버를 경유해서 옴을 알 수 있다. 역시 망 비용이 지나치게 비싼 대한민국의 현실을 반영해서인지, ICN(서울) 지역 서버를 무료 플랜에서 보기란 힘든 것 같다.

AWS니까 해외망도 잘 대응하는 편이라 큰 체감은 없지만, 염가 VPS를 쓰는 사람들이라면 역시 한국 유저를 위한 Cloudflare 설정에 퍼포먼스를 고려해 재고해야 할 것이다. 아니면 Cloudflare 요금제를 비싼 것으로 끌어올리고 Argo를 적용하든가…

다른 헤더로부터 방문객 IP 주소로 덮어씌우기

Nginx는 Cloudflare 프록시 서버를 담고 있는 방문객의 IP 주소 부분을, 실제 방문객의 IP 주소로 덮어씌울 수 있다. 시작하기 전에, ngx_http_realip_module 플러그인이 활성화되어 있어야 한다.

/etc/nginx/에 위치해있을, 메인 nginx.conf 설정 파일의 http 단락으로 이동한다. 상당히 윗부분에 이미 코멘트 처리된 흔적도 보일텐데, 여기에 아래 내용을 삽입하자.

    set_real_ip_from 103.21.244.0/22;
    set_real_ip_from 103.22.200.0/22;
    set_real_ip_from 103.31.4.0/22;
    set_real_ip_from 104.16.0.0/12;
    set_real_ip_from 108.162.192.0/18;
    set_real_ip_from 131.0.72.0/22;
    set_real_ip_from 141.101.64.0/18;
    set_real_ip_from 162.158.0.0/15;
    set_real_ip_from 172.64.0.0/13;
    set_real_ip_from 173.245.48.0/20;
    set_real_ip_from 188.114.96.0/20;
    set_real_ip_from 190.93.240.0/20;
    set_real_ip_from 197.234.240.0/22;
    set_real_ip_from 198.41.128.0/17;
    set_real_ip_from 2400:cb00::/32;
    set_real_ip_from 2606:4700::/32;
    set_real_ip_from 2803:f800::/32;
    set_real_ip_from 2405:b500::/32;
    set_real_ip_from 2405:8100::/32;
    set_real_ip_from 2c0f:f248::/32;
    set_real_ip_from 2a06:98c0::/29;

이 주소들은 Cloudflare에서 사용하는 IP 주소 대역이다. 여기 바로 아래에 이어서 붙여넣는다.

real_ip_header CF-Connecting-IP;

이렇게 하면 CF-Connecting-IP의 내용으로 Nginx는 요청자 IP를 덮어씌우게 된다.

이 방법의 장점은 PHP 등 서버 스크립트의 다른 곳에서도 평소처럼 HTTP 헤더의 IP를 구해오면 방문자를 특정할 수 있다는 것이다. 서버 기반 방문자 분석 프로그램도 정상 작동하게 만들 수 있다.

CF-IPCountry로 방문자 국가 판별하기

방문자 국가 판별은 별로 중요한게 아니지만, 의외로 서버 보안에서 가장 중요한 핵심 중 하나다.

이를테면 관리자 페이지가 있고, 이곳은 관리자가 체류하는 나라 이외에서 들어갈 일은 없다고 봐야 한다. (출장이 그렇게 잦은 것도 아니고 말이다)

사실 Cloudflare의 대시보드를 보면 이미 이런 기능이 있음을 알 수 있다. Firewall 메뉴에 Firewall Rules 눌러보면 국가별 차단도 가능함을 알 수 있는데, 이건 열람도 막아버리는지라 조금 과하다는 느낌이다.

관리자 페이지에 접속하려는 시도인지 판별하고, 국가를 판별하여 허용 여부를 가려내면 될 것이다. 아래 설정을 http절 적당히 위쪽에 넣어주자.

map $http_cf_ipcountry $reject_country {
        default yes;
        KR no;
        JP no;
        US no;
    }

map $request_uri $reject_sudo {
        default no;
        ~/wp-admin/? $reject_country;
        ~/wp-login(/|\.php)? $reject_country;
    }

아래는 $request_uri 기준으로 액세스를 엄격히 관리해야할 주소인지 판단한다. 패턴이 일치하면 위에서 map한 값을 그대로 넣으니 거절해야하면 yes로 값이 들어가있을 것이다.

location / {} 블록을 찾아서 아래 내용을 집어넣어보자. try_files나 다른 것들보다 가장 위에 등장해야 한다.

if ($reject_sudo = yes) {
    return 406;
}

Nginx 전용 HTTP Response code 444를 쓰면 묻지도 따지지도 않고 바로 연결을 끊어버려서 서버에 참 좋은데, Cloudflare를 상대로 그렇게 하면 502 에러로 넘어가서 뭔가 서버에 문제가 있는 것처럼 Cloudflare를 오해하게 만든다. 연결이 끊긴 경우에는 Always Online 같은 서버 다운 대응 동작으로 들어가게 되니 이건 우리가 의도한 동작이 전혀 아니다.

따라서 HTTP 404도 아니고 적당히 돌려보낼 근거가 있는 응답 코드를 골라서 응답해주기로 했다. 관련 참고자료는 다음과 같다.

이렇게 하면 무사히 올바르게 서버의 에러 응답 그대로 클라이언트에 도달하게 되고, Brute Force 공격으로 두근거리며 들어왔을 공격자는 관리자 페이지에서 좌절하게 된다.

해외의 공격자가 관리자 페이지에 들어가면 보게될 오류

물론 VPN으로 나라 세탁해서 들어오거나, 국내에서 활동중인 공격자들에 대한 대책도 게을리 해서는 안 될 것이다.

다음 단계

다음으로 의도치 않게 내 서버의 실제 IP가 노출된 경우에도, Cloudflare를 거치지 않은, 직접 액세스를 거부할 수 있는 방법을 알아보고자 한다.

2년차 개발자 이진백입니다. 현재 일본에서 웹 프로그래머로 근무중입니다. 주로 Java, Vue (Nuxt), TypeScript 언어를 활용하고 있습니다. 취미로 C#과 Windows Universal Store App을 오래 만져왔고, 개인 서버 운영에도 관심이 많습니다. 최신 이슈를 알기 쉽게 전달해드리도록, 더욱 더 노력하겠습니다.