はじめに
Python Advent Calendar 2021 25日目です。
機械学習でデファクトスタンダードとなっているPythonは、セキュリティとも相性が良くスクリプトなどで使用されてハッカー達に好まれています。
本記事はペネトレーションテストなど、ユーティリティとして使うための方法を踏まえて、ソケット通信の仕組みについて解説しています。
Pythonのバージョンは3.9.4を使用。
ソケット通信の仕組み
ソケットは、コンピュータがOSを介してネットワーク通信を行うために標準化された仕組みです。
ソケットの仕組みを用いてTCP/IPの通信を行い、HTTPなど上位のアプリケーションプロトコルを使用することができます。
また、ネットワーク通信を行うためのインターフェースとして、同じコンピュータ上の他のプロセスとも通信(プロセス間通信)を行ったり、リアルタイムなどで通信を行うWeb開発などで使用されています。
OSI参照モデルでは、セッション層の位置付けとして、トランスポート層の構造を決定します。基本的にTCPで使用されるストリームソケットと、UDPで使用されるデータストリームソケットに大別されます。
なお、rawソケットの場合、ICMPを制御することができます。OSによって、どのソケットタイプをカバーしているか異なるため、低レイヤのソケットプラグラミングを実装する場合は、OSの仕様を確認した方が良いでしょう。
悪意のあるアプリケーションの場合、rawソケットを使用して、IPアドレスの偽装などトリッキーなネットワーク通信を行うこともできます。
ソケットプログラミング
Pythonでソケットプログラミングを行う場合、socketモジュールを使用します。
以下にソケットの仕組みを用いて、クライアントとサーバでプログラムを実行し、通信の仕組みを理解します。
TCPクライアント
- tcp_client.py
import argparse
import socket
def main():
target_host = get_args()
server_connect(target_host)
def get_args():
parser = argparse.ArgumentParser()
parser.add_argument('-host', help='target host')
parser.add_argument('-p', type=int, help='target port')
args = parser.parse_args()
return args
def server_connect(host):
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect((host.host, host.p))
client.send(b"GET / HTTP/1.1\r\nHost: host.host\r\n\r\n")
response = client.recv(4096)
print(response)
if __name__ == '__main__':
main()
TCPサーバ
- tcp_server.py
import argparse
import socket
import threading
def main():
local_host = get_args()
server_start(local_host)
def get_args():
parser = argparse.ArgumentParser()
parser.add_argument('-i', help='bind ip')
parser.add_argument('-p', type=int, help='bind port')
args = parser.parse_args()
return args
def server_start(host):
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((host.i, host.p))
server.listen(5)
print(f"[*] Listening on {host.i} {host.p}")
while True:
client,addr = server.accept()
print(f"[*] Accepted connection from: {addr[0]} {addr[1]}")
client_handler = threading.Thread(target=handle_client, args=(client,))
client_handler.start()
def handle_client(client_socket):
request = client_socket.recv(1024)
print(f"[*] Received: {request}")
client_socket.send(b"ACK!")
client_socket.close()
if __name__ == '__main__':
main()
ソケット通信の検証
上記、プログラムを実行して、ソケット通信を確認します。
ターミナルなどを起動し、サーバとなるtcp_server.py
を実行します。
以下のコマンド実行後、コンソールに80番ポートが起動した出力が確認できます。
# python3 tcp_server.py -i 0.0.0.0 -p 80
[*] Listening on 0.0.0.0 80
次に、クライアントとなるtcp_client.py
を実行します。
以下のコマンド実行後、サーバ側からレスポンスとして、ACK!の文字列が帰ってきます。
python3 tcp_cleint.py -host localhost -p 80
b'ACK!'
再度、サーバ側のコンソールを見ると、クライアントの56855ポートから、GETメソッドのHTTPリクエストが行われていることが確認できます。
[*] Listening on 0.0.0.0 80
[*] Accepted connection from: 127.0.0.1 56855
[*] Received: b'GET / HTTP/1.1\r\nHost: host.host\r\n\r\n'
Wiresharkで通信をキャプチャして見てみると、サーバ側のレスポンスはHTTPではなく、TCPプロトコルを用いてデータを返していることが確認できます。
おわりに
Pythonは万能ナイフです。
netcatがインストールされれていない環境にて、Pythonを用いて代用することができます。