Python安全开发之简易csrf检测工具

核心代码:

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
相关推荐
何政@2 小时前
Agent Skills 完全指南:从概念到自定义实践
人工智能·python·大模型·claw·404 not found 罗
zmj3203242 小时前
ISO/SAE 21434:2021(道路车辆 - 网络安全工程) 汇总
网络·安全·web安全·21434
AmyLin_20012 小时前
【pdf2md-3:实现揭秘】福昕PDF SDK Python 开发实战:从逐字符提取到 LR 版面分析
开发语言·python·pdf·sdk·markdown·pdf2md
IP老炮不瞎唠2 小时前
Scrapy 高效采集:优化方案与指南
网络·爬虫·python·scrapy·安全
沪漂阿龙2 小时前
深入浅出 Pandas apply():从入门到向量化思维
人工智能·python·pandas
我材不敲代码2 小时前
OpenCV 实战——Python 实现图片人脸检测 + 视频人脸微笑检测
人工智能·python·opencv
zmj3203242 小时前
UNR -155 Annex 5提示的威胁及其编号
网络·安全·网络安全
jaysee-sjc2 小时前
【项目三】用GUI编程实现局域网群聊软件
java·开发语言·算法·安全·intellij-idea
七夜zippoe3 小时前
模型部署优化:ONNX与TensorRT实战——从训练到推理的完整优化链路
人工智能·python·tensorflow·tensorrt·onnx