Fail2Ban과 ipset으로 워드프레스 웹 방화벽 구축하기

클라우드의 VM 또는 홈서버에 직접 서버를 구축하고 워드프레스와 같은 블로그 또는 웹사이트를 운영하는 사람들은 모든 문제를 직접 해결해야 한다. 검색엔진의 봇이 아닌 불법적인 웹 크롤러와 봇이 무단으로 저작물을 가져가는 것을 막아야 하고 광고 댓글이 달리는 것도 차단해야 한다. 게다가 로그인 페이지를 찾아내 무단으로 접속을 시도하는 것도 막아야 하고 웹서버의 해킹 시도도 차단해야 하며 심지어 운영체제에 직접 로그인하려는 원격접속(SSH) 시도도 방어해야 한다.

필자 또한 많은 고민을 하며 이런 저런 방법으로 공격을 막아내고 있다.

스팸댓글 차단하기

먼저 스팸댓글, 특히나 영어로 달리는 광고성 댓글을 차단하기 위해 직접 댓글 스팸 필터 플러그인을 만들어 적용하였다. 예전엔 워드프레스의 코드를 직접 수정했지만 업그레이드 할 때 마다 코드를 다시 수정해주는 불편함이 있어서 AI의 도움을 받아 직접 플러그인을 만들어 적용했다.

직접 만들어 적용한 워드페르스 스팸 댓글 차단 플러그인
직접 만들어 적용한 워드페르스 스팸 댓글 차단 플러그인

댓글 작성 시 입력하는 이메일 주소의 도메인과 웹사이트 주소의 도메인으로 차단할 수 있는 기능도 작성했지만 현재는 한글 입력이 없을 때에만 차단하도록 사용하고 있다.

워드프레스 wp-admin, wp-login 접근 차단하기

광고성 댓글은 해킹 시도는 아니다. 실제 가장 많은 해킹시도는 워드프레스의 관리자 페이지의 취약점 공격과 워드프레스 로그인 페이지에 무작위 로그인을 시도하는 공격이다. 이 공격은 Apache 웹서버의 설정(httpd.conf 또는 sites-enabled 디렉토리의 도메인 주소로 생성한 .conf 파일)에서 특정 IP에서만 접속을 허용하고 나머지 IP에서는 접근을 차단하도록 설정하여 막고 있다.

<Directory /var/www/html>
    Options Indexes FollowSymLinks
    AllowOverride None

    Require all granted

    # 재작성 규칙 (Rewrite Rules) 및 봇 차단
    # ==========================================
    <IfModule mod_rewrite.c>
        RewriteEngine On

        # 악성 User-Agent 차단 (404 Not Found 응답)
        RewriteCond %{HTTP_USER_AGENT} ^-?$ [OR]
        RewriteCond %{HTTP_USER_AGENT} ^.*(Bytespider|GrapeshotCrawler|Amazonbot|proximic).*$ [NC,OR]
        RewriteCond %{HTTP_USER_AGENT} ^.*(SemrushBot|DataForSeoBot|AhrefsBot|ias_crawler|peer39_crawler).*$ [NC,OR]
        RewriteCond %{HTTP_USER_AGENT} ^.*(TTD-Content|CriteoBot|bidswitchbot|ZoominfoBot|YaK|Buck).*$ [NC,OR]
        RewriteCond %{HTTP_USER_AGENT} ^.*(Y!J-WSC|coccocbot|DotBot|cubebot|ImagesiftBot|Verity|PetalBot).*$ [NC,OR]
        RewriteCond %{HTTP_USER_AGENT} ^.*(ias-or|ias-va|ias-ie|ias-sg|ias-ie|ias-au|ias-jp|proximic|webz.io).*$ [NC,OR]
        RewriteCond %{HTTP_USER_AGENT} ^.*(AwarioBot|SeekportBot|BrightEdge|GenomeCrawlerd|ips-agent).*$ [NC,OR]
        RewriteCond %{HTTP_USER_AGENT} ^.*(HubSpot|wpbot|node-fetch|Go-http-client|Sogou|Pixalate).*$ [NC,OR]
        RewriteCond %{HTTP_USER_AGENT} ^.*(Mail.RU_Bot|aiohttp|VelenPublicWebCrawler).*$ [NC,OR]
        RewriteCond %{HTTP_USER_AGENT} ^.*(project_patchwatch|nhcapacketmng|serpstatbot|Timpibot|Orbbot).*$ [NC,OR]
        RewriteCond %{HTTP_USER_AGENT} ^.*(FancyPlayer|Firebolt|semantic-vision|SirdataBot|Leikibot).*$ [NC,OR]
        RewriteCond %{HTTP_USER_AGENT} ^.*(netEstate|TheSafeInternetSearch|Scrapy|SERankingBacklinksBot).*$ [NC,OR]
        RewriteCond %{HTTP_USER_AGENT} ^.*(CensysInspect|HeadlessChrome|Barkrowler|Odin|BLEXBot|CCBot|StartmeBot).*$ [NC]
        RewriteRule .* - [R=404,L]

        # PHP 인증을 위한 환경변수 설정
        RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

        # index.php 자체 요청은 통과
        RewriteRule ^index\.php$ - [L]

        # 파일 또는 디렉토리가 존재하지 않을 경우 index.php로 리다이렉션 (WordPress 라우팅)
        RewriteCond %{REQUEST_FILENAME} !-f
        RewriteCond %{REQUEST_FILENAME} !-d
        RewriteRule . /index.php [L]
    </IfModule>

    # ==========================================
    # 3. 특정 파일 접근 원천 차단. 접근 시도 시 403 Forbbiden 발생
    # ==========================================
<FilesMatch "(xmlrpc\.php|\.htaccess)">
        Require all denied
    </FilesMatch>
</Directory>

# ----------------------------------------------------------------------
# [보안] 특정 관리자 IP(집과 클라우드의 프록시서버만 허용하는 마스터 설정
# 허용 IP: 159.123.123.123, 125.123.123.123 
# ----------------------------------------------------------------------

# 1. 로그인 페이지 접근 제한 (입구 컷) 해당되는 IP가 아니면 403 Forbidden 에러 발생.
# 산호세 proxy와 집의 IP
<FilesMatch "wp-login.php">
        Require ip 159.123.123.123 125.123.123.123
</FilesMatch>

# 2. 관리자 디렉토리 접근 제한
<Directory "/var/www/html/wp-admin">
        # 기본적으로 내 IP만 허용
        Require ip 159.123.123.123 125.123.123.123

        # [수정] admin-ajax.php는 외부(플러그인 기능 등)에서도 써야 하므로 예외 허용
        <Files "admin-ajax.php">
                Require all granted
        </Files>
</Directory>

사실 이 설정만 있어도 워드프레스 관리자 페이지에서 취약점이 발견된 취약점을 공격하더라도 웬만한 공격은 성공하기 어렵다. 워드프레스의 관리자 페이지에 접근 자체가 지정된 IP 이외에는 차단되기 때문이다.

하지만 이렇게 설정을 해둬도 눈에 가시처럼 아파치 웹서버의 access.log에 엄청난 403, 404 에러 로그가 기록된다. 아파치 웹서버 자체에는 접근이 이루어지기 때문이다. 그래서 이러한 공격 접근을 iptable + ipset + fail2ban의 조합으로 자동으로 차단하고 싶어졌다.

ipset과 fail2ban 설치하기

fail2ban은 로그파일을 지정해 모니터링하다 미리 정의된 패턴의 로그가 발생할 경우 iptable이나 ipset에 IP 차단정책을 자동으로 추가하고 관리하는 매우 고급 수준의 보안 패키지다. fail2ban을 사용하면 무작위 id와 비밀번호로 ssh 접속을 시도하는 IP나 웹서버에서 취약한 파일을 찾기위해 무작위로 접근하여 404 Not Found 에러를 연속적으로 발생시키는 IP를 자동으로 iptable로 차단할 수 있다. 이 때 iptable의 정책 검색 속도 향상을 위해 ipset과 연동하는 것이 효과적이다. ipset은 앞의 포스트에 개념과 사용법을 포스팅해두었다. (보러기기)

ipset과 fail2ban을 다음 명령어로 설치한다.

# 시스템 패키지 업데이트
sudo apt update

# fail2ban과 ipset 설치
sudo apt install fail2ban ipset -y

ipset이 이미 설치되어 있고 정책을 사용하고 있다면 fail2ban만 설치하면 된다.

fail2ban에 커스텀 필터(filter) 만들기

fail2ban은 로그에서 탐지할 패턴을 먼저 만들어야 하는데 이 패턴을 “필터”라고 한다. Ubuntu 리눅스에 fail2ban을 설치하면 /etc/fail2ban 이라는 디렉토리가 생성되고 설정파일들이 위치하게 된다.

이 포스트에서는 3개의 필터를 사용한다. 첫 번재는 무작위 ssh 접근을 잡아낼 필터이고 두 번째는 워드프레스 블로그에 방문하여 403, 404 에러를 남발하는 IP를 잡아내기 위한 필터이며 세 번째는 워드프레스의 사용자 로그인 URL인 wp-login에 접근을 시도하다 403 에러를 발생시키는 IP다.

ssh 필터는 fail2ban에 기본적으로 생성되어 있다. (/etc/fail2ban/filter.d/sshd.conf) 이 필터를 그대로 사용하면 된다.

두 번째 필터는 다음과 같이 필터를 정의하고 /etc/fail2ban/filter.d/에 apache2-40x.conf 파일로 저장한다.

[Definition]
failregex = ^<HOST> -.*"(GET|POST|HEAD).*HTTP/[0-9.]+" (403|404)
# 태그 정리 등 일반 방문자가 404를 겪을 수 있는 수집 경로는 차단 예외 처리
# ignoreregex = ^<HOST> -.*"(GET|POST|HEAD) /tag/.*" 404

이 필터는 403, 404 에러를 뿜어내는 접근을 시도하는 IP를 차는 필터다. 주석으로 막혀있는 ignoreregex는 특정 UR 즉 /tag/ 가 포함된 url에서 404가 발생하는 경우 이 필터를 적용하지 않는 예외를 설정한 것인데 사용하지 않아서 삭제해도 된다. 만약 필요하다면 살리면 된다.

세 번째 필터도 다음과 같이 필터를 정의하고 /etc/fail2ban/filter.d/에 apache2-wplogin.conf 파일로 저장한다.

[Definition]
failregex = ^<HOST> -.*"(GET|POST|HEAD) /wp-login\.php.*HTTP/[0-9.]+" 403
ignoreregex =

접근 경로에 /wp-login이 있고 403 에러가 발생한 IP를 찾는 필터다. 일반적으로 wp-login에 접근하면 403 에러가 발생하지 않는다. 이 필터는 이 포스트의 앞부분에서 설명한 wp-login 경로에 접근할 수 있는 IP를 제한하고 허용되지 않은 IP에서의 접근 시 403 에러를 리턴하도록 Apache 웹서버의 설정을 적용한 경우에만 정상 작동하므로 주의하자.

차단에 사용할 커스텀 액션(Action) 만들기

필터를 만들었다면 필터에서 걸러내는 IP를 저장할 ipset의 hash:ip 그룹을 만들고 앞에서 만든 커스텀 필터에 의해 걸러진 IP를 hash:ip 그룹에 추가하는 액션을 만든다. 그리고 이 액션에는 iptable에 ipset에 만든 hash:ip 그룹의 IP를 차단하는 차단(Drop) 정책도 추가하는 액션이 포함된다.

다음과 같이 액션을 정의하고 /etc/fail2ban/action.d에 iptables-ipset-ipv4-allports.conf 파일로 저장한다.

[INCLUDES]
before = iptables-common.conf

[Definition]
actionstart = ipset create <ipmset> hash:ip timeout 0
              iptables -I <chain> -m set --match-set <ipmset> src -j <blocktype>

actionstop = iptables -D <chain> -m set --match-set <ipmset> src -j <blocktype>
             ipset flush <ipmset>
             ipset destroy <ipmset>

actionban = ipset add <ipmset> <ip> -exist

actionunban = ipset del <ipmset> <ip> -exist

[Init]
ipmset = f2b-<name>
chain = INPUT

이 액션은 fail2ban 서비스가 시작될 때 ipset에 hash:ip 타입의 그룹을 생성하고 iptable의 INPUT 체인에 jail에서 정의한 blocktype의 차단정책을 추가한다. 반대로 fail2ban 서비스가 정지될 때는 해당 정책을 삭제하고 ipset의 그룹도 삭제(destroy)한다.

그리고 필터에 의해 걸러진 IP를 ban 할 대는 ipset의 hash:ip 그룹에 IP를 추가해 차단하고 반대로 ban 기간이 만료되면 ipset의 그룹에서 제거해 풀어준다.

액션은 동일하게 iptable과 ipset을 연동해 차단(Drop)할 것이므로 하나의 액션만 만들면 된다.

필터를 사용할 커스텀 jail 만들기

이제 앞에서 만든 필터를 사용해 걸러지는 IP를 액션에 의해 차단하는 jail을 생성한다.

먼저 무작위 SSH 로그인 시도하는 공격을 방어하는 jail을 다음과 같이 작성하고 /etc/fail2ban/jail.d 경로에 sshd.conf 파일로 저장한다.

[sshd]
enabled = true
# 실제 사용하는 sshd 서비스 포트로 바꿔준다. 
port = 6060
bantime = 86400
findtime = 1800
maxretry = 3
# 아파치 방어에 썼던 [DROP + ipset + 모든 포트 차단] 무기를 꺼냅니다.
banaction = iptables-ipset-ipv4-allports[blocktype=DROP]

사용할 필터는[sshd]다. /etc/fail2ban/filter.d에 정의되어 있는 기본 필터다. 그래서 별도로 필터의 이름을 알려주지 않아도 되는 듯 하다. sshd 기본 필터의 파일명이 그대로 사용되는 것으로 보인다. 모니터링 할 ssh 포트는 6060이다. 필터에 걸린 IP는 86400 초 동안 차단(ban) 한다.

그리고 최근 1800 초 동안(findtime) 3번 로그인에 실패(maxretry)하면 차단한다.

차단할때 액션은 iptables-ipset-ipv4-allports 이고 blocktype은 DROP 이라고 정의되어 있다.

마찬가지로 워드프레스 블로그에 접근하다 403, 404 에러를 쏟아내는 공격을 방어할 apache2-40x 라는 jail도 다음과 같이 정의하여 /etc/fail2ban/jail.d 경로에 apache2-40x.conf 에 저장한다.

[DEFAULT]
# 본인 접속 IP나 캐시 서버 IP 예외 처리 (공백으로 구분)
ignoreip = 127.0.0.1/8 ::1 131.*.*.* 125.*.*.* 159.*.*.*

[apache2-40x]
enabled = true
port = http,https
filter = apache2-40x
# (CentOS 계열이라면 /var/log/httpd/access_log 로 수정)
logpath = /var/log/apache2/access.log
backend = polling
findtime = 600
maxretry = 6
bantime = 86400
banaction = iptables-ipset-ipv4-allports[blocktype=DROP]  

DEFAULT 블록이 보이는데 필터에 걸러진 IP 중에서 이 jail을 적용하지 않을 IP를 ignoreip에 리스트업 하면 예외처리가 된다. 나머지는 특별히 설명하지 않아도 될 듯 하다. 다만 backend = poling 라인만 추가해주면 된다. fail2ban이 직접 로그파일을 열어 봐야 하는 경우에는 backend = poling을 추가해 주어야 한다.

마지막으로 wp-login 페이지에 반복적으로 접근하는 행위를 차단하는 jail을 다음과 같이 작성하고 /etc/fail2ban/jail.d 디렉토리에 apache2-wplogin.conf 파일로 저장한다.

[DEFAULT]
# 관리자 페이지에 접속할 수 있는 집의 IP나 접속 시 경유할 Proxy 서버 IP는 예외 처리 (공백으로 구분)
ignoreip = 127.0.0.1/8 ::1 131.*.*.* 125.*.*.* 159.*.*.*

[apache2-wplogin]
enabled = true
port = http,https
logpath = /var/log/apache2/access.log
backend = polling

# 1 번만 봐줌. 2번 이상 연속으로 로그인을 시도하면 차단
maxretry = 2

# 1시간(3600초) 동안 차단
bantime = 3600

# 10분(600초) 이내에 발생한 기록만 유효하게 인정
findtime = 600

# 우리가 만든 완벽한 DROP 방화벽 적용
banaction = iptables-ipset-ipv4-allports[blocktype=DROP]

이제 기본적인 준비는 완료되었다.

서버 (VM)에서 IPv6를 사용하지 않을 때 주의 사항

fail2ban을 구현하다 보니 iptable을 사용해 차단할 때 서버(VM)와 IPTable에서 IPv6를 지원하지 않거나 운영체제 자체에서는 지원하더라도 외부와 통신이 가능한 IPv6 공인 주소가 할당되어 있지 않으면 에러가 발생한다. 따라서 fail2ban에게 IPv6는 사용하지 않도록 알려주어야 한다.

다음과 같이 IPv6를 사용하지 않도록 설정파일을 작성해 /etc/fail2ban 디렉토리에 fail2ban.local 파일로 저장한다.

[Definition]
allowipv6 = no

fail2ban의 구동

fail2ban에서 사용할 필터와 액션 그리고 jail을 작성하고 오류가 없다면 다음 명령으로 fail2ban 서비스를 시작할 수 있다.

$ sudo systemctl start fail2ban

정상적으로 시작되면 다음과 같이 상태 확인이 가능하다.

fail2ban client status
fail2ban에서 구동중인 jail의 개수와 목록

그리고 fail2ban 서비스가 시작되면서 jail에서 정의한 액션이 수행되면서 iptable의 INPUT 체인 맨 앞에 3개의 jail 이름으로 match-set DROP 정책이 생성된다.

fail2ban 서비스 시작 시 action에 의해 생성된 Drop 정책
fail2ban 서비스 시작 시 action에 의해 생성된 Drop 정책

이 두 명령의 상태가 앞 화면과 같다면 정상적으로 fail2ban 서비스가 동작되고 있다고 보면 된다.

fail2ban의 실제 차단 예시

처음엔 없지만 시간이 지나면서 jail 에서 정의한 조건에 해당되는 IP들이 fail2ban에 의해 발견되고 있다는 건 로그파일을 모니터링하면 알 수 있다.

Fail2ban log monitor
fail2ban log monitor

로그에서 “Found”는 필터 조건에 부합되는 IP가 발견되었다는 의미이고 “Ban”은 jail에서 정의한 조건에 충족되어 해당 IP가 차단되었다는 의미다.

그리고 이따금씩 “Unban”도 보이는데 차단 시간이 지나 차단이 해제되었다는 의미다.

그리고 iptable에서 적용된 match-set 정책에서 차단할 ipset 그룹에 Ban 된 IP가 다음과 같이 등록되어 있는지 확인할 수 있다. 다음은 ipset list 명령으로 apache2-40x 필터를 사용해 만든 jail에서 차단한 IP 목록이다.

fail2ban ipset banip
fail2ban ipset banip

또한 iptable 에서 실시간으로 차단되는 패킷의 수가 변화하는 것도 다음 화면과 같이 확인할 수 있다.

$ watch -d -n 5 "sudo iptables -L INPUT -v -n"
Iptable monitor
Iptable monitor

#wordpress #fail2ban #ipset #웹방화벽

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다