news 2026/8/29 11:58:17

Selenium自动化测试框架在安全演练中的应用:对抗钓鱼网站的实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Selenium自动化测试框架在安全演练中的应用:对抗钓鱼网站的实战指南

1. 项目概述:当自动化测试框架“误入”安全演练

最近在和一些做安全的朋友聊天时,他们提到了一个挺有意思的需求:如何高效、安全地对一些已知的钓鱼网站进行“压力测试”或数据污染,以避免这些陷阱危害到更多普通用户。当然,这里的“测试”并非攻击,而是指在安全研究、内部演练或教育演示的授权范围内,向这些恶意页面填充大量无效的垃圾数据,从而干扰其背后的数据收集链条。这让我立刻想到了一个老朋友——Selenium。

Selenium,这个在测试工程师圈子里无人不晓的浏览器自动化工具,其核心能力就是模拟真实用户的操作。我们通常用它来确保自己开发的Web应用功能正常,但它的能力远不止于此。通过编写脚本,Selenium可以自动打开浏览器,导航到指定网页,定位页面元素,并执行点击、输入等操作。那么,如果我们把目标网址从一个正常的登录页换成一个钓鱼页面,把要输入的账号密码从测试用例数据换成随机生成的字符串,一个用于“对抗”钓鱼网站的自动化工具就诞生了。

这个项目的核心,就是利用Selenium自动化框架,结合随机数据生成技术,实现对钓鱼网站登录表单的自动化、批量化虚假信息填充。它不寻求获取任何有效信息,恰恰相反,它的目的是“污染”攻击者的数据库,增加其筛选真实数据的成本,同时也能用于安全意识的演示,直观地展示自动化攻击的简易性,从而强调安全防护的重要性。对于安全研究人员、企业内负责安全意识培训的同事,或是想深入了解Web自动化与安全交叉领域的技术爱好者来说,这是一个兼具实用性和学习价值的练手项目。

2. 核心思路与技术选型解析

2.1 为什么是Selenium?而不只是Requests

看到“自动输入”几个字,很多朋友的第一反应可能是直接用Python的requests库发送POST请求,这样不是更快更隐蔽吗?确实,对于很多结构简单的表单,requests直接发包效率极高。但在这个特定场景下,Selenium有着不可替代的优势。

首先,现代钓鱼网站,尤其是那些针对金融机构或大型企业的,反自动化检测机制越来越复杂。它们可能会通过JavaScript动态生成表单字段名、添加隐藏的验证码(Honeypot)、或者检查浏览器环境(如WebDriver属性)。requests库模拟的是纯粹的HTTP请求,很容易被这类前端检测机制识别并拦截。而Selenium驱动的是一个真实的、完整的浏览器实例(如Chrome、Firefox),它执行的是真实的JavaScript,渲染的是真实的DOM,其行为指纹与真人用户高度相似,能够绕过绝大多数基于前端行为的简单检测。

其次,我们的目标是“模拟输入”,这不仅仅是提交数据,更是一个完整的用户交互过程。有些钓鱼页面会监控键盘事件、焦点事件,甚至鼠标移动轨迹。Selenium的send_keys方法会真实地触发这些事件,使得我们的自动化脚本在页面看来更像是一个“活人”。这对于我们研究钓鱼网站的交互逻辑和收集机制本身,也提供了更真实的视角。

最后,从学习和演示的角度,Selenium的操作是可视化的。你可以清晰地看到浏览器打开、跳转到钓鱼页面、输入框被逐个填满的过程。这种视觉反馈对于安全演示和教育来说,冲击力远胜于命令行里滚动的日志。它直观地揭示了“一个脚本就能如此简单地模拟大量用户行为”这一事实。

2.2 随机数据生成:策略与伦理边界

项目的另一个核心是“随机账号和密码”。这里的随机不是随便乱填,而是需要遵循一定策略,以达到更好的效果并规避潜在风险。

1. 格式模仿与数据污染:最有效的“污染”是让数据看起来像真的。因此,我们的随机生成器应该模仿真实用户的习惯。

  • 账号:可以是“邮箱格式”(如random123@dummy.com)、“手机号格式”(如13800138000)或“用户名格式”(如user_xxxx)。混合使用这些格式,能使垃圾数据更难以被简单规则过滤。
  • 密码:应模拟常见密码策略,如“大小写字母+数字+特殊符号”的组合,长度在8-16位之间。避免使用连续的、有规律的字符串(如“123456”或“aaaaaa”),因为钓鱼网站后端可能本身就设有弱密码过滤器。

2. 唯一性与可追溯性:虽然数据是随机的,但在单次运行中,最好保证生成的每条记录都是唯一的,避免重复提交相同数据降低污染效率。同时,可以考虑为每次运行或每批数据添加一个无害的、可识别的“标记”(例如,在邮箱用户名部分包含一个固定的、无意义的字符串如testbot_),这纯粹是为了在后续分析中(如果有权限且合法)识别出哪些数据来自你的脚本。必须强调的是,绝对不要使用任何真实的、他人的个人信息片段,哪怕是一个可能存在的手机号段。

3. 伦理与法律红线:这是本项目最重要的前提。所有操作必须在合法授权的范围内进行。这通常意味着:

  • 目标:仅限于自己拥有完全控制权的测试环境(如自己搭建的钓鱼演示页面),或是在明确获得授权的安全演练、众测项目中指定的目标。
  • 目的:仅限于安全研究、教育演示或授权的防御性测试(如污染攻击者数据库以保护潜在受害者)。
  • 禁止:绝对禁止对任何未授权的网站进行此类操作,这不仅是非法的(可能违反《计算机信息系统安全保护条例》等相关法规,涉及非法侵入、破坏系统功能、干扰系统运行),更是极不道德的。自动化脚本的威力很大,务必用在正途。

2.3 整体架构设计

整个脚本的运行流程可以概括为以下几个步骤:

  1. 环境启动:初始化WebDriver,配置浏览器选项(如无头模式、禁用图片加载以加速)。
  2. 目标导航:驱动浏览器打开指定的钓鱼网站URL。
  3. 元素定位:在页面中精准找到用户名输入框、密码输入框和提交按钮的HTML元素。
  4. 数据生成:调用随机生成函数,产生一对符合格式要求的虚假账号和密码。
  5. 模拟输入:将生成的账号密码通过send_keys方法填入对应输入框。
  6. 提交表单:模拟点击提交按钮或按回车键。
  7. 循环与间隔:根据设定的次数,重复步骤3-6。每次循环之间应添加随机的时间间隔(如1-3秒),以更好地模拟人类操作节奏,避免因请求频率过高被IP封锁或触发风控。
  8. 清理退出:任务完成后,关闭浏览器,释放资源。

3. 实战环境搭建与核心代码拆解

3.1 环境准备与依赖安装

首先,你需要一个Python环境(建议3.7及以上)。核心库就是selenium。通过pip可以轻松安装:

pip install selenium

其次,你需要下载与浏览器版本匹配的WebDriver。这是Selenium控制浏览器的桥梁。以最常用的Chrome为例:

  1. 查看你电脑上Chrome的版本(在浏览器地址栏输入chrome://version/)。
  2. 访问ChromeDriver官网或国内镜像站,下载对应版本的chromedriver
  3. 将下载的chromedriver可执行文件放在一个已知路径下(如/usr/local/bin(Mac/Linux)或添加到系统环境变量PATH中(Windows)),或者后续在代码中指定其路径。

3.2 浏览器驱动初始化与配置

直接使用Selenium打开浏览器可能会被一些网站检测到WebDriver特征。我们可以通过添加一些选项来进行基本的伪装。

from selenium import webdriver from selenium.webdriver.chrome.options import Options import time import random def init_driver(): chrome_options = Options() # 可选:启用无头模式,不显示浏览器界面,运行更快更隐蔽 # chrome_options.add_argument('--headless') # 禁用图片加载,加速页面渲染 chrome_options.add_argument('--blink-settings=imagesEnabled=false') # 禁用GPU加速,避免一些兼容性问题 chrome_options.add_argument('--disable-gpu') # 禁用浏览器通知 chrome_options.add_argument('--disable-notifications') # 尝试隐藏“自动化控制”特征(并非百分百有效,但能应对基础检测) chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"]) chrome_options.add_experimental_option('useAutomationExtension', False) # 初始化驱动,指定chromedriver路径(如果已加入PATH则不需要) driver = webdriver.Chrome(options=chrome_options) # 执行CDP命令,进一步隐藏自动化特征 driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', { 'source': ''' Object.defineProperty(navigator, 'webdriver', { get: () => undefined }) ''' }) return driver

注意:无头模式(--headless)虽然隐蔽且节省资源,但有些网站会针对无头浏览器进行检测。在实战中,可以先使用非无头模式观察页面加载和交互是否正常,调试成功后再启用无头模式。另外,上述隐藏navigator.webdriver属性的方法对于较新版本的Chrome和反检测技术可能已失效,更复杂的伪装需要更深入的技术,这超出了基础项目的范围。

3.3 随机数据生成器实现

我们来创建一个简单的随机数据生成函数,它能够生成多种格式的虚假账号。

import random import string def generate_fake_credential(): """生成一对随机的虚假账号和密码""" # 随机选择一种账号格式 account_type = random.choice(['email', 'mobile', 'username']) if account_type == 'email': # 生成邮箱格式账号 username = ''.join(random.choices(string.ascii_lowercase + string.digits, k=8)) domain = random.choice(['dummy.com', 'example.net', 'test.org', 'mail.io']) account = f"{username}@{domain}" elif account_type == 'mobile': # 生成中国大陆手机号格式(请注意,这里是完全随机生成的虚假号段,仅用于演示格式) # 常见虚拟号段开头,避免使用真实分配号段 prefix = random.choice(['131', '132', '145', '155', '171', '188']) suffix = ''.join(random.choices(string.digits, k=8)) account = f"{prefix}{suffix}" else: # username # 生成用户名格式 prefix = random.choice(['user', 'admin', 'test', 'demo', 'guest']) suffix = ''.join(random.choices(string.digits, k=6)) account = f"{prefix}_{suffix}" # 生成密码:8-12位,包含大小写字母、数字和特殊符号 password_length = random.randint(8, 12) # 定义字符池 lower = string.ascii_lowercase upper = string.ascii_uppercase digits = string.digits symbols = '!@#$%^&*' # 确保每种类型至少有一个字符 password = [ random.choice(lower), random.choice(upper), random.choice(digits), random.choice(symbols) ] # 填充剩余长度 all_chars = lower + upper + digits + symbols password += random.choices(all_chars, k=password_length - 4) # 打乱顺序 random.shuffle(password) password = ''.join(password) return account, password

这个函数会随机返回像('john_doe_123', 'aB3$kL9oP')('fakeuser@dummy.com', 'Xy8!qT2#mN')这样的数据对。

3.4 页面元素定位与自动化输入

这是Selenium的核心操作。钓鱼网站的登录表单可能千奇百怪,我们需要使用多种定位策略来找到输入框。

from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC def fill_form(driver, url, account, password): """导航到目标URL并填写表单""" try: driver.get(url) # 等待页面主要元素加载,这里假设页面包含一个标题或某个标志性元素 WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.TAG_NAME, "body")) ) time.sleep(random.uniform(1, 2)) # 随机等待,模拟人类阅读时间 # 策略1:优先尝试通过常见的id和name属性定位 # 用户名输入框常见的id或name username_selectors = [ (By.ID, 'username'), (By.NAME, 'username'), (By.ID, 'user'), (By.NAME, 'user'), (By.ID, 'email'), (By.NAME, 'email'), (By.ID, 'account'), (By.NAME, 'account'), ] # 密码输入框常见的id或name password_selectors = [ (By.ID, 'password'), (By.NAME, 'password'), (By.ID, 'pass'), (By.NAME, 'pass'), (By.ID, 'pwd'), (By.NAME, 'pwd'), ] username_field = None password_field = None # 遍历选择器列表,尝试定位用户名输入框 for by, value in username_selectors: try: element = driver.find_element(by, value) if element.is_displayed() and element.is_enabled(): username_field = element print(f"找到用户名输入框: {by}='{value}'") break except: continue # 遍历选择器列表,尝试定位密码输入框 for by, value in password_selectors: try: element = driver.find_element(by, value) if element.is_displayed() and element.is_enabled(): password_field = element print(f"找到密码输入框: {by}='{value}'") break except: continue # 策略2:如果通过id/name没找到,尝试通过CSS选择器查找input类型 if not username_field or not password_field: print("未通过常见属性找到,尝试通过input类型查找...") all_inputs = driver.find_elements(By.TAG_NAME, 'input') text_inputs = [inp for inp in all_inputs if inp.get_attribute('type') in ['text', 'email', 'tel'] and inp.is_displayed()] pwd_inputs = [inp for inp in all_inputs if inp.get_attribute('type') == 'password' and inp.is_displayed()] # 简单的启发式规则:通常第一个文本输入框是账号,第一个密码输入框是密码 if text_inputs and not username_field: username_field = text_inputs[0] if pwd_inputs and not password_field: password_field = pwd_inputs[0] if not username_field: raise Exception("无法定位用户名输入框") if not password_field: raise Exception("无法定位密码输入框") # 模拟人工输入:逐个字符输入,并加入随机延迟 print(f"正在输入账号: {account}") username_field.clear() for char in account: username_field.send_keys(char) time.sleep(random.uniform(0.05, 0.2)) # 每个字符输入间隔50-200毫秒 time.sleep(random.uniform(0.5, 1)) # 账号输完后稍作停顿 print(f"正在输入密码: {password}") password_field.clear() for char in password: password_field.send_keys(char) time.sleep(random.uniform(0.05, 0.2)) # 策略3:寻找提交按钮并点击 submit_selectors = [ (By.CSS_SELECTOR, "input[type='submit']"), (By.CSS_SELECTOR, "button[type='submit']"), (By.XPATH, "//button[contains(text(), '登录') or contains(text(), 'Sign in') or contains(text(), 'Submit')]"), (By.ID, 'submit'), (By.NAME, 'submit'), ] submit_button = None for by, value in submit_selectors: try: element = driver.find_element(by, value) if element.is_displayed() and element.is_enabled(): submit_button = element print(f"找到提交按钮: {by}='{value}'") break except: continue if submit_button: time.sleep(random.uniform(0.5, 1.5)) submit_button.click() print("表单已提交。") else: # 如果没找到按钮,尝试在密码输入框按回车键提交 print("未找到提交按钮,尝试模拟回车键提交。") from selenium.webdriver.common.keys import Keys password_field.send_keys(Keys.RETURN) # 提交后等待一段时间,观察页面反应(例如跳转或错误信息) time.sleep(random.uniform(2, 4)) except Exception as e: print(f"处理页面 {url} 时发生错误: {e}") # 可以在这里截图保存,便于调试 # driver.save_screenshot(f'error_{int(time.time())}.png')

这段代码展示了健壮的元素定位策略:首先尝试最常见的HTML属性(id,name),如果失败则回退到更通用的方法(按input标签查找)。输入时模拟了人类的输入速度,并加入了随机等待,使得自动化行为更不易被察觉。

4. 完整脚本组装与运行策略

将上述模块组合起来,并添加循环和日志功能,就构成了完整的脚本。

import logging from datetime import datetime def main(): # 配置日志,记录操作 logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler(f'selenium_phishing_filler_{datetime.now().strftime("%Y%m%d_%H%M%S")}.log'), logging.StreamHandler() ] ) logger = logging.getLogger(__name__) # **!!!重要:请务必在合法授权的目标上测试!!!** # 这里仅为示例格式,请替换为你有权测试的URL(例如自己搭建的测试页面) TARGET_URL = "https://your-authorized-test-site.com/login" # 替换成你的授权目标 NUMBER_OF_SUBMISSIONS = 10 # 计划提交的次数 driver = None try: driver = init_driver() logger.info(f"开始对 {TARGET_URL} 进行自动化填充,计划提交 {NUMBER_OF_SUBMISSIONS} 次。") for i in range(1, NUMBER_OF_SUBMISSIONS + 1): logger.info(f"--- 开始第 {i} 次提交 ---") account, password = generate_fake_credential() logger.info(f"生成凭证: 账号={account}, 密码={password}") fill_form(driver, TARGET_URL, account, password) # 本次操作完成后的长间隔,模拟不同用户访问 wait_time = random.uniform(5, 15) logger.info(f"第 {i} 次提交完成,等待 {wait_time:.2f} 秒后继续。") time.sleep(wait_time) logger.info("所有计划提交已完成。") except KeyboardInterrupt: logger.info("用户中断操作。") except Exception as e: logger.error(f"主程序运行出错: {e}", exc_info=True) finally: if driver: driver.quit() logger.info("浏览器驱动已关闭。") if __name__ == "__main__": main()

运行策略与参数调整:

  • 提交次数(NUMBER_OF_SUBMISSIONS:根据实际需要设置。对于演示,10-20次足以;对于研究或演练,可能需要更多,但要密切注意目标服务器的响应,避免造成拒绝服务(DoS)影响。
  • 等待时间:脚本中有两处等待。一处是操作间隔(wait_time),模拟不同用户访问的间隔,建议设置在5秒以上。另一处是操作内的微间隔(如输入字符间隔、点击前后间隔),用于模拟人类反应速度,这对绕过简单行为检测很有帮助。
  • 会话管理:当前脚本每次提交都在同一个浏览器会话中。有些网站会使用会话(Session)或Cookie来跟踪用户。你可以修改脚本,在每次循环后清除Cookies甚至重启浏览器,来模拟完全独立的用户访问,但这会大大降低执行速度。

5. 常见问题、反检测策略与进阶思考

5.1 典型问题与排查技巧

在实际运行中,你可能会遇到以下问题:

问题现象可能原因排查与解决思路
NoSuchElementException(找不到元素)1. 页面未加载完成。
2. 元素定位器(如ID、CSS选择器)写错或已变更。
3. 元素在iframe框架内。
4. 页面是动态加载的(SPA)。
1. 增加WebDriverWait显式等待,等待元素出现、可点击等状态。
2. 使用浏览器开发者工具(F12)重新检查元素属性,尝试更通用的定位方式(如XPath包含文本)。
3. 使用driver.switch_to.frame()切换到对应的iframe。
4. 等待特定的AJAX加载完成,或通过等待某个动态出现元素的出现来判断。
ElementNotInteractableException(元素不可交互)1. 元素被隐藏(display: none)或禁用(disabled)。
2. 元素被其他元素(如遮罩层)覆盖。
1. 检查元素状态,尝试定位其他可交互的替代元素。
2. 使用driver.execute_script("arguments[0].click();", element)通过JavaScript直接点击,有时可绕过覆盖问题。
提交后无反应或页面报错1. 网站有前端验证(如验证码、滑块)。
2. 提交的请求被后端风控拦截(如频率过高、IP异常)。
3. 表单需要其他隐藏字段(如CSRF Token)。
1.验证码是自动化天敌,本项目不涉及破解验证码,遇到即应停止或切换目标。这是合法性的重要边界。
2. 大幅增加操作间隔,模拟更真实的人类行为。考虑使用代理IP池(需合法来源)。
3. 分析页面源代码或网络请求,查看提交时携带的所有参数,并在脚本中构造。这需要更深入的分析。
浏览器被检测出是自动化工具网站检测到了navigator.webdriver等属性。使用上文init_driver中的CDP命令进行基础隐藏。更高级的检测需要更复杂的反反检测策略,如使用undetected-chromedriver等第三方库,但这会显著增加复杂度。

实操心得:在编写定位器时,优先使用idname,因为它们通常最稳定且唯一。其次是CSS选择器,它比XPath更简洁、性能更好。XPath虽然强大,但过于依赖页面结构,一旦结构微调就容易失效。在脚本中实现“定位器后备列表”是一个好习惯,就像我们上面代码做的那样,能大大提高脚本的鲁棒性。

5.2 对抗更高级的反自动化检测

如果目标网站采用了更先进的反机器人技术,基础的Selenium脚本可能会很快失效。这时需要更深入的策略:

  1. 指纹伪装:除了隐藏webdriver,还需要处理其他浏览器指纹,如插件列表、语言、时区、屏幕分辨率、Canvas指纹等。这可以通过CDP命令或加载特定插件/修改浏览器启动参数来实现,但这是一场持续的攻防战。
  2. 行为模拟:不仅仅是随机等待,还需要模拟更复杂的人类行为模式,如非直线的鼠标移动轨迹(使用ActionChains)、随机的滚动页面行为、在输入前先点击一下输入框等。
  3. 代理与用户代理(UA)轮换:频繁的请求来自同一个IP和浏览器UA,极易被封锁。可以集成代理IP池和随机UA库,在每次循环或每几次循环后更换。
  4. 使用更底层的自动化工具:对于极其严苛的环境,可以考虑使用Puppeteer(Node.js)或Playwright,它们提供了更精细的控制和更好的反检测特性。但核心思路与Selenium相通。

必须再次强调,所有这些进阶技术都必须在绝对合法和授权的范围内使用。技术的双刃剑属性在此体现得淋漓尽致。

5.3 项目的延伸思考与价值

完成这个基础项目后,你可以沿着几个方向进行深化:

  • 从“污染”到“监测”:修改脚本,使其在提交虚假信息后,不立即关闭,而是监测页面的后续行为。例如,记录页面跳转到了哪个“成功”或“错误”页面,甚至尝试捕获可能弹出的“钓鱼成功”后续步骤(如要求输入短信验证码的页面),这能帮助你更深入地分析钓鱼链路的全貌。
  • 数据收集与分析:将脚本提交的虚假账号密码、提交时间、目标URL、页面响应状态(如HTTP状态码、返回的特定文本)记录到数据库或文件中。长期运行可以积累数据,用于分析钓鱼网站的活跃时间、响应模式等。
  • 与威胁情报结合:如果你在安全团队,可以将此脚本作为内部威胁情报收集流程的一环。当发现新的钓鱼URL时,自动将其加入任务队列,进行快速的自动化“探针”测试,收集其表单结构、提交地址等基本信息,丰富内部的威胁情报库。
  • 安全教育演示工具:这是本项目最具正面价值的应用。你可以构建一个完全受控的、模拟的钓鱼页面,然后运行此脚本,向同事或公众现场展示:“看,只需要这么简单的代码,攻击者就能在几分钟内提交成千上万条数据。如果你不小心在类似的假页面上输入了真实密码,后果会怎样?”这种视觉化演示比任何文字宣传都更有说服力。

这个项目始于一个简单的自动化想法,但深入下去,它触及了Web自动化、前端安全、反机器人技术、数据分析和安全伦理等多个领域。它像一把钥匙,帮你打开了一扇门,门后是一个需要技术能力与责任意识并重的广阔世界。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/29 11:57:59

lazygit 一天上手:终端 Git 图形界面搞定 90% 日常操作

lazygit 一天上手:终端 Git 图形界面搞定 90% 日常操作 【免费下载链接】lazygit simple terminal UI for git commands 项目地址: https://gitcode.com/GitHub_Trending/la/lazygit 想查一下改了哪些文件,得在 git status 和 git log 之间来回切…

作者头像 李华
网站建设 2026/8/29 11:52:59

AI盈利困局:从成本结构到客户价值锚点的工程破局

最近两年AI行业有个现象特别值得玩味:各家大模型公司的营收数字看起来在涨,融资新闻一条接一条,但真正靠客户付费跑通盈利模型的却少之又少。圈内甚至流传一种说法——AI行业目前的利润不是从客户那里赚来的,而是由投资人“续费”…

作者头像 李华
网站建设 2026/8/29 11:52:27

C++泛型编程实战:基于函数模板与std::sort的通用数组排序方案

1. 项目概述与核心价值在C开发中,处理数组排序是再基础不过的操作,但你是否曾为不同类型的数据(比如一堆int、一堆string,甚至是一堆自定义的Student对象)写出一堆大同小异的排序函数而感到烦躁?或者&#…

作者头像 李华