ウェブサイト検索

Python を使用したシンプルな Web アプリケーション セキュリティ スキャナーの構築: 初心者ガイド


この記事では、Web アプリケーションの一般的な脆弱性を特定するのに役立つ基本的なセキュリティ ツールの作成方法を学習します。

ここでの目標は 2 つあります。 1 つ目は、Web サイト全体のセキュリティ体制を強化するツールを開発するスキルを身につけることです。 2 つ目は、Python プログラミングの練習を支援することです。

このガイドでは、XSS、SQLインジェクション、および敏感なPII(個人を特定できる情報)を検出できるPythonベースのセキュリティスキャナーを構築します。

脆弱性の種類

一般に、Web セキュリティの脆弱性は次のカテゴリに分類できます (さらに多くのカテゴリについては、OWASP トップ 10 を確認してください)。

  • SQLインジェクション:攻撃者が未検証の入力を介して悪意のあるSQLコードをSQLクエリに挿入し、データベースの内容を変更 /読み取ることができる技術。

  • クロスサイト スクリプティング (XSS): 攻撃者が信頼できる Web サイトに悪意のある JavaScript を挿入する手法。これにより、ブラウザのコンテキストで JavaScript コードを実行し、機密情報を盗んだり、不正な操作を実行したりすることが可能になります。

  • 機密情報の漏洩: アプリケーションがログ、安全でないストレージ、その他の脆弱性を通じて、パスワードや API キーなどの機密データを意図せず公開してしまうセキュリティ問題。

  • 一般的なセキュリティ構成ミス: Web サーバーの不適切な構成によって発生するセキュリティ問題。管理者アカウントのデフォルトの認証情報、デバッグ モードの有効化、認証情報が弱い公開管理者ダッシュボードなど。

  • 基本認証の弱点:パスワードポリシーの失効、ユーザー認証プロセス、不適切なセッション管理などのために発生するセキュリティの問題。

目次

  • 前提条件

  • 開発環境のセットアップ

  • コアスキャナクラスの構築

  • クローラーの実装

  • セキュリティチェックの設計と実装

    • SQL注入検出チェック

    • XSS (クロスサイト スクリプティング) チェック

    • 機密情報露出チェック

  • メインスキャンロジックの実装

  • セキュリティスキャナーの拡張

  • まとめます

前提条件

このチュートリアルをフォローするには、次のことが必要になります。

  • Python 3.x

  • HTTP プロトコルの基本的な理解

  • Webアプリケーションの基本的な理解

  • XSS、SQLインジェクション、および基本的なセキュリティ攻撃がどのように機能するかについての基本的な理解

開発環境を設定します

次のコマンドを使用して、必要な依存関係をインストールしましょう。

pip install requests beautifulsoup4 urllib3 colorama

コード ファイルでこれらの依存関係を使用します。

# Required packages
import requests
from bs4 import BeautifulSoup
import urllib.parse
import colorama
import re
from concurrent.futures import ThreadPoolExecutor
import sys
from typing import List, Dict, Set

コアスキャナークラスを構築します

依存関係を取得したら、コア スキャナ クラスを作成します。

このクラスは、Webセキュリティスキャン機能を処理するメインクラスとして機能します。訪問したページを追跡し、調査結果を保存します。

normalize_url 関数を使用して、以前に確認された URL を再スキャンしないようにします。この関数は基本的に、URL から HTTP GET パラメータを削除します。たとえば、https://example.com/page?id=1 は正規化すると https://example.com/page になります。

class WebSecurityScanner:
    def __init__(self, target_url: str, max_depth: int = 3):
        """
        Initialize the security scanner with a target URL and maximum crawl depth.

        Args:
            target_url: The base URL to scan
            max_depth: Maximum depth for crawling links (default: 3)
        """
        self.target_url = target_url
        self.max_depth = max_depth
        self.visited_urls: Set[str] = set()
        self.vulnerabilities: List[Dict] = []
        self.session = requests.Session()

        # Initialize colorama for cross-platform colored output
        colorama.init()

    def normalize_url(self, url: str) -> str:
        """Normalize the URL to prevent duplicate checks"""
        parsed = urllib.parse.urlparse(url)
        return f"{parsed.scheme}://{parsed.netloc}{parsed.path}"

クローラーの実装

スキャナーの最初のステップは、特定のターゲットアプリケーションでページとURLを発見するWebクローラーを実装することです。これらの関数を websecurityscanner クラスに書いていることを確認してください。

def crawl(self, url: str, depth: int = 0) -> None:
    """
    Crawl the website to discover pages and endpoints.

    Args:
        url: Current URL to crawl
        depth: Current depth in the crawl tree
    """
    if depth > self.max_depth or url in self.visited_urls:
        return

    try:
        self.visited_urls.add(url)
        response = self.session.get(url, verify=False)
        soup = BeautifulSoup(response.text, 'html.parser')

        # Find all links in the page
        links = soup.find_all('a', href=True)
        for link in links:
            next_url = urllib.parse.urljoin(url, link['href'])
            if next_url.startswith(self.target_url):
                self.crawl(next_url, depth + 1)

    except Exception as e:
        print(f"Error crawling {url}: {str(e)}")

この crawl 関数は、ウェブサイトの深さのクロールを実行するのに役立ちます。指定されたドメイン内にとどまっている間、Webサイトのすべてのページを検討します。

たとえば、このスキャナを https://google.com で使用する予定の場合、この関数は最初にすべての URL を取得し、次にそれらが指定されたドメインに属しているかどうかを 1 つずつチェックします (つまり、google.com)。その場合、関数の引数として Depth パラメータで指定された指定の深さまで、表示された URL のスキャンが再帰的に続行されます。また、エラーをスムーズに処理し、クロール中にエラーを報告できるようにするために、いくつかの例外処理も用意されています。

セキュリティチェックの設計と実装

次に、ついにジューシーな部分に到達し、セキュリティチェックを実装しましょう。最初にSQLインジェクションから始めます。

SQL注入検出チェック

def check_sql_injection(self, url: str) -> None:
    """Test for potential SQL injection vulnerabilities"""
    sql_payloads = ["'", "1' OR '1'='1", "' OR 1=1--", "' UNION SELECT NULL--"]

    for payload in sql_payloads:
        try:
            # Test GET parameters
            parsed = urllib.parse.urlparse(url)
            params = urllib.parse.parse_qs(parsed.query)

            for param in params:
                test_url = url.replace(f"{param}={params[param][0]}", 
                                     f"{param}={payload}")
                response = self.session.get(test_url)

                # Look for SQL error messages
                if any(error in response.text.lower() for error in 
                    ['sql', 'mysql', 'sqlite', 'postgresql', 'oracle']):
                    self.report_vulnerability({
                        'type': 'SQL Injection',
                        'url': url,
                        'parameter': param,
                        'payload': payload
                    })

        except Exception as e:
            print(f"Error testing SQL injection on {url}: {str(e)}")

この関数は基本的に、一般的な SQL インジェクション ペイロードに対して URL をテストし、セキュリティの脆弱性を示唆する可能性のあるエラー メッセージを探すことによって、基本的な SQL インジェクション チェックを実行します。

URLで簡単なGETリクエストを実行した後に受信されたエラーメッセージに基づいて、そのメッセージがデータベースエラーであるかどうかを確認します。もしそうなら、 report_vulnerability 関数を使用して、このスクリプトが生成する最終レポートのセキュリティ問題として報告します。この例のために、いくつかの一般的にテストされたSQLインジェクションペイロードを選択していますが、これをさらにテストするために拡張できます。

XSS(クロスサイトスクリプト)チェック

次に、XSSペイロードの2番目のセキュリティチェックを実装しましょう。

def check_xss(self, url: str) -> None:
    """Test for potential Cross-Site Scripting vulnerabilities"""
    xss_payloads = [
        "<script>alert('XSS')</script>",
        "<img src=x onerror=alert('XSS')>",
        "javascript:alert('XSS')"
    ]

    for payload in xss_payloads:
        try:
            # Test GET parameters
            parsed = urllib.parse.urlparse(url)
            params = urllib.parse.parse_qs(parsed.query)

            for param in params:
                test_url = url.replace(f"{param}={params[param][0]}", 
                                     f"{param}={urllib.parse.quote(payload)}")
                response = self.session.get(test_url)

                if payload in response.text:
                    self.report_vulnerability({
                        'type': 'Cross-Site Scripting (XSS)',
                        'url': url,
                        'parameter': param,
                        'payload': payload
                    })

        except Exception as e:
            print(f"Error testing XSS on {url}: {str(e)}")

この機能は、SQLインジェクションテスターと同様に、一般的なXSSペイロードのセットを使用し、同じアイデアを適用します。しかし、ここでの重要な違いは、挿入されたペイロードを探して、エラーメッセージを探すのではなく、応答で修正されていないように見えることです。

挿入されたペイロードを確認できる場合は、反射された XSS 攻撃として被害者のブラウザのコンテキストで実行される可能性が高くなります。

機密情報露出チェック

次に、機密 PII の最終チェックを実装しましょう。

def check_sensitive_info(self, url: str) -> None:
    """Check for exposed sensitive information"""
    sensitive_patterns = {
        'email': r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}',
        'phone': r'\b\d{3}[-.]?\d{3}[-.]?\d{4}\b',
        'ssn': r'\b\d{3}-\d{2}-\d{4}\b',
        'api_key': r'api[_-]?key[_-]?([\'"|`])([a-zA-Z0-9]{32,45})\1'
    }

    try:
        response = self.session.get(url)

        for info_type, pattern in sensitive_patterns.items():
            matches = re.finditer(pattern, response.text)
            for match in matches:
                self.report_vulnerability({
                    'type': 'Sensitive Information Exposure',
                    'url': url,
                    'info_type': info_type,
                    'pattern': pattern
                })

    except Exception as e:
        print(f"Error checking sensitive information on {url}: {str(e)}")

この関数は、事前定義された正規表現パターンのセットを使用して、電子メール、電話番号、SSN、API キー (api-key- というプレフィックスが付く) などの PII を検索します。

前の2つの関数と同様に、URLの応答テキストとREGEXパターンを使用して、これらのPIIを応答テキストで見つけます。見つかった場合は、 report_vulnerability 関数でそれらを報告します。 websecurityscanner クラスでこれらすべての機能を定義しているようにしてください。

メインスキャンロジックの実装

最後に、WebSecurityScanner クラスで scan 関数と report_vulnerability 関数を定義して、すべてをつなぎ合わせましょう。

def scan(self) -> List[Dict]:
    """
    Main scanning method that coordinates the security checks

    Returns:
        List of discovered vulnerabilities
    """
    print(f"\n{colorama.Fore.BLUE}Starting security scan of {self.target_url}{colorama.Style.RESET_ALL}\n")

    # First, crawl the website
    self.crawl(self.target_url)

    # Then run security checks on all discovered URLs
    with ThreadPoolExecutor(max_workers=5) as executor:
        for url in self.visited_urls:
            executor.submit(self.check_sql_injection, url)
            executor.submit(self.check_xss, url)
            executor.submit(self.check_sensitive_info, url)

    return self.vulnerabilities

def report_vulnerability(self, vulnerability: Dict) -> None:
    """Record and display found vulnerabilities"""
    self.vulnerabilities.append(vulnerability)
    print(f"{colorama.Fore.RED}[VULNERABILITY FOUND]{colorama.Style.RESET_ALL}")
    for key, value in vulnerability.items():
        print(f"{key}: {value}")
    print()

このコードは、基本的に crawl 関数を呼び出し、Web サイトのクロールを再帰的に開始する scan 関数を定義します。マルチスレッドでは、訪問した URL に 3 つのセキュリティ チェックすべてが適用されます。

また、コンソールに対する脆弱性を効果的に印刷し、脆弱性配列に保存する report_vulnerability 関数も定義しました。

それでは、最後にスキャナを scanner.py として保存して使用してみましょう。

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("Usage: python scanner.py <target_url>")
        sys.exit(1)

    target_url = sys.argv[1]
    scanner = WebSecurityScanner(target_url)
    vulnerabilities = scanner.scan()

    # Print summary
    print(f"\n{colorama.Fore.GREEN}Scan Complete!{colorama.Style.RESET_ALL}")
    print(f"Total URLs scanned: {len(scanner.visited_urls)}")
    print(f"Vulnerabilities found: {len(vulnerabilities)}")

ターゲットURLはシステム引数として提供され、スキャンの最後に見つかったURLの概要と脆弱性を取得します。次に、スキャナーを拡張して機能を追加する方法について説明しましょう。

セキュリティスキャナーの拡張

この基本的なセキュリティスキャナーをさらに高度なものに拡張するためのいくつかのアイデアがあります。

  1. CSRF 検出、ディレクトリ トラバーサルなどの脆弱性チェックをさらに追加します。

  2. HTMLまたはPDF出力でレポートを改善します。

  3. スキャン強度と検索範囲の構成オプションを追加します (CLI 引数を使用してスキャンの深さを指定します)。

  4. 適切なレート制限を実装します。

  5. セッションベースの認証を必要とするURLのテストの認証サポートを追加します。

まとめます

これで、基本的なセキュリティスキャナーを構築する方法がわかりました!このスキャナーは、Webセキュリティのいくつかのコア概念を示しています。

このチュートリアルは教育目的のみに使用する必要があることに注意してください。 Burp Suite や OWASP Zap など、専門的に設計されたエンタープライズ グレードのアプリケーションがいくつかあり、数百ものセキュリティ脆弱性をより大規模にチェックできます。

WebセキュリティとPythonプログラミングの基本も学んだことを願っています。