安全测试中,你的目录爆破脚本真的‘优雅’吗?聊聊Python脚本的健壮性与可复用性设计
·
安全测试中,你的目录爆破脚本真的‘优雅’吗?聊聊Python脚本的健壮性与可复用性设计
在渗透测试或CTF比赛中,目录爆破脚本往往是我们的"瑞士军刀"。但你是否遇到过这样的尴尬:脚本运行到一半因为网络波动崩溃,重复结果堆满终端,或是需要反复修改代码来适配不同目标?这些问题背后,反映的是脚本在健壮性和可复用性上的不足。本文将带你从工程化角度,重新思考如何将一次性的"玩具脚本"升级为真正的安全工具。
1. 从脆弱到健壮:基础防护机制设计
1.1 网络异常处理:让脚本优雅地失败
一个专业的爆破脚本应该像经验丰富的探险家,面对未知环境仍能保持稳定。以下是关键的网络异常处理策略:
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
def create_retry_session(retries=3, backoff_factor=0.3):
session = requests.Session()
retry = Retry(
total=retries,
read=retries,
connect=retries,
backoff_factor=backoff_factor,
status_forcelist=(500, 502, 503, 504)
)
adapter = HTTPAdapter(max_retries=retry)
session.mount('http://', adapter)
session.mount('https://', adapter)
return session
这段代码实现了:
- 指数退避重试机制(backoff_factor)
- 对5xx服务器错误的自动重试
- 可配置的重试次数和间隔
提示:实际项目中建议将timeout参数设置为(连接超时, 读取超时)元组,避免无限等待
1.2 结果去重与持久化存储
临时脚本常将结果直接打印到终端,这既不专业也不利于后续分析。改进方案:
import json
from pathlib import Path
class ResultStorage:
def __init__(self, output_dir="results"):
self.output_dir = Path(output_dir)
self.output_dir.mkdir(exist_ok=True)
self.seen_urls = set()
def add_result(self, url, status_code, content_length):
if url not in self.seen_urls:
self.seen_urls.add(url)
result_file = self.output_dir / f"{status_code}_results.json"
with open(result_file, 'a') as f:
json.dump({
'url': url,
'status': status_code,
'length': content_length,
'timestamp': datetime.now().isoformat()
}, f)
f.write('\n') # 换行分隔JSON记录
这种设计实现了:
- 自动按状态码分类存储结果
- 内置去重功能
- 结构化日志记录(包含时间戳)
2. 用户体验优化:让脚本会"说话"
2.1 实时进度反馈
长时间运行的脚本需要提供清晰的进度反馈。以下是实现方案对比:
| 方案 | 实现复杂度 | 信息丰富度 | 终端兼容性 |
|---|---|---|---|
| 简单print | 低 | 低 | 高 |
| tqdm进度条 | 中 | 中 | 中 |
| 多线程日志 | 高 | 高 | 高 |
推荐使用tqdm与日志结合的方式:
from tqdm import tqdm
import logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('scan.log'),
logging.StreamHandler()
]
)
def scan_with_progress(wordlist, target_url):
with tqdm(total=len(wordlist), desc="Scanning") as pbar:
for word in wordlist:
try:
# 扫描逻辑...
pbar.update(1)
pbar.set_postfix({'last': word, 'found': len(found_items)})
except Exception as e:
logging.error(f"Error testing {word}: {str(e)}")
continue
2.2 速率控制与性能调优
不加限制的并发请求可能导致目标服务崩溃或被封禁。智能速率控制需要考虑:
- 动态调整并发数(基于响应时间/错误率)
- 遵守robots.txt中的Crawl-delay
- 自动避让业务高峰时段
import time
from collections import deque
class RateLimiter:
def __init__(self, max_rate, time_window=1.0):
self.max_rate = max_rate
self.time_window = time_window
self.request_times = deque()
def wait(self):
now = time.time()
# 移除超出时间窗口的记录
while self.request_times and now - self.request_times[0] > self.time_window:
self.request_times.popleft()
if len(self.request_times) >= self.max_rate:
sleep_time = self.time_window - (now - self.request_times[0])
time.sleep(sleep_time)
now = time.time()
self.request_times.append(now)
3. 工程化进阶:配置与封装
3.1 配置文件分离
将可变参数从代码中分离是专业工具的标志。推荐使用YAML配置:
# config.yaml
target:
base_url: "https://example.com"
ports: [80, 443]
scan:
wordlist: "common_dirs.txt"
extensions: [".php", ".bak", ".zip"]
threads: 10
timeout: 5
rate_limit: 50 # 每分钟最大请求数
对应的Python配置加载:
import yaml
from dataclasses import dataclass
@dataclass
class ScanConfig:
wordlist: str
extensions: list
threads: int
timeout: int
rate_limit: int
def load_config(config_path="config.yaml"):
with open(config_path) as f:
raw = yaml.safe_load(f)
return ScanConfig(**raw['scan'])
3.2 命令行接口设计
专业的工具应该提供友好的CLI界面。argparse的高级用法示例:
import argparse
def create_parser():
parser = argparse.ArgumentParser(
description="高级目录爆破工具",
formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
# 必需参数
parser.add_argument('target', help="目标URL")
# 可选参数
parser.add_argument('-w', '--wordlist',
default="wordlists/common.txt",
help="字典文件路径")
parser.add_argument('-t', '--threads',
type=int, default=10,
help="并发线程数")
# 互斥组
group = parser.add_mutually_exclusive_group()
group.add_argument('-v', '--verbose', action='store_true')
group.add_argument('-q', '--quiet', action='store_true')
# 子命令
subparsers = parser.add_subparsers(dest='command')
scan_parser = subparsers.add_parser('scan')
scan_parser.add_argument('--rate-limit', type=int)
return parser
4. 测试与持续改进
4.1 单元测试策略
为安全工具编写测试可能看起来多余,但却是保证长期可维护性的关键:
import unittest
from unittest.mock import patch
from scanner import DirectoryScanner
class TestScanner(unittest.TestCase):
@patch('scanner.requests.Session')
def test_scan_404(self, mock_session):
# 配置mock
mock_resp = mock_session.return_value.get.return_value
mock_resp.status_code = 404
# 执行测试
scanner = DirectoryScanner("http://test.com")
results = scanner.test_path("/nonexistent")
# 验证
self.assertEqual(results, [])
mock_session.return_value.get.assert_called_with(
"http://test.com/nonexistent",
timeout=5
)
4.2 性能分析与优化
使用cProfile和memory_profiler进行性能调优:
# 运行性能分析
python -m cProfile -o profile.stats scanner.py -w big_wordlist.txt
# 查看热点函数
python -m pstats profile.stats
常见优化方向:
- I/O密集型操作改为异步
- 内存占用高的数据结构优化
- 重复计算缓存
在真实项目中,将这些组件组合起来,就形成了一个从简单脚本到专业工具的蜕变。记得在GitHub上看到一个优秀的爆破工具实现,它通过插件系统支持不同扫描策略,使用SQLite存储结果,甚至实现了分布式扫描——这些都是我们可以逐步演进的方向。
更多推荐



所有评论(0)