本記事は、オライリージャパンから発行されている「サイバーセキュリティプログラミング ―Pythonで学ぶハッカーの思考(原題:Black Hat Python)」の学習メモとして、書籍ではPython2で書かれていますが、自分なりに解釈した上でPython3に書き直しをしています。
今回はPythonのsocketモジュールを使用したTCP通信(クライアント)の基本について学んでいきます。
- socketについて
- TCPクライアントの実装
- socketの生成
- TCPコネクションの作成(3 Way Handshake)
- sslの実装
- GETリクエストの送信
- レスポンスの表示
- 最後に
- 参考書籍
socketについて
Socket(ソケット)はTCP/IPの誕生時にBSD UNIXに実装され、その使い勝手の良さから様々なOSにも実装され、今日に至るAPIです。
なお、BSDとは「Berkeley Software Distribution」の略で、カリフォルニア大学バークレー校で1977年から1995年の間に開発されたディストリビューションになります。
TCPクライアントの実装
Python2では文字列とバイト列が区別されていないため、データの送受信はそのまま文字列としていましたが、Python3ではバイト列としなければなりません。
今回はせっかくなので、HTTPS通信をするためにsslモジュールをインポートし、TLSでラップしています。
以下がコードになります。
# tcp_client.py import socket import ssl buf_size = 4096 target_host = "engineeringnote.hateblo.jp" target_port = 443 soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM) soc.connect((target_host, target_port)) soc = ssl.wrap_socket(soc, keyfile=None, certfile=None, server_side=False, cert_reqs=ssl.CERT_NONE, ssl_version=ssl.PROTOCOL_TLSv1_2) soc.send(b"GET / HTTP/1.1\r\nHost: engineeringnote.hateblo.jp\r\n\r\n") while True: res = soc.recv(buf_size) print(res) if len(res) < buf_size: break
socketの生成
9行目でsocket()を生成します。
"AF_INET"はアドレスファミリ(Address Family)と呼ばれるネットワークの種類を表すもので、"AF_INET"はIPv4を意味し、AF_INET6はIPv6を意味します。
インターネット上では、基本的にはまだIPv4が主流なので、"AF_INET"を指定しています。
また"SOCK_STREAM"はTCPを使用することを指定しています(UDPの場合は"SOCK_DGRAM"を指定します)。
TCPコネクションの作成(3 Way Handshake)
10行目のconnect()でTCPのコネクションを張ります(いわゆる3ウェイハンドシェイクと呼ばれるものです)。
データ送受信をする前に、クライアント側からSynフラグを立てたパケットを送り、それを受け取ったサーバ側がSyn/Ackフラグを立て、パケットを送り返します。
最後にクライアントがAckフラグを立てたパケットを送信し、TCPコネクションが作成されます。
なお、サーバのアドレス(ホスト名)とポート番号は必ずタプルにします。
sslの実装
11行~13行目はsocketオブジェクトをSSL/TLS通信用にラップしています。
"ssl_version"のオプション指定で様々なバージョンのSSL/TLSを選択することができます。
しかし、SSL3.0と一部のTLS1.0/1.1では、2014年のPOODLE(CVE-2014-3566)でその脆弱性が発見されたり、またサーバ側でのSSL3.0は非推奨となっていることなど、指定には注意が必要です。
なお、2018年にリリースされた「TLS1.3」が現在の最新バージョンになります。
GETリクエストの送信
14行目ではリクエストを送信しています。
"\r\n"は「CR(Carriage Return)およびLF(Line Feed)」と呼ばれるもので、テキストの改行を意味します。
HTTPの各ヘッダを区切るために使い、最後に"\r\n"を付与することでヘッダ自体の終了を表しています。
レスポンスの表示
16行~20行目ではサーバからのレスポンスを表示しています。
socketオブジェクトからrecv()で指定したバッファサイズを読み取り、このバッファサイズは基本的に2の累乗の値を指定します(ここでは4096byteを指定)。
最後に
以上がTCPクライアントをPythonで実装したものになります。
ブラウザでURLを入力しEnterを押すだけのことですが、裏では様々な技術が相互に連携していていることを改めて実感しました。