零基础玩转 bWAPP 靶场(五):HTML 注入——存储型(博客)

摘要: 本文是 bWAPP 靶场系列的第五篇,聚焦于 HTML Injection - Stored (Blog)(存储型 HTML 注入------博客)漏洞。文章从零基础角度出发,详细讲解存储型 HTML 注入与反射型的本质区别、该漏洞的产生原因与巨大危害,然后按照 Low、Medium、High 三个安全级别逐一进行源码分析、通关演示和防御方案讲解。


一、前言

在前四篇文章中,我们分别介绍了 bWAPP 的环境搭建、HTML Injection - Reflected (GET)、HTML Injection - Reflected (POST) 以及 HTML Injection - Reflected (Current URL)。前三篇讲的都是反射型 HTML 注入,而今天我们要进入一个危害更大、更持久 的漏洞类型------存储型 HTML 注入(Stored HTML Injection)。

反射型 HTML 注入就像"一次性烟花"------点燃了炸一下,页面刷新就没了。而存储型 HTML 注入就像"埋在地雷"------攻击代码被永久保存在服务器上,任何访问页面的用户都可能踩中。

在本篇中,我们将以 博客(Blog) 场景为例,完整走一遍存储型 HTML 注入的攻击与防御全流程。

二、存储型 HTML 注入概述

2.1 什么是存储型 HTML 注入?

存储型 HTML 注入(Stored HTML Injection),是指攻击者提交的恶意 HTML 代码被永久存储在目标服务器的数据库(或文件系统)中,当其他用户访问包含该数据的页面时,恶意代码从服务器被读取并输出到浏览器中执行。

简单来说,攻击流程是这样的:

  • 注入:攻击者在评论区、留言板、个人资料等位置提交恶意 HTML 代码。

  • 存储 :服务器将恶意代码存入数据库,没有做任何过滤或编码。

  • 触发:当其他用户(或攻击者自己)访问该页面时,服务器从数据库读取恶意代码并直接输出到 HTML 中。

  • 执行:受害者的浏览器解析并执行了恶意 HTML 标签。

2.2 存储型 vs 反射型------一张表说清楚

这是初学者最容易混淆的概念,我们用一张表彻底讲明白:

对比维度 反射型 HTML 注入 存储型 HTML 注入
注入点 URL 参数、表单输入等 评论区、留言板、个人资料等
存储位置 不存储,一次性输出 存储在服务器数据库
触发方式 需要诱导用户点击恶意链接 用户正常访问页面就会触发
持久性 一次性,刷新即消失 永久存在,直到被删除
危害范围 单次攻击,针对特定用户 所有访问者都可能受害
攻击难度 需要社会工程学配合 提交即生效,难度更低
危害等级 中 高

2.3 为什么会产生存储型 HTML 注入?

根本原因和反射型一样:应用程序将用户输入的数据未经处理地直接输出到了 HTML 页面中。

但存储型多了一个环节------数据库。漏洞产生的完整链条是:

  • 用户提交的数据没有经过过滤就被存入数据库。

  • 从数据库读取数据时,没有经过编码就直接输出到 HTML 页面。

  • 浏览器将输出的内容当作 HTML 代码解析执行。

关键点:防御存储型注入,不仅要防御"写入数据库"的环节,还要防御"从数据库读出并输出"的环节。很多开发者只在写入时做了防护,却忽略了读取输出的安全性------这同样是漏洞!

2.4 存储型 HTML 注入的危害

存储型 HTML 注入的危害远大于反射型:

  • 持久性攻击:恶意代码永久存在于服务器上,只要漏洞不修复,攻击就一直在。

  • 大规模影响:所有访问该页面的用户都会受到影响,攻击面极大。

  • 蠕虫传播:恶意代码可以自动在评论区再次注入,实现自我复制和传播。

  • 隐蔽性强:攻击者不需要发送钓鱼链接,用户正常访问就会被攻击。

  • 组合攻击:配合 XSS 可以窃取所有访问者的 Cookie、会话信息等。


三、存储型 HTML 注入的历史背景与经典案例

3.1 历史背景

存储型注入类漏洞(包括存储型 XSS)是 Web 安全历史上最早被发现、也是最广泛存在的漏洞类型之一。

早在 2000 年代初,随着博客、论坛、留言板等 Web 2.0 应用的兴起,用户生成内容(UGC)成为网站的核心功能。然而,大量开发者对安全问题认识不足,直接将用户输入存入数据库并原样展示------存储型注入漏洞由此大规模爆发。

3.2 经典案例:MySpace 蠕虫(Samy Worm)

2005 年,世界上最大的社交网站之一 MySpace 爆发了一场著名的 Samy 蠕虫攻击。

攻击者 Samy Kamkar 在 MySpace 的个人资料中注入了一段 JavaScript 代码。任何查看他个人资料的用户,都会自动将该攻击者添加为好友,并在自己的个人资料中复制同样的恶意代码 。在短短 20 小时内,超过 100 万 MySpace 用户被感染。

这个案例完美展示了存储型注入的蠕虫特性------恶意代码可以像生物病毒一样自我复制和传播。

3.3 为什么选择"博客"作为攻击场景?

bWAPP 选择 Blog(博客) 作为存储型注入的演示场景,是因为:

  • 博客的评论/留言功能是典型的用户输入场景。

  • 用户提交的内容会被存入数据库并在页面中展示。

  • 所有访问该博客的人都会看到并执行恶意代码。

  • 这是现实世界中最常见、最真实的攻击场景之一。


四、Low 安全级别

4.1 通关步骤

步骤一:进入漏洞页面

在 bWAPP 主界面选择 HTML Injection - Stored (Blog) ,将安全级别设置为 Low,然后点击"Hack"按钮进入漏洞页面。

页面是一个博客留言板,包含:

  • 一个文本输入框 (textarea),用于输入博文/评论内容

  • Submit 按钮,用于提交内容

  • 下方表格展示所有已提交的博客条目(包括 Owner 、Date 、Entry 三列)

步骤二:提交正常内容

先在输入框中输入一段正常文本,比如:Hello, this is my first blog post!

点击 Submit,页面刷新后,下方表格中会出现你刚刚提交的内容。

步骤三:注入 HTML 代码(核心!)

现在,输入框中输入一段 HTML 代码:

复制代码
<h1 style="color:red;">Hacked! This blog is compromised!</h1>

点击 Submit,你会发现------页面下方显示的不再是纯文本 ,而是一个红色的大号标题"Hacked! This blog is compromised!"。

这说明:服务器将你的 HTML 代码原样存入数据库,并在页面中直接输出,浏览器成功解析并渲染了它!

步骤四:更高级的攻击------植入恶意链接

试试注入一个超链接:

复制代码
<a href="http://evil.com/phishing">点击领取免费比特币!</a>

提交后,博客中会出现一个可点击的链接,诱导其他用户点击。

步骤五:注入 JavaScript(XSS 攻击)

存储型 HTML 注入的终极形态是注入 JavaScript 脚本:

复制代码
<script>alert('You have been hacked!')</script>

提交后,每次有人访问这个博客页面,都会弹出警告框 。而且这个弹窗会一直存在,直到管理员删除这条记录或修复漏洞。

4.2 源码分析

打开 bWAPP 源码目录下的 htmli_stored.php 文件,分析 Low 级别的核心逻辑。

写入数据库部分

当用户提交博客内容时,PHP 代码大致如下:

php 复制代码
// 自定义过滤函数:htmli()
function htmli($data)
{
    include("connect_i.php");
    switch($_COOKIE["security_level"])
    {
        case "0" : // Low 级别
            $data = sqli_check_3($link, $data); // 仅做SQL转义
            break;
        case "1" : // Medium
            $data = sqli_check_3($link, $data);
            // $data = xss_check_4($data); 注释未启用过滤
            break;
        case "2" : // High
            $data = sqli_check_3($link, $data);
            // $data = xss_check_3($data); 注释未启用过滤
            break;
        default :
            $data = sqli_check_3($link, $data);
            break;
    }
    return $data;
}
// SQL防注入函数:仅转义单双引号,无HTML过滤
function sqli_check_3($link, $data)
{
    return mysqli_real_escape_string($link, $data);
}

// 表单提交判断:点击Add复选框提交博客
if(isset($_POST["entry_add"]))
{
    $entry = htmli($_POST["entry"]); // 用户输入进入过滤函数
    $owner = $_SESSION["login"];
    if($entry == "")
    {
        $message =  "<font color=\"red\">Please enter some text...</font>";
    }
    else
    {
        // 拼接SQL插入语句
        $sql = "INSERT INTO blog (date, entry, owner) VALUES (now(),'" . $entry . "','" . $owner . "')";
        $recordset = $link->query($sql);
    }
}

代码解析:

  • $entry = htmli($_POST["entry"]); 用户表单输入直接传入自定义过滤函数 htmli(),Low 分支仅执行 sqli_check_3()。

  • sqli_check_3() 底层为 mysqli_real_escape_string() 仅作用:SQL 语句转义单引号、双引号,防御 SQL 注入,完全不处理 HTML 标签、<script>、<img>、<a> 等 HTML 注入载荷。

  • 无任何 HTML 标签过滤函数,未调用 strip_tags()、未做 HTML 实体转义,用户输入的 HTML/JS 代码完整保留。

  • 拼接 SQL 入库,$entry 经过 SQL 转义后直接拼接进 INSERT 语句存入数据库 entry 字段,HTML 恶意载荷原样存入库中。

读取并展示部分

当页面加载时,从数据库读取所有博客条目并展示:

php 复制代码
// 查询博客数据
$entry_all = isset($_POST["entry_all"]) ? 1 : 0;
if($entry_all == false)
    $sql = "SELECT * FROM blog WHERE owner = '" . $_SESSION["login"] . "'";
else
    $sql = "SELECT * FROM blog";
$recordset = $link->query($sql);

// 循环输出数据库记录
while($row = $recordset->fetch_object())
{
    // 区分安全等级分支
    if($_COOKIE["security_level"] == "1" or $_COOKIE["security_level"] == "2")
    {
        // Medium/High:输出前调用xss_check_3做HTML编码
?>
        <tr height="40">
            <td align="center"><?php echo $row->id; ?></td>
            <td><?php echo $row->owner; ?></td>
            <td><?php echo $row->date; ?></td>
            <td><?php echo xss_check_3($row->entry); ?></td>
        </tr>
<?php
    }
    else
    {
        // Low级别(security_level=0):无任何输出编码处理
?>
        <tr height="40">
            <td align="center"><?php echo $row->id; ?></td>
            <td><?php echo $row->owner; ?></td>
            <td><?php echo $row->date; ?></td>
            <!-- 关键漏洞点:直接输出数据库原始entry内容 -->
            <td><?php echo $row->entry; ?></td>
        </tr>
<?php
    }
}
$link->close();
?>

代码解析:

  • 分支判断 $_COOKIE["security_level"] == "1" or "2",Low(0)会走 else 分支,不会执行 xss_check_3() 过滤函数。

  • echo $row->entry; 高危输出,直接从数据库取出原始存储的用户内容,无任何 HTML 实体编码、无标签过滤。

  • 浏览器原生解析标签,若 entry 字段包含 <script>alert(1)</script>、<img src=x onerror=alert(1)>、<h1>test</h1> 等 HTML/JS 载荷,浏览器会正常渲染、执行脚本。

存储环节:仅做 SQL 注入转义,未过滤 HTML/JS 标签,恶意代码完整存入数据库;

输出环节:Low 级别无 XSS/HTML 实体编码函数,数据库原始内容直接渲染至前端页面;

一句话根源:入库仅防御 SQL 注入,未拦截 HTML 载荷;出库无 HTML 编码过滤,双重缺失导致存储型 HTML 注入漏洞。

4.3 如何防御

对于 Low 级别暴露的问题,防御需要两个环节同时下手:

环节一:写入数据库前进行过滤

在 htmli() 函数 Low 分支增加 HTML 标签清除,移除所有可执行 HTML/JS 标签:

php 复制代码
function htmli($data)
{
    include("connect_i.php");
    switch($_COOKIE["security_level"])
    {
        case "0" :
            $data = sqli_check_3($link, $data); // 保留SQL注入防御
            // 新增:移除全部HTML标签,阻断HTML注入载荷
            $data = strip_tags($data);
            break;
        // 其余分支省略
    }
    return $data;
}

或者使用更严格的白名单过滤,只允许特定字符。

环节二:输出到页面前进行编码(更重要!)

无论入库是否过滤,输出页面必须使用 htmlspecialchars 转义特殊字符,适配 UTF-8 编码、单双引号全部转义:

php 复制代码
while($row = $recordset->fetch_object())
{
    // 统一编码,不再区分安全等级
    $safe_entry = htmlspecialchars($row->entry, ENT_QUOTES, 'UTF-8');
    echo "<td>" . $safe_entry . "</td>";
}

htmlspecialchars() 会将 <、>、"、'、& 转换为 HTML 实体,浏览器会将其显示为普通文本而不是 HTML 标签。

注意 :即使写入时做了过滤,输出时仍然要编码------这是纵深防御的原则。


五、Medium 安全级别

5.1 通关步骤

将安全级别切换为 Medium,再次访问该页面。

尝试一:直接输入 HTML 标签

在输入框中输入之前的 payload:

php 复制代码
<h1 style="color:red;">Hacked!</h1>

点击 Submit------你会发现,输入框中的内容变成了纯文本,HTML 标签没有被解析!

页面显示的是:

php 复制代码
<h1 style="color:red;">Hacked!</h1>

而不是红色的大号标题。

尝试二:尝试各种绕过方法

网上有一些关于 Medium 级别绕过的讨论,但大多数情况下,bWAPP 的 Medium 级别确实无法被直接绕过。有研究者提到可以通过 Burp Suite 抓包修改参数强行注入,或者在 entry 中插入 SVG 语句仍可弹窗,但在标准 bWAPP 环境中,Medium 级别的防护是有效的。

5.2 源码分析

写入数据库部分
php 复制代码
function htmli($data)
{
    include("connect_i.php");
    switch($_COOKIE["security_level"])
    {
        case "0" : // Low
            $data = sqli_check_3($link, $data);
            break;
        case "1" : // Medium
            $data = sqli_check_3($link, $data);
            // $data = xss_check_4($data); 关键:该过滤代码被注释,未生效
            break;
        case "2" : // High
            $data = sqli_check_3($link, $data);
            // $data = xss_check_3($data); 注释未启用入库过滤
            break;
        default :
            $data = sqli_check_3($link, $data);
            break;
    }
    return $data;
}

// SQL注入转义函数
function sqli_check_3($link, $data)
{
    return mysqli_real_escape_string($link, $data);
}

// 博客提交入库逻辑
if(isset($_POST["entry_add"]))
{
    $entry = htmli($_POST["entry"]);
    $owner = $_SESSION["login"];
    if($entry == "")
    {
        $message =  "<font color=\"red\">Please enter some text...</font>";
    }
    else
    {
        $sql = "INSERT INTO blog (date, entry, owner) VALUES (now(),'" . $entry . "','" . $owner . "')";
        $recordset = $link->query($sql);
        $message = "<font color=\"green\">Your entry was added to our blog!</font>";
    }
}

代码解析:

  • Medium 分支仅执行 sqli_check_3(),底层 mysqli_real_escape_string(),仅转义 SQL 引号,不处理 HTML 标签 < >;

  • 用户输入的完整 HTML/JS 载荷(<script>、<h1>、事件注入)原样存入数据库,入库无任何 HTML 防护。

读取并展示部分
php 复制代码
// 查询博客数据
$entry_all = isset($_POST["entry_all"]) ? 1 : 0;
if($entry_all == false)
    $sql = "SELECT * FROM blog WHERE owner = '" . $_SESSION["login"] . "'";
else
    $sql = "SELECT * FROM blog";
$recordset = $link->query($sql);

// 遍历数据库记录渲染页面
while($row = $recordset->fetch_object())
{
    // Medium(1)、High(2) 进入防护分支
    if($_COOKIE["security_level"] == "1" or $_COOKIE["security_level"] == "2")
    {
?>
        <tr height="40">
            <td align="center"><?php echo $row->id; ?></td>
            <td><?php echo $row->owner; ?></td>
            <td><?php echo $row->date; ?></td>
            <!-- 关键:输出前调用 xss_check_3 做HTML编码 -->
            <td><?php echo xss_check_3($row->entry); ?></td>
        </tr>
<?php
    }
    else
    {
        // Low(0) 无输出过滤,直接输出原始内容
?>
        <tr height="40">
            <td align="center"><?php echo $row->id; ?></td>
            <td><?php echo $row->owner; ?></td>
            <td><?php echo $row->date; ?></td>
            <td><?php echo $row->entry; ?></td>
        </tr>
<?php
    }
}
$link->close();
?>

代码解析:

  • Medium 安全等级命中 if($_COOKIE["security_level"] == "1") 分支;

  • 数据库取出的 entry 字段传递给 xss_check_3() 处理后再 echo 输出;

  • xss_check_3() 内部封装 htmlspecialchars(),将 < > & " ' 转为 HTML 实体;

  • 浏览器收到实体字符后只会渲染纯文本,不会解析 HTML 标签、执行 JS 脚本,因此简单 HTML 注入载荷失效。

Medium 级别防护缺陷总结
  • 防护逻辑单一,仅依赖输出编码,无入库过滤纵深防御;

  • 入库仅使用 SQL 转义函数,完全不拦截 HTML 恶意载荷,数据库持久存储恶意代码;

  • 若开发者误删输出端 xss_check_3(),漏洞会立刻复现;

  • 代码设计混淆 SQL 注入防护与 HTML 注入防护,xss_check_4(addslashes) 本应做入库过滤却被注释,防护逻辑设计混乱。

5.3 如何防御

环节 1:入库阶段,过滤恶意 HTML 标签(辅助防线)

在 Medium 分支启用标签过滤,清除所有 HTML 标签:

php 复制代码
case "1" :
    $data = sqli_check_3($link, $data);
    $data = strip_tags($data); // 移除全部HTML、JS标签
    break;
环节 2:输出阶段,强制 HTML 实体编码(核心防线,不可省略)

统一使用 htmlspecialchars 全字符转义,适配 UTF-8 编码,无论入库是否过滤都必须执行:

php 复制代码
$safe_text = htmlspecialchars($row->entry, ENT_QUOTES, 'UTF-8');
echo "<td>{$safe_text}</td>";
纵深防御核心原则
  • 输出编码是必须的核心防护,不能依靠入库过滤替代;

  • 入库过滤仅作为辅助,用于减少数据库存储恶意内容;

  • SQL 转义、HTML 实体编码分场景使用,不可混用替代;

  • 禁止依靠 addslashes() 防御 HTML 注入、XSS 漏洞。


六、High 安全级别

6.1 页面展示

将安全级别切换为 High,再次尝试注入。

输入任何 HTML 标签 payload,结果------全部失败。

页面只会显示原始的文本内容,所有 HTML 标签都被安全地转义了。

6.2 源码分析

分级过滤函数 htmli () 核心逻辑

php 复制代码
function htmli($data)
{
    include("connect_i.php");
    switch($_COOKIE["security_level"])
    {
        case "0" : // Low
            $data = sqli_check_3($link, $data);
            break;
        case "1" : // Medium
            $data = sqli_check_3($link, $data);
            // $data = xss_check_4($data); 代码注释,入库未生效
            break;
        case "2" : // High
            $data = sqli_check_3($link, $data);
            // $data = xss_check_3($data); 入库过滤代码注释,仅输出端生效
            break;
        default :
            $data = sqli_check_3($link, $data);
            break;
    }
    return $data;
}

页面输出区分防护代码

php 复制代码
while($row = $recordset->fetch_object())
{
    // Medium(1)、High(2) 共用同一套输出防护逻辑
    if($_COOKIE["security_level"] == "1" or $_COOKIE["security_level"] == "2")
    {
?>
        <tr height="40">
            <td align="center"><?php echo $row->id; ?></td>
            <td><?php echo $row->owner; ?></td>
            <td><?php echo $row->date; ?></td>
            <!-- High 核心防护:输出前调用 xss_check_3 转义 -->
            <td><?php echo xss_check_3($row->entry); ?></td>
        </tr>
<?php
    }
    else
    {
        // Low 无任何输出编码
?>
        <tr height="40">
            <td align="center"><?php echo $row->id; ?></td>
            <td><?php echo $row->owner; ?></td>
            <td><?php echo $row->date; ?></td>
            <td><?php echo $row->entry; ?></td>
        </tr>
<?php
    }
}

xss_check_3 () 底层函数实现

php 复制代码
function xss_check_3($data, $encoding = "UTF-8")
{

    // htmlspecialchars - converts special characters to HTML entities    
    // '&' (ampersand) becomes '&amp;' 
    // '"' (double quote) becomes '&quot;' when ENT_NOQUOTES is not set
    // "'" (single quote) becomes '&#039;' (or ') only when ENT_QUOTES is set
    // '<' (less than) becomes '&lt;'
    // '>' (greater than) becomes '&gt;'  

    return htmlspecialchars($data, ENT_QUOTES, $encoding);

}

代码解析:

High 级别使用了 htmlspecialchars() 对输入进行 HTML 实体编码。

htmlspecialchars() 会将以下字符转换为对应的 HTML 实体:

  • & → &amp;

  • " → &quot;

  • ' → &#039;(当 ENT_QUOTES 启用时)

  • < → &lt;

  • > → &gt;

当我们输入 <h1>Hacked!</h1> 时:

  • < 被转换为 &lt;

  • > 被转换为 &gt;

最终存入数据库并输出到页面的内容是 &lt;h1&gt;Hacked!&lt;/h1&gt;。

浏览器在渲染时,会将 &lt; 显示为 < 字符,但不会将其解析为 HTML 标签------它只是普通文本!

6.3 如何防御

High 级别的防护逻辑是标准、有效的安全实现,优势如下:

  • 使用正确防护函数 :htmlspecialchars() 专门针对 HTML 输出上下文做转义,区别于仅用于 SQL 注入防护的addslashes()/mysqli_real_escape_string();

  • 规范编码配置 :指定第三参数UTF-8,规避多字符编码漏洞绕过;

  • 全覆盖引号转义 :ENT_QUOTES 同时转义单引号、双引号,杜绝引号闭合类 XSS 绕过;

  • 核心防御逻辑落地在输出环节:遵循 Web 安全黄金标准 ------ 输出编码。

缺陷:仅依赖输出单层防护,入库无输入过滤,恶意 HTML 代码仍完整持久存储在数据库,若其他页面读取该字段未做编码,漏洞会复现。


七、三种安全级别对比总结

关键洞察

从 Low 到 High,防护逻辑呈现明显的安全认知升级:

  • Low:无 HTML 安全意识,用户输入直接入库、直接渲染,完全裸奔;

  • Medium:具备基础安全意识,但混淆防护工具,错误认为 SQL 转义可防御 HTML 注入,仅靠输出编码兜底;

  • High:掌握标准安全实践,使用 HTML 专用转义函数,在输出环节完成核心防护。

开发者启示:安全防护不能只追求 "有过滤",必须匹配漏洞场景使用对应防护函数,输出编码是防御 HTML 注入、XSS 的核心底线。


八、存储型 HTML 注入 vs 存储型 XSS

很多初学者会问:存储型 HTML 注入和存储型 XSS 有什么区别?

简单回答:本质上是同一个漏洞的不同叫法,但 XSS 更强调 JavaScript 代码的执行。

对比维度 存储型 HTML 注入 存储型 XSS
注入内容 HTML 标签(<h1>、<a>、<img>) JavaScript 脚本(<script>)
核心目的 修改页面内容、钓鱼、挂马 窃取 Cookie、劫持会话、执行任意脚本
危害程度 中到高 极高
关系 基础 是 HTML 注入的"升级版"

通俗理解:

  • HTML 注入是"改页面"------把页面内容改成你想让人看到的样子。

  • XSS 是"跑程序"------在别人的浏览器里执行你的 JavaScript 代码。

而 <script> 标签本身就是 HTML 标签 ,所以能注入 HTML 的地方,通常也能注入 XSS。bWAPP 中的存储型 HTML 注入关卡,直接输入 <script>alert(1)</script> 就能弹窗,说明它本质上也是一个存储型 XSS 漏洞。


九、实战防御方案总结

9.1 开发人员必知的防御措施

方案一:输出编码(最推荐、最有效)

无论数据来自哪里(数据库、用户输入、API),在输出到 HTML 页面之前,统一进行 HTML 实体编码:

php 复制代码
echo htmlspecialchars($data, ENT_QUOTES, 'UTF-8');

这是防御 HTML 注入和 XSS 最有效、最标准的方法。

方案二:输入过滤(白名单)

对用户输入进行严格的格式验证,只允许符合预期格式的数据通过:

php 复制代码
// 例如:只允许字母、数字、空格和常见标点
if (!preg_match('/^[a-zA-Z0-9\s\.,!?]+$/', $user_input)) {
    die("Invalid input");
}
方案三:数据库写入也做防护

虽然输出编码是最关键的,但在写入数据库时也建议进行过滤或参数化查询:

php 复制代码
// 使用参数化查询防止 SQL 注入
$stmt = $link->prepare("INSERT INTO blog (owner, date, entry) VALUES (?, ?, ?)");
$stmt->bind_param("sss", $owner, $date, $entry);
$stmt->execute();
方案四:使用安全框架和模板引擎

现代 Web 框架(如 Laravel、Django、Spring)和模板引擎(如 Twig、Jinja2)默认会对输出进行自动编码,大大降低了注入风险。

方案五:内容安全策略(CSP)

通过设置 HTTP 响应头 Content-Security-Policy,限制浏览器只能执行来自可信源的脚本,即使攻击者注入了恶意代码,浏览器也不会执行。

9.2 安全测试人员必知的检测方法

  • 基础 HTML 测试: 输入<h1>test</h1>,查看页面是否渲染为一级标题;

  • XSS 弹窗测试: 输入<script>alert(document.cookie)</script>,观察是否弹窗;

  • **持久化验证:**提交载荷后刷新页面、清除 Cookie 重新访问,判断恶意代码是否永久存储(区分存储型 / 反射型);

  • **多用户复现:**切换浏览器 / 账号访问页面,验证其他用户是否受攻击;

  • 源码核验: 右键查看网页源代码,确认输入符号是否被转义为&lt;/&gt;。

9.3 现实世界中的存储型注入场景

在现实世界中,以下功能点最容易出现存储型注入漏洞:

  • 博客、文章评论区

  • 论坛帖子、留言板

  • 用户个人简介、个性签名

  • 商品评价、晒单评论

  • 公开反馈表单、社群动态

  • 站内私信、公开公告投稿

任何"用户输入 → 存储 → 展示给其他用户"的环节,都可能存在存储型注入风险。


十、总结

本文完整拆解了bWAPP中存储型HTML注入(HTML Injection - Stored (Blog))漏洞,明确存储型HTML注入是恶意HTML、JS代码持久存入数据库,任意访问页面的用户都会触发攻击,危害显著高于仅靠恶意链接诱导、单次生效的反射型注入;同时梳理出三种安全等级的防护差异:Low级别无任何HTML相关防护,可直接完成注入攻击;Medium级别仅依靠输出编码实现防护,入库环节未做过滤,整体设计存在安全缺陷;High级别采用htmlspecialchars()标准HTML实体输出编码,基础场景下漏洞得到修复;最终得出核心安全准则:将数据渲染至HTML页面前统一做实体编码,是防御HTML注入与XSS漏洞最优且通用的防护手段。

写在最后

存储型 HTML 注入是 Web 安全中最危险的漏洞类型之一。它的持久性和广泛影响,使得它成为黑客最青睐的攻击方式之一,也是安全防御中最需要重视的漏洞类型。

记住一句话 :所有用户输入都是邪恶的,所有数据库输出都不可信。永远编码,永远安全。

**注意:**仅供靶场练习参考,不指向明确漏洞。


**重要声明:**本教程及文中所有操作仅限于合法授权的安全学习与研究。作者及发布平台不承担因不当使用本教程所引发的任何直接或间接法律责任。请务必遵守中华人民共和国网络安全相关法律法规。

如果这篇文章帮你解决了实操上的困惑,别忘记点击点赞、分享 ,也可以留言告诉我你遇到的其它问题,我会尽快回复。你的关注是我坚持原创和细节共享的力量来源,谢谢大家。

相关推荐
HackTwoHub19 小时前
Clown SRC skill第二代2.x最新版企业安全研究 Skill|证据优先的 SRC 与白盒审计工作流正式更新
网络·安全·web安全·网络安全·系统安全·密码学·安全架构
白帽攻防录20 小时前
SRC 挖洞:Certighost AD CS 域控制器伪造深度复盘,CVE-2026-54121 一个普通域用户怎么拿走整个企业域
网络·安全·web安全·网络安全·域安全
xianghongtao01161 天前
麦肯锡2026技术趋势06_网络安全与可信系统_研究解读
人工智能·安全·web安全
IT研究室1 天前
最新大数据毕业设计选题推荐-基于大数据的网络安全入侵流量特征分析与可视化-大数据-Spark-Hadoop-Bigdata
大数据·web安全·课程设计
传人once1 天前
网页文字滚动效果如何做
前端·css·html·jquery·html5
redfred1 天前
HyperFrames 部署教程:5.6 万 Star 开源「写 HTML 出视频」框架,Windows 从零上手(CLI 三条命令 + 7 个避坑)
ffmpeg·开源·自动化·html·音视频
乌暮1 天前
拆解网页的底层逻辑:用费曼学习法讲透 HTML 与 CSS
前端·css·学习·html
传人once2 天前
多图片上传预览功能如何写
前端·css·html·jquery·html5
汤姆yu2 天前
Gemini 4 Argon模型综述:技术、能力与实际使用指南
网络·安全·web安全·ai·大模型
Chengbei112 天前
拒绝无效洞!蚂蚁Src榜一Clown 2.0 SRC SKill 第二代带状态闭环的白盒 & 黑盒审计 Skill
网络·人工智能·安全·web安全·网络安全·自动化·安全架构