fail2ban 사용하여 NGINX 동적 IP 차단

이 블로그 포스트에서는 웹 보안 스택의 다른 레이어로서 fail2ban 의 사용에 대해 논의합니다. Fail2ban은 침입 탐지 시스템(IDS)으로 로그 파일을 계속 모니터링하여 수상한 활동을 감지하고, 사전에 구성된 하나 이상의 작업을 수행합니다.
일반적으로 fail2ban은 로그인 실패 시도를 모니터링하고, 그러한 시도를 한 Dynamic IP 주소를 일정 기간 동안 차단(ban)합니다.
이는 Brute Force 패스워드 공격에 대한 간단하면서도 효과적인 방어 수단입니다.

당신의 웹사이트가 지속적인 위협에 노출되고 있다는 것을 깨닫지 못할 수도 있습니다.
만약 워드프레스를 사용하고 있다면, 봇들이 스팸을 시도하고 있을 것입니다. 로그인 페이지가 있다면, 무차별적으로 비밀번호 공격이 시도됩니다. 검색 엔진 크롤러를 원하지 않는 방문자로 간주할 수도 있습니다.

의심스러운, 악성 및 해로운 활동으로부터 사이트를 보호하는 것은 쉬운 일이 아닙니다.
NGINX STORE에서 제공하는 NGINX App Protect 및 NGINX Plus 인증 모듈과 같은 웹 애플리케이션 방화벽은 효과적인 도구로, 보안 스택의 일부로 고려해야 합니다.
대부분의 환경에서 과도한 보안은 필요없으며 다계층 접근 방식이 언제나 가장 효과적입니다.

목록

1. fail2ban이란?

2. Dynamic IP 거부 목록에 Key-Value Store 사용
3. fail2ban을 사용하여 Dynamic IP 거부 목록 관리
 3-1. Dynamic IP 주소 거부 목록 구현
4. Dynamic IP 구성, 결론

1. fail2ban 이란?

fail2ban은 시스템 로그 파일을 모니터링하여 오류나 실패한 시도를 찾은다음 그런 시도를 하는 IP 주소를 차단하는 오픈 소스 소프트웨어입니다. 주로 서버에서 보안 위협으로부터 보호하는데 사용됩니다. fail2ban은 시스템 로그 파일을 지속적으로 검사합니다. 로그에 특정 오류나 실패한 시도 패턴이 발견되면, 해당 IP 주소를 자동으로 방화벽 규칙에 추가하여 차단합니다. 이렇게 함으로써 공격자가 시스템에 반복적으로 액세스하는 것을 방지할 수 있습니다.

fail2ban은 서버 보안을 강화하는 데 매우 유용한 도구입니다. 이 도구를 통해 서버에 접근을 시도하는 공격자를 자동으로 차단하고, 시스템 자원을 보호할 수 있습니다. 하지만 fail2ban만으로 충분한 보안을 제공하지는 못합니다. 보안은 많은 층으로 이루어진 체계이기 때문에, fail2ban은 그 체계의 일부일 뿐입니다. 다양한 보안 도구와 전략을 함께 사용하는 것이 중요합니다.

2. Dynamic IP 거부 목록에 Key-Value Store 사용

NGINX Plus의 Key-Value Store는 세 가지 기본적인 특징을 가진 Native In-Memory Store 입니다.

  • Key-Value는 JSON 객체로 표현됩니다.
  • Key-Value는 API를 통해 완전히 관리됩니다.
  • 값은 일반적인 구성 변수로 NGINX Plus에서 사용할 수 있습니다.

Key-Value Store는 keyval_zone 지시문으로 지정된 공유 메모리 영역을 생성하여 정의됩니다. 이 Key-Value Store는 HTTP POST 메서드를 사용하여 API에 JSON 개체를 제출하여 초기 값 집합으로 채울 수 있습니다. 그런 다음 keyval 지시문은 기존 변수($remote_addr 예제에서)가 조회 키로 사용되며 해당 값과 연관된 새 변수($num_failures)의 이름이 정의됩니다. 이 저장소를 사용하여 NGINX Plus에서 정규 구성 변수처럼 값을 사용할 수 있습니다.

NGINX Plus Key-Value Store 구성 및 관리

API는 location 블록을 NGINX Plus API Endpoint로 지정하여 활성화 합니다.

server {
    listen 1111;
    allow  127.0.0.1; # Only allow access from localhost,
    deny   all;       # and prevent remote access.

    location /api {
        api write=on; # The NGINX Plus API endpoint in read/write mode
    }
}

여기에 Key-Value를 추가하기 전에, 거부(deny) 목록 저장소의 콘텐츠를 요청하면 빈 JSON 객체를 반환합니다.

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

이제 HTTP POST 방법을 사용하여 초기 Key-Value를 제출하여 Key-Value Store에 초기 값을 추가할 수 있습니다.
(curl 명령의 -d 인자 사용) 빈 Key-Value Store에 여러 개의 Key-Value를 POST하고, 그 이후로는 개별적으로 POST 할 수 있습니다.

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

Key-Value는 null값으로 PATCH하면 제거됩니다.

$ curl -iX PATCH -d '{"10.0.0.1":null}' http://localhost:1111/api/6/http/keyvals/denylist
HTTP/1.1 204 No Content
...

모든 Key-Value는 DELETE 메서드를 보내서 Key-Value Store에서 제거할 수 있습니다.

$ curl -iX DELETE http://localhost:1111/api/6/http/keyvals/denylist
HTTP/1.1 204 No Content
...

이제 Dynamic IP 차단 목록의 간단한 구현을 완성할 수 있습니다.

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;
        }
    }
}

이 구성 스니펫은 포트 80을 웹 서버로 구성합니다. 18번째 줄은 $remote_addr 변수에 해당하는 키와 일치하는 거부(deny) 목록 공유 메모리 영역 내 Key-Value의 값을 $num_failures 변수에 할당합니다. 이 $num_failures를 계산하는 과정은 다음과 같이 순차적으로 나타낼 수 있습니다.

  1. $remote_addr 값을 거부(deny) 목록 Key-Value Store에 전달합니다.
  2. $remote_addr이 key로 정확히 일치하면 해당 Key-Value의 값을 가져옵니다.
  3. 이 값을 $num_failures로 반환합니다.

따라서, 클라이언트 IP 주소가 Key-Value Stroe에 POST된 경우 모든 요청은 HTTP 403 Forbidden 오류로 처리됩니다.
map 블록을 사용하는 것과 달리 이 접근 방식의 장점은 Dynamic IP 거부(deny) 목록을 외부 시스템에서 제어할 수 있다는 것입니다.

3. fail2ban 을 사용하여 Dynamic IP 거부 목록 관리

위에서 언급한 대로, fail2ban은 로그 파일에서 의심스러운 또는 악성 활동을 감지한 다음 시스템을 보호하기 위해 조치를 취하는 데 일반적으로 사용됩니다.
기본 동작은 iptables을 구성하여 기록된 IP 주소에서 발생하는 모든 패킷을 삭제하는 것입니다.
이 접근 방식은 악성 행위를 차단하는 데 효과적이지만, 비밀번호를 잊어버린 사용자들에게는 매우 나쁜 사용자 경험을 제공합니다. 이러한 사용자들에게는 웹 사이트가 단순히 사용 불가능한 것으로 보입니다.

다음 구성은 fail2ban 작업으로서 동작합니다. 이 구성은 문제가 되는 IP 주소를 NGINX Plus 거부(deny) 목록 Key-Value Store에 제출합니다.
그러면 NGINX Plus는 더 유용한 오류 페이지를 표시하고 동시에 해당 IP 주소에 대한 요청 속도 제한(Rate Limit)을 적용하여 만약 이러한 작업이 공격의 일부인 경우, 공격이 효과적으로 제거됩니다.

우리는 NGINX Plus와 함께 기본 설치된 fail2ban을 가정하며, 모든 구성이 /etc/fail2ban 디렉토리 아래에 있다고 가정합니다.

다음 fail2ban 작업은 단순한 예제와 마찬가지로 NGINX Plus API를 사용하여 denylist Key-Value Store 내에서 “금지(banned)”된 IP 주소를 추가하고 제거합니다. 우리는 nginx-plus-denylist.conf 파일을 /etc/fail2ban/action.d 디렉토리에 놓습니다.

[Definition]
actionban = curl -s -o /dev/null -d '{"<ip>":"<failures>"}' http://localhost:1111/api/6/http/keyvals/denylist
actionunban = curl -s -o /dev/null -X PATCH -d '{"<ip>":null}' http://localhost:1111/api/6/http/keyvals/denylist

다음 fail2ban jail은 NGINX의 HTTP Basic Authentication 모듈을 사용하여 로그인 실패를 감지하고 nginx-plus-denylist.conf에 정의된 동작을 적용합니다. 많은 다른 내장 필터가 있으며, NGINX Access Log에서 원하지 않는 활동을 감지하는 것으로 쉽게 생성할 수 있습니다.

[DEFAULT]
bantime   = 120
banaction = nginx-plus-denylist
 
[nginx-http-auth]
enabled = true

다음 NGINX Plus 구성 Snippet은 기본 “Welcome to NGINX” 페이지에 HTTP 기본 인증을 적용합니다. password_site.conf 파일을 /etc/nginx/conf.d 디렉토리에 놓습니다.

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) {
            rewrite ^.* /banned.html;
        }
    }

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

3-1. Dynamic IP 주소 거부 목록 구현

11번째 줄에서는 denylist_keyval.conf와 동일한 keyval_zone을 정의하지만, 상태 매개변수가 추가되어 있습니다. 이 상태 매개변수는 Key-Value Store의 상태가 저장되는 파일을 지정합니다. 따라서 NGINX Plus가 중지되고 다시 시작되어도 Key-Value Store의 상태가 유지됩니다. 정기적인 구성 reload에서는 상태 파일을 지정하지 않아도 Key-Value Store가 유지됩니다. (1-10번째 줄은 표시되지 않았지만, denylist_keyval.conf와 동일합니다.)

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

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

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

fail2ban nginx example
Dynamic IP 주소가 거부 목록에 있을 때 사용자에게 표시되는 페이지

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

$ curl -i http://admin:password@www.example.com/
HTTP/1.1 401 Unauthorized ...
$ curl -i http://admin:admin@www.example.com/
HTTP/1.1 401 Unauthorized ...
$ curl -i http://admin:pass1234@www.example.com/
HTTP/1.1 401 Unauthorized ...
$ curl -i http://admin:letmein@www.example.com/
HTTP/1.1 401 Unauthorized ...
$ curl -i http://admin:fred@www.example.com/
HTTP/1.1 401 Unauthorized ...
$ curl  http://admin:P@ssw0rd@www.example.com/
<!DOCTYPE html>
<html>
<head>
<title>Banned</title>
...
$ tail -f /var/log/fail2ban.log
2017-09-15 13:55:18,903 fail2ban.filter   [28498]: INFO    [nginx-http-auth] Found 172.16.52.1
2017-09-15 13:55:28,836 fail2ban.filter   [28498]: INFO    [nginx-http-auth] Found 172.16.52.1
2017-09-15 13:57:49,228 fail2ban.filter   [28498]: INFO    [nginx-http-auth] Found 172.16.52.1
2017-09-15 13:57:50,286 fail2ban.filter   [28498]: INFO    [nginx-http-auth] Found 172.16.52.1
2017-09-15 13:57:52,015 fail2ban.filter   [28498]: INFO    [nginx-http-auth] Found 172.16.52.1
2017-09-15 13:57:52,088 fail2ban.actions  [28498]: NOTICE  [nginx-http-auth] Ban 172.16.52.1
2017-09-15 13:59:52,379 fail2ban.actions  [28498]: NOTICE  [nginx-http-auth] Unban 172.16.52.1

4. 결론

이 Dynamic IP 차단 솔루션은 추가 구성 변경 없이 실행될 수 있습니다. fail2ban은 NGINX 로그 파일을 감시하고 API를 사용하여 NGINX Plus Key-Value Store 에 차단된 IP 주소를 추가합니다. 지정된 시간 (jail.local에 구성된 bantime인 120초)이 지나면, 해당 IP 주소는 다시 NGINX Plus API를 사용하여 거부 목록(deny list)에서 제거되며 해당 주소에서 로그인 시도가 다시 허용됩니다.

NGINX Plus API 및 Key-Value Store는 이러한 종류의 통합을 가능하게 합니다. 구성 파일 작성 및 reload 없이 고급 구성 솔루션을 구현할 수 있습니다. 사용자들이 이러한 기능을 사용하여 자체 Dynamic 구성 솔루션을 만드는 방법에 대한 이야기를 듣고 싶습니다. 아래에 댓글을 남겨주세요.

NGINX Plus를 사용해 보려면 무료 30일 체험을 시작하거나 사용 사례에 대해 문의하십시오.

NGINX STORE 뉴스레터 및 최신 소식 구독하기

* indicates required