Creación de un escáner de seguridad de aplicaciones web sencillo con Python: una guía para principiantes
En este artículo, aprenderá a crear una herramienta de seguridad básica que puede resultar útil para identificar vulnerabilidades comunes en aplicaciones web.
Tengo dos objetivos aquí. La primera es brindarle las habilidades necesarias para desarrollar herramientas que puedan ayudar a mejorar la postura general de seguridad de sus sitios web. El segundo es ayudarte a practicar algo de programación en Python.
En esta guía, creará un escáner de seguridad basado en Python que puede detectar XSS, inyección SQL e PII (información de identificación personal) confidencial.
Tipos de vulnerabilidades
En general, podemos clasificar las vulnerabilidades de seguridad web en los siguientes cubos (para obtener aún más cubos, consulte el Top 10 de OWASP):
-
inyección SQL : una técnica en la que los atacantes pueden insertar código SQL malicioso en consultas SQL a través de entradas no validadas, lo que les permite modificar/leer el contenido de la base de datos.
Cross-Site Scripting (XSS): una técnica mediante la cual los atacantes inyectan JavaScript malicioso en sitios web confiables. Esto les permite ejecutar el código JavaScript en el contexto del navegador y robar información confidencial o realizar operaciones no autorizadas.
Exposición de información confidencial: un problema de seguridad en el que una aplicación revela involuntariamente datos confidenciales como contraseñas, claves API, etc. a través de registros, almacenamiento inseguro y otras vulnerabilidades.
Configuraciones erróneas de seguridad comunes : problemas de seguridad que ocurren debido a la configuración inadecuada de los servidores web, como las credenciales predeterminadas para cuentas de administrador, modo de depuración habilitado, paneles de administrador disponibles públicamente con credenciales débiles, etc.
debilidades básicas de autenticación : problemas de seguridad que ocurren debido a lapsos en las políticas de contraseña, procesos de autenticación de usuarios, gestión inadecuada de sesiones, etc.
Tabla de contenido
Requisitos previos
Configurando nuestro entorno de desarrollo
Construyendo nuestra clase de escáner central
Implementación del rastreador
Diseño e implementación de las comprobaciones de seguridad
-
Verificación de detección de inyección SQL
-
Comprobación XSS (secuencias de comandos entre sitios)
Verificación de exposición de información confidencial
Implementación de la lógica de escaneo principal
Ampliación del escáner de seguridad
Concluyendo
Requisitos previos
Para seguir junto con este tutorial, necesitará:
Pitón 3.x
Comprensión básica de los protocolos HTTP
Comprensión básica de las aplicaciones web
-
Comprensión básica de cómo funcionan el XSS, la inyección SQL y los ataques básicos de seguridad
Configurando nuestro entorno de desarrollo
Instalemos nuestras dependencias requeridas con el siguiente comando:
pip install requests beautifulsoup4 urllib3 colorama
Usaremos estas dependencias en nuestro archivo de código:
# 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
Construyendo nuestra clase de escáner central
Una vez que tenga las dependencias, es hora de escribir la clase Core Scanner.
Esta clase servirá como nuestra clase principal que manejará la funcionalidad de escaneo de seguridad web. Hará un seguimiento de nuestras páginas visitadas y también almacenará nuestros hallazgos.
Tenemos la función normalize_url
que usaremos para asegurarnos de que no vuelva a rescatar URL que ya se han visto antes. Esta función esencialmente eliminará los parámetros HTTP GET de la URL. Por ejemplo, https://example.com/page?id=1
se convertirá en https://example.com/page
después de normalizarlo.
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}"
Implementación del rastreador
El primer paso en nuestro escáner es implementar un rastreador web que descubra páginas y URL en una aplicación objetivo determinada. Asegúrese de escribir estas funciones en nuestra clase 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)}")
Esta función rastreo
nos ayuda a realizar un rastreo en profundidad de un sitio web. Explorará todas las páginas de un sitio web mientras permanece dentro del dominio especificado.
Por ejemplo, si planea utilizar este escáner en https://google.com
, la función primero obtendrá todas las URL y luego, una por una, comprobará si pertenecen al dominio especificado ( es decir, google.com
). Si es así, continuará escaneando recursivamente la URL vista hasta una profundidad especificada que se proporciona con el parámetro profundidad
como argumento de la función. También tenemos algún manejo de excepciones para asegurarnos de manejar los errores sin problemas e informar cualquier error durante el rastreo.
Diseño e implementación de las comprobaciones de seguridad
Ahora finalmente vamos a la parte jugosa e implementemos nuestras verificaciones de seguridad. Comenzaremos primero con la inyección SQL.
Verificación de detección de inyección 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)}")
Básicamente, esta función realiza comprobaciones básicas de inyección de SQL probando la URL con cargas útiles de inyección de SQL comunes y buscando mensajes de error que puedan indicar una vulnerabilidad de seguridad.
Según el mensaje de error recibido después de realizar una solicitud GET simple en la URL, verificamos si ese mensaje es un error de la base de datos o no. Si es así, utilizamos la función report_vulnerability
para informarlo como un problema de seguridad en nuestro informe final que generará este script. Por el bien de este ejemplo, estamos seleccionando algunas cargas útiles de inyección SQL comúnmente probadas, pero puede ampliar esto para probar aún más.
Comprobación XSS (secuencias de comandos entre sitios)
Ahora implementemos la segunda verificación de seguridad para cargas 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)}")
Esta función, al igual que el probador de inyección SQL, utiliza un conjunto de cargas útiles XSS comunes y aplica la misma idea. Pero la diferencia clave aquí es que buscamos que nuestra carga útil inyectada aparezca sin modificaciones en nuestra respuesta en lugar de buscar un mensaje de error.
Si puede ver nuestra carga útil inyectada, lo más probable es que se ejecute en el contexto del navegador de la víctima como un ataque XSS reflejado.
Verificación de exposición de información confidencial
Ahora implementemos nuestra verificación final de PII sensible.
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)}")
Esta función utiliza un conjunto de patrones de regex predefinidos para buscar PII como correos electrónicos, números de teléfono, SSN y claves API (que tienen prefijo API-Key-
Al igual que las dos funciones anteriores, utilizamos el texto de respuesta de la URL y nuestros patrones Regex para encontrar estas PII en el texto de respuesta. Si encontramos alguna, la informamos con la función report_vulnerability
. Asegúrese de tener todas estas funciones definidas en la clase WebSecurityScanner
.
Implementación de la lógica de escaneo principal
Finalmente juntemos todo definiendo las funciones scan
y report_vulnerability
en la clase 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()
Este código define nuestra función scan
que esencialmente invocará la función rastrew
y comenzará a rastrear recursivamente el sitio web. Con la lectura múltiple, aplicaremos las tres verificaciones de seguridad en las URL visitadas.
También hemos definido la función report_vulnerability
que imprimirá efectivamente nuestra vulnerabilidad a la consola y también las almacenará en nuestra matriz Vulnerabilities
.
Ahora finalmente usemos nuestro escáner guardándolo como 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)}")
La URL de destino se proporcionará como argumento del sistema y obtendremos el resumen de las URL analizadas y las vulnerabilidades encontradas al final de nuestra exploración. Ahora analicemos cómo puede ampliar el escáner y agregar más funciones.
Extender el escáner de seguridad
Aquí hay algunas ideas para extender este escáner de seguridad básico a algo aún más avanzado:
Agregue más controles de vulnerabilidad como la detección de CSRF, el recorrido del directorio, etc.
Mejorar los informes con una salida HTML o PDF.
Agregue opciones de configuración para la intensidad del análisis y el alcance de la búsqueda (especificando la profundidad de los análisis mediante un argumento CLI).
Implementar una limitación de velocidad adecuada.
Agregar soporte de autenticación para pruebas de URL que requieren autenticación basada en sesión.
Concluyendo
¡Ahora sabe cómo construir un escáner de seguridad básico! Este escáner demuestra algunos conceptos centrales de seguridad web.
Tenga en cuenta que este tutorial solo debe usarse con fines educativos. Existen varias aplicaciones de grado empresarial de diseño profesional como BURP Suite y OWASP ZAP que pueden verificar cientos de vulnerabilidades de seguridad a una escala mucho mayor.
Espero que hayas aprendido los conceptos básicos de la seguridad web y un poco de programación de Python también.