Создание простого сканера безопасности веб-приложений с помощью Python: Руководство для начинающих

В этой статье вы научитесь создавать базовый инструмент безопасности, который может быть полезен при выявлении распространенных уязвимостей в веб-приложениях.

У меня здесь две цели. Первая - дать вам навыки разработки инструментов, которые помогут повысить общую безопасность ваших веб-сайтов. Вторая - помочь вам немного попрактиковаться в программировании на Python.

В этом руководстве вы будете создавать сканер безопасности на базе Python, который может обнаруживать XSS, SQL-инъекции и конфиденциальные данные (Personal Identifiable Information).

Типы уязвимостей

Как правило, мы можем разделить уязвимости веб-безопасности на следующие группы (чтобы найти еще больше групп, ознакомьтесь с Топ-10 OWASP).:

    SQL-инъекция: метод, при котором злоумышленники могут вставлять вредоносный SQL-код в SQL-запросы с помощью неподтвержденных входных данных, что позволяет им изменять / считывать содержимое базы данных. Межсайтовый скриптинг (XSS): метод, при котором злоумышленники внедряют вредоносный JavaScript на надежные веб-сайты. Это позволяет им выполнять JavaScript-код в контексте браузера и красть конфиденциальную информацию или выполнять несанкционированные операции. Раскрытие конфиденциальной информации: проблема безопасности, при которой приложение непреднамеренно раскрывает конфиденциальные данные, такие как пароли, ключи API и т.д., через журналы, небезопасное хранилище и другие уязвимости. Распространенные ошибки в настройках безопасности: проблемы с безопасностью, возникающие из-за неправильной настройки веб-серверов, такие как учетные данные по умолчанию для учетных записей администратора, включенный режим отладки, общедоступные панели мониторинга администратора с ненадежными учетными данными и так далее. Основные недостатки аутентификации: проблемы безопасности, возникающие из-за ошибок в политике паролей, процессах аутентификации пользователей, неправильном управлении сеансами и так далее.

содержание

    Предпосылки Настройка нашей среды разработки Создание нашего основного класса сканера Внедрение сканера-обходчика Разработка и внедрение проверок безопасности Проверка на обнаружение SQL-инъекций Проверка XSS (межсайтового скриптинга) Проверка доступа к конфиденциальной информации Реализация основной логики сканирования Расширение сканера безопасности Завершение

Предпосылки

Чтобы следовать этому руководству, вам понадобится:

    Python 3.x Базовое понимание протоколов HTTP Базовое понимание веб-приложений Базовое понимание того, как работают 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

Создание нашего основного класса сканеров

Как только у вас будут зависимости, пришло время написать базовый класс scanner.

Этот класс будет служить нашим основным классом, который будет управлять функциями сканирования веб-безопасности. Он будет отслеживать посещенные страницы, а также сохранять наши результаты.

У нас есть функция normalize_url, которую мы будем использовать, чтобы гарантировать, что вы не будете повторно сканировать URL-адреса, которые уже просматривались ранее. Эта функция, по сути, удалит параметры HTTP GET из URL-адреса. Например, 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-адреса в заданном целевом приложении. Убедитесь, что вы записываете эти функции в наш класс 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)}")

Эта функция сканирования помогает нам выполнить полный обход веб-сайта. Она позволяет просматривать все страницы веб-сайта, оставаясь в пределах указанного домена.

Например, если вы планируете использовать этот сканер на https://google.com, функция сначала получит все URL-адреса, а затем один за другим проверит, принадлежат ли они указанному домену (то есть google.com). Если это так, то программа продолжит рекурсивное сканирование просмотренного URL-адреса до заданной глубины, которая задается параметром depth в качестве аргумента функции. У нас также есть некоторая обработка исключений, чтобы обеспечить бесперебойную обработку ошибок и сообщать о любых ошибках во время сканирования.

Разработка и внедрение проверок безопасности

Теперь давайте, наконец, перейдем к самой интересной части и выполним наши проверки безопасности. Сначала мы начнем с 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-инъекциями, и ищет сообщения об ошибках, которые могут указывать на уязвимость в системе безопасности.

На основании сообщения об ошибке, полученного после выполнения простого запроса GET по URL-адресу, мы проверяем, является ли это сообщение ошибкой базы данных или нет. Если это так, мы используем функцию report_vulnerability, чтобы сообщить об этом как о проблеме безопасности в нашем окончательном отчете, который сгенерирует этот скрипт. Для этого примера мы выбрали несколько наиболее часто тестируемых полезных приложений для внедрения SQL, но вы можете расширить их, чтобы протестировать еще больше.

Проверка XSS (межсайтового скриптинга)

Теперь давайте выполним вторую проверку безопасности для полезных нагрузок XSS.

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 injection tester, использует набор распространенных полезных функций XSS и применяет ту же идею. Но ключевое отличие здесь в том, что мы хотим, чтобы введенная полезная нагрузка отображалась в нашем ответе без изменений, а не в виде сообщения об ошибке.

Если вы сможете увидеть введенную нами полезную нагрузку, скорее всего, она будет выполнена в контексте браузера жертвы в качестве отраженной атаки XSS.

Проверка доступа к конфиденциальной информации

Теперь давайте выполним нашу последнюю проверку на наличие конфиденциальных персональных данных.

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-<номер>).

Как и в предыдущих двух функциях, мы используем текст ответа для URL-адреса и наши шаблоны регулярных выражений, чтобы найти эти данные в тексте ответа. Если мы их находим, мы сообщаем о них с помощью функции report_vulnerability. Убедитесь, что все эти функции определены в классе WebSecurityScanner.

Реализация основной логики сканирования

Давайте, наконец, сведем все воедино, определив функцию сканирования и report_vulnerability в классе WebSecurityScanner:

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()

Этот код определяет нашу функцию сканирования, которая, по сути, вызовет функцию обхода и начнет рекурсивный обход веб-сайта. При многопоточной обработке мы применим все три проверки безопасности к посещаемым URL-адресам.

Мы также определили функцию 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-адресов и найденных уязвимостей. Теперь давайте обсудим, как вы можете расширить возможности сканера и добавить дополнительные функции.

Расширение возможностей сканера безопасности

Вот несколько идей, как превратить этот базовый сканер безопасности в нечто еще более совершенное:

    Добавьте дополнительные проверки на уязвимости, такие как обнаружение CSRF, обход каталогов и т.д. Улучшите создание отчетов с выводом в формате HTML или PDF. Добавьте параметры конфигурации для интенсивности сканирования и объема поиска (задавая глубину сканирования с помощью аргумента CLI). Реализуйте правильное ограничение скорости. Добавлена поддержка проверки подлинности для тестирования URL-адресов, требующих проверки подлинности на основе сеанса.

Сворачивание

Теперь вы знаете, как создать базовый сканер безопасности! Этот сканер демонстрирует несколько основных концепций веб-безопасности.

Имейте в виду, что это руководство следует использовать только в образовательных целях. Существует несколько профессионально разработанных приложений корпоративного уровня, таких как Burp Suite и OWASP Zap, которые могут проверять сотни уязвимостей в системе безопасности в гораздо большем масштабе.

Я надеюсь, вы освоили основы веб-безопасности и немного программирования на Python.