본문으로 바로가기

안녕하세요, SATAz입니다.

이번 포스팅은 IPTables에 대해서 소개드리는 아홉 번째 내용입니다.



지난 포스팅에서는 recent 매치를 이용해서 DOS Flooding 공격을 완화시키는 방법에 대해서 다루었는데요,

이번 포스팅은 IPTables의 hashlimit를 이용하여 DOS Flooding 공격을 차단시키는 방법에 대해서 알려드리고자 합니다.


IPTables에서는, Flooding 공격을 방어하는 데에 다양한 방법을 사용할 수 있기 때문에 여러 포스팅에 나눠서 설명드리고 있습니다.



▣주의!!! : 이 블로그에 작성된 IPTables 포스팅들은 교육용으로 작성된 내용입니다. 

 서비스 중인 서버에 아래의 내용만 갖고 바로 적용하지 마시고, 서버에 적용 전에 반드시 테스트를 거치시기 바랍니다.  IPTables도 방화벽인지라, 무턱대고 서버에 적용했다가는 서비스 중단되는 사태가 발생할 수 있습니다. 필히 주의하셔야합니다!!!


#IPTables 설정를 위한 환경


- 테스트 머신 : VMware WorkStation 12 Player

- 운영체제 : CentOS/RHEL 7 - 64bit ( Minimal 설치 )

- 메모리 : 2GB

- IPTables Version : iptables v1.4.21

- 서버 아이피 : 192.168.0.18


# 공격을 위한 서버 설정 환경

- 공격자 테스트 머신 : VMware WorkStation 12 Player

- 공격자 운영체제 : CentOS/RHEL 6 - 64bit ( Minimal 설치 )

- 공격자 메모리 : 2GB

- 공격자 서버 아이피 : 192.168.0.19


1. hashlimit 매치를 이용한 설정을 한번 살펴보자

 IPTables에는 hashlimit라고 하는 매치가 있습니다. hashlimit도 이전에 설명드리자면.. 조금 복잡합니다.

특정 갯수 이상의 패킷이 지나간 후에 패킷의 속도를 제한(DROP)시키는 설정을 사용합니다.


어렵습니다... 차근차근 짚고 넘어가겠습니다. 


# hashlimit를 이용한 패킷 차단 명령어

iptables -I INPUT -d 192.168.0.18 -p tcp --dport 80 -m hashlimit --hashlimit-name flood_list --hashlimit-above 20/second --hashlimit-mode srcip --hashlimit-burst 100 --hashlimit-htable-expire 3000 -j DROP

iptables -I INPUT -d 192.168.0.18 -p tcp --dport 80 -m hashlimit --hashlimit-name flood_list --hashlimit-above 20/second --hashlimit-mode srcip --hashlimit-burst 100 --hashlimit-htable-expire 3600 -j LOG --log-prefix "[Im attack]:"


위의 명령어를 여러분이 이해하기 쉽게 해석하면 아래와 같습니다.

1번째줄 : 목적지 192.168.0.18의 80포트로 들어오는 패킷들 중에서, 아이피 별로 패킷을 100번 이상 허용하고, 그 이후에 접근하는 패킷들은 초당 20패킷(20pps)속도로 허용한다. 그리고 Source IP 목록(해시테이블, htable)의 이름은 flood_list 이며, 패킷 유입이 멈추고나서 3000ms(3초)가 지나면 차단 목록에서 source ip를 뺀다.

2번째줄 : 조건이 맞으면 로그를 남긴다


더 쉽게 말해서 일단 source ip별로 100개의 패킷을 허용한 뒤, 초당 20개의 패킷만을 허용하겠다는 의미입니다.

그리고 마지막 패킷이 들어온지 3초가 지나면 source ip는 지워진다는 의미입니다.



사실... 위에 같이 그냥 그렇게 받아들이시면 편합니다.


하지만 이것 말고 더 명확한 해석이 필요한 분이 계실지 몰라서!!! 더 자세하게 알려드리겠습니다.

계산이 필요합니다...

더 깊게 파고들어가면 명령어 해석이 명확해지실 것이라고 믿습니다.



※자세한 해석부터 보고싶으신 분은, 스크롤을 맨 아래로 내려주세요 ^^



# 명령어의 적용

[root@localhost ~]# iptables -I INPUT -d 192.168.0.18 -p tcp --dport 80 -m hashlimit --hashlimit-name flood_list --hashlimit-above 20/second --hashlimit-mode srcip --hashlimit-burst 100 --hashlimit-htable-expire 3000 -j DROP

[root@localhost ~]# iptables -I INPUT -d 192.168.0.18 -p tcp --dport 80 -m hashlimit --hashlimit-name flood_list --hashlimit-above 20/second --hashlimit-mode srcip --hashlimit-burst 100 --hashlimit-htable-expire 3000 -j LOG --log-prefix "[Im attack]:"

[root@localhost ~]# 

[root@localhost ~]# 

[root@localhost ~]# iptables -nL

Chain INPUT (policy ACCEPT)

target     prot opt source               destination         

LOG        tcp  --  0.0.0.0/0            192.168.0.18         tcp dpt:80 limit: above 20/sec burst 100 mode srcip htable-expire 3000 LOG flags 0 level 4 prefix "[Im attack]:"

DROP       tcp  --  0.0.0.0/0            192.168.0.18         tcp dpt:80 limit: above 20/sec burst 100 mode srcip htable-expire 3000

ACCEPT     tcp  --  0.0.0.0/0            192.168.0.18         tcp dpt:80

ACCEPT     all  --  0.0.0.0/0            192.168.0.18         state RELATED,ESTABLISHED

ACCEPT     tcp  --  192.168.0.2          192.168.0.18         tcp dpt:22

ACCEPT     all  --  192.168.0.1          192.168.0.18        

ACCEPT     all  --  8.8.8.8              192.168.0.18        

DROP       all  --  0.0.0.0/0            192.168.0.18        


Chain FORWARD (policy ACCEPT)

target     prot opt source               destination         


Chain OUTPUT (policy ACCEPT)

target     prot opt source               destination 


일단, 이번에도 역시 공격을 보내서 로그가 잘 찍히는지 확인해보겠습니다.


# 로그 상태 - DOS 공격 (SYN Flooding)

[root@localhost log]# tail -f iptables_log

Aug  5 13:28:30 localhost kernel: [Im attack]:IN=ens33 OUT= MAC=00:0c:29:a9:76:18:00:0c:29:3d:ed:2e:08:00 SRC=192.168.0.19 DST=192.168.0.18 LEN=45 TOS=0x10 PREC=0x00 TTL=64 ID=0 DF PROTO=TCP SPT=1756 DPT=80 WINDOW=65535 RES=0x00 PSH SYN URGP=0 

Aug  5 13:28:30 localhost kernel: [Im attack]:IN=ens33 OUT= MAC=00:0c:29:a9:76:18:00:0c:29:3d:ed:2e:08:00 SRC=192.168.0.19 DST=192.168.0.18 LEN=45 TOS=0x10 PREC=0x00 TTL=64 ID=0 DF PROTO=TCP SPT=20000 DPT=80 WINDOW=65535 RES=0x00 PSH SYN URGP=0 

Aug  5 13:28:30 localhost kernel: [Im attack]:IN=ens33 OUT= MAC=00:0c:29:a9:76:18:00:0c:29:3d:ed:2e:08:00 SRC=192.168.0.19 DST=192.168.0.18 LEN=45 TOS=0x10 PREC=0x00 TTL=64 ID=0 DF PROTO=TCP SPT=2001 DPT=80 WINDOW=65535 RES=0x00 PSH SYN URGP=0  

......


차단은 잘 됩니다...



2. hashlimit 계산이 필요하다???

그렇습니다... hashlimit에는 계산이 필요합니다. 우리가 계산해야하는 것은 cat /proc/net/ipt_hashlimit/flood_list 입니다.

명령어에서 "--hashlimit-name flood_list"로 source ip의 목록을 만들어놓은 것인데, 한번 확인해보도록 하겠습니다.


# flood_list 목록

[root@localhost ~]# cat /proc/net/ipt_hashlimit/flood_list 

3       192.168.0.19:0->0.0.0.0:0     1536 160000 1600

____   ___________________________    _____  _______  ______

<1>             <2>                     <3>   <4>     <5>


여려분은 지금 공격이 들어오는 중의 source ip 목록을 확인하고 있습니다. 구분을 짓기 위해서, 약간의 공백을 추가했습니다.

flood_list의 내용을 설명드리자면 아래와 같습니다.


항목 <1> 

  3

 : 패킷이 들어오는것이 멈추고, 3초 뒤에 source ip를 지울 것입니다.

--hashlimit-htable-expire 3000 설정에 의해서 나오는 값입니다.

항목 <2>

  192.168.0.19:0->0.0.0.0:0

 : source ip 192.168.0.19에서 0.0.0.0.0 (내 아이피)들어오는 패킷이라는 것을 의미합니다.

항목 <3>

  1536

 : 일종의 임계치 count라고 보시면 됩니다. 이 숫자가 1600이 되는 순간에 1개의 패킷이 더 들어올 수 있습니다.

항목 <4>

  160000

 : 한번도 접속을 하지 않았을 경우의 count 값입니다.

항목 <5>

  1600

 : 한번 접속할 때 마다 까이는(뺄셈이 되는) 수치입니다. 한번 접속할 때마다 [항목 <3>] 항목의 숫자가 160000에서 1600씩 줄어듭니다.




2-1. 패킷이 들어올때마다 변화하는 [항목 <3>]

만약 192.168.0.19 아이피에서 서버로 접속했을 때 한개의 패킷이 들어왔다면, [항목 <3>]이 아래와 같이 바뀌었을 것입니다.


# flood_list 목록 (패킷이 1개만 들어왔을 때...)

[root@localhost ~]# cat /proc/net/ipt_hashlimit/flood_list 

3       192.168.0.19:0->0.0.0.0:0     158400 160000 1600


패킷 두개가 동시에 들어왔을 때는 [항목 <3>]이 아래와 같이 바뀌었겠죠...


# flood_list 목록 (패킷 두개가 동시에 들어왔을 때...)

[root@localhost ~]# cat /proc/net/ipt_hashlimit/flood_list

3       192.168.0.19:0->0.0.0.0:0     156800 160000 1600


패킷 100개가 동시에 들어왔을 때에는 [항목 <3>]이 0이 될 것입니다.


# flood_list 목록 (패킷 100개가 동시에 들어왔을 때...)

[root@localhost ~]# cat /proc/net/ipt_hashlimit/flood_list 

3       192.168.0.19:0->0.0.0.0:0     0 160000 1600




2-2. 패킷이 안들어오면 변화하는 [항목 <3>]

그리고 패킷이 들어오지 않고 있을 때에는 숫자가 실시간으로 증가합니다.

실시간이 대략적으로 10ms정도라고 계산했을 때, 아래의 명령어와 같은 화면 이 나타날 것입니다.


# flood_list 목록 (패킷이 들어오지 않을 때...)

[root@localhost ~]# cat /proc/net/ipt_hashlimit/flood_list             #0ms가 지난 후...

3       192.168.0.19:0->0.0.0.0:0     320 160000 1600

[root@localhost ~]# cat /proc/net/ipt_hashlimit/flood_list             #10ms가 지난 후...

3       192.168.0.19:0->0.0.0.0:0     640 160000 1600

[root@localhost ~]# cat /proc/net/ipt_hashlimit/flood_list             #20ms가 지난 후...

3       192.168.0.19:0->0.0.0.0:0     960 160000 1600

[root@localhost ~]# cat /proc/net/ipt_hashlimit/flood_list             #30ms가 지난 후...

3       192.168.0.19:0->0.0.0.0:0     1280 160000 1600

....

[root@localhost ~]# cat /proc/net/ipt_hashlimit/flood_list             #50ms(0.05초)가 지난 후...

3       192.168.0.19:0->0.0.0.0:0     1600 160000 1600


물론... 현실적으로 10ms 단위로 명령어를 입력하고 엔터를 누른다는 것은 말이 안되는 것이지만...


10ms가 지날 때 마다 [항목 <3>]의 숫자는 320씩 증가합니다.

그리고 0.05초(50ms) 후, [항목 <3>]의 숫자는 1600이 되어있을 것입니다..

[1초를 20으로 나눔. (1000ms/20=50ms) 0.05초]


1초를 20으로 나누는 이유는 "--hashlimit-above 20/second"설정 때문입니다.


0.05초가 지나 [항목 <3>]이 1600이 되면, 패킷은 한번의 접근이 허용됩니다.


# flood_list 목록 (패킷이 안들어오고 0.05초가 지났을 때...)

[root@localhost ~]# cat /proc/net/ipt_hashlimit/flood_list             #50ms가 지난 후...

3       192.168.0.19:0->0.0.0.0:0     1600 160000 1600


그리고나서 계산상으로는 [0.05*100=5] 5초가 지나면 [항목 <3>]은 160000이 되어 다시 100회 접속이 가능해질 것입니다.

하지만 --hashlimit-htable-expire 3000 설정 때문에, 3초만 지나도 다시 [항목 <3>]이 160000이 되어 100회 접속이 가능해질 것입니다.




3. 그럼 명령어 해석을 다시한번, 더욱 깊게 살펴보자.


# hashlimit를 이용한 패킷 차단 명령어

iptables -I INPUT -d 192.168.0.18 -p tcp --dport 80 -m hashlimit --hashlimit-name flood_list --hashlimit-above 20/second --hashlimit-mode srcip --hashlimit-burst 100 --hashlimit-htable-expire 3000 -j DROP

iptables -I INPUT -d 192.168.0.18 -p tcp --dport 80 -m hashlimit --hashlimit-name flood_list --hashlimit-above 20/second --hashlimit-mode srcip --hashlimit-burst 100 --hashlimit-htable-expire 3000 -j LOG --log-prefix "[Im attack]:"


명령어 해석을 다시한번 정확하게 해보겠습니다.


-------------------첫 번째 줄-------------------

 목적지 192.168.0.18의 80포트로 들어오는 패킷들 중에서,

 -d 192.168.0.18 -p tcp --dport 80

 Source IP 별로,

 --hashlimit-mode srcip

 100개의 패킷을 먼저 허용한 다음에,

 --hashlimit-burst 100

 그 다음에 들어오는 패킷은 0.05초마다 1개의 패킷을 허용한다.

 --hashlimit-above 20/second

 그리고 source IP 목록의 이름은 "flood_list" 이며,

 --hashlimit-name flood_list

 패킷 유입이 멈추고나서 3초가 지나면, source ip를 파기한다.

 --hashlimit-htable-expire 3000


-------------------두 번째 줄-------------------

첫번째 줄과 조건은 동일하고, 그 조건에 일치하면 로그를 남긴다.



만약, source ip 파기 시간을 길게 설정하고싶으신 분들도 계실 것입니다. 그러신 분들은 --hashlimit-htable-expire 값을

4294967295 까지 설정 가능하시니, 참고하시면 될 것 같습니다.

[4294967295ms ≒ 4294967초 ≒ 71582분 ≒ 1193시간 ≒ 49일 ≒ 약 1개월 19일]


만약  --hashlimit-htable-expire 설정을 넣지 않으신 분들은, 패킷이 들어오지 않은 상태에서 1초뒤에 source ip가 사라지니 조심하셔야합니다.




4. 방화벽 설정을 걸기 전에! hashlimit의 한계를 알고, 실 서비스에 적용하자

다른 것과 마찬가지로, 아이피를 변조하거나 DDOS 공격을 받게되면 차단할 수가 없습니다.

1. 특정 아이피에 대해서 패킷을 제한하는 것이기 때문에, 아이피를 변조하거나 DDOS 공격이 들어오면 차단할 수가 없다.


사실 hashlimit 또한 recent와 마찬가지로, 특정 시간이 지나면 추가적인 연결이 가능하다는 점이 장점입니다. 

다만, 이것도 DDOS처럼 전혀 다른 아이피들이 1번씩만 접근을 시도하는 공격은 차단할 수가 없습니다.


마찬가지로 hashlimit도, 아이피 변조 및 DDOS 공격의 경우에는 POSTROUTING 과 PREROUTING을 이용해서 방어가 가능합니다.




5. 안되는 것은 알지만... DDOS가 들어오면 어떻게 될까?


아이피 변조 또는 DDOS가 들어오게 되면 어떠한 상태가 나오는지 확인해보도록 하겠습니다.

공격 프로그램은 따로 공개하지 않고, 그의 결과가 어떻게 나오는지 확인해보도록 하겠습니다.


# netstat 상태 - DOS 공격 (SYN Flooding)

[root@localhost ~]# netstat -anp | more

Active Internet connections (servers and established)

Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    

tcp        0      0 192.168.0.18:80         223.229.216.219:20000   SYN_RECV    -                   

tcp        0      0 192.168.0.18:80         216.216.219.222:6000    SYN_RECV    -                   

tcp        0      0 192.168.0.18:80         219.212.217.221:20000   SYN_RECV    -                   

tcp        0      0 192.168.0.18:80         222.228.221.210:4055    SYN_RECV    -                   

tcp        0      0 192.168.0.18:80         218.226.226.215:9132    SYN_RECV    -                   

tcp        0      0 192.168.0.18:80         222.212.216.226:5476    SYN_RECV    -             


역시 차단이 안됩니다...

이러한 한계점은 반드시 이해하시고 hashlimit 매치를 적용하시기 바랍니다.



5. hashlimit 매치는 어떤 경우에 사용이 가능할까?

hashlimit 매치의 기능은 recent 매치와 상당히 비슷합니다. 대표적으로 아래의 상황에서 이용이 가능합니다.

가) 특정 아이피에서 flooding 공격이 들어오는 경우. (syn flooding / ack flooding / get flooding / etc... )

나) 크롤링으로 내 웹사이트에 트래픽을 유발하는 경우.



마지막으로 hashlimit 매치에서 말하는 패킷 개수는, 접속 횟수를 의미하는 것이 아닙니다. 

통신 과정에서 발생하는 모든 패킷을 의미합니다. 따라서 3way handshake 에 의해서 연결이 성립이 되면, 

최소 한번 접속할 때에는 두개 정도의 패킷이 들어오는 것입니다. 


이를 꼭 명심하신 다음에 제한을 걸어야 합니다.



6. hashlimit vs recent 매치는 어떤 차이가 있을까?

가) hashlimit는 특정 시간이 지나면 source ip를 지울 수 있는 --hashlimit-htable-expire 3000 설정이 있습니다. 하지만 recent에서는 STACK 방식으로 접속이 이루어진 이후(--second 값 만큼)가 지난 다음에 패킷 1개가 허용이 됩니다. 물론 echo -x.x.x.x > /proc/net/xt_recent/oooooo 명령어로 아이피를 수동으로 빼줄 수는 있습니다.

나) hashlimit는 --hashlimit-burst을 최대 10000까지 설정이 가능한데 반해서, recent는 -hitcount가 최대 4294967295번 까지 가능합니다.


뭐... 어떤 매치가 더 좋으냐가 중요한 것이 아니라,

상황에 맞게 적절한 매치를 사용하는 것이 중요하다고 생각합니다.




다음 시간에는 hashlimit가 아닌 그냥 limit 매치를 이용하는 방법에 대해서 알려드리도록 하겠습니다.

limit 매치가 상당히 골때립니다 ㅎㅎ


긴 글 읽어주셔서 감사합니다 ^^

반응형