
미처 인식하지 못할 수도 있지만, 여러분의 웹사이트는 끊임없는 위협에 노출되어 있습니다. WordPress를 사용 중이라면 봇이 스팸을 보내려 할 것이고, 로그인 페이지가 있다면 무차별 대입 방식의 비밀번호 공격을 받을 수 있습니다. 검색 엔진 크롤러 역시 원치 않는 방문자로 간주할 수 있습니다.
원치 않거나 의심스럽고 악의적인 활동으로부터 사이트를 보호하는 일은 결코 쉽지 않습니다. NGINX App Protect와 파트너사가 제공하는 NGINX Plus 인증 모듈 같은 웹 애플리케이션 방화벽은 효과적인 도구이므로 보안 체계의 일부로 반드시 고려해야 합니다. 대부분의 환경에서는 보안이 지나쳐서 나쁠 것이 없으며, 다계층 접근 방식이 언제나 가장 효과적입니다.
이 블로그 게시물에서는 웹 보안 스택의 또 다른 계층으로 fail2ban을 사용하는 방법을 살펴보겠습니다. Fail2ban은 의심스러운 활동이 있는지 로그 파일을 지속적으로 모니터링한 후, 미리 구성된 하나 이상의 조치를 수행하는 침입 탐지 시스템(IDS)입니다. 일반적으로 fail2ban은 로그인 실패 시도를 모니터링한 다음, 해당 IP 주소를 일정 기간 차단(금지)합니다. 이는 무차별 대입 암호 공격을 방어하는 간단하면서도 효과적인 방법입니다.
In NGINX Plus R13에서는 네이티브 키-값 저장소와 새로운 NGINX Plus API를 도입했습니다. 이를 통해 외부 시스템에서 NGINX Plus의 구성과 동작을 제어할 수 있는 다양한 동적 구성 솔루션을 구현할 수 있습니다. 또한 이 블로그 게시물에서는 fail2ban을 사용하여 인증에 여러 번 실패한 IP 주소의 요청을 무시하도록 NGINX Plus를 자동으로 재구성하는 방법을 살펴보겠습니다.
편집자 주 – NGINX Plus R16 이상에서는 키-값 저장소를 클러스터의 모든 NGINX Plus 인스턴스 간에 동기화할 수 있습니다. (클러스터 내 상태 공유는 다른 NGINX Plus 기능에서도 사용할 수 있습니다.) 자세한 내용은 블로그 및 NGINX Plus 관리 가이드를 참조하세요.
IP 차단 목록에 키-값 저장소 사용하기
NGINX Plus 키-값 저장소는 다음과 같은 세 가지 주요 특성을 갖춘 네이티브 인메모리 저장소입니다:
- 키-값 쌍은 JSON 객체로 표현됩니다
- 키-값 쌍은 전적으로 API를 통해 관리됩니다
- 해당 값은 NGINX Plus에서 일반 구성 변수로 사용할 수 있습니다
키-값 저장소는 keyval_zone 지시문으로 이름이 지정된 공유 메모리 영역을 생성하여 정의합니다. 그런 다음 HTTP POST 메서드로 JSON 객체를 API에 제출하여 이 키-값 저장소를 초기 값 집합으로 채울 수 있습니다. keyval 지시문은 조회 키로 사용할 기존 변수(예제에서는 $remote_addr)와 해당 키에 대응하는 값으로 평가될 새 변수의 이름($num_failures)을 정의합니다.

API를 활성화하려면 location 블록을 NGINX Plus API 엔드포인트로 지정합니다.
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
}
}
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키-값 쌍을 추가하기 전에는 denylist 저장소의 콘텐츠를 요청하면 빈 JSON 객체가 반환됩니다.
$ curl 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/denylistHTTP/1.1 201 Created
...키-값 쌍을 삭제하려면 키의 값을 PATCH 요청으로 null로 설정합니다.
$ curl -iX PATCH -d '{"10.0.0.1":null}' http://localhost:1111/api/6/http/keyvals/denylistHTTP/1.1 204 No Content
...키-값 저장소에 있는 모든 키-값 쌍은 DELETE 메서드를 전송하여 삭제할 수 있습니다.
$ curl -iX DELETE http://localhost:1111/api/6/http/keyvals/denylistHTTP/1.1 204 No Content
...이제 간단한 IP 차단 목록 기능을 구성할 수 있습니다.
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
}
}
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를 평가하는 이 과정은 다음과 같이 순서대로 나타내면 더 명확합니다:
- Pass the value of
$remote_addr값을 denylist 키-값 저장소에 전달합니다 - If
$remote_addr가 키와 정확히 일치하면 해당 키-값 쌍의 값을 가져옵니다. - 값을 다음으로 반환합니다:
$num_failures
따라서 클라이언트 IP 주소가 키-값 저장소에 POST되었다면 모든 요청에 대해 HTTP 403 Forbidden 오류가 발생합니다. 동일한 목적으로 map 블록을 사용하는 방식과 달리, 이 접근 방식은 외부 시스템에서 IP 차단 목록을 제어할 수 있다는 장점이 있습니다.
fail2ban을 사용하여 IP 차단 목록을 동적으로 관리하기
앞서 언급했듯이 fail2ban은 일반적으로 로그 파일에서 의심스럽거나 악의적인 활동을 감지한 다음, 시스템을 보호하기 위한 조치를 취하는 데 사용됩니다. 기본 동작은 iptables를 구성하여 로그에 기록된 IP 주소에서 들어오는 모든 패킷을 차단하는 것입니다. 이 방식은 악의적인 사용자를 차단하는 데 효과적이지만, 정상 사용자, 특히 비밀번호를 잊어버린 사용자에게는 매우 좋지 않은 사용자 경험을 제공합니다. 이러한 사용자에게는 웹사이트가 단순히 접속 불가능한 것처럼 보입니다.
다음 구성은 문제가 되는 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 디렉터리에 저장합니다.
[Definition]
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 jail은 NGINX의 HTTP 기본 인증 모듈을 사용한 로그인 실패 시도를 감지하는 기본 제공 필터를 활성화하고, nginx-plus-denylist.conf에 정의된 작업을 적용합니다. 이 밖에도 다양한 기본 제공 필터를 사용할 수 있으며, NGINX 액세스 로그에서 원치 않는 활동을 감지하는 필터도 쉽게 만들 수 있습니다.
[DEFAULT]
bantime = 120
banaction = nginx-plus-denylist
[nginx-http-auth]
enabled = true다음 NGINX Plus 구성 코드 조각은 기본 “Welcome to NGINX” 페이지에 HTTP 기본 인증을 적용합니다. password_site.conf 파일은 /etc/nginx/conf.d 디렉터리에 배치합니다.
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
}
}
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;
}
}
vim: syntax=nginx11행에서는 위의 keyval_zone을 denylist_keyval.conf에서와 같이 정의하되, state 매개변수를 추가합니다. 이 매개변수는 키-값 저장소의 상태를 저장할 파일을 지정하므로 NGINX Plus가 중지되었다가 다시 시작되어도 상태가 유지됩니다. 상태 파일을 지정하지 않아도 일반적인 구성 다시 로드 시에는 키-값 저장소가 유지됩니다. (1~10행은 표시되지 않았지만 denylist_keyval.conf과 동일합니다.)
14행에서는 20permin이라는 공유 메모리 영역을 생성하고 각 클라이언트 IP 주소의 최대 요청 속도를 분당 20개로 지정합니다. 이후 fail2ban이 차단 목록에 IP 주소를 추가하면 이 요청 속도 제한이 적용됩니다(30행).
The server 블록은 기본 포트(80)에서 수신 대기하는 웹 서버를 정의하며, 모든 요청은 location / 블록(20행)과 일치합니다. root 지시어(18행)는 웹 콘텐츠가 위치한 경로를 지정합니다. 21~22행에서는 이 사이트에 HTTP 기본 인증이 필요하며, 인증된 사용자의 비밀번호 데이터베이스가 users.htpasswd 파일에 있음을 지정합니다. auth_basic_user_file 지시어의 문서에는 해당 파일의 형식이 설명되어 있습니다. 이 구성은 테스트 목적으로 암호화되지 않았으므로, HTTP 기본 인증을 사용하는 모든 프로덕션 웹 사이트에서는 전송 보안을 위해 SSL/TLS를 사용해야 합니다.
클라이언트 IP 주소가 차단 목록에 추가된 경우(24~26행), HTTP 403 오류 코드를 반환하는 대신 요청을 /banned.html(으)로 다시 작성합니다. 그러면 해당 요청은 이 URI와 정확히 일치하는 location 블록에서 처리됩니다(29행). 이 블록은 과도한 로그인 실패로 인해 사용자의 IP 주소가 차단되었음을 설명하는 정적 웹 페이지를 반환합니다. 또한 엄격한 요청 속도 제한(30행)을 적용하여 악의적인 클라이언트가 불필요하게 시스템 리소스를 소모하지 못하도록 합니다.

(이 샘플 웹 페이지의 HTML은 이 게시물의 GitHub 저장소에서 banned.html 파일로 제공됩니다.)
다음과 같이 연속적인 로그인 실패 시도와 그에 따른 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이제 이 동적 IP 차단 목록 솔루션은 추가로 구성을 변경하지 않아도 실행할 수 있습니다. Fail2ban은 NGINX 로그 파일을 모니터링하고 API를 사용하여 차단된 IP 주소를 NGINX Plus 키-값 저장소에 추가합니다. 120초가 지나면(bantime은 jail.local에서 설정됨) 문제가 된 IP 주소가 NGINX Plus API를 통해 차단 목록에서 제거되고, 해당 주소에서의 로그인 시도가 다시 허용됩니다.
NGINX Plus API와 키-값 저장소를 사용하면 이러한 유형의 통합이 가능합니다. 이제 NGINX Plus 구성 파일을 작성하거나 다시 로드하지 않고도 고급 구성 솔루션을 구현할 수 있습니다. 이러한 기능을 활용해 자체 동적 구성 솔루션을 어떻게 만들고 계신지 알려 주세요. 아래에 댓글을 남겨 주세요.
NGINX Plus를 사용해 보려면 지금 30일 무료 체험을 시작하거나 사용 사례에 대해 논의하려면 문의해 주세요.
작성자 소개

관련 블로그 게시물
시청하기: KubeCon North America 2023에서의 NGINX Gateway Fabric
이 영상에서는 KubeCon의 NGINX 부스를 찾아가, 저희가 공개한 Kubernetes Gateway API 구현을 살펴봅니다.
F5 NGINX로 대규모 WAF 플릿을 확인하고 관리하는 2가지 방법
앱 보안을 강화하는 두 가지 방법을 소개합니다. NGINX Management Suite를 통해 WAF 운영에 대한 가시성을 높이고, NGINX App Protect WAF를 활용해 코드형 보안을 구현하는 것입니다.
Ingress 컨트롤러 선택 가이드, 4부: NGINX Ingress Controller 옵션
개발 주체, 개발 철학, 프로덕션 환경 준비도, 보안 및 지원을 기준으로 어떤 NGINX Ingress 컨트롤러가 가장 적합한지 알아보세요.
진화하는 공격 환경에 NGINX App Protect Denial of Service가 적응하는 방법
NGINX App Protect Denial of Service(DoS)가 계층 7 DoS 공격을 방어하기 위해 완화 방법을 실시간으로 조정하는 방식을 설명합니다. 정상적인 사용자 행동과 서버 상태에 대한 모델을 생성하여 공격을 나타내는 이상 징후를 감지하고 맞춤형 완화 시그니처를 적용할 수 있습니다.
NGINX Instance Manager를 활용한 대규모 구성 업데이트 및 인증서 관리
NGINX Instance Manager 버전 1.0에는 두 가지 새로운 기능이 도입되었습니다. NGINX 인스턴스에 태그와 사용자 역할을 지정하면 전체 그룹에 구성을 한 번에 적용할 수 있습니다. 인증서 검사는 만료 예정인 인증서를 감지하여 서비스 중단을 방지할 수 있도록 인증서를 교체할 수 있게 해줍니다.
Netflix가 자사 CDN의 핵심으로 NGINX를 선택한 이유
Netflix가 글로벌 콘텐츠 전송 네트워크인 Open Connect의 스트리밍 미디어 기술로 NGINX를 선택한 이유를 NGINX 개발자 Gleb Smirnoff에게서 알아보세요.