0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PythonでWebサーバーのディレクトリ探索をする

Last updated at Posted at 2025-01-14

PythonのRequestsライブラリを使用して、Webサーバーのディレクトリ探索をします。
HTTP、HTTPSおよびProxy経由の場合を想定しています。
Proxyは、Burp SuiteやOWASP ZAP等を想定しています。
OSは、Parrot OS Security Editionです。
Requestsライブラリの使い方の備忘録です。

プログラム

IPアドレス、ポート、HTTP、HTTPS、Proxy有り無しの設定は、変数の初期値の変更や、コメントアウトに位置を変更することで切り替えます。

DirBursting.py
#!/usr/bin/python3
from signal import signal, SIGINT
from sys import exit
import requests

# 証明書の検証を行わないときにInsecureRequestWarningを表示させないため
import urllib3
from urllib3.exceptions import InsecureRequestWarning
urllib3.disable_warnings(InsecureRequestWarning)

# Ctrl+Cでプログラムを中止するため
def handler(signal_received, frame):
    # Handle any cleanup here
    print('   [+]Exiting...')
    exit(0)

signal(SIGINT, handler)

# 変数の初期化
# HTTPかHTTPSを選択する
hostname = "http://192.168.56.116"
#hostname = "https://192.168.56.116"
# デフォルトのポートから変更するとき
port = 80
#port = 443
# ワードリストファイルを指定する
wordlist_file = "/usr/share/dirb/wordlists/small.txt"
wordlist = ""
word = ""
# ヘッダファイルを指定する
header_file = "header.txt"
headrslist = ""
header = ""
headerlist = ""
header_result = {}
proxies = {
    'http': 'http://127.0.0.1:8080',
    'https': 'http://127.0.0.1:8080',
}

# メインルーチン
if __name__=="__main__":
    try:
        with open(wordlist_file) as f:
            wordlist = [s.rstrip() for s in f.readlines()]
            print("Wordlist: " + wordlist_file)
            try:
                with open(header_file) as f2:
                    headerslist = [s.rstrip() for s in f2.readlines()]
                    print("Headerlist: " + header_file)
                    for header in headerslist:
                        if header == '':
                            break
                        if (header.startswith('GET') == False
                        and header.startswith('POST') == False
                        and header.startswith('Host') == False
                        and header.startswith('Connection') == False
                        and header.startswith('Content-Type') == False
                        and header.startswith('Content-Length') == False):
                            headerlist = header.split(': ')
                            header_result[headerlist[0]] = headerlist[1]
            except FileExistsError:
                header = ""
                print("ヘッダーファイルが存在しません")
            except FileNotFoundError:
                header = ""
                print("ヘッダーファイルが存在しません")
            keep_alive = requests.Session()
            #keep_alive.proxies.update(proxies)
            for word in wordlist:
                # デフォルトのポートから変更するとき
                url = hostname + '/' + word + '/'
                #url = hostname + ':' + str(port) + '/' + word + '/'
                # 証明書を検証しないとき
                #response = keep_alive.get(url)
                response = keep_alive.get(url, headers=header_result)
                #response = keep_alive.get(url, verify=False)
                #response = keep_alive.get(url, headers=header_result, verify=False)
                status = response.status_code
                connection = response.headers['Connection']
                if status != 404:
                    print(word + " " + str(status))
                if connection == "close":
                    keep_alive.close()
                    keep_alive = requests.Session()
                    #keep_alive.proxies.update(proxies)
            keep_alive.close()
    except FileExistsError:
        print("Wordlistファイルが存在しません")
    except FileNotFoundError:
        print("Wordlistファイルが存在しません")

説明

モジュール

Ctrl+Cでプログラムを中止するために、signal, SIGINT
http通信のために、requests
をインポートしています。

from signal import signal, SIGINT
from sys import exit
import requests

警告表示をさせない

オレオレ証明書のWebサーバーでテストする際に、証明書の検証を無効にしただけでは、Warningが表示されてしまうので、警告表示させないようにします。

import urllib3
from urllib3.exceptions import InsecureRequestWarning
urllib3.disable_warnings(InsecureRequestWarning)

Ctrl+Cでプログラムを中止する

これを書いておけば、Ctrl+Cでプログラムを中止できます。

def handler(signal_received, frame):
    # Handle any cleanup here
    print('   [+]Exiting...')
    exit(0)

signal(SIGINT, handler)

ホストのIPアドレスおよびプロトコル

ホストのIPアドレス(ホスト名でもOKなはず)を記載します。
HTTPSの場合は、上側をコメントアウトして、下側を有効にします。

hostname = "http://192.168.56.116"
#hostname = "https://192.168.56.116"

デフォルトのポートから変更するとき

デフォルトのポートから変更するときに設定します。
ただし、URLを作成するところのコードを有効にしないと変更されません。

port = 80
#port = 443

ワードリストファイルを指定する

ワードリストのファイルを指定します。
wordlist_fileは、ワードリストが記載されたファイル名
wordlistは、ファイルからワードを読み込みリスト形式にしたリスト
wordは、wordlistから一つずつ取り出したワード

wordlist_file = "/usr/share/dirb/wordlists/small.txt"
wordlist = ""
word = ""

ヘッダーファイルを指定する

ヘッダーを指定したい場合、ヘッダーファイルを指定します。
ヘッダーファイルのフォーマットは、通常のリクエストヘッダーと同じです。
Burp Suite等で、リクエストをそのままファイルに保存してもいいです。
リクエスト行、Hostヘッダー、Connectionヘッダー、Content-Typeヘッダー、Content-Lengthヘッダーは、追加せずに削除します。
header_fileは、ヘッダーが記載されたファイル名
headrslistは、ファイルからワードを読み込みリスト形式にしたリスト
headerは、headrslistから一つずつ取り出したヘッダー
headerlistは、headerをヘッダー名と値で分けたリスト
header_resultは、ヘッダー名をキーとした辞書

header_file = ""
headrslist = ""
header = ""
headerlist = ""
header_result = {}

プロキシーの設定

プロキシーのアドレス、ポートを指定する。
ただし、セッションを作成するところのコードを有効にしないとプロキシーが有効になりません。

proxies = {
    'http': 'http://127.0.0.1:8080',
    'https': 'http://127.0.0.1:8080',
}

ワードリスト読み込み

ディレクトリ名の一覧ファイルを読み込みます。
readlines()で読み込んだ時点では、改行コードが含まれているため、rstrip()で改行コードを削除しています。

with open(wordlists) as f:
    wordlist = [s.rstrip() for s in f.readlines()]
    print("Wordlist: " + wordlists)

ヘッダーの読み込み

ヘッダーを読み込みます。
リクエスト行、Hostヘッダー、Connectionヘッダー、Content-Typeヘッダー、Content-Lengthヘッダーは、削除します。
読み込んだヘッダーは、ヘッダー名をキーとした辞書を作成します。

for header in headerslist:
    if header == '':
        break
    if (header.startswith('GET') == False
    and header.startswith('POST') == False
    and header.startswith('Host') == False
    and header.startswith('Connection') == False
    and header.startswith('Content-Type') == False
    and header.startswith('Content-Length') == False):
        headerlist = header.split(': ')
        header_result[headerlist[0]] = headerlist[1]

セッションを作成

キープアライブのために、セッションを作成します。
プロキシーを有効する場合は、2行目を有効にします。

keep_alive = requests.Session()
#keep_alive.proxies.update(proxies)

リクエストの送信

wordlistから1個ずつワードを取り出して、リクエストを送信します。
デフォルトポートから変更する場合は、urlを作成する式の上側をコメントアウトして、下側を有効にします。
ヘッダーを追加する場合は、headers=header_resultがある式を有効にします。
httpsの証明書の検証を無効にする場合は、verify=Falseがある式を有効にします。
statusは、HTTPステータスコードを取得します。
connectionは、レスポンスヘッダーのConnectionを取得します。
HTTPステータスコードが、404でなければ、その時のワードとステータスを表示します。
レスポンスヘッダーのConnectionが、closeになると、一旦、セッションをcloseして、再度、セッションを作成します。
プロキシーを有効する場合は、ここも有効にします。

for word in wordlist:
    url = hostname + '/' + word + '/'
    #url = hostname + ':' + str(port) + '/' + word + '/'
    #response = keep_alive.get(url)
    response = keep_alive.get(url, headers=header_result)
    #response = keep_alive.get(url, verify=False)
    #response = keep_alive.get(url, headers=header_result, verify=False)
    status = response.status_code
    connection = response.headers['Connection']
    if status != 404:
        print(word + " " + str(status))
    if connection == "close":
        keep_alive.close()
        keep_alive = requests.Session()
        #keep_alive.proxies.update(proxies)
keep_alive.close()

プロキシーを使用する場合

OWASP ZAPの場合は、上記のコードで問題ないですが
Burp Suiteの場合、上記のコードでは、予期しないときにBurp Suiteからセッションを閉じられて、エラーになります。
エラーにならないように、毎回セッションを閉じるコードです。
このコードでも、OWASP ZAP、Burp Suiteの両方とも、サーバーとの間は、キープアライブになるようです。

DirBursting.py(キープアライブ無し)
#!/usr/bin/python3
from signal import signal, SIGINT
from sys import exit
import requests

# 証明書の検証を行わないときにInsecureRequestWarningを表示させないため
import urllib3
from urllib3.exceptions import InsecureRequestWarning
urllib3.disable_warnings(InsecureRequestWarning)

# Ctrl+Cでプログラムを中止するため
def handler(signal_received, frame):
    # Handle any cleanup here
    print('   [+]Exiting...')
    exit(0)

signal(SIGINT, handler)

# 変数の初期化
# HTTPかHTTPSを選択する
hostname = "http://192.168.56.116"
#hostname = "https://192.168.56.116"
# デフォルトのポートから変更するとき
port = 80
#port = 443
# ワードリストファイルを指定する
wordlist_file = "/usr/share/dirb/wordlists/small.txt"
wordlist = ""
word = ""
# ヘッダファイルを指定する
header_file = "header.txt"
headrslist = ""
header = ""
headerlist = ""
header_result = {}
proxies = {
    'http': 'http://127.0.0.1:8080',
    'https': 'http://127.0.0.1:8080',
}

# メインルーチン
if __name__=="__main__":
    try:
        with open(wordlist_file) as f:
            wordlist = [s.rstrip() for s in f.readlines()]
            print("Wordlist: " + wordlist_file)
            try:
                with open(header_file) as f2:
                    headerslist = [s.rstrip() for s in f2.readlines()]
                    print("Headerlist: " + header_file)
                    for header in headerslist:
                        if header == '':
                            break
                        if (header.startswith('GET') == False
                        and header.startswith('POST') == False
                        and header.startswith('Host') == False
                        and header.startswith('Connection') == False
                        and header.startswith('Content-Type') == False
                        and header.startswith('Content-Length') == False):
                            headerlist = header.split(': ')
                            result[headerlist[0]] = headerlist[1]
            except FileExistsError:
                header = ""
                print("Headerファイルが存在しません")
            except FileNotFoundError:
                header = ""
                print("Headerファイルが存在しません")
            for word in wordlist:
                keep_alive = requests.Session()
                keep_alive.proxies.update(proxies)
                # デフォルトのポートから変更するとき
                url = hostname + '/' + word + '/'
                #url = hostname + ':' + str(port) + '/' + word + '/'
                # 証明書を検証しないとき
                #response = keep_alive.get(url)
                response = keep_alive.get(url, headers=header_result)
                #response = keep_alive.get(url, verify=False)
                #response = keep_alive.get(url, headers=header_result, verify=False)
                status = response.status_code
                if status != 404:
                    print(word + " " + str(status))
                keep_alive.close()
    except FileExistsError:
        print("Wordlistファイルが存在しません")
    except FileNotFoundError:
        print("Wordlistファイルが存在しません")
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?