Engineering Note

プログラミングなどの技術的なメモ

2.4 TCPサーバ (サイバーセキュリティプログラミング Pythonで学ぶハッカーの思考)

本記事は、オライリージャパンから発行されている「サイバーセキュリティプログラミング ―Pythonで学ぶハッカーの思考(原題:Black Hat Python)」の学習メモとして、書籍ではPython2で書かれていますが、自分なりに解釈した上でPython3に書き直しをしています。

今回はPythonのsocketモジュールを使用したTCP通信(サーバ)の基本について学んでいきます。

 

 

TCPサーバ(簡易HTTPサーバ)の実装

前回ではTCPおよびUDPクライアントの実装について学びました。

engineeringnote.hateblo.jp

engineeringnote.hateblo.jp

今回の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: */*
 
 ------------------------

 

最後に

以上がTCPサーバをPythonで実装したものになります。

サーバはクライアントと違い、bind()とlisten()およびaccept()の処理が追加されます。

次回はここまでで学んだことを生かして、netcatの代わりとしたバックドア用のツールを作成します。

 

参考書籍

サイバーセキュリティプログラミング ―Pythonで学ぶハッカーの思考