ScapyでPort Forwardを実現
今回のPythonもScapyのネタ。Port Forwardを実現してみる。
ネットワーク
今回も、VirtualBoxのVM利用。
NAT環境下とみなし、Port Forwardを行うNATルーターが中央に位置する。左側がNATの外側、右側がNATの内側。左側のPCからNATルーターのWAN側IPアドレス(10.1.1.254)の特定TCPポート(ここでは、1111と2222)にアクセスすると、設定したNAT内側PCに転送する(1111:192.168.10.1、2222:192.168.10.2)ものである。
ソースコード
参考情報は下記であり、多くの部分を利用させて頂いた。
import
from scapy.all import *
import select
import os
ここは特記事項なし。
IPパケット再作成
# Recreate IP Packet #
def recreate_ip_packet(ip_pkt):
# Removal Check-Sum
if ip_pkt.haslayer(TCP):
del ip_pkt.getlayer(TCP).chksum
if ip_pkt.haslayer(UDP):
del ip_pkt.getlayer(UDP).chksum
if ip_pkt.haslayer(ICMP):
del ip_pkt.getlayer(ICMP).chksum
del ip_pkt.chksum
result = IP(ip_pkt.build())
return result
IPアドレス変更などによるIPパケット再作成。ここは、以前の記事「ろうとるがPythonを扱う、、(その20:Scapyで改ざん)」参照。
定義
### Main ###
# Inside Terminal Information #
in_ip_addr1 = '192.168.10.1'
in_ip_port1 = 1111
in_ip_addr2 = '192.168.10.2'
in_ip_port2 = 2222
# NAT Table List
nat_table1 = {}
nat_table2 = {}
# Transform List: IP address, Dest Port, NAT Table
trans_list = [[in_ip_addr1, in_ip_port1, nat_table1],\
[in_ip_addr2, in_ip_port2, nat_table2]]
# NAT Outside & Inside Mac Address & IP Address
out_eth_mac = get_if_hwaddr('enp0s3')
out_eth_ip = get_if_addr('enp0s3') # 10.1.1.254
in_eth_mac = get_if_hwaddr('enp0s8')
in_eth_ip = get_if_addr('enp0s8') # 192.168.10.254
- 転送先IPアドレスおよびポート
- NATテーブル
- NATルーターのMacアドレスおよびIPアドレス取得
ソケット定義、パケット受信
# Layer2 socket
out_sock = conf.L2socket(iface='enp0s3')
in_sock = conf.L2socket(iface='enp0s8')
# Not send RST in order not to disconnect from NAT router
os.system('sudo iptables -A OUTPUT -p tcp --tcp-flags RST RST -j DROP')
while True:
# select()
read_sockets, _, _ = select.select([out_sock, in_sock], [], [])
for s in read_sockets:
# Data receive
p = s.recv()
# Drop non TCP packet
if not p or not p.haslayer(TCP):
#print("Not TCP packet")
continue
- NAT外側・内側のソケット
- TCPセッションを維持するため、NATルーターからRSTを送信しない
- これについても、上述した参考情報を参照
- selectによるパケット待受け
- データ受信(TCP以外は捨てる(何もしない))
外側からのパケット処理
# From Outside to Inside (Initiate from Outside)
if p.dst == out_eth_mac:
ip_pkt_out_to_in = p.getlayer(IP)
found = 0
for i in range(len(trans_list)):
if ip_pkt_out_to_in.dport == trans_list[i][1]:
found = 1
# Keep Source IP and Source Port at NAT Table
trans_list[i][2][ip_pkt_out_to_in.src] = ip_pkt_out_to_in.sport
print(trans_list[i][2])
# Replace IP address with local IP adress
ip_pkt_out_to_in.dst = trans_list[i][0]
# Recreate IP address
pkt_to_ip_addr = recreate_ip_packet(ip_pkt_out_to_in)
print(pkt_to_ip_addr)
send(pkt_to_ip_addr, verbose=0)
break
if found == 0:
print("Closed Port: " + str(ip_pkt_out_to_in.dport))
- 宛先MacアドレスがNATルーターの外側
- IPパケットを取得(ip_pkt_out_to_int)
- 受信したパケットの宛先ポートがNATテーブルに含まれている場合
- 送信側IPアドレスと送信ポートをテーブルに格納
- 宛先IPアドレスを転送先IPアドレスに置き換え
- 前述した関数recreate_ip_packet()によりIPパケットを再生成(pkt_to_ip_addr)
- 送信
- 受信したパケットの宛先ポートがNATテーブルに含まれていない場合
- ポートCloseのメッセージ表示
内側からのパケット処理
# From Inside to Outside
elif p.dst == in_eth_mac:
ip_pkt_in_to_out = p.getlayer(IP)
found = 0
for i in range(len(trans_list)):
if ip_pkt_in_to_out.src == trans_list[i][0]:
found = 1
# Get destination port from destination IP at NAT Table
dport = trans_list[i][2].get(ip_pkt_in_to_out.dst)
#print(dport)
if dport is None:
print("No data in NAT Table")
continue
# Replace IP address with send IP address
ip_pkt_in_to_out.src = out_eth_ip
# Recreate IP address
pkt_from_ip_addr = recreate_ip_packet(ip_pkt_in_to_out)
print(pkt_from_ip_addr)
send(pkt_from_ip_addr, verbose=0)
break
if found == 0:
print("Not allowed inside IP address")
- 宛先MacアドレスがNATルーターの内側
- IPパケットを取得(ip_pkt_in_to_out)
- 送信先IPアドレスがNATテーブルに含まれている場合
- 宛先ポートがテーブルに含まれているか確認
- 内側起動のデータ通信はここでは考慮していない
- 送信IPアドレスをNATルーター外側IPアドレスに置き換え
- IPパケットを再生成(pkt_from_ip_addr)
- 送信
- 宛先ポートがテーブルに含まれているか確認
- 送信先IPアドレスがNATテーブルに含まれていない場合
- 登録されていないメッセージを表示
全体
from scapy.all import *
import select
import os
# Recreate IP Packet #
def recreate_ip_packet(ip_pkt):
# Removal Check-Sum
if ip_pkt.haslayer(TCP):
del ip_pkt.getlayer(TCP).chksum
if ip_pkt.haslayer(UDP):
del ip_pkt.getlayer(UDP).chksum
if ip_pkt.haslayer(ICMP):
del ip_pkt.getlayer(ICMP).chksum
del ip_pkt.chksum
result = IP(ip_pkt.build())
return result
### Main ###
# Inside Terminal Information #
in_ip_addr1 = '192.168.10.1'
in_ip_port1 = 1111
in_ip_addr2 = '192.168.10.2'
in_ip_port2 = 2222
# NAT Table List
nat_table1 = {}
nat_table2 = {}
# Transform List: IP address, Dest Port, NAT Table
trans_list = [[in_ip_addr1, in_ip_port1, nat_table1],\
[in_ip_addr2, in_ip_port2, nat_table2]]
# NAT Outside & Inside Mac Address & IP Address
out_eth_mac = get_if_hwaddr('enp0s3')
out_eth_ip = get_if_addr('enp0s3') # 10.1.1.254
in_eth_mac = get_if_hwaddr('enp0s8')
in_eth_ip = get_if_addr('enp0s8') # 192.168.10.254
# Layer2 socket
out_sock = conf.L2socket(iface='enp0s3')
in_sock = conf.L2socket(iface='enp0s8')
# Not send RST in order not to disconnect from NAT router
os.system('sudo iptables -A OUTPUT -p tcp --tcp-flags RST RST -j DROP')
while True:
# select()
read_sockets, _, _ = select.select([out_sock, in_sock], [], [])
for s in read_sockets:
# Data receive
p = s.recv()
# Drop non TCP packet
if not p or not p.haslayer(TCP):
#print("Not TCP packet")
continue
# From Outside to Inside (Initiate from Outside)
if p.dst == out_eth_mac:
ip_pkt_out_to_in = p.getlayer(IP)
found = 0
for i in range(len(trans_list)):
if ip_pkt_out_to_in.dport == trans_list[i][1]:
found = 1
# Keep Source IP and Source Port at NAT Table
trans_list[i][2][ip_pkt_out_to_in.src] = ip_pkt_out_to_in.sport
print(trans_list[i][2])
# Replace IP address with local IP adress
ip_pkt_out_to_in.dst = trans_list[i][0]
# Recreate IP address
pkt_to_ip_addr = recreate_ip_packet(ip_pkt_out_to_in)
print(pkt_to_ip_addr)
send(pkt_to_ip_addr, verbose=0)
break
if found == 0:
print("Closed Port: " + str(ip_pkt_out_to_in.dport))
# From Inside to Outside
elif p.dst == in_eth_mac:
ip_pkt_in_to_out = p.getlayer(IP)
found = 0
for i in range(len(trans_list)):
if ip_pkt_in_to_out.src == trans_list[i][0]:
found = 1
# Get destination port from destination IP at NAT Table
dport = trans_list[i][2].get(ip_pkt_in_to_out.dst)
#print(dport)
if dport is None:
print("No data in NAT Table")
continue
# Replace IP address with send IP address
ip_pkt_in_to_out.src = out_eth_ip
# Recreate IP address
pkt_from_ip_addr = recreate_ip_packet(ip_pkt_in_to_out)
print(pkt_from_ip_addr)
send(pkt_from_ip_addr, verbose=0)
break
if found == 0:
print("Not allowed inside IP address")
検証
事前準備
Scapyによる転送を実施するため、NATルーターにてルーティングを無効化する。
$ sudo sysctl -w net.ipv4.ip_forward=0
Port 1111へのフォワード
netcatによる確認。Server(192.168.10.1)にて、ループバックを実現する下記コマンド実行。
$ rm -f /tmp/f; mkfifo /tmp/f
$ cat /tmp/f | nc -l -p 1111 > /tmp/f
Client(10.1.1.1)で、相手先としてNATルーター外側IPアドレスを指定した時の実行の様子。
$ nc 10.1.1.254 1111
test
test
123
123
NATルーターで、指定された宛先である192.168.10.1へPort Forwardされ、"test"と入力して"test"がエコーバックしている。このときのNATルーターのログ(ソースコード:print)の状況は下記となる。
{'10.1.1.1': 52600}
IP / TCP 10.1.1.1:52600 > 192.168.10.1:1111 S
IP / TCP 10.1.1.254:1111 > 10.1.1.1:52600 SA
{'10.1.1.1': 52600}
IP / TCP 10.1.1.1:52600 > 192.168.10.1:1111 A
{'10.1.1.1': 52600}
IP / TCP 10.1.1.1:52600 > 192.168.10.1:1111 PA / Raw
IP / TCP 10.1.1.254:1111 > 10.1.1.1:52600 A
IP / TCP 10.1.1.254:1111 > 10.1.1.1:52600 PA / Raw
{'10.1.1.1': 52600}
IP / TCP 10.1.1.1:52600 > 192.168.10.1:1111 A
{'10.1.1.1': 52600}
IP / TCP 10.1.1.1:52600 > 192.168.10.1:1111 PA / Raw
IP / TCP 10.1.1.254:1111 > 10.1.1.1:52600 PA / Raw
{'10.1.1.1': 52600}
IP / TCP 10.1.1.1:52600 > 192.168.10.1:1111 A
Port 2222へのフォワード
httpサーバーによる確認。Server(192.168.10.2)にて、待受けポート2222を指定し、httpサーバーを起動。Client(10.1.1.1および10.1.1.2)のブラウザにてアクセス。NATルーターで、指定された宛先である192.168.10.2へPort Forwardされ、HTTPレスポンスが得られる。
NATルーターのログの状況は下記となる。
{'10.1.1.1': 48826}
IP / TCP 10.1.1.1:48826 > 192.168.10.2:2222 S
IP / TCP 10.1.1.254:2222 > 10.1.1.1:48826 SA
{'10.1.1.1': 48826}
IP / TCP 10.1.1.1:48826 > 192.168.10.2:2222 A
{'10.1.1.1': 48826}
IP / TCP 10.1.1.1:48826 > 192.168.10.2:2222 PA / Raw
IP / TCP 10.1.1.254:2222 > 10.1.1.1:48826 A
IP / TCP 10.1.1.254:2222 > 10.1.1.1:48826 PA / Raw
IP / TCP 10.1.1.254:2222 > 10.1.1.1:48826 A / Raw
{'10.1.1.1': 48826}
IP / TCP 10.1.1.1:48826 > 192.168.10.2:2222 A
IP / TCP 10.1.1.254:2222 > 10.1.1.1:48826 FPA / Raw
{'10.1.1.1': 48826}
IP / TCP 10.1.1.1:48826 > 192.168.10.2:2222 A
{'10.1.1.1': 48826}
IP / TCP 10.1.1.1:48826 > 192.168.10.2:2222 FA
IP / TCP 10.1.1.254:2222 > 10.1.1.1:48826 A
{'10.1.1.1': 48826, '10.1.1.2': 59426}
IP / TCP 10.1.1.2:59426 > 192.168.10.2:2222 S
IP / TCP 10.1.1.254:2222 > 10.1.1.2:59426 SA
{'10.1.1.1': 48826, '10.1.1.2': 59426}
IP / TCP 10.1.1.2:59426 > 192.168.10.2:2222 A
{'10.1.1.1': 48826, '10.1.1.2': 59426}
IP / TCP 10.1.1.2:59426 > 192.168.10.2:2222 PA / Raw
IP / TCP 10.1.1.254:2222 > 10.1.1.2:59426 A
IP / TCP 10.1.1.254:2222 > 10.1.1.2:59426 PA / Raw
IP / TCP 10.1.1.254:2222 > 10.1.1.2:59426 A / Raw
{'10.1.1.1': 48826, '10.1.1.2': 59426}
IP / TCP 10.1.1.2:59426 > 192.168.10.2:2222 A
IP / TCP 10.1.1.254:2222 > 10.1.1.2:59426 FPA / Raw
{'10.1.1.1': 48826, '10.1.1.2': 59426}
IP / TCP 10.1.1.2:59426 > 192.168.10.2:2222 A
{'10.1.1.1': 48826, '10.1.1.2': 59426}
IP / TCP 10.1.1.2:59426 > 192.168.10.2:2222 FA
IP / TCP 10.1.1.254:2222 > 10.1.1.2:59426 A
Port Forwardの対象ではないポートへのアクセス
Client(10.1.1.1)で下記実行。
$ nc 10.1.1.254 3333
NATルーターのログ。
Closed Port: 3333
NAT内側からのアクセス
未対象のIPアドレス(192.168.10.3)からの送信
$ echo | nc 10.1.1.1 3333
NATルーターのログ。
Not allowed inside IP address
未登録状況でのIPアドレス(192.168.10.2)からの送信
$ echo 123 | nc 10.1.1.1 1234
NATルーターのログ。
No data in NAT Table
EOF