완벽한 방어수단은 없습니다. 다만, 시도를 어렵게 할 뿐! iptables를 사용하여 해킹 시도 자체를 줄여보는건 어떨까요?
해킹은 어떤 경로를 통해 일어나는가?
해킹은 시스템의 취약점을 통해 이루어지거나, 브루트포스로 자주 사용될만한 인증정보를 대입하여 성공하거나
크게 이렇게 2가지의 방법을 통해 이루어지게 됩니다.
전자의 경우 예시를 들어보면, 최근에 핫했던 NextJS와 React 에 존재했던, React2Shell 이 있을겁니다.
이 때는, 사전 공격 조건이 해당 프레임워크로 서버를 운용하기만 해도 공격이 100프로 가능한 상황이어서 매우 심각한 이슈였습니다.
프론트엔드 영역에서, 서버단의 코드를 간편하게 호출하려는 기능인 Server Action 기능에서 프론트의 요청을 서버에서 파싱하는 영역에서 미처 JavaScript코드가 Injection될 수 있는 부분을 놓쳐서 발생한 취약점이었습니다.
이렇듯, 개발자의 의도치 않은 실수로, 취약점은 피할 수 없는 운명입니다.
가장 흔한 예시는, SQL Injection을 제대로 막지 못한 경우가 있겠습니다. SQL Injection은 이미 대안이 있음에도, 여전히 아주 효과적인 공격 수단으로 알려져 있습니다!
후자의 경우는 SSH 공격을 계속 username과 password를 변경해가면서, 공격하는 것이 되겠네요!
편의상 SSH를 그냥 외부에 노출시켜서 어디서든 접근하는 경우가 많습니다.
이런 경우, 평소에 암호를 너무 단순하게만 해두었다던가.., 이와 더불어 공격을 막아주는 fail2ban과 같은 장치가 없다면,
언젠가는 서버는 해킹당할 운명에 처하게 될 것 입니다! (이건 경고입니다! SSH는 반드시, 제한된 IP에서만 접근할 수 있게 하는 것이 좋습니다!)
iptables는 무엇인가?
iptables는 linux시스템에서 네트워크 패킷을 특정 조건에 맞게 허용 또는 폐기 시켜버리는 동작을 제어합니다.
netfilter라는 커널내부기능이 실제로 ‘네트워크패킷’을 확인하고, 허용 및 폐기를 결정합니다.
이 때 이런 netfilter가 수행할 조건을 iptables로 제어할 수 있습니다.
iptables는 서비스가 아닙니다. 단순한 CLI 명령입니다. 방화벽을 켜고 끈다는 개념이 없습니다.
보통 방화벽을 켜고 끄는 개념을 도입하려면, iptables에 CHAIN을 걸어 기생하는, ufw 라는 방화벽 도구를 이용하게 됩니다.
ufw는 단순히 규칙을 관리하는 서비스입니다.
방화벽을 끄면, iptables에 걸어두었던 CHAIN 규칙을 FLUSH 명령을 통해 제거하게 됩니다.
다시 키면, 다시 CHAIN에 규칙을 추가하게 됩니다.
ubuntu를 기본적으로 어느정도의 기본 이미지로 설치하셨다면, ufw가 기본적으로 설치되어있습니다.
mini iso와 같은 최소한의 버전만 설치하였다면, ufw는 설치되지 않습니다.
ufw가 편의성에는 매우 좋지만, ip대역 관리나, 세밀한 조작에서는 아쉬움이 큽니다.
특히, docker 네트워크를 직접 ufw로 막을 수 없다는 점이 아쉽습니다.
ufw는 왜 docker 네트워크를 막지 못하는가?
사실 이건, Github issue 에서도 현재 까지도 계속 제기되고있는 보안 이슈 문제입니다.
특히, 개발에 처음 입문하시는 분들은, 방화벽도 공부했고, Docker도 공부해서 오픈하게 되면, 굳이 방화벽을 열지 않아도 그냥 허용되는 신기한 현상을 보게 됩니다.
이는 Docker가 소프트웨어 정의된 내부망 네트워크 인터페이스를 정의하고 사용하기 때문에, iptables의 기본정책에 알 수 없는 이유로 막히지 않기 위해,
내놓은 대안이 아닐까 생각합니다.
일반적으로 방화벽은 lo 루프백 인터페이스를 기본적으로 전부 ACCEPT 하고 들어갑니다.
내부 대역의 통신을 가능하게 해야하니까요.
하지만, docker는 내부 대역이지만, lo 인터페이스를 사용하여 통신하지 않고, 별도의 구축된 네트워크 인터페이스를 사용하여 통신합니다. (심지어 ip도 172 내부망 대역)
이렇게 되면, 방화벽을 활성화 하기만 해도, 도커 내부 간의 통신은 허용되지 않게 됩니다.
그래서 docker는 이런 알 수 없는 상황을 통해, 도커 간의 네트워크 통신이 제한되는 걸 방지하기 위해서,
별도의 iptables 체인을 생성하여 관리합니다.
결국에는, Docker만의 네트워크를 이용하기 위해서, Docker가 iptables에 꼼수를 부린 것과 같습니다.
그래서, Docker는 네트워크 제한의 커스텀을 사용자에게 위임할 수 있는 권한도 같이 넘겨주었습니다.
그건 바로 ‘DOCKER-USER‘ 라는 체인입니다.
우리는 ‘DOCKER-USER‘라는 체인을 통해서, 특정한 port 에는 특정한 ip만 허용할 수 있도록 별도의 제한이 가능합니다!
하지만, ufw에서는 이를 별도의 후처리를 통해 iptables를 직접 다뤄 제한해야 합니다.
/etc/ufw/after.rules 파일에서 지정할 수 있다고 합니다.
iptables에 대한 체인 유형
간단하게 살펴보면, iptables에는 INPUT, FORWARD, OUTPUT 이렇게 3가지의 기본적인 체인이 존재합니다.
모든 요청은 전부 INPUT을 통해 들어오게 됩니다. 모든 아웃바운드 요청은 전부 OUTPUT을 통해 나가게 됩니다.
FORWARD는 특수한 경우인데, 네트워크를 중간에 중계(경유)하는 경우에 사용되는 체인입니다.
Docker도 이를 사용합니다. 일반적으로는 FORWARD에 사용자 정의 규칙을 사용하는 경우는 적습니다.
쉽게 생각하면, 외부 사용자가 해당 서버로 80(http)요청을 날린건, INPUT 체인으로 들어와 방화벽 처리를 수행합니다.
특정 서버에서 고객에게 PUSH 알림을 보내기 위해서, 구글 FCM 서버로 보내는 요청은 OUTPUT을 통해 나가게 됩니다.
또한, 각 체인에는 기본 정책을 지정할 수 있습니다.
ACCEPT 또는 DROP으로 말이죠!
INPUT은 일반적으로 요청을 받는 쪽이라, 기본 정책을 DROP으로 합니다.
FORWARD도 마찬가지로, 받는 쪽이라, 기본 정책을 DROP으로 합니다.
OUTPUT는 서버가 주체가 되어 외부로 요청을 보내는 경우라, 일반적으로 ACCEPT으로 지정합니다.
OUTPUT이 기본 정책이 DROP이라면, 새로운 OUTPUT요청이 발견될 때 마다, 직접 하나씩, 추가해야 되는 끔찍한 경험을 하게 됩니다.
백도어가 정보를 털어가지 못하게 막는 방어장치가 될 수는 있겠지만, 이 또한, 통신을 위해 80이나 443을 모든 아이피로 오픈한다던지,
INPUT 자체가 ESTABLISHED된 통신이면, INPUT 명령에 의해 응답이 돌아오는 구조면, 사실 그렇게 OUTPUT 정책이 큰의미를 가진다는 생각은 어렵습니다.
OUTPUT 제한이 중요한 경우라면, 확실히 모든 OUTPUT을 막지 않는 한, 의미가 크게 없다고 생각합니다.
그리고, INPUT을 통해서 데이터 유출이 가능하므로, INPUT에도 강한 제약이 있는 시스템에서 유효하게 보여집니다.
그래서, 일반적으로는 OUTPUT이 ACCEPT로 모든 요청을 허용하게 됩니다.
특히, 시간서버(ntp)나, FCM이나 패키지 설치와 같은 것들이 있어, OUTPUT은 내부망 서비스가 아니고서야, 대부분 오픈하게 됩니다.
성능 좋은 보안 방지는 포트를 변경하는 것
사실 IP대역을 전부 오픈함이 불가피한 경우, 공격의 대부분을 막을 수 있는 효과적인 방법은 기본포트를 사용하지 않는다! 입니다.
http 이런 공통된 규칙은 제외하고 말이죠.
일반적으로 공격 도구가 기본 포트를 향해, 타겟팅 되어 사용되는 경우가 많기 때문에,
기본적인 포트를 변경만 해줘도, 많은 공격을 애초부터 받지 않아도 되는 경우를 막을 수 있습니다.
42.9억개 정도 ipv4가 존재하는데, 이중에서, 포트가 변경되기만 해도, 하나의 ip당 nmap을 돌려야 하기 때문에,
많은 비용이 들게 됩니다. 또한 변경된 포트가 현재 공격하는 대상 서비스인지도 확인해야하는 비용도 들게 될 것이구요!
그래서, 사실 기본포트를 사용하지 않는 전략은 정말 유효한 전략이 됩니다.
서브넷 마스크
본격적으로, iptables를 사용하기 전에, 서브넷마스크에 대한 개념을 알고가야 합니다.
서브넷마스크는 효율적으로 IP대역을 지정할 수 있는 훌륭한 도구 입니다.
192.168.0.0/16 과 같은 표기법을 많이들 본 적? 이 있을겁니다!
192.168.0.0/255.255.0.0 과 같이 표기하기도 합니다!
이는 비트마스크 연산 중, AND 연산을 통해서, 현재 진입하려는 IP가 특정 대역에 속하는지 빠르게 검증하기 위해 사용되는 형태입니다.
ipv4는 한 ‘.‘3개로 총 4개의 섹션으로 구성된 형태입니다. 하나의 섹션 당 8비트(0~255)의 크기를 가지고 있습니다.
192.168.0.0을 비트로 표현하면,
11000000.10101000.00000000.00000000
와 같이 이진수 형태로 표현할 수 있습니다.
그리고, 비트마스크를 해줄 연산자는 /16과 같은 표기입니다. 또는 255.255.0.0 이죠.
이는 앞에서부터 순차적으로, 16개의 비트를 AND 연산해서 같은지 비교하여 같은 대역인지 확인하겠다는 의미입니다.
이는 앞에 16개의 비트를 모두 1로 만들어서, AND연산을 하겠다는 의미입니다.
11111111.11111111.00000000.00000000 이게 이제 외부에서 들어온 IP에 대해 Masking할 값이 됩니다.
그리고, 기준점을 앞에 192.168.0.0 (11000000.10101000.00000000.00000000) 으로 잡아둔 것이구요!
당연히 11111111을 192(11000000)와 비트연산 AND를 수행하면, 그대로 192(11000000)가 나오게 됩니다.
즉, 쉽게 말하면, 8비트를 모두 1로 만들었다는 건, 앞에 작성한 숫자와 정확히 일치하는 IP만 허용하겠다는 의미와도 같게 됩니다.
이를 이해하기 위해서는 이진수와 비트연산에 대해 더 소개가 필요하지만, 이 글을 보는 사람들이라면, 아마 다들 기본적으로 알고 계시지 않을까 생각하므로, 생략하려고 합니다.
192.168.0.0/16 이므로, 두 번째 영역도 8비트를 모두 마스킹하므로,
결국에는, IP가 192.168이 모두 일치하는 경우에만 보안에 통과할 수 있게 됩니다.
놀랍게도, 이런 서브넷마스크 표기법을 사용하면, AND 연산 한 번이면, 해당 IP대역을 바로 판별할 수 있습니다. 이는 정말 단순하면서도, 직관적인 기술입니다.
이는 192.168.0.0 ~ 192.168.255.255 대역의 IP를 모두 통과시키겠다라는 의미와도 같습니다.
서브넷 마스크는 뒤에 마스킹 비트가 포함되지 않는 영역은,계산에서 제외되므로, 마스킹 비트 외의 영역은 자유롭게 어떤 값이 와도 됩니다!
그래서 사실, 192.168.255.255/16 과 같은 표기법도 같은 형태가 됩니다. 앞에 16개의 비트를 제외한 뒤에 16개의 비트는 사실 아무숫자나 넣어도 상관은 없지만,
보기 좋게 0이나 255로 통일하고는 합니다.
사실 여기서 더 중요한 건, 172 대역의 내부망입니다.
Docker IP 체계가 해당 대역을 자주 사용하게 되는데요,
이 때, 172 대역만을 허용할 때,
172.16.x.x, 172.17.x.x, 172.18.x.x 이렇게 다양하게 지정될 가능성이 있어서,
이걸 무심코 172.255.255.255/8 이렇게 표기하거나, 172.0.0.0/8 이렇게 표기할 가능성도 있겠습니다.
언뜻보면, 내부망 대역만 허용한 것 같이 보이죠?
하지만, 이는 놀랍게도 외부망 IP대역도 포함되어 있습니다.
공식적으로 지정된 IP 대역은
172.16.0.0/12 로 표기합니다. 즉, 172.16.0.0 ~ 172.31.255.255 까지 허용한 형태입니다.
172.32.x.x로 올라가거나 172.15.x.x로 내려가거나 하는 순간 외부망 대역을 오픈한 것이나 다름없습니다.
이게 헷갈릴 수 밖에 없는게,
A클래스 -> 10.0.0.0/8
C클래스 -> 192.168.0.0/16
B클래스 -> 172.16.0.0/12
이렇게 A클래스와 C클래스는 명확히 8비트 단위 마스킹으로 허용되는데,
B클래스 (172내부망) 대역만, 2번째 섹션을 완벽히 마스킹되지 않은 형태로 존재합니다.
/12면, 앞에 8비트하고, 두 번째 섹션을 상위 4개의 비트만 마스킹하는 형태이므로,
.16(00010000) 에서 앞에 (0001)만 일치하는 경우에만 허용됩니다. 이 경우에 하위 20개의 비트만 자유로운 경우만 허용됩니다.
즉, 16보다 적게 되면, 상위 비트가 마스킹이 하위로 풀리면서, 대역 외의 범위로 벗어나게 되고,
31보다 크게 되면, (00011111)이 (00100000)으로 올라가므로, 마찬가지로 상위 비트의 마스킹이 풀리면서, 대역 외의 범위로 벗어나게 됩니다.
만약 제한하려고 하는 서비스가, 내부망 172대역에서만 허용되는 인증이 필요없는 서비스였다면,
이런 실수는, 꽤나 위험합니다.
ipset을 통하여, ip집합을 정의하자
ipset은 ip의 집합들을 손쉽게 관리하는 위한 도구입니다. 관리자 IP들만 모아두고, 여기에 ipset으로 지정하고, 이를 iptables 규칙에 반영할 수 있습니다.
이는 중복되는 iptables 코드를 줄여주고, 깔끔한 방화벽 규칙을 유지할 수 있도록 도와줍니다.
또한, 특정 국가 대역을 허용하게 하고 싶을 때, ipset을 사용하게 되면, 유지보수 및 성능상의 큰 이점을 제공하게 됩니다.
서비스 하려는 지역의 ip만 허용해도, 공격의 대부분을 막는데 유용합니다!
apt install ipset- 해당 명령을 통해, ipset을 설치합니다.
ipset create ADMIN_SET hash:ip -exist
ipset create INTERNAL_SET hash:net -exist
ipset create KR_NET hash:net -exist- 저는 3가지의 ipset을 만드려고 합니다.
1. ADMIN_SET : 관리자 IP를 모아둔 IPSET
2. INTERNAL_SET : 내부망 IP를 모아둔 IPSET (반드시 내부망 통신에서만 접근해야 하는 경우)
3. KR_NET : 한국 대역의 IP를 모아둔 IPSET
- hash:ip 옵션은 ip를 하나씩 한땀 한땀 입력하는 방식을 의미합니다.
- hash:net 옵션은 서브넷마스크 표기법 CIDR 형태로, 범위 형태의 ipset을 지정할 때 사용합니다. 내부망이나, 한국대역은 전부 서브넷마스크 형태로 넣을 예정이기 때문에, 이와 같이 지정합니다.
- -exist는 이미 있으면, 만들지 말라는 옵션입니다. 이미 만들어둔 ipset으로 오류가 메세지가 나는 것을 막기 위함입니다.
ipset add ADMIN_SET 111.111.111.111
ipset add ADMIN_SET 222.222.222.222- 관리자 IP는 이렇게 2개를 등록해보겠습니다.
ipset add INTERNAL_SET 172.16.0.0/12
ipset add INTERNAL_SET 192.168.0.0/16
ipset add INTERNAL_SET 10.0.0.0/8
ipset add INTERNAL_SET 127.0.0.0/8- 사실 이렇게 모든 대역을 넣는건 그렇게 좋은건 아니지만, 직관적인 이해를 위해서, 이렇게 구성하였습니다.
- KR_NET은 외부 데이터베이스를 이용해야 합니다. 저는 ipdeny라는 사이트에서 제공하는 한국 대역 IP를 사용하려고 합니다.
- https://www.ipdeny.com/ipblocks/data/countries/kr.zone 여기서, 한국대역의 IP 대역을 받을 수 있습니다.
#!/bin/bash
set -e
SET_NAME="KR_NET"
TMP_SET="KR_NET_TMP"
ZONE_FILE="/tmp/kr.zone"
echo "[1] 한국 IP 목록 다운로드"
wget -q -O "$ZONE_FILE" https://www.ipdeny.com/ipblocks/data/countries/kr.zone
echo "[2] 임시 ipset 생성"
ipset create "$TMP_SET" hash:net -exist
echo "[3] 기존 데이터 초기화"
ipset flush "$TMP_SET"
echo "[4] CIDR 추가"
while read cidr; do
ipset add "$TMP_SET" "$cidr" -exist
done < "$ZONE_FILE"
echo "[5] 기존 set 없으면 생성"
ipset create "$SET_NAME" hash:net -exist
echo "[6] swap (무중단 교체)"
ipset swap "$SET_NAME" "$TMP_SET"
echo "[7] 임시 set 삭제"
ipset destroy "$TMP_SET"
echo "[완료] KR_NET 업데이트 완료"- 쉘스크립트를 이용하여, 좀 더 쉽게 바로 KR_NET을 생성하도록 할 수도 있습니다!
여담 이지만, 저기 사이트에서 한국 대역(남한) IP대역이 총 2400개 가량 나오게 됩니다. 대역만 2400개 구요, ip낱개로 치면 1억개 이상이 넘어갑니다.
1억개가 넘는 ip를 모두 iptables로 규칙으로 허용 또는 거절 했다고 하면, iptables는 요청 하나에 과부하가 걸리게 됩니다.
이를 서브넷마스크 방식을 통해, 1억개 넘는 ip를 2,400개의 대역으로 줄일 수 있게 됩니다.
서브넷마스크는 언뜻 보면, 정말 왜 만들었는지 쓸데 없는 기술처럼 보이지만, 이렇게 대용량 처리를 할 때, 그 진가를 발휘하게 됩니다.
만약 서브넷마스크라는 개념 자체가 없었다면, 여러분은 IP제한 처리를 어떻게 진행할 수 있었을까요? 그냥 안하거나, 하나씩 하거나.. 둘 중 하나이지 않을까 싶습니다.
그리고, 사실 매 요청마다 2,400개의 필터를 대역 필터를 거치는 방식도 좋지 않습니다.
iptables에 2,400개의 제한 규칙을 그대로 쉘스크립트 만들어서 추가한다면, 요청 하나에 과부하가 되는 현상을 경험할 수 있겠습니다.
iptables 자체에는 규칙을 선형(Linear)하게 탐색하는 방식으로, 정책을 제한 또는 허용합니다.
2,400개 컴퓨터 한테는 별 거 아니죠? 아닙니다. 네트워크 요청 하나에, 2,400개의 대역을 스캔하는건 별 거 맞습니다.
그래서, 우리는 ipset을 사용합니다.
ipset는 등록된 ip들을 해시화 해서 보관합니다.
아까 ipset을 생성할 때 주었던 옵션, hash:ip, hast:net 과 같은 옵션입니다.
그래서 요청 자체의 ip를 판별하는데, 평균 O(1)의 시간복잡도를 가지게 됩니다.
iptables에는 이런 기능이 없으므로, 국가 대역의 IP를 허용 및 제한하기 위해서는 ipset은 선택이 아니라 필수 입니다.
ufw 비활성화 하기
ipset을 적극적으로 이용하려면, ufw의 after.rules에 추가해줘도 되는데, 사실 이러면 굳이 ufw를 사용하는 의미가 없어집니다.
그래서 그냥 ufw를 비활성화 하는 방법도 다루려고 합니다.
ufw는 iptables를 거의 장악하다시피 CHAIN을 많이 걸어두므로, 비활성화 할 때도, 조심해서 해야 합니다.
DOCKER는 별개의 체인으로 분리하지만, ufw는 실제로 INPUT체인에 추가하므로, ufw를 사용하지 않으려면 제거해야 합니다.
또한, ufw를 제거한다고 하더라도, ufw가 생성한 정책이나, 체인은 알아서 제거해주지 않습니다.
안에 있는 사용자 정의 규칙만 제거하지, ufw틀은 그대로 남아있어, 제대로 사용하려면 직접 제거가 필요합니다.
우선적으로, ufw가 활성화 된 상태라면, iptables의 INPUT 기본정책이 DROP으로 되어있을 가능성이 매우 높습니다.
이 상태에서 바로, ufw를 내리게 되면, SSH에 접근하지 못하게 되는 불상사가 일어나게 됩니다.
따라서, 상단에 미리 SSH 및 기타 포트를 INPUT 체인 상단에 허용하는 내용을 추가합니다.
# 1. 기존 연결 유지
iptables -I INPUT 1 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# 2. 내부망 통신 허용 (루프백 인터페이스)
iptables -I INPUT 2 -i lo -j ACCEPT
# 3. SSH (가장 중요)
iptables -I INPUT 3 -p tcp --dport 22 -s 111.111.111.111 -j ACCEPT이렇게 최상단에 INPUT 정책을 추가한 이후에, ufw를 내려야 합니다.
이를 수행하지 않은 상태에서 ufw를 내렸다면, 가상서버라면 직접 가상콘솔로 들어가서 확인하면 되지만,
물리 서버가 원격 IDC에 있는 경우라면, 상당히 힘들어지는 상황이 발생합니다. 반드시 유의해야 합니다.
ufw disable
systemctl disable ufw
apt purge ufw해당 과정을 통해서, ufw를 삭제할 수 있습니다.
다만, 이제 ufw가 기본적으로 만든 체인 참조와, 체인을 제거는 수동으로 해야 합니다.
# 체인 참조 제거
iptables -D INPUT -j ufw-before-logging-input
iptables -D INPUT -j ufw-before-input
iptables -D INPUT -j ufw-after-input
iptables -D INPUT -j ufw-after-logging-input
iptables -D INPUT -j ufw-reject-input
iptables -D INPUT -j ufw-track-input
iptables -D FORWARD -j ufw-before-logging-forward
iptables -D FORWARD -j ufw-before-forward
iptables -D FORWARD -j ufw-after-forward
iptables -D FORWARD -j ufw-after-logging-forward
iptables -D FORWARD -j ufw-reject-forward
iptables -D FORWARD -j ufw-track-forward
iptables -D OUTPUT -j ufw-before-logging-output
iptables -D OUTPUT -j ufw-before-output
iptables -D OUTPUT -j ufw-after-output
iptables -D OUTPUT -j ufw-after-logging-output
iptables -D OUTPUT -j ufw-reject-output
iptables -D OUTPUT -j ufw-track-output
# 체인 제거
iptables -F ufw-before-logging-input
iptables -X ufw-before-logging-input
iptables -F ufw-before-input
iptables -X ufw-before-input
iptables -F ufw-after-input
iptables -X ufw-after-input
iptables -F ufw-after-logging-input
iptables -X ufw-after-logging-input
iptables -F ufw-reject-input
iptables -X ufw-reject-input
iptables -F ufw-track-input
iptables -X ufw-track-input
iptables -F ufw-before-logging-forward
iptables -X ufw-before-logging-forward
iptables -F ufw-before-forward
iptables -X ufw-before-forward
iptables -F ufw-after-forward
iptables -X ufw-after-forward
iptables -F ufw-after-logging-forward
iptables -X ufw-after-logging-forward
iptables -F ufw-reject-forward
iptables -X ufw-reject-forward
iptables -F ufw-track-forward
iptables -X ufw-track-forward
iptables -F ufw-before-logging-output
iptables -X ufw-before-logging-output
iptables -F ufw-before-output
iptables -X ufw-before-output
iptables -F ufw-after-output
iptables -X ufw-after-output
iptables -F ufw-after-logging-output
iptables -X ufw-after-logging-output
iptables -F ufw-reject-output
iptables -X ufw-reject-output
iptables -F ufw-track-output
iptables -X ufw-track-outputufw는 INPUT, FORWARD, OUTPUT에 각각 6개의 체인 총 18개의 체인을 걸어둡니다. 버전마다 다를 수도 있고요.
이를 확인해서 모두 제거해줍니다.
ufw 설치는 기본적으로 설치되거나, 따로 나중에도 설치할 수 있는데,
제거할 때, 이런 체인을 왜 제거 안해주는지는 참 의문입니다.
적어도 비활성화 할 때는, 없애주어야 하는게 아닌가 싶습니다.
여기까지 되었다면, ufw를 없애는데 성공하였습니다.
iptables INPUT 규칙 지정 쉘스크립트 작성
드디어 iptables에 규칙을 지정하는 부분입니다.
한 번에 너무 많은걸 작성하려고 보니, 글이 길어졌지만, 보안 관점에서는 정말로 필수적인 내용이라 간과할 수는 없었습니다.
iptables는 ufw와는 달리, 내부망도 확실히 고려해야 한다는 점이 불편합니다. 그만큼 정교한 제어가 가능합니다.
lo 대역과 내부망 대역에 대한 제한 사항은 확실히 하고 가야 합니다.
또한, ping에 대한 요청도 직접 허용해야 합니다. 이것도 허용하지 않으면 ping 요청을 보내도 응답이 없는 서버가 됩니다.
또한, 성능에 대한 이점을 가져가기 위해, 이미 성립된 패킷은 그냥 허용하는 요청도 고려해야 합니다.
그것만 고려하면, 나머지는 ufw와 그렇게 큰 차이는 없다고 생각합니다.
#!/bin/bash
set -euo pipefail
# 기본 정책 설정 및, INPUT 정책 임시 해제.
iptables -P INPUT ACCEPT
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
# INPUT 체인 재설정
iptables -F INPUT # INPUT 체인 내용삭제
iptables -A INPUT -i lo -j ACCEPT # 로컬 인터페이스는 모두 허용
iptables -A INPUT -p icmp -j ACCEPT # ping 통신 허용
iptables -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT # 성능을 위해 이미 연결된 건, 허용하기.
# 22번 SSH포트는 ADMIN_SET에 해당되는 IP들만 허용하기.
iptables -A INPUT -m set --match-set ADMIN_SET src -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT
# 웹 연결은 한국 IP대역만 허용하기.
iptables -A INPUT -m set --match-set KR_NET src -p tcp --dport 80 -m conntrack --ctstate NEW -j ACCEPT
iptables -A INPUT -m set --match-set KR_NET src -p tcp --dport 443 -m conntrack --ctstate NEW -j ACCEPT
# DB는 관리자, 내부망만 오픈.
iptables -A INPUT -m set --match-set ADMIN_SET src -p tcp --dport 3306 -m conntrack --ctstate NEW -j ACCEPT
iptables -A INPUT -m set --match-set INTERNAL_SET src -p tcp --dport 27017 -m conntrack --ctstate NEW -j ACCEPT
# INPUT 정책 다시 막기
iptables -P INPUT DROPiptables의 설정하는 쉘스크립트는 단순합니다.
초기 체인 정책 및 INPUT만 ACCEPT를 풀어주는 부분 (이는 INPUT 체인이 Flush되면서, 연결이 끊기는걸 막기 위함.)
INPUT 체인 재설정 부분
허용된 INPUT 정책 막는 부분
엄청 복잡하지는 않습니다. 여기서 실패해서 IDC에 가는 것이 두렵다면, 제한된 시간내에 다시 원래의 정책으로 복구시키는 데몬을 해당 쉘스크립트 상단에 따로 띄우는 코드를 추가해주시면 됩니다.
위에서 많이 설명한 이론은 꽤나 많은 내용들이 나왔지만,
사실 막상 적용하는건 그렇게 큰 어려움이 없습니다. 꽤나 아름답게, 꽤나 단순하게 수행되는걸 볼 수 있습니다.
iptables를 재시작해도 유지시키기
apt install netfilter-persistent # netfilter 관련 영구저장 프레임워크
apt install iptables-persistent # iptables 영구저장 플러그인
apt install ipset-persistent # ipset 영구저장 플러그인해당 패키지를 설치하고
netfilter-persistent save저장 명령어를 수행하면,
서버가 재시작되어도 방화벽이 유지됩니다.
마무리
요즘 따라, 보안에 대한 이슈가 점점 많아지고 있는 추세입니다.
방화벽 설정은 선택이 아닌, 필수인 시대가 찾아왔습니다.
그냥 아무렇게나 대충 사용하는 방화벽이 아닌,
필요한 부분을 확실히 이해하고 사용해야 합니다.
iptables는 오늘 소개한 내용 이외에도, 트래픽을 제어하는 기능인 rate-limit 기능도 존재합니다.
저는 오늘 아주 기초적인 셋팅에 대한 내용을 알아보았고,
나머지는 충분히 직접 더 필요한 부분을 파악하고, 추가해볼 수 있지 않나 생각합니다.
여기까지 모두 이해하셨다면,
저야말로 정말로 감사드립니다.
모두 안전한 서버를 구축하시길 바래봅니다.
글 읽어주셔서 감사합니다!
답글 남기기