본문으로 바로가기



안녕하세요, SATAz입니다.

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



지난 포스팅에서는 connlimit으로 DOS Flooding 공격을 완화시키는 방법에 대해서 다루었는데요,

이번 포스팅은 IPTables의 recent를 이용하여 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. recent 매치를 이용한 설정을 한번 살펴보자

 IPTables에는 recent라는 매치가 있습니다. recent는, 특정 시간동안 특정 갯수 이상의 패킷을 받으면 DROP할 수 있도록 해줍니다. 


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

iptables -I INPUT -p tcp -s 0.0.0.0/0 -d 192.168.0.18 --dport 80 -m recent --set --name flood_list

iptables -I INPUT -p tcp -s 0.0.0.0/0 -d 192.168.0.18 --dport 80  -m recent --update --seconds 60 --hitcount 5 --name flood_list -j DROP

iptables -I INPUT -p tcp -s 0.0.0.0/0 -d 192.168.0.18 --dport 80 -m recent --update --seconds 60 --hitcount 5 --name flood_list -j LOG --log-prefix "[Im attack]:"


1번째 줄의 해석은, 192.168.0.18(서버)의 80포트로 들어오는 패킷들의 정보를 저장하는 "flood_list" 라는 리스트를 생성(set)한다.

2번째 줄의 해석은, 192.168.0.18(서버)의 80포트로 들어오는 패킷들이 Source IP 기준으로 60초동안에 5번 이상 들어오는 패킷들은 차단한다.

3번째 줄의 해석은, 192.168.0.18(서버)의 80포트로 들어오는 패킷들이 Source IP 기준으로 60초동안에 5번 이상 들어오는 패킷들은 로그를 남긴다.


위의 정책에 의해서, Source IP별로 60초동안에 5번 이상 접근하면 차단하고 로그를 남기는 정책이 생성됩니다.

Source IP의 목록은 "flood_list" 라는 목록에 저장됩니다.

그리고 마지막으로 차단된 시간으로부터 정확하게 60초 뒤에 다시 접속이 가능합니다.


# 명령어의 적용

[root@localhost ~]# iptables -I INPUT -p tcp -s 0.0.0.0/0 -d 192.168.0.18 --dport 80 -m recent --set --name flood_list

[root@localhost ~]# iptables -I INPUT -p tcp -s 0.0.0.0/0 -d 192.168.0.18 --dport 80  -m recent --update --seconds 60 --hitcount 5 --name flood_list -j DROP

[root@localhost ~]# iptables -I INPUT -p tcp -s 0.0.0.0/0 -d 192.168.0.18 --dport 80 -m recent --update --seconds 60 --hitcount 5 --name flood_list -j LOG --log-prefix "[Im attack]:"

[root@localhost ~]# iptables -nL

Chain INPUT (policy ACCEPT)

target     prot opt source               destination         

LOG        tcp  --  0.0.0.0/0            192.168.0.18         state NEW tcp dpt:80 recent: UPDATE seconds: 60 hit_count: 5 name: flood_list side: source mask: 255.255.255.255 LOG flags 0 level 4 prefix "[Im attack]:"

DROP       tcp  --  0.0.0.0/0            192.168.0.18         state NEW tcp dpt:80 recent: UPDATE seconds: 60 hit_count: 5 name: flood_list side: source mask: 255.255.255.255

           tcp  --  0.0.0.0/0            192.168.0.18         state NEW tcp dpt:80 recent: SET name: flood_list side: source mask: 255.255.255.255

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 

Jul 30 12:57:59 localhost kernel: [Im attack]:IN=ens33 OUT= MAC=00:0c:29:a9:76:18:00:0c:29:c8:f6:8a:08:00 SRC=192.168.0.19 DST=192.168.0.18 LEN=52 TOS=0x00 PREC=0x00 TTL=128 ID=32645 DF PROTO=TCP SPT=49405 DPT=80 WINDOW=8192 RES=0x00 SYN URGP=0 

Jul 30 12:57:59 localhost kernel: [Im attack]:IN=ens33 OUT= MAC=00:0c:29:a9:76:18:00:0c:29:c8:f6:8a:08:00 SRC=192.168.0.19 DST=192.168.0.18 LEN=52 TOS=0x00 PREC=0x00 TTL=128 ID=32646 DF PROTO=TCP SPT=49406 DPT=80 WINDOW=8192 RES=0x00 SYN URGP=0 

Jul 30 12:58:00 localhost kernel: [Im attack]:IN=ens33 OUT= MAC=00:0c:29:a9:76:18:00:0c:29:c8:f6:8a:08:00 SRC=192.168.0.19 DST=192.168.0.18 LEN=52 TOS=0x00 PREC=0x00 TTL=128 ID=32647 DF PROTO=TCP SPT=49407 DPT=80 WINDOW=8192 RES=0x00 SYN URGP=0 


로그가 잘 찍히네요 ^^



2. recent 매치에 걸린 flood_list 라는 목록을 한번 살펴보자.

recent 매치에 의해서 생성(--set)된 목록은 /proc/net/xt_recent/[목록이름] 에 저장됩니다. 목록을 cat으로 한번 살펴보겠습니다.


# flood_list 목록

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

src=192.168.0.19 ttl: 128 last_seen: 4309148388 oldest_pkt: 12 4309145814, 4309145814, 4309146313, 4309146313, 4309146843, 4309146843, 4309147358, 4309147358, 4309147889, 4309147889, 4309148388, 4309148388, 4309143318, 4309143318, 4309144222, 4309144222, 4309144753, 4309144753, 4309145252, 4309145252


flooding_list에 나오는 목록은 처음 접속할 때부터 쌓이기 시작합니다. 그리고 5개가 되었을 때부터 차단이 되기 시작하죠.

위의 목록중에서 last_seen 항목이 보이실텐데요, 마지막으로 접근한 시간을 TimeStamp로 저장해두었다고 보시면 됩니다.


TimeStamp만으로 보면 시간을 알 수가 없습니다. 복잡한 계산을 통해서 시간을 알아낼 수 있는데요,

아래의 스크립트를 활용하시면 마지막 접근 시간을 볼 수 있습니다.



2-1. BASH로 TIMSTAMP 계산하기


# TIMESTAMP 계산하는 스크립트

#!/bin/bash


FILE=/proc/net/xt_recent/flood_list #[목록 이름]


 #This is file name of recent module output. It may vary on your system (like iplist)


TICKS=$(grep CONFIG_HZ= /boot/config-$(uname -r)|awk -F= '{print $2}') # Get current ticks per sec


printit()

{

    Len=`echo $1|wc -c`

    Date=$DATE

    Dot="."

    Loop=`echo 50-$Len|bc`

    loop=0

    while [ $loop -le $Loop ]

    do

    loop=`echo $loop+1|bc`

    Dot=`echo $Dot.`

    done

    echo "$1$Dot$DATE"

}

cat $FILE|while read LINE

do

    IP=`echo $LINE|awk '{print $1}'|awk -F= {'print $2'}`

    DATE=$(date -d@$(date +'%s'-$(echo \($(cat /proc/timer_list|grep -m1 -E '^jiffies'|cut -d" " -f2)-$(awk '{print $5}' $FILE)\)/$TICKS|bc)|bc))

    printit $IP $DATE

done

출처 : https://serverfault.com/questions/699072/what-is-timestamp-format-of-iptables-recent-proc-file


스크립트를 한번 실행시켜보겠습니다.


# 스크립트 결과값

[root@localhost ~]# sh script.sh 

192.168.0.19.......................................2017. 07. 30. (일) 13:08:27 KST



2-2. Python으로 TIMESTAMP 계산하기


파이썬 프로그램 코드는 저작권 문제로... 스크립트가 있는 링크만 남겨놓도록 하겠습니다.

출처 및 프로그램 코드 받는 곳 : https://github.com/peppelinux/iptables_xt_recent_parser/blob/master/xt_recent_parser.py


# TIMESTAMP 계산하는 프로그램 실행 결과 - 공격 전

[root@localhost ~]# python3 ./script2.py

XT_RECENT python parser

<giuseppe.demarco@unical.it>



Standard readable view:

192.168.0.19, last seen: 2017-07-30 13:08:27 after 2 connections


CSV view:

ip_src;last_seen;connections;deltas_mean;delta_seconds

192.168.0.19;2017-07-30 13:08:27.248387;2;3.0;3


한번 공격을 넣어본 다음에 다시 실행시켜볼까요?


# TIMESTAMP 계산하는 프로그램 실행 결과 - 공격 후

[root@localhost ~]# python3 script2.py 

XT_RECENT python parser

<giuseppe.demarco@unical.it>



Standard readable view:

192.168.0.19, last seen: 2017-07-30 14:03:22 after 20 connections


CSV view:

ip_src;last_seen;connections;deltas_mean;delta_seconds

192.168.0.19;2017-07-30 14:03:22.596636;20;4547.315789473684;0,0,0,0,0,0,0,0,86399,0,0,0,0,0,0,0,0,0,0



recent의 기본 설정이  ip_pkt_list_tot=20 이기 때문에, after 20 connections (20개)가 최대로 표시되고 있습니다.



3. 만약 차단되어있는 아이피를 풀어주고 싶다면?

아래와 같은 간단한 명령어로 flood_list 목록에서 아이피를 제거할 수가 있습니다.


# 차단 목록에서 아이피 빼는 법

echo -192.168.0.19[차단된 아이피] > /proc/net/xt_recent/flood_list[목록이름]


# 명령어의 적용

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

src=192.168.0.19 ttl: 64 last_seen: 4313148640 oldest_pkt: 13 4313148638, 4313148638, 4313148638, 4313148639, 4313148639, 4313148639, 4313148639, 4313148639, 4313148639, 4313148640, 4313148640, 4313148640, 4313148640, 4313148638, 4313148638, 4313148638, 4313148638, 4313148638, 4313148638, 4313148638

[root@localhost ~]# 

[root@localhost ~]# echo -192.168.0.19 > /proc/net/xt_recent/flood_list 

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

[root@localhost ~]# 


이렇게 차단된 아이피를 목록에서 지워준다고 해도, 60동안에 5번 이상 접근을 또 하면 다시 차단될 것입니다.


또는 아래의 명령어로, flood_list 목록의 아이피들을 전부다 삭제할 수도 있습니다.


# 차단 목록에서 모든 아이피를 지우는 법

echo / > /proc/net/xt_recent/flood_list



4. --update와 --rcheck의 차이점

맨 위에 명시한 명령어에서, --update  옵션을 사용한 것을 확인하실 수 있습니다. 그런데 recent 매치는 update옵션과 rcheck 옵션 두 가지를 지원하고 있습니다.


각각의 설정을 하고난 뒤에, 공격을 시도했을 때의 결과를 통해서 차이점을 설명드리도록 하겠습니다.


# update 옵션으로 적용 후의 DOS 공격 상황

[root@localhost ~]# python3 ./script2.py

XT_RECENT python parser

<giuseppe.demarco@unical.it>



Standard readable view:

192.168.0.19, last seen: 2017-07-30 14:46:24 after 20 connections


CSV view:

ip_src;last_seen;connections;deltas_mean;delta_seconds

192.168.0.19;2017-07-30 14:46:24.755145;20;4547.210526315789;0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,86394,0,0

[root@localhost ~]# python3 ./script2.py

XT_RECENT python parser


--update 옵션을 적용하고난 뒤에 공격이 들어왔을 때에는, 20 connections가 이루어지고난 뒤에도 last seen(날짜)이 계속 업데이트됩니다.

패킷이 차단된 후에 새로운 접근이 있을 경우에도 count 및 timestamp를 계속 업데이트 합니다.


# rcheck 옵션으로 적용 후의 DOS 공격 상황

[root@localhost ~]# python3 ./script2.py

XT_RECENT python parser

<giuseppe.demarco@unical.it>



Standard readable view:

192.168.0.19, last seen: 2017-07-30 14:47:05 after 5 connections


CSV view:

ip_src;last_seen;connections;deltas_mean;delta_seconds

192.168.0.19;2017-07-30 14:47:05.687905;5;0.0;0,0,0,0

[root@localhost ~]# python3 ./script2.py

XT_RECENT python parser


--rcheck 옵션을 적용하고난 뒤에 공격이 들어왔을 때에는, 아무리 공격이 들어와도 5 connection까지만 last seen(날짜)을 목록에 업데이트합니다. ( 5 connection은 hit-count로 설정된 값입니다.)

패킷이 차단된 후에는 flood_list 에는 count 및 timestamp를 업데이트하지 않습니다. 마지막으로 차단된 순간의 timestamp만을 기록하죠.



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

위와같이... 패킷이 차단되는 것을 확인하실 수 있습니다. 이제는 recent의 한계를 말씀드리도록 하겠습니다.

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


사실 connlimit에 비해 지정한 시간동안에 들어오는 패킷량을 제한하는 것이기 때문에, 특정 시간이 지나면 추가적인 연결이 가능하다는 점이 장점입니다. 다만, 이것도 DDOS처럼 전혀 다른 아이피들이 1번씩만 접근을 시도하는 공격은 차단할 수가 없습니다.


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

PREROUTING에 대해서는 추후에 포스팅할 예정이니 기다려주세요 ^^



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


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

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


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

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

Active Internet connections (servers and established)

Proto Recv-Q Send-Q Local Address           Foreign Address         State      

tcp        0      0 192.168.0.18:80         223.220.229.211:5476    SYN_RECV   

tcp        0      0 192.168.0.18:80         217.219.224.216:1026    SYN_RECV   

tcp        0      0 192.168.0.18:80         223.218.213.226:1756    SYN_RECV   

tcp        0      0 192.168.0.18:80         223.219.229.220:1999    SYN_RECV   

tcp        0      0 192.168.0.18:80         219.221.220.212:4055    SYN_RECV   

tcp        0      0 192.168.0.18:80         222.226.221.222:1756    SYN_RECV   

tcp        0      0 192.168.0.18:80         221.210.213.214:6000    SYN_RECV   

tcp        0      0 192.168.0.18:80         216.210.211.222:1026    SYN_RECV   

tcp        0      0 192.168.0.18:80         213.224.225.214:2873    SYN_RECV   

tcp        0      0 192.168.0.18:80         215.214.222.212:1739    SYN_RECV                


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

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



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

recent 매치는 대표적으로 아래의 상황에서 이용이 가능합니다.

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

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



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

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

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


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


개인적으로 recent 매치 룰은 참으로 쓸만한 기능이 아닐까 싶습니다 ^^


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

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




댓글을 달아 주세요