核心代码:
python
复制代码
# csrf_detector.py
# CSRF (跨站请求伪造) 漏洞检测工具
# 通过检查 HTML 表单中是否包含反 CSRF 令牌来判断潜在风险
import requests
# requests 库 - HTTP/HTTPS 请求处理库
# 【本例使用的功能】:
# requests.Session() - 创建会话对象,自动管理 Cookie 和连接池
# session.get(url) - 发送 HTTP GET 请求获取网页内容
# response.content - 获取原始响应内容(字节形式)
# RequestException - 所有 requests 相关异常的基类
import argparse
# argparse 库 - 命令行参数解析工具
# 【本例使用的功能】:
# ArgumentParser(description="...") - 创建参数解析器
# add_argument("url", help="...") - 添加位置参数
# parse_args() - 解析命令行参数
import sys
# sys 库 - Python 系统相关的标准库
# 【本例使用的功能】:
# sys.stderr - 标准错误输出流,用于输出错误信息
# 【其他常用功能】:
# sys.argv - 命令行参数列表
# sys.exit() - 退出程序
# sys.path - 模块搜索路径
from bs4 import BeautifulSoup
# BeautifulSoup 库 - HTML/XML 文档解析库
# 【核心功能】:
# 将 HTML 文档解析为树形结构,便于遍历和查找
# 【本例使用的功能】:
# BeautifulSoup(content, 'html.parser') - 创建解析器对象
# soup.find_all('form') - 查找所有<form>标签
# soup.find_all('input') - 查找所有<input>标签
# tag.attrs - 获取标签的所有属性字典
# tag.attrs.get('name', default) - 获取指定属性值
# 【其他常用方法】:
# soup.find('tag') - 查找第一个匹配标签
# soup.select('css 选择器') - 使用 CSS 选择器查找
# tag.text / tag.string - 获取标签文本内容
from urllib.parse import urljoin
# urllib.parse 模块 - URL 处理工具
# 【本例使用的功能】:
# urljoin(base_url, relative_url) - 将相对 URL 转换为绝对 URL
# 【示例】:
# urljoin('http://test.com/page', '/login') → 'http://test.com/login'
# urljoin('http://test.com/page', 'submit') → 'http://test.com/submit'
# 【作用】: 处理表单 action 属性可能是相对路径的情况
# 一些常见的反 CSRF 令牌字段名
# 【说明】: 不同框架使用不同的令牌名称,这里列出常见的几种
# 【各框架典型命名】:
# csrf_token - 通用命名,许多框架使用
# authenticity_token - Ruby on Rails 框架
# _token - Laravel (PHP) 框架
# xsrf_token - Angular 等框架
# csrfmiddlewaretoken- Django (Python) 框架
CSRF_TOKEN_NAMES = ["csrf_token", "authenticity_token", "_token", "xsrf_token", "csrfmiddlewaretoken"]
def check_csrf(url):
"""
爬取一个 URL,分析其中的所有表单,检查是否存在 CSRF 漏洞的迹象。
【CSRF 漏洞原理】:
CSRF (Cross-Site Request Forgery) 跨站请求伪造
攻击者诱导用户在已登录状态下访问恶意网站
恶意网站伪造请求发送到目标网站(如转账、修改密码)
由于浏览器自动携带 Cookie,服务器误认为是合法请求
【防御机制】:
在每个表单中添加随机生成的 CSRF 令牌(Token)
服务器验证令牌的正确性,拒绝无令牌或令牌错误的请求
【检测原理】:
1. 获取网页 HTML 内容
2. 解析所有<form>标签
3. 对使用 POST 方法的表单,检查是否包含 CSRF 令牌字段
4. 如果没有令牌字段,则存在 CSRF 风险
【参数说明】:
url - 待检测的网页 URL
【返回值】:
bool - True 表示发现 CSRF 漏洞风险,False 表示未发现或检测失败
"""
print(f"[*] 正在分析 {url} 页面上的表单...")
is_vulnerable = False # 漏洞标志位,初始化为 False(假设安全)
try:
# 使用 Session 对象,它可以自动处理可能存在的 Cookie
# 【Session 的优势】:
# 1. 自动管理 Cookie(如会话 Cookie、认证 Cookie)
# 2. 保持 TCP 连接(连接池),提高性能
# 3. 可以在多次请求间共享数据
session = requests.Session()
# 发送 HTTP GET 请求获取网页内容
response = session.get(url)
# 使用 BeautifulSoup 解析 HTML 文档
# 参数说明:
# response.content - 原始响应内容(字节类型)
# 'html.parser' - Python 内置的 HTML 解析器
# 其他选项:'lxml' (更快), 'html5lib' (更准确)
soup = BeautifulSoup(response.content, 'html.parser')
# 找到页面中所有的 <form> 标签
# 【find_all 方法】:
# 查找所有匹配的标签,返回列表
# 可以按标签名、属性、CSS 类等多种方式查找
forms = soup.find_all('form')
# 检查是否找到表单
if not forms:
# 页面没有任何表单,不存在 CSRF 风险
print("[-] 页面上未发现任何表单。")
return False
print(f"[+] 发现 {len(forms)} 个表单。")
# 遍历每个表单进行详细分析
# enumerate(forms, 1) - 同时获取索引和内容,索引从 1 开始
for i, form in enumerate(forms, 1):
# 获取表单的 action 属性(提交地址)
# form.attrs - 标签所有属性的字典,如 {'action': '/login', 'method': 'post'}
# .get('action', 'N/A') - 获取 action 值,如果不存在则返回默认值 'N/A'
action = form.attrs.get('action', 'N/A')
# 获取表单的 method 属性(提交方法),并转换为小写
# 默认值为 'get'(HTML 表单默认方法是 GET)
method = form.attrs.get('method', 'get').lower()
print(f"\n--- 正在分析第 {i} 个表单 ---")
print(f" - 表单提交地址 (Action): {action}")
print(f" - 表单提交方法 (Method): {method}")
# 我们主要关心会改变服务器状态的 POST 表单
# 【为什么只检查 POST?】:
# GET 请求应该是幂等的(不改变服务器状态)
# 敏感操作(如转账、删除)应该使用 POST
# CSRF 攻击主要针对改变状态的请求
if method != 'post':
print(" - [信息] 表单未使用 POST 方法,跳过 CSRF 检查。")
continue # 跳过本次循环,继续检查下一个表单
# 在表单中寻找反 CSRF 令牌
has_csrf_token = False # 令牌存在标志位
# 查找表单中所有的 <input> 标签
# 【input 标签】: 表单字段,如文本框、隐藏域、提交按钮等
for input_tag in form.find_all('input'):
# 获取 input 标签的 name 属性(字段名),转换为小写便于比较
# 例如:<input type="hidden" name="csrf_token" value="abc123">
input_name = input_tag.attrs.get('name', '').lower()
# 检查输入字段的名称是否包含已知的令牌关键词
# 【any() 函数】:
# 判断可迭代对象中是否有任意一个元素为 True
# 短路求值:找到一个 True 就立即返回
# 【检查逻辑】:
# token_name in input_name - 检查字段名是否包含令牌关键词
# 例如:'csrf_token' in 'csrf_token_value' → True
if any(token_name in input_name for token_name in CSRF_TOKEN_NAMES):
print(f" - [安全] 发现潜在的反 CSRF 令牌:'{input_tag.attrs.get('name')}'")
has_csrf_token = True
break # 找到令牌后无需继续检查,跳出 input 循环
# 如果表单没有 CSRF 令牌,标记为存在风险
if not has_csrf_token:
print(f" - [高危] 警告:此 POST 表单中未发现可识别的反 CSRF 令牌!")
is_vulnerable = True # 标记存在漏洞
except requests.RequestException as e:
# 捕获所有 requests 相关的网络异常
# 【可能的异常】:
# ConnectionError - 连接失败
# TimeoutError - 请求超时
# HTTPError - HTTP 错误(如 404, 500)
# TooManyRedirects- 重定向次数过多
print(f"[!] 无法连接到 {url}。错误:{e}", file=sys.stderr)
# 输出到标准错误流(sys.stderr),与正常输出区分
return False # 检测失败
# 返回检测结果
# True - 至少有一个 POST 表单缺少 CSRF 令牌
# False - 所有 POST 表单都有令牌,或没有表单,或检测失败
return is_vulnerable
def main():
"""
主函数:程序入口点
【执行流程】:
1. 打印警告声明(法律声明)
2. 创建命令行参数解析器
3. 定义并添加参数
4. 解析用户输入的命令行参数
5. 调用 CSRF 检测函数
6. 根据检测结果输出总结信息
"""
# 打印分隔线和警告信息
# "=" * 60 - 重复 60 个等号,创建视觉分隔
print("=" * 60)
print("!!! 警告:本工具仅用于经授权的教育和安全测试目的 !!!")
print("!!! 未经许可,对任何系统进行漏洞扫描都是非法的 !!!")
print("=" * 60 + "\n")
# 创建命令行参数解析器
# description 参数显示在帮助信息中(python script.py --help)
parser = argparse.ArgumentParser(description="一个通过检查反 CSRF 令牌来发现潜在 CSRF 漏洞的工具。")
# 添加位置参数 url
# 【参数说明】:
# "url" - 参数名称,也是属性名
# help="..." - 帮助信息,显示在 --help 中
# 【使用方法】:
# python csrf_detector.py http://test.com/login
# args.url = "http://test.com/login"
parser.add_argument("url", help="待分析页面的 URL (例如:'http://test.com/profile.php')")
# 解析命令行参数
# 【parse_args 的作用】:
# 1. 检查命令行参数是否符合定义
# 2. 将参数值存储到命名空间对象
# 3. 可以通过 args.参数名 访问
args = parser.parse_args()
# 调用 CSRF 检测函数
# 【返回值处理】:
# 如果 check_csrf 返回 True - 进入 if 分支,输出发现漏洞
# 如果 check_csrf 返回 False - 进入 else 分支,输出未发现漏洞
if check_csrf(args.url):
print("\n[!] 总结:在页面上发现了潜在的 CSRF 漏洞。")
else:
print("\n[*] 总结:未在页面表单中发现明显的 CSRF 漏洞迹象。")
if __name__ == "__main__":
# 【__name__ 变量的作用】:
# 1. 当文件被直接运行时,__name__ = "__main__"
# 2. 当文件被导入时,__name__ = 模块名
#
# 【这样写的目的】:
# 确保只有直接运行此脚本时才执行 main()
# 如果被其他模块导入,不会自动执行代码
#
# 【示例】:
# python csrf_detector.py http://test.com → 执行 main()
# from csrf_detector import check_csrf → 不执行 main()
main()
1. CSRF 攻击原理
python
复制代码
场景:用户已登录银行网站
1. 用户在浏览器登录 bank.com
→ 浏览器保存会话 Cookie
2. 攻击者诱导用户访问恶意网站 evil.com
→ 用户点击链接或访问页面
3. evil.com 页面包含隐藏的恶意表单
<form action="bank.com/transfer" method="POST">
<input type="hidden" name="amount" value="10000">
<input type="hidden" name="to_account" value="攻击者账号">
</form>
→ JavaScript 自动提交表单
4. 浏览器发送请求到 bank.com/transfer
→ 自动携带 bank.com 的 Cookie
5. 银行服务器验证 Cookie 有效
→ 执行转账操作 ❌
2. CSRF 防御机制
python
复制代码
<!-- 有 CSRF 保护的表单 -->
<form action="/transfer" method="POST">
<!-- 隐藏的 CSRF 令牌 -->
<input type="hidden" name="csrf_token" value="a1b2c3d4e5f6">
<input type="text" name="amount">
<input type="submit" value="转账">
</form>
<!-- 服务器验证流程 -->
1. 生成随机令牌存储在 Session 中
2. 渲染表单时将令牌放入隐藏字段
3. 接收请求时验证令牌
4. 令牌不匹配 → 拒绝请求 ✅
3. BeautifulSoup 使用方法
python
复制代码
from bs4 import BeautifulSoup
html = """
<html>
<body>
<form action="/login" method="post">
<input type="hidden" name="csrf_token" value="abc123">
<input type="text" name="username">
<input type="password" name="password">
</form>
<form action="/search" method="get">
<input type="text" name="q">
</form>
</body>
</html>
"""
soup = BeautifulSoup(html, 'html.parser')
# 查找所有 form 标签
forms = soup.find_all('form') # 返回 [<form>, <form>]
# 遍历表单
for form in forms:
# 获取属性
action = form.attrs.get('action') # '/login', '/search'
method = form.attrs.get('method') # 'post', 'get'
# 查找所有 input 标签
inputs = form.find_all('input')
for input_tag in inputs:
name = input_tag.attrs.get('name') # 字段名
print(f"字段:{name}")
4. Session 对象的优势
python
复制代码
# ❌ 不使用 Session - 每次请求都创建新连接
requests.get(url1)
requests.get(url2)
requests.get(url3)
# ✅ 使用 Session - 复用连接,自动管理 Cookie
session = requests.Session()
session.get(url1) # 建立连接,保存 Cookie
session.get(url2) # 复用连接,自动携带 Cookie
session.get(url3) # 同上
5. any() 函数用法
python
复制代码
# 检查列表中是否有任意元素满足条件
CSRF_TOKEN_NAMES = ["csrf_token", "_token", "xsrf_token"]
# 示例 1: 包含令牌关键词
input_name = "my_csrf_token_field"
result = any(token in input_name for token in CSRF_TOKEN_NAMES)
# 结果:True ('csrf_token' 在字符串中)
# 示例 2: 不包含令牌关键词
input_name = "username"
result = any(token in input_name for token in CSRF_TOKEN_NAMES)
# 结果:False
# 等价的传统写法
has_token = False
for token in CSRF_TOKEN_NAMES:
if token in input_name:
has_token = True
break