본문 바로가기

Language/Python

iptables nfqueue 를 이용한 python packet 처리

반응형

개요


 

  • Packet payload에 연속 된 HEX 값을 가지는 Garbage DATA packet을 drop 하기 위한 목적

  • iptables nfqueue 모듈을 이용하여 python에서 packet을 처리하고자 함.

 

필터링 조건


iptables

  • 3way-handshaking 후에 client에서 인입되는 첫번째 psh ack packet만을 nfqueue로 보내 python에서 처리

  • iptables 정책 설명
    1. Client SYN 전송 -> Client src ip를 SYN2 set -> SERVER로 SYN 전송
    2. SERVER에 Client로 SYN ACK 전송
    3. Client ACK 전송 -> Client src ip가 SYN2 에 있으면 ACK2 set 하고 SYN2에서 삭제 -> SERVER로 ACK 전송
    4. Client PSH ACK 전송 -> Client src ip가 ACK2에 있으면 nfqueue로 보내고 ACK2에서 삭제
    5. python에서 필터링 조건에 의해 drop or forward 결정

 

python

  • packet payload에 처음 3 Byte 값을 서로 비교하여 같은 HEX 값을 가지면 drop
    • payload.data1 == payload.data2 == payload.data3 => pkt.drop()
  • packet payload에 2번째 Byte 값이 특정 hex값을 가지지 않으면 drop
    • payload.data2 != [<사용자 정의>] => pkt.drop()

 

 

설치 및 설정


iptables

  • 인라인 방식으로 물리 구성되고 transparent 하고 동작하는 linux 머신이므로 iptables FORWARD 체인에 설정
  • WHITELIST chain : 최상위 우선순위를 가지는 chain으로 python에서 조건을 모두 만족하는 src ip는 WHITELIST chain 으로 accept 정책이 들어감.
  • BLACKLIST chain : WHITELIST 다음 순위를 가지는 chain으로 python에서 조건이 어긋나는 src ip는 BLACKLIST chain으로 drop 정책이 들어감
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [845:52660]
:OUTPUT ACCEPT [0:0]
:LEARN2 - [0:0]
:LEARN2_ACK - [0:0]
:LEARN2_PA - [0:0]
:SERVICE - [0:0]
:WHITELIST - [0:0]
:BLACKLIST - [0:0]
:LIMIT - [0:0]
:CONTROL - [0:0]
:CONNLIMIT - [0:0]

# 모든 packet을 SERVICE chain 으로 보냄
-A FORWARD -j SERVICE


# WHITELIST chain은 python에서 처리하며 무조건 accept 되야하는src ip.
-A SERVICE -j WHITELIST
# BLACKLIST chain은 python에서 처리하며 무조건 drop 되야하는 src ip.
-A SERVICE -j BLACKLIST
# CONTROL chain으로 보낼 dstip,port를 설정
-A SERVICE -d <dst ip> -p tcp --dport <dst port> -j CONTROL


-A CONTROL -p tcp -m state --state NEW -j LIMIT
-A CONTROL -p tcp -m state --state NEW -j CONNLIMIT
-A CONTROL -j LEARN2
-A CONTROL -p tcp -m state --state RELATED,ESTABLISHED -j ACCEPT


# limit 모듈을 이용하여 패킷 흐름 제한(qos)
-A LIMIT -d <dst ip> -p tcp --dport <dst port> -m limit --limit 10/s --limit-burst 10 -j RETURN

# connlimit 모듈을 이용하여 connection 제한
-A CONNLIMIT -d <dst ip> -p tcp --dport <dst port> -m connlimit --connlimit-above 3 --connlimit-mask 32 --connlimit-saddr -j REJECT --reject-with tcp-reset
-A CONNLIMIT -j RETURN


# syn flag를 가지는 packet의 src ip를 SYN2에 넣음.
-A LEARN2 -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -m recent --set --name SYN2 --mask 255.255.255.255 --rsource -j RETURN
# ack flag를 가지는 packet의 src ip를 SYN2에서 비교하여 있으면 LEARN2_ACK으로 보냄
-A LEARN2 -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG ACK -m recent --rcheck --name SYN2 --mask 255.255.255.255 --rsource -j LEARN2_ACK
# psh ack flag를 가지는 packet의 src ip를 ACK2와 비교하여 있으면 LEARN2_PA로 보냄
-A LEARN2 -p tcp -m tcp --tcp-flags PSH,ACK PSH,ACK -m recent --rcheck --name ACK2 --mask 255.255.255.255 --rsource -j LEARN2_PA
# ack flag를 가지는 packet의 src ip를 ACK2와 비교하여 있으면, 
# 그리고 packet length가 60~1514Bytes 사이값이 아니면 return
-A LEARN2 -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG ACK -m recent --rcheck --name ACK2 --mask 255.255.255.255 --rsource -m length ! --length 60:1514 -j RETURN
# ACK2에 있으면 DROP
-A LEARN2 -p tcp -m recent --rcheck --name ACK2 --mask 255.255.255.255 --rsource -m comment --comment ACK_DROP -j DROP


# ACK2 에 src ip를 set
-A LEARN2_ACK -p tcp -m recent --set --name ACK2 --mask 255.255.255.255 --rsource
# SYN2에 src ip를 삭제
-A LEARN2_ACK -p tcp -m recent --remove --name SYN2 --mask 255.255.255.255 --rsource
-A LEARN2_ACK -j RETURN


# ACK2에 src ip를 삭제
-A LEARN2_PA -p tcp -m recent --remove --name ACK2 --mask 255.255.255.255 --rsource
# nfqueue 1번에 packet 보냄
-A LEARN2_PA -p tcp -m tcp --tcp-flags PSH,ACK PSH,ACK -j NFQUEUE --queue-num 1
COMMIT

 

python

  • 모듈 설치
pip3 install NetfilterQueue
pip3 install python-iptables
yum install libnetfilter_queue-devel

 

  • 소스
#!/usr/bin/python3
import struct
import sys
import datetime
import iptc
import subprocess

from time import time
from netfilterqueue import NetfilterQueue

# 로그 
def log_insert(result,srcip,srcport,data,dstip,dstport):
	now = datetime.datetime.now()
	b = now.strftime('%Y-%m-%d %H:%M:%S')
	
	a = '[{}] [{}] [{}:{}] [{}] [{}:{}]'.format(result, b, srcip, srcport, data, dstip, dstport)
               
	try:
		f = open("/opt/nfqueue/String_Checker.log", 'a')  
	except:
		print("ERROR: can't open file")
		sys.exit(1)
	f.write(a)
	f.write('\n')
	f.close()

# iptables 삽입
def ipt_insert(srcip,target,target_chain,expire):
	num1 = 0
	if num1 == 0:
		b_time = expire
		n_time = int(time())
		f_time = n_time+b_time
		rule = iptc.Rule()
		rule.protocol = "tcp"
		rule.target = rule.create_target(target)
		if target == 'REJECT':
			rule.target.reject_with = "tcp-reset"
		rule.src = srcip
		match = rule.create_match("comment")
		match.set_parameter('comment', "expire=%s" % f_time)
		chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), target_chain)
		chain.insert_rule(rule)

# src ip를 string으로 변환
def ip_to_string(ip):
	return ".".join(map(lambda n: str(ip>>n & 0xff), [24,16,8,0]))

# packet 값 추출 및 필터링 조건
def print_and_accept(pkt):
	pl = pkt.get_payload()
	src_ip = struct.unpack('>I', pl[12:16])[0]
	dst_ip = struct.unpack('>I', pl[16:20])[0]
	tcp_offset = (struct.unpack('>B', pl[0:1])[0] & 0xf) * 4
	tmp = struct.unpack('>B', pl[tcp_offset+12:tcp_offset+13])[0]
	data_offset = ((tmp & 0xf0) >> 4) * 4 
	src_port = struct.unpack('>H', pl[tcp_offset+0:tcp_offset+2])[0]
	dst_port = struct.unpack('>H', pl[tcp_offset+2:tcp_offset+4])[0]
	data1 = pl[tcp_offset + data_offset:tcp_offset + data_offset + 1]
	data2 = pl[tcp_offset + data_offset + 1:tcp_offset + data_offset + 2]
	data3 = pl[tcp_offset + data_offset + 2:tcp_offset + data_offset + 3]
	data_log = pl[tcp_offset + data_offset:tcp_offset + data_offset + 6]

	num = 0
	srcip = "%s/255.255.255.255" % ip_to_string(src_ip)

	# packet payload data 1~3Byte 각 값이 같을 경우 num=0
	if data1.hex() == data2.hex() and data3.hex() == data2.hex():	
		num = 0
    # packet payload data 2Byte 자리 값이 var중 일치하지 하면 num+=1
	else:
		var = ["00","01","02","03","04","05","45"]
		for plo in var:
			if data2.hex() == plo:
				num = num+1
                
    # iptables 정책 삽입 및 로그 기록            
	if num == 0:
		pkt.drop()
		ipt_insert(srcip,'REJECT','BLACKLIST',60)
		log_insert('REJECT',ip_to_string(src_ip),src_port,data_log.hex(),ip_to_string(dst_ip),dst_port)
	else:
		ipt_insert(srcip,'ACCEPT','WHITELIST',172800)
		log_insert('ACCEPT',ip_to_string(src_ip),src_port,data_log.hex(),ip_to_string(dst_ip),dst_port)
		pkt.accept()

# nfqueue bind
nfqueue = NetfilterQueue()
nfqueue.bind(1, print_and_accept)

try:
	nfqueue.run()
except KeyboardInterrupt:
	print

 

 

WHITELIST, BLACKLIST 정책 삭제

 

  • python에서 iptables로 정책 삽입시 comment로 시간값을 주고 넣음
> iptables -L BLACKLIST -nv 
0     0 DROP       all  --  *      *       1.1.1.1              0.0.0.0/0            /* expire=1652428067 */
  • 정책 삭제를 위한 것으로 정책 삽입시 "현 unixtime + 정책을 유지할 시간(unixtime)" 값을 comment로 넣음
match.set_parameter('comment', "expire=%s" % f_time)
  • 정책 삭제 스크립트
iptables -L BLACKLIST -n --line-numbers  | perl -ne 'next unless /(^\d+).*expire=(\d+)/; if ($2 < time)tables -D 'BLACKLIST' $1\n"; }'

 

반응형