サイバーセキュリティプログラミング ―Pythonで学ぶハッカーの思考 5章 勉強まとめ

nnpo.hatenablog.com

nnpo.hatenablog.com

nnpo.hatenablog.com

遅くなりましたが続きです。

本来は7月中に全章終えるつもりだった……。

Pythonを使ってwebサーバーとやり取りをする章。


webのソケットライブラリ:urllib

Webを扱うツールを作る際にはurllib2ライブラリを使う……と本にはあるんですが、このライブラリはpython3では使えないらしい。

なので、Python3の標準ライブラリであるurllibを使っていきます。

import urllib.request

body = urllib.request.urlopen("http://www.google.com")
print(body.read())

import urllib.request

URLを取得するためのモジュール。

urllib.request.urlopen("http://www.google.com")

URLを開く。bytes オブジェクトを返す。

import urllib.request

url = "http://www.google.com"

headers = {}
headers['User-Agent'] = "Googlebot"

request = urllib.request.Request(url, headers=headers)
response = urllib.request.urlopen(request)

print(response.read())
response.close()

headers = {}

headers['User-Agent'] = "Googlebot"

HTTPヘッダーとその値を設定。HTTPヘッダーとは、ユーザーエージェント名やリファラなどをことを言うらしい

ここでは、Googlebotとしてユーザーエージェント名を設定している。

request = urllib.request.Request(url, headers=headers)

URLリクエストを抽象化したもの。URLとHTTPヘッダーを与える。


オープンソースのWebアプリケーションのインストール

保留。気になったものだけまとめておきます。

os.chdir()

指定したディレクトリを移動する。

16.1. os — 雑多なオペレーティングシステムインタフェース — Python 3.5.1 ドキュメント

queue.Queue()

FIFOキューのコンストラクタ。引数を指定すると、要素の上限を決められる。

キューに入れるときは、put。キューから出すときは、getを用いる。分かりやすい。

ちなみに、LIFOキューは、

queue.LifoQueue

優先順位付きキューは、

queue.PriorityQueue

です。優先順位付きキューっていまいちよく分からなかったので調べたところ…よく分からなかった。

17.7. queue — 同期キュークラス — Python 3.5.1 ドキュメント

優先度つきキュー - Wikipedia


ディレクトリとファイルの総当り攻撃

総当り攻撃用の辞書を使って、ツールのテスト用に公開されている脆弱なWebアプリケーションに総当り攻撃をしてみる。

# -*- coding: utf-8 -*-

import urllib.request
import threading
import queue
import urllib.parse

threads       = 5
target_url    = "http://testphp.vulnweb.com"
wordlist_file = "/root/SVNDigger/all.txt"
resume        = None
user_agent    = "Mozilla/5.0 (X11; Linux x86_64; rv:19.0) Gecko/20100101 Firefox/19.0"

def build_wordlist(wordlist_file):
    #単語の辞書を読み取る
    fd = open(wordlist_file, "rb")
    raw_words = fd.readlines()
    fd.close()

    founnd_resume = False
    words = queue.Queue()

    for word in raw_words:

        word = word.rstrip()

        if resume is not None:

            if founnd_resume:
                words.put(word)
            else:
                if word == resume:
                    founnd_resume = True
                    print("Resuming wordlist from {}".format(resume))

        else:
            words.put(word)

    return words

def dir_bruter(word_queue, extensions=None):

    while not word_queue.empty():
        attempt = word_queue.get().decode()

        attempt_list = []

        #ファイルに拡張子があるかどうかチェック。
        #なければ、ディレクトリのパスとして総当り攻撃の対象に

        if "." not in attempt:
            attempt_list.append("/{}/".format(attempt))
        else:
            attempt_list.append("/{}".format(attempt))

        #拡張子の総当りをする場合
        if extensions:
            for extension in extensions:
                attempt_list.append("/{0}{1}".format(attempt, extension))

        #作成したリストを最後まで繰り返す
        for brute in attempt_list:

            url = "{0}{1}".format(target_url, urllib.parse.quote(brute))

            try:
                headers = {}
                headers["User-Agent"] = user_agent
                r = urllib.request.Request(url, headers=headers)

                response = urllib.request.urlopen(r)

                if len(response.read()):
                    print("[{0}] => {1}".format(response.code, url))
            except urllib.error.URLError as e:

                if hasattr(e, "code") and e.code != 404:
                    print("!!! {0} => {1}".format(e.code, url))

                pass

word_queue = build_wordlist(wordlist_file)
extensions = [".php", ".bak", ".orig", ".inc"]

for i in range(threads):
    t = threading.Thread(target=dir_bruter, args=(word_queue, extensions,))
    t.start()

target_url = "http://testphp.vulnweb.com"

今回のターゲット。脆弱なWebアプリケーションのURL。

wordlist_file = "/root/SVNDigger/all.txt"

総当り攻撃用の辞書。よくあるファイル名やディレクトリ名が載ってる。

def build_wordlist(wordlist_file)

辞書から一行ずつ取り出してキューに入れる関数。

def dir_bruter(word_queue, extensions=None)

キューより、検索対象のURLを総当りする。

attempt = word_queue.get().decode()

キューから取り出したデータがbyte型だったのでデコードした。

response.code

urlopen関数は、ファイルのようなオブジェクトを返します。なので、ファイルを扱うように、readだのなんだのしてきましたが、codeという属性を持っているオブジェクトみたいです。

codeという属性がHTTPステータスコードを指しているのでしょう。

公式ドキュメントに

この関数は コンテクストマネージャ として機能するオブジェクトを常に返します。このオブジェクトには以下のメソッドがあります。

geturl() — 取得されたリソースの URL を返します。 主に、リダイレクトが発生したかどうかを確認するために利用します

info() — 取得されたページのヘッダーなどのメタ情報を、 email.message_from_string() インスタンスとして返します。 (Quick Reference to HTTP Headers を参照してください)

getcode() – レスポンスの HTTP ステータスコードです。

とあるので、メソッドを呼び出して使うのもありですね。

21.6. urllib.request — URL を開くための拡張可能なライブラリ — Python 3.5.1 ドキュメント

実際実行してみると、(実行速度は遅かったですが)無事結果が得られた。

何度か、

Remote end closed connection without response

とエラーが出てしまったので、プログラム側か処理速度に問題があるのかも。


HTMLファームの認証を総当り攻撃で破る

こちらもとりあえず保留。Cookie関連が2系とだいぶ違うみたい。

再読時に躓きそうだから、参考になりそうなリンクを貼っておく。

http://www.yoheim.net/blog.php?q=20151101

http://matsulib.hatenablog.jp/entry/2015/03/06/161546

http://ymotongpoo.hatenablog.com/entry/20081211/1228985067

20.2. html.parser— HTML および XHTML のシンプルなパーサー — Python 3.5.1 ドキュメント