블로그 | NGINX

NGINX Plus 및 fail2ban을 사용한 동적 IP 거부 목록

NGINX-F5-수평-검정-유형-RGB의 일부
리엄 크릴리 썸네일
리암 크릴리
2017년 9월 19일 게시
사진: Arnold Reinhold – 본인 작업, CC BY‑SA 3.0

여러분은 깨닫지 못할 수도 있지만, 여러분의 웹사이트는 끊임없이 위협을 받고 있습니다. WordPress를 실행 중이면 봇이 스팸을 보내려고 합니다. 로그인 페이지가 있는 경우 무차별 대입 공격이 가능합니다. 검색 엔진 스파이더를 원치 않는 방문자로 생각할 수도 있습니다.

원치 않는, 의심스럽고 악의적인 활동으로부터 사이트를 보호하는 것은 쉬운 일이 아닙니다. NGINX App Protect 및 파트너가 제공하는 NGINX Plus 인증 모듈 과 같은 웹 애플리케이션 방화벽은 효과적인 도구이므로 보안 스택의 일부로 고려해야 합니다. 대부분의 환경에서 보안이 지나치다는 것은 있을 수 없으며, 다층적 접근 방식이 항상 가장 효과적입니다.

이 블로그 게시물에서는 웹 보안 스택의 또 다른 계층인 fail2ban을 사용하는 것에 대해 논의하겠습니다. Fail2ban은 의심스러운 활동을 감지하기 위해 로그 파일을 지속적으로 모니터링하고, 사전 구성된 하나 이상의 조치를 취하는 침입 탐지 시스템(IDS)입니다. 일반적으로 fail2ban은 실패한 로그인 시도를 모니터링하고, 일정 기간 동안 해당 IP 주소를 차단(금지)합니다. 이것은 무차별 대입 공격에 대항하는 간단하면서도 효과적인 방어수단입니다.

NGINX Plus R13 에서 우리는 네이티브 키-값 저장소와 새로운 NGINX Plus API를 도입했습니다. 이를 통해 NGINX Plus 구성 및 동작을 외부 시스템에서 구동할 수 있는 풍부한 동적 구성 솔루션이 가능해졌습니다. 이 블로그 게시물에서는 fail2ban을 사용하여 여러 번의 인증 실패 이벤트를 발생시킨 IP 주소의 요청을 무시하도록 NGINX Plus를 자동으로 재구성하는 방법도 보여드리겠습니다.

편집기 – NGINX Plus R16 이상에서는 키‑값 저장소를 클러스터의 모든 NGINX Plus 인스턴스에서 동기화할 수 있습니다. (클러스터 내의 상태 공유 기능은 다른 NGINX Plus 기능에서도 사용 가능합니다.) 자세한 내용은 블로그NGINX Plus 관리자 가이드를 참조하세요.

IP 거부 목록에 키 값 저장소 사용

NGINX Plus 키-값 저장소는 세 가지 주요 특징을 갖춘 기본 메모리 내 저장소입니다.

  1. 키-값 쌍은 JSON 객체로 표현됩니다.
  2. 키-값 쌍은 API를 통해 완전히 관리됩니다.
  3. 값은 NGINX Plus에서 일반 구성 변수로 사용할 수 있습니다.

키‑값 저장소는 keyval_zone 지시어로 명명된 공유 메모리 영역을 생성하여 정의됩니다. 그런 다음 이 키-값 저장소는 HTTP POST 메서드를 사용하여 JSON 객체를 API에 제출하여 초기 값 집합으로 채워질 수 있습니다. 그런 다음 keyval 지시문은 조회 키로 사용될 기존 변수(예시에서는 $remote_addr )와 해당 키의 해당 값에서 평가될 새 변수의 이름( $num_failures )을 정의합니다.

NGINX Plus 키-값 저장소 구성 및 관리

API는 위치 블록을 NGINX Plus API 엔드포인트로 지정하여 활성화됩니다.


server {
listen 1111;
allow 127.0.0.1; # 로컬호스트에서만 접근 허용,
deny all; # 원격 접근 차단.

location /api {
api write=on; # 읽기/쓰기 모드의 NGINX Plus API 엔드포인트
}
}
keyval_zone zone=denylist:1M;
keyval $remote_addr $num_failures zone=denylist;

server {
listen 80;

location / {
root /usr/share/nginx/html;
if ($num_failures) {
return 403;
}
}
}

vim: syntax=nginx

키‑값 쌍을 추가하기 전에 거부 목록 저장소의 내용에 대한 요청은 빈 JSON 객체를 반환합니다.

$ 컬 http://localhost:1111/api/6/http/keyvals/denylist {}

이제 HTTP POST 메서드( curl 명령에 -d 인수 형식)를 사용하여 새 JSON 객체를 제출하여 키-값 저장소를 초기 키-값 쌍으로 채울 수 있습니다. 여러 개의 키-값 쌍을 빈 키-값 저장소에 POST 할 수 있으며 그 이후에는 단독으로 실행할 수 있습니다.

$ curl -iX POST -d '{"10.0.0.1":"1"}' http://localhost:1111/api/6/http/keyvals/denylist HTTP/1.1 201 생성됨 ...

키‑값 쌍은 키에 null 값을 PATCH 하여 제거합니다.

$ curl -iX PATCH -d '{"10.0.0.1":null}' http://localhost:1111/api/6/http/keyvals/denylist HTTP/1.1 204 콘텐츠가 없습니다 ...

모든 키-값 쌍은 DELETE 메서드를 전송하여 키-값 저장소에서 제거할 수 있습니다.

$ curl -iX DELETE http://localhost:1111/api/6/http/keyvals/denylist HTTP/1.1 204 콘텐츠 없음 ...

이제 IP 차단 목록 작성을 간단하게 구현할 수 있습니다.


server {
listen 1111;
allow 127.0.0.1; # 로컬호스트에서만 접근 허용,
deny all; # 원격 접근 차단.

location /api {
api write=on; # 읽기/쓰기 모드의 NGINX Plus API 엔드포인트
}
}
keyval_zone zone=denylist:1M;
keyval $remote_addr $num_failures zone=denylist;

server {
listen 80;

location / {
root /usr/share/nginx/html;
if ($num_failures) {
return 403;
}
}
}

vim: syntax=nginx

이 구성 스니펫은 포트 80을 웹 서버로 구성합니다. 18번째 줄에서는 $num_failures 변수를 denylist 공유 메모리 영역의 키-값 쌍의 값 부분으로 평가하여 $remote_addr 변수(클라이언트 IP 주소)에 해당하는 키와 일치시킵니다. $num_failures를 평가하는 이 프로세스는 순차적으로 더 명확하게 표현됩니다.

  1. $remote_addr 값을 denylist 키-값 저장소에 전달합니다.
  2. $remote_addr 가 키와 정확히 일치하는 경우 해당 키-값 쌍의 값을 가져옵니다.
  3. $num_failures 로 값을 반환합니다.

따라서 클라이언트 IP 주소가 키 값 저장소에 POST 된 경우 모든 요청은 HTTP로 생성됩니다. 403금지된 오류입니다. 이 접근 방식의 장점은 동일한 목적으로 블록을 사용하는 것과 달리 IP 차단 목록을 외부 시스템에서 제어할 수 있다는 것입니다.

fail2ban을 사용하여 IP 거부 목록을 동적으로 관리

위에서 언급했듯이, fail2ban은 로그 파일에서 의심스럽거나 악의적인 활동을 감지하고 시스템을 보호하기 위한 조치를 취하는 데 일반적으로 사용됩니다. 기본 동작은 기록된 IP 주소에서 발생한 모든 패킷을 삭제하도록 iptables를 구성하는 것입니다. 이러한 접근 방식은 악의적인 행위자를 차단하는 데는 효과적이지만, 일반 사용자에게는 매우 열악한 사용자 환경을 제공하며, 특히 비밀번호를 잊어버린 경우 더욱 그렇습니다. 이런 사용자에게는 해당 웹사이트를 전혀 이용할 수 없는 것처럼 보입니다.

다음 구성은 문제가 있는 IP 주소를 NGINX Plus 거부 목록 키-값 저장소에 제출하는 fail2ban 작업을 설명합니다. NGINX Plus는 더욱 유용한 오류 페이지를 표시하고 동시에 해당 IP 주소에 요청 속도 제한을 적용합니다. 이를 통해 해당 동작이 공격의 일부인 경우 해당 공격을 효과적으로 무력화할 수 있습니다.

NGINX Plus와 동일한 호스트에 fail2ban이 기본 설치되어 있고, 모든 구성이 /etc/fail2ban 디렉토리에 있다고 가정합니다.

다음 fail2ban 작업은 NGINX Plus API를 사용하여 위의 간단한 예제와 같은 방식으로 denylist 키-값 저장소 내에서 "차단된" IP 주소를 추가하고 제거합니다. nginx-plus-denylist.conf 파일을 /etc/fail2ban/action.d 디렉토리에 저장합니다.


[정의]
actionban = curl -s -o /dev/null -d '{"":""}' http://localhost:1111/api/6/http/keyvals/denylist
actionunban = curl -s -o /dev/null -X PATCH -d '{"":null}' http://localhost:1111/api/6/http/keyvals/denylist

다음 fail2ban 감옥은 NGINX의 HTTP 기본 인증 모듈을 사용하여 로그인 시도 실패를 감지하는 내장 필터를 활성화하고 nginx-plus-denylist.conf 에 정의된 작업을 적용합니다. 원치 않는 활동을 NGINX 액세스 로그에서 감지하기 위해 사용할 수 있는 다른 내장 필터도 많이 있으며, 이를 쉽게 만들어서 사용할 수 있습니다.


[기본값]
금지시간 = 120
금지조치 = nginx-plus-denylist

[nginx-http-auth]
활성화 = 참

다음 NGINX Plus 구성 스니펫은 기본 "NGINX에 오신 것을 환영합니다" 페이지에 HTTP 기본 인증을 적용합니다. password_site.conf 파일을 /etc/nginx/conf.d 디렉토리에 저장합니다.


server {
listen 1111;
allow 127.0.0.1; # 로컬호스트에서만 접근 허용,
deny all; # 원격 접근 차단.

location /api {
api write=on; # 읽기/쓰기 모드의 NGINX Plus API 엔드포인트
}
}

keyval_zone zone=denylist:1M state=denylist.json;
keyval $remote_addr $num_failures zone=denylist;

limit_req_zone $binary_remote_addr zone=20permin:10M rate=20r/m;

server {
listen 80;
root /usr/share/nginx/html;

location / {
auth_basic "closed site";
auth_basic_user_file users.htpasswd;

if ($num_failures) {
다시 쓰기 ^.* /banned.html;
}
}

location = /banned.html {
limit_req zone=20permin burst=100;
}
}

vim: syntax=nginx

11번째 줄에서는 위의 denylist_keyval.conf 와 같이 keyval_zone을 정의하지만, 키‑값 저장소의 상태가 저장되는 파일을 지정하는 state 매개변수를 추가하여 NGINX Plus가 중지되었다가 다시 시작될 때에도 상태가 유지되도록 했습니다. 키-값 저장소는 상태 파일을 지정하지 않고도 정기적인 구성 재로드에도 유지됩니다. (1~10번째 줄은 표시되지 않았지만 denylist_keyval.conf 와 동일합니다.)

14번째 라인은 20permin 이라는 공유 메모리 영역을 생성하고 각 클라이언트 IP 주소에 대해 분당 최대 20개의 요청의 요청 속도를 지정합니다. 이러한 속도 제한은 fail2ban에 의해 IP 주소가 거부 목록에 추가될 때 적용됩니다(30번째 줄).

서버 블록은 기본 포트(80)에서 수신하는 웹 서버를 정의하며, 모든 요청은 위치 / 블록(20번째 줄)과 일치합니다. 루트 지시문(18번째 줄)은 웹 콘텐츠가 있는 위치를 지정합니다. 21~22행은 이 사이트에 HTTP 기본 인증이 필요하고 권한이 있는 사용자의 비밀번호 데이터베이스가 users.htpasswd 파일에 있다는 것을 지정합니다. auth_basic_user_file 지시어에 대한 설명서에서는 해당 파일의 형식을 설명합니다. 테스트 목적으로 이 구성은 암호화되지 않았으며 HTTP 기본 인증을 사용하는 모든 프로덕션 웹 사이트는 전송 보안을 위해 SSL/TLS를 사용해야 합니다.

클라이언트 IP 주소가 거부 목록에 추가된 경우(24~26행) HTTP를 반환하는 대신403 오류 코드가 발생하면 요청을 /banned.html 로 다시 작성하여 해당 URI(29번째 줄)와 정확히 일치하는 위치 블록에서 처리합니다. 이는 과도한 로그인 실패로 인해 사용자의 IP 주소가 차단되었다는 것을 설명하는 정적 웹 페이지를 반환합니다. 또한 악의적인 클라이언트가 불필요하게 시스템 리소스를 소비하지 못하도록 제한적인 속도 제한(30번째 줄)을 적용합니다.

IP 주소가 거부 목록에 추가되었을 때 사용자에게 표시되는 페이지

(이 샘플 웹 페이지의 HTML은 이 게시물의 GitHub 저장소에서 banned.html 로 제공됩니다.)

다음과 같이 연속적으로 실패한 로그인 시도와 해당 fail2ban 활동을 시뮬레이션할 수 있습니다.

$ curl -i http://admin:password@www.example.com/HTTP/1.1 401 허가되지 않음 ...
$ curl -i http://admin:admin@www.example.com/
HTTP/1.1 401 허가되지 않음 ...
$ curl -i http://admin:pass1234@www.example.com/
HTTP/1.1 401 허가되지 않음 ...
$ curl -i http://admin:letmein@www.example.com/
HTTP/1.1 401 허가되지 않음 ...
$ curl -i http://admin:fred@www.example.com/
HTTP/1.1 401 허가되지 않음 ...
$ 컬 http://admin:P@ssw0rd@www.example.com/
<!DOCTYPE html>
<html>
<head>
<title>금지됨</제목>
...
$ tail -f /var/log/fail2ban.log
2017-09-15 13:55:18,903 fail2ban.filter [28498]: INFO [nginx-http-auth] 172.16.52.1 2017-09-15 13:55:28,836 fail2ban.filter [28498]을 찾았습니다. INFO [nginx-http-auth] 172.16.52.1 2017-09-15 13:57:49,228 fail2ban.filter [28498]을 찾았습니다. INFO [nginx-http-auth] 172.16.52.1 2017-09-15 13:57:50,286 fail2ban.filter [28498]을 찾았습니다. INFO [nginx-http-auth] 172.16.52.1 2017-09-15 13:57:52,015 fail2ban.filter [28498]을 찾았습니다. INFO [nginx-http-auth] 172.16.52.1 2017-09-15 13:57:52,088 fail2ban.actions [28498]을 찾았습니다. 공지 [nginx-http-auth] Ban 172.16.52.1 2017-09-15 13:59:52,379 fail2ban.actions [28498]: 공지 [nginx-http-auth] 172.16.52.1 차단 해제

이 동적 IP 차단 목록 솔루션은 이제 추가적인 구성 변경 없이 실행할 수 있습니다. Fail2ban은 NGINX 로그 파일을 감시하고 API를 사용하여 NGINX Plus 키-값 저장소에 금지된 IP 주소를 추가합니다. 120초( jail.local 에서 구성된 bantime ) 후에 문제가 있는 IP 주소가 거부 목록에서 제거되고, 다시 NGINX Plus API를 사용하여 로그인 시도가 해당 주소에서 다시 허용됩니다.

NGINX Plus API와 키-값 저장소는 이러한 유형의 통합을 가능하게 합니다. 이제 NGINX Plus 구성 파일을 구축하거나 다시 로드하지 않고도 고급 구성 솔루션을 구현할 수 있습니다. 여러분이 이러한 기능을 사용하여 어떻게 동적 구성 솔루션을 직접 만들고 있는지 알고 싶습니다. 아래에 댓글을 남겨주세요.

NGINX Plus를 사용해보려면 오늘 무료 30일 체험판을 시작하거나 저희에게 연락해 사용 사례에 대해 논의해 보세요.


"이 블로그 게시물에는 더 이상 사용할 수 없거나 더 이상 지원되지 않는 제품이 참조될 수 있습니다. 사용 가능한 F5 NGINX 제품과 솔루션에 대한 최신 정보를 보려면 NGINX 제품군을 살펴보세요. NGINX는 이제 F5의 일부가 되었습니다. 이전의 모든 NGINX.com 링크는 F5.com의 유사한 NGINX 콘텐츠로 리디렉션됩니다."