개발
Nginx 접속 차단 설정 (IP차단, 요청URI차단, 리퍼러차단, 에이전트차단)
웹서버를 운용하다보면 정말 예상외의 접속을 참 많이 받는다.
워드프레스좀 만져본 사람들이라면 흔히 관리자 주소를 wp-admin에서 바꿔야한다는것도 다 같은 맥락이다.
웹 접속을 이용해 특정권한을 탈취하고 악의적인 작업을 하는경우도 있고, 그냥 통상적인 방법으로는 크롤링 서버를 통해 웹사이트가 제공하는 콘텐츠를 얻어내기위한 목적도 있다.
이런 부분에 대해서 악의적인건 둘째치고 크롤링같은건 사실 서버에 부담만 안가게 가끔가다 와주면 상관이 없는데 그냥 대책없이 1초에 5~10번씩 무자비하게 수집요청을해버리면 왠만한 서버는 뻗어버린다.
아마존같은 가용성 서버를 이용해 이런 요청을 수용한다쳐도 그만큼 비용의 증가가 당연하게 따라오는것이다.
차단해야지 어쩌겠음?

그럼 우리는 이런경우에 뭘해야하는가?
목적에 맞게 불필요하다고 판단되는 요청을 차단해버면 되는것이다.
이런 요청에 대한 차단 기능은 AWS에서도 ALB+WAF의 조합으로 걸러낼 수 있지만 나같은경우엔 프록시 서버 방식으로 하나의 서버에서 수십개의 웹서비스를 제공하고 각각의 사이트의 SSL 관리를 통합으로 하고 있기때문에 ALB 방식의 작업은 할수가 없었다.
그래서 고전적인 방법으로 Nginx 단에서 특정 IP 접속 차단을 하는 방법을 예전부터 써왔었다.
근데 솔직히 이 방법도 접속요청이 한두개여야 차단하지 수십개의 관리는 거의 불가능할정도로 힘들고 무엇보다도 아이피를 기준으로만 차단이 가능해서 크롤러나 봇같이 여러 아이피를 사용해 지속적으로 접근하는걸 일일히 검증해 차단하는거는 거의 불가능에 가까웠다.
Nginx 조건문으로 차단 조건 설정하기
그럼 위에서 필요한 상황에 맞게 일일히 조건문을 만들어 요청을 분석한 후 플래그 처리를 해서 그 플래그값이 하나라도 걸리면 향후 웹서버에 대한 접속을 차단하면된다.
말은 쉬운데 이제 하나하나 조건문을 따져보면서 어떻게 차단하는지 서술해보려한다.
IP 대역 차단
우선 가장 기본이 되는 아이피 차단이다.
기존에 Nginx 설정에서 특정아이피를 차단하려면 deny 0.0.0.0; 처럼 하나의 아이피를 차단했지만 더 여러개의 아이피대역을 차단하기위해 CIDR 방식의 아이피 차단을위해 아래와 같은 조건을 적용해볼 수 있다.
geo $deny_ip {
default 0;
64.124.8.0/24 1; #Crawler access (Hive.ai, United States)
64.7.198.0/24 1; #Malicious access(Bucharest, Romania)
...
}
Copy
geo는 아이피 대역 검사 CIDR를 하기 위한 조건 분기용 전용 변수로 위에처럼 아이피대역을 처리하는데 특화된 조건문이다.
단일 아이피를 차단하려한다면 0.0.0.0/32 처럼 아이피 끝에 서브넷 마스크 크기를 지정한 CIDR 방식의 아이피를 적고 한칸 띄고 1;을 입력한다.
위 조건식의 플로우는 $deny_ip의 값을 기본 0으로 설정하고 이후에 아래부분을 돌면서 해당 아이피대역에 현재 아이피가 매핑되면 1을 반환하는 방식이다.
여기에 차단할 아이피를 쭉 나열하면 그게 아이피 차단 체크 조건블럭이된다.
요청 URI 차단
이 경우는 불손한 요청 URI가 이루어질때 차단할 수 있는 조건블럭이다.
위에서도 언급했듯이 웹서비스의 취약점을 파고드는 요청이 생각보다 많이 발생하는데, 대표적으로 워드프레스 사이트의 로그인 페이지인 wp-admin/login 같은게 있다.
그럼 이런경우는 아이피차단을 실행하기보다는 불손한 요청이있으면 미리 차단하자라는 방식으로 접근할 수 있을것이다.
map $request_uri $deny_uri {
default 0;
~*wp- 1;
~*\.php 1;
~*\.asp 1;
...
}
Copy
여기서는 무난하게 매핑함수를 이용해 조건 블럭을 구현한다.
서버 변수인
$request_uri
의 문자열을 분석해 매핑되는 값이 있으면 1 플래그를 반환하는 코드이다.위의 예제에서는
wp-
문자열이 들어있는 모든 uri요청, .php
문자열이 들어있는 모든 요청등을 판독해 차단 uri인지 검증한다.생각보다 아이피 대역 차단은 문제가 일어난 이후에 역추적을 통해서 처리하는 후처리방식인것에비해 위 요청 URI 차단은 익히 알려진 공격 URI를 미리 차단할 수 있는 선처리방식이어서 잘만쓰면 많은 악의적인 요청을 커트할 수 있을것이다.
리퍼러 차단
리퍼러는 내 서버에 오기전까지 이전서버의 url 정보를 뜻한다.
가끔 이 리퍼러가 제대로된 수치가아니면 관리자 권한이 아님을 바탕으로 튕겨내는식의 보안도있고, 특정 사이트에서의 유입을 막을때도 주로 참조하는 좋은 변수이다.
map $http_referer $deny_referer {
default 0;
~*wp- 1;
}
Copy
접근변수는
$http_referer
이고 위 예제 에서는 리퍼러 주소에 wp- 문자열이 있으면 차단 플래그 1을 반환하는 방식이다.특정 사이트의 접근은 차단하고싶으면 그 사이트 도메인을 입력하면된다.
예를들어 네이버에서의 접근을 막고싶다면
~*naver.com 1;
이렇게 적으면 될것이다.에이전트 차단
가장 크롤러나 봇을 유효하게 막을 수 있는 수단이다.
보통 대부분의 봇이나 크롤러는 유저 에이전트의 자신의 소속이나 태그를 남기게 되는게 그 값을 이용해서 차단 작업을 진행해볼 수 있다.
map $http_user_agent $deny_agent {
default 0;
~*crawler 1;
~*Scrapy 1;
~*DotBot 1;
~*AhrefsBot 1;
...
}
Copy
위에처럼 $http_user_agent 서버 변수에 담긴 값을 가져와서 그 문자열에 우리가 지정한 크롤러나 봇의 이름이 포함되어있다면 플래그 1을 반환하는 조건 블럭이라고 이해하면된다.
실제로 가장 많이 쓸 부분이고 이 에이전트 기준 차단을 적용하기위해서 글을 쓴 이유기도 하다.
실제 차단을 위한 작업
위에서 조건블럭을 통해서 우리는 4개의 플래그 변수를 뽑아낼 수 있었다.
플래그 변수 | 차단 대상 |
---|---|
$deny_ip | 아이피 대역 차단 |
$deny_uri | 요청 URI 차단 |
$deny_referer | 리퍼러 차단 |
$deny_agent | 유저 에이전트 차단 |
이를 이용해 최종적으로 요청 구문에 대한 차단 여부를 결정하는 코드를 아래에 적용해볼 수 있다.
map "$deny_ip$deny_uri$deny_referer$deny_agent" $deny_all {
default 0;
~*1 1;
}
Copy
위 맵 블럭의 역할은 4개의 변수값을 이어붙여 하나의 문자열로 만들고 그중에 1이 포함된 값이 있으면 최종적으로 $deny_all 변수에 1을 반환, 없으면 0을 반환하는것이다.
만약에 위에서 얻은 4개의 플래그변수값이 순서대로 0, 1, 0, 0 이면 0100이 해당 맵 변수로 들어가게되고 그중 1이 있으므로 최종적으로 $deny_all은 1이 되는 느낌이다.
그리고 이렇게 최종적으로 차단여부가 0 또는 1로 정해졌으면 이제 서버블럭의 location / 블럭 파트에가서 아래 조건문을 입력하면 차단 작업이 완료된다.
server {
listen 80;
server_name _;
location / {
if ($deny_all = 1) {
return 403;
}
proxy_pass http://html;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Copy
if ($deny_all = 1) { return 403; } 조건문으로 위에서 변수체크된 내용을 바탕으로 403 에러를 반환하는것으로 추가적인 웹서버 작업을 요청하지않는 방식으로 잘못된 처리에대해 예외처리가 가능하다.
최종적인 nginx.conf의 구조는 아래와 같다.
http {
geo $deny_ip {
...
}
map $request_uri $deny_uri {
...
}
map $http_referer $deny_referer {
...
}
map $http_user_agent $deny_agent {
...
}
map "$deny_ip$deny_uri$deny_referer$deny_agent" $deny_all {
default 0;
~*1 1;
}
... #http 설정값들
server {
listen 80;
server_name _;
location / {
if ($deny_all = 1) {
return 403;
}
... #location 설정값들
}
}
...
}
Copy
이렇게 서버설정을 완료해두고
nginx -s reload
같은 서버 명령어로 이 설정파일을 적용시키면 차단 설정이 적용이 된다.결론
사실 이 방식을 예전부터 하고싶었는데, 이미 나는 해당 프록시 서버에서 관리중인 사이트만 몇십개가 넘어가는 상황에서 일일히 server location 블럭에 저 조건문처리하는게 너무 양이많아서 미루고 미루다가 방법이 없어 작업하게된 케이스이다.
만약에 자기가 단일 웹서비스를 쓴다면 위의 방식보다 원초적으로 아마존 자체에서 차단시킬 수 있는 ALB+WAF 조합으로 차단을 거는게 제일 현명하지않을까 싶다.
#Nginx
0
개의 댓글