本記事は、オライリージャパンから発行されている「サイバーセキュリティプログラミング ―Pythonで学ぶハッカーの思考(原題:Black Hat Python)」の学習メモとして、書籍ではPython2で書かれていますが、自分なりに解釈した上でPython3に書き直しをしています。
今回はPythonのsocketモジュールを使用したTCP通信(サーバ)の基本について学んでいきます。
TCPサーバ(簡易HTTPサーバ)の実装
前回ではTCPおよびUDPクライアントの実装について学びました。
今回のTCPサーバについても、前回と同様にPythonのsocketモジュールを用いて実装をしていきます。
以下が今回のコードになります。
# tcp_server.py import socket from threading import Thread from datetime import datetime buf_size = 1024 bind_ip = "0.0.0.0" bind_port = 8000 server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind((bind_ip, bind_port)) server.listen(5) print("[*] Listening on {0}:{1}".format(bind_ip, bind_port)) def handle_client(client_socket): request = client_socket.recv(buf_size) print("[*] Received:\n{}{}".format(request.decode('utf-8'), "-"*24)) now = datetime.now().strftime('%a, %d %b %Y %H:%M:%S GMT') html = """<!DOCTYPE html><html lang="ja"><body><p>Test Server<p></body></html>""" header = """HTTP/1.0 200 OK\r\nDate: {}\r\nServer: TestHTTPServer\r\nContent-Type: text/html; charset=utf-8\r\n\r\n""".format(now) if request.startswith(b'HEAD'): client_socket.send(header.encode('utf-8')) elif request.startswith(b'GET'): response = header[:-2] + "Content-Length: {}\r\n".format(len(html)) + "\r\n" + html client_socket.send(response.encode('utf-8')) while True: client, addr = server.accept() print("[*] Accepted connection from: {}:{}".format(addr[0], addr[1])) client_handler = Thread(target=handle_client, args=[client]) client_handler.start()
なお、今回はせっかくなので簡易HTTPサーバとして実装してみました。
socketの生成
10行目でsocketを生成します。
"AF_INET"はアドレスファミリ(Address Family)と呼ばれるネットワークの種類を表すもので、"AF_INET"はIPv4を意味し、AF_INET6はIPv6を意味します。
インターネット上では、基本的にはまだIPv4が主流なので、"AF_INET"を指定しています。
また"SOCK_STREAM"はTCPを使用することを指定しています(UDPの場合は"SOCK_DGRAM"を指定します)。
11行目のbind()で、socketオブジェクトをaddressにバインドします。
13行目のlisten()で、接続キューの最大を5として、接続を受け付けるようにします。
簡易HTTPサーバ用の関数
17~30行目は、クライアントからの接続を処理するための簡易HTTPサーバ用の関数です。
引数としてクライアントのsocketオブジェクトを受け取ります。
19行目で接続してきたクライアントのリクエストヘッダを表示します。
21行目はレスポンスヘッダのDateに入れる時刻フォーマットを生成し、こちらは"RFC 1123"の時刻フォーマットに従います。
なお、今回は簡易HTTPサーバとして、"GET"および"HEAD"のメソッドのみ受付をするようにしています。
クライアントからの接続処理スレッド
32~37行目で接続してきたクライアントを処理するスレッドを定義しています。
33行目のaccept()でクライアントからの接続を受け付けます。
なお、ここで使用するsocketオブジェクトはbind済みかつlisten中でなければいけません。
簡易HTTPサーバを起動してみる
> python tcp_server.py [*] Listening on 0.0.0.0:8000
別のコマンドプロンプトからcurlコマンド(要インストール)を使用して、HEADとGETをそれぞれ実行します。
> curl -I 127.0.0.1:8000 HTTP/1.0 200 OK Date: Wed, 26 Dec 2018 14:22:47 GMT Server: TestHTTPServer Content-Type: text/html; charset=utf-8 > curl 127.0.0.1:8000 <!DOCTYPE html><html lang="ja"><body><p>Test Server<p></body></html>
curlコマンドの"-I"オプションでHEADのみの表示が可能です。
サーバ側では、接続してきたクライアントのリクエストヘッダが表示されます。
[*] Accepted connection from: 127.0.0.1:31374 [*] Received: HEAD / HTTP/1.1 Host: 127.0.0.1:8000 User-Agent: curl/7.62.0 Accept: */* ------------------------ [*] Accepted connection from: 127.0.0.1:31375 [*] Received: GET / HTTP/1.1 Host: 127.0.0.1:8000 User-Agent: curl/7.62.0 Accept: */* ------------------------
最後に
サーバはクライアントと違い、bind()とlisten()およびaccept()の処理が追加されます。
次回はここまでで学んだことを生かして、netcatの代わりとしたバックドア用のツールを作成します。