news 2026/10/21 6:06:44

PHP留言板安全开发:从SQL注入到XSS防御的实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PHP留言板安全开发:从SQL注入到XSS防御的实践

1. 这个“第22天”到底在练什么

1.1 为什么选择“留言板”而不是一个复杂的订单系统

很多刚开始转做Web安全的人,学了一段时间渗透之后会产生一个错觉,觉得“能用工具拿SHELL”就是安全了。但真正到面试或真实业务里,最容易被问倒的一句话是:这个漏洞是怎么产生的?你要怎么在开发阶段防住?这两年在跟进安全开发方向的学习时,我看到不少课程都会把“PHP留言板”当作一个标准教学项目,第22天这个节点尤其典型,因为它正好卡在基础知识已经铺完、开始进入“代码审计与安全开发结合”的阶段。

为什么是留言板?因为这个功能麻雀虽小,五脏俱全。一个留言板必然包含:用户输入、数据库存取、页面渲染展示、用户身份标识(访客或后台管理员)、文件资源引用。而这些环节恰恰对应Web安全里最高频的一批漏洞:SQL注入、XSS跨站脚本、CSRF、越权访问、文件上传、变量覆盖。选一个庞大复杂的系统来学安全开发,会把自己淹没在业务逻辑里,根本看不清漏洞的本质。选留言板这种单点功能,每一行代码都能跟漏洞类型对得上号,开发者和渗透测试者都容易建立直觉。

换句话说,这个项目不是为了做一个“能跑的网站”,而是为了在几十行代码里把“攻击者的利用链”和“开发者的防御链”摆在同一张桌子上去对照。

1.2 一天覆盖开发、渗透、防御三条线,怎么安排才不乱

第22天这一天的强度其实很高,因为要让参与者同时完成三件事。第一件事是把留言板功能开发出来,至少要能正常发表留言、展示留言、删除留言,还要有后台登录判断。第二件事是站到攻击者视角,对自己写的代码做一遍SQL注入和XSS测试,记录下哪些位置是脏数据入口、哪些位置最终被拼进了SQL语句或输出到了HTML。第三件事才是关键,拿着测试结果回头改代码,用参数绑定、HTML实体编码、会话校验去把漏洞堵上。

我自己在本地跟做的时候,推荐的学习顺序是:先把功能写通,再故意写出有漏洞的版本,用Burp Suite或直接改URL参数去打一遍,最后再把修复版代码和漏洞版代码放在一起对比。不要直接照着“安全写法”一步到位,否则你会完全感觉不到自己避免了什么,防御方案在脑子里就永远是一堆散装函数。这也是为什么这类课程笔记会被大量转发,因为它不是纯理论,而是把“攻击如何发生”和“防御为什么有效”用一个具体功能串起来了。

1.3 开写之前必须补的PHP基础

要在这一步跟上进度,至少要有几个基础:PHP的基本语法、数组和循环、GET和POST表单提交、MySQL建表。不需要会框架,因为这个项目刻意不用框架,目的就是把原生PHP的行为暴露出来。一旦用了框架,路由分发、ORM、模板引擎会替你挡掉一大堆问题,你反而看不明白底层发生了什么。

如果你对PHP还比较生疏,建议先花两个小时过一遍变量、函数、include引入、PDO连接数据库这四件事,今天笔记里的代码才不会被卡住。安全开发最大的门槛从来不是代码本身难,而是脑子里没有“数据流”的概念——不知道用户输入从哪里进入、经过哪些处理、最终拼接到了哪里。接下来我按自己在学习时的顺序,先把PHP的超全局变量这部分讲透,因为所有漏洞追根溯源都是从这些“入口”开始的。

2. 超全局变量:先搞清楚数据从哪里进来

2.1 七个常用超全局变量逐个盘点

超全局变量是PHP提供的一组在脚本任何位置都可以直接访问的预定义数组,简单理解就是PHP帮你把所有外部输入先收进固定的“收件箱”里。在做安全开发时,第一步不是写功能,而是先盘点这份“外部输入清单”。

  • $_GET:来自URL查询字符串,比如留言板的分页参数、留言ID。因为它直接暴露在URL里,攻击者最容易手动修改,是SQL注入和逻辑越权的重灾区。
  • $_POST:来自表单提交的请求体,留言内容、用户名、密码大多走这里。比GET隐蔽一点,但用Burp Suite改包照样可以随意篡改。
  • $_COOKIE:来自浏览器携带的Cookie,一般放会话标识、用户偏好。攻击者可以伪造Cookie或篡改里面的JSON数据,遇到开发者直接把用户角色写进Cookie时,越权漏洞就出现了。
  • $_SESSION:服务端保存的会话数据,不直接来自用户输入,但它的值常常由登录逻辑写入,如果登录验证有缺陷,SESSION里的角色也会不可信。
  • $_SERVER:包含请求头、客户端IP、脚本路径等服务器环境信息,其中HTTP_USER_AGENT、HTTP_REFERER、REMOTE_ADDR这些字段可以被请求方伪造,如果被拼进SQL或输出到页面,就会变成注入点。
  • $_FILES:文件上传数据,包含文件名、临时路径、文件类型。文件名被拼接进路径时可能触发路径穿越,文件类型被信任时可能直接上传WebShell。
  • $_GLOBALS:这是一个非常特殊的超全局变量,保存了所有全局变量,也是最容易出现变量覆盖漏洞的元凶。

2.2 $_REQUEST 和 $GLOBALS 是最容易翻车的地方

在这套课程笔记里,老师特别强调了一个很多人忽略的点:不要图方便直接使用 $_REQUEST 去接收参数。初学者很容易写出类似 $_REQUEST['action'] 这样的代码,逻辑是“GET里有就用GET的,POST里有就用POST的,Cookie里有就用Cookie的”。问题在于它是三合一的数据源,会把不同入口混在一起,既让代码逻辑难以预测,还会提高参数污染的风险。攻击者可以同时用GET和POST提交同一个参数名,PHP对不同来源的解析顺序在不同版本里不完全一致,这会让程序行为变得不稳定,也更容易绕过一些半吊子的过滤逻辑。

$GLOBALS 的坑更明显。有些老项目为了让操作更简洁,会写类似 extract($_GET) 的代码,把用户提交的所有参数直接变成全局变量。比如脚本里本来有一个 $permission = 'guest',攻击者只要在URL里传一个 permission=admin,extract之后就会把原来的变量覆盖掉,这就是经典的变量覆盖漏洞,经常造成登录绕过和任意文件写入。我在实际测试一些老PHP CMS的时候遇到过不止一次,开发者的本意是少写几行代码,结果等于把自己家里所有锁都卸了。

2.3 设计参数过滤的“第一道防线”

理解了入口之后,安全开发的原则就清晰了:所有超全局变量里的值,都不可以信任。拿到变量的第一秒就应该做一件事——验证它的“类型和格式”而不是直接“过滤关键词”。

比如留言ID,正确的姿势是强制转成整型:$id = (int)($_GET['id']); 这样输入什么乱七八糟的字符都变整数,注入根本不成立。再比如用户名,就用正则限定只允许中英文、数字和下划线。这条“白名单校验”思路比“黑名单过滤”要可靠得多,因为攻击者永远在变体绕过滤,而白名单直接把非法输入挡在外层。

我自己的习惯是在项目里建一个小函数专门处理入口参数,统一做类型转换和正则校验,任何位置要用输入数据都从这层走。这听起来很死板,但这是把“安全开发”从口号变成实操的第一步,后面写SQL和输出HTML时,你只要在脑子记着一个原则:进入SQL的数据要用预处理,进入HTML输出要用实体编码。防线分层,而不是靠某一道魔法过滤。

3. 数据库操作:SQL注入的正面防御

3.1 选 PDO 还是 mysqli

留言板肯定要连数据库来存数据,PHP这边最常见的两个选择是 mysqli 和 PDO。课程笔记里直接选了PDO,我练完之后也赞同这个选择。原因有三点:PDO支持多种数据库驱动,以后换库不用重写代码;PDO的预处理语句用法更直观,绑定参数的方式会强迫你养成“SQL模板和数据分离”的习惯;mysqli也有预处理,但它的函数风格更陈旧,API参差不齐,新手很容易在某些方法里又用字符串拼接绕回去。

做个简单对比你就明白了:

对比项PDOmysqli
支持的数据库MySQL、PostgreSQL、SQLite等仅MySQL
预处理与参数绑定统一风格,写法清晰有面向过程和面向对象两套
错误处理可抛出异常,便于捕获大多靠函数返回值判断
团队协作友好度更符合现代编码习惯老项目较常见

无论是哪种扩展,只要你用的是“原生SQL字符串拼接”:“SELECT * FROM messages WHERE id = $id”,那这个注入漏洞就存在。扩展本身帮不了你,只有写法变了才有用。

3.2 预处理语句从原理上断开注入链

为什么PDO预处理能防SQL注入?最通俗的解释是:你得先把SQL的“骨架”交给数据库服务器,让它知道这里就是一个值的位置,然后再把用户的数据单独传过去当“值”用。数据库从始至终不会把用户数据当成SQL指令去解析。

我打个比方。普通拼接SQL就像你把一句话挖空后去交给翻译:用户写什么内容都能直接变成这个句子里的一部分,如果他写的是“把这句话的结尾改成别的内容”,翻译会照做。预处理相当于你先跟翻译说清楚这句话的语法,再把参数当成纸片放进对应的槽位,纸片上的字再奇怪,也只会被当成纸片,不会改变句子的语法。

下面是留言板项目里标准的PDO预处理写法:

// 连接已经在别处初始化: $pdo $stmt = $pdo->prepare("SELECT title, content, created_at FROM messages WHERE id = :id"); $stmt->execute([':id' => $id]); $row = $stmt->fetch(PDO::FETCH_ASSOC);

只要执行了 execute 这一步,参数就是参数,你不需要再去手动加反斜杠、转义单双引号,因为没有东西会被拼进SQL语句。如果项目中有一段代码是用“.”拼接用户输入直接放进SQL的,不管前面过滤了关键字还是替换了单引号,都应该直接重写成预处理,而不是继续在拼接逻辑上打补丁。

3.3 SQL 错误信息只进日志,不进响应

数据库操作中还有一个很容易被忽略的安全细节:SQL语句报错后,错误信息展示给谁看。很多入门开发在本地调试时会开启错误显示,页面直接输出 “SQLSTATE[23000]: Integrity constraint violation” 这样的原生报错,上线后忘了关掉。攻击者看到这些报错等于直接捡到了情报,数据库类型、表名、字段名、语法结构全暴露了,后面注入的效率会成倍提升。

正确做法是在生产环境里关闭错误显示,打开错误日志记录,同时用try/catch把PDO异常接住,对外只返回一个统一提示:“数据操作失败,请稍后再试”。留言板场景下尤其重要,因为我们常常会根据数据库操作结果给用户反馈,比如“昵称已存在”“留言发送失败”,如果没有统一错误出口,异常信息就容易顺着业务逻辑漏出去。

3.4 表结构设计里的安全细节

留言板表结构很简单,但里面有几个安全细节值得写一下。字段类型要用得足够严格:ID用INT或BIGINT,不要用VARCHAR;IP地址用VARCHAR(45),因为IPv6字符串会很长;内容字段用TEXT可以,但不要给它太长,免得一次请求塞进几十兆数据拖垮数据库。

更重要的是字符集和排序规则,统一设置为 utf8mb4,不要为了省空间用 utf8,因为某些特殊字符会造成字符集转换混乱,绕过部分过滤逻辑。内容字段不要直接存HTML,只存纯文本。如果你非要支持富文本,也要坚持入库时只存“白名单过滤后的安全HTML”,而不是把用户提交的原始HTML原样存下去。很多XSS洞都是一个不做过滤的富文本组件带进来的,数据入库这一层就已经脏了,后面再怎么防都很难救回来。

4. 留言板功能的完整落地

4.1 功能需求清单和表结构

把留言板功能拆一下,至少要有:查看留言列表、发表新留言、后台登录、后台删除留言。为了练习安全点,我还会给自己额外加两个需求:记录访客IP和User-Agent,以及后台操作走Session权限校验。

对应数据库表,我是这样设计的:

CREATE TABLE messages ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, nickname VARCHAR(32) NOT NULL, content TEXT NOT NULL, ip VARCHAR(45) DEFAULT NULL, user_agent VARCHAR(255) DEFAULT NULL, is_approved TINYINT DEFAULT 1, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

管理员另开一张 admin 表,只有用户名、密码哈希两个字段,不要用MD5直接存密码,要加盐,PHP里直接用 password_hash() 就可以,验证用 password_verify()。这张表不需要太多字段,越简单越不容易被利用。

这里“is_approved”字段其实是为安全练习加的:如果留言需要审核,那么未审核的内容不应该展示给普通用户。做过内容审核功能的同学都懂,这个字段一旦没拼在查询条件里,就可能是水平越权或信息泄露的源头。

4.2 发布留言的逻辑与入库代码

发布留言的核心逻辑包括:接收POST参数、校验字段格式、再次确认Session里验证码或时间间隔、然后写入数据库。校验这部分很容易被跳过,但我要特别说一下,安全性不只是防注入,还包括防垃圾内容。

先展示带注释的入库代码:

if ($_SERVER['REQUEST_METHOD'] === 'POST') { $nickname = trim($_POST['nickname']); $content = trim($_POST['content']); if ($nickname === '' || mb_strlen($nickname, 'UTF-8') > 32) { exit('昵称不能为空且不能超过32个字符'); } if ($content === '' || mb_strlen($content, 'UTF-8') > 2000) { exit('内容不能为空且不能超过2000字'); } if (!preg_match('/^[\x{4e00}-\x{9fa5}A-Za-z0-9_]+$/u', $nickname)) { exit('昵称只允许中文、字母、数字和下划线'); } $stmt = $pdo->prepare( "INSERT INTO messages (nickname, content, ip, user_agent) VALUES (:nickname, :content, :ip, :ua)" ); $stmt->execute([ ':nickname' => $nickname, ':content' => $content, ':ip' => $_SERVER['REMOTE_ADDR'], ':ua' => substr($_SERVER['HTTP_USER_AGENT'], 0, 255) ]); header('Location: index.php'); exit(); }

注意我在取值时都用了 trim(),这是防“纯空格绕过”的基础操作。用户名用正则限定白名单,可以提前挡住一大批基于特殊字符的XSS向量,比如<script>,"onmouseover",javascript:这类内容在昵称这个字段里就没机会进来。内容字段虽然只做长度校验,但后面输出时会统一编码。

关于 $_SERVER['REMOTE_ADDR'],这里存的是直连用户的IP,如果网站背后挂了CDN或反向代理,这个值会变成代理IP,需要额外信任代理层传的 X-Forwarded-For。但切记不能直接信任客户端传入的 X-Forwarded-For,必须来源可信,否则IP可以随意伪造。

4.3 列表展示和XSS编码

留言板一定会把用户提交的昵称和内容重新输出到页面上。这一段是XSS的高发区,测试者最喜欢在内容里贴一段<img src=x onerror=alert(1)>,如果你直接把内容原样打印,弹窗就会跟着页面一起出现。

修复方案非常明确:输出编码,而不是输入过滤。我用的是 htmlspecialchars():

function e($str) { return htmlspecialchars($str ?? '', ENT_QUOTES, 'UTF-8'); }

在HTML模板里这样用:

<?php foreach ($list as $item): ?> <div class="message-item"> <div><?php echo e($item['nickname']); ?></div> <p><?php echo e($item['content']); ?></p> <span><?php echo e($item['created_at']); ?></span> </div> <?php endforeach; ?>

这里有一个关键细节:要不要在写入数据库之前就调用 htmlspecialchars()?我的建议是不要。存储层只存原始数据,只在输出层编码。因为同一个数据可能在HTML、JSON、邮件模板等不同场景输出,每种场景的编码规则不一样,如果在存储时就把HTML实体编码的事做完了,以后想返回JSON给接口时还得先反转义,转来转去反而容易出错,甚至被二次编码绕过。

search还有一类特殊场景会专门引XSS,那就是留言板里的“搜索”和“分页”参数。比如页面上的搜索文本框会把关键词回显到页面上,搜索关键词本身也需要做HTML编码,否则这里又是一处独立的反射型XSS。很多同学只防了留言内容,忘了搜索框,这个坑我后面会再提。

4.4 权限、防刷、CSRF三件套

留言板虽说内容简单,但至少要有一个最简单的后台删除功能。后台路由必须判断Session里是否已登录,未登录则直接跳转登录页。不要只在前端靠一个隐藏页面来保证“别人找不到”,服务端不鉴权就是裸奔。

权限判断这样写:

session_start(); if (empty($_SESSION['admin_id'])) { header('Location: login.php'); exit(); }

这个判断必须放在后台脚本的最前面,而且不能用URL参数来判断是否管理员,有些项目喜欢把admin=1放在链接里,这等于把后台钥匙贴在大门口。

防刷方面,最简单的做法是限制两次留言之间的时间间隔,用Session记录上一次留言的时间戳,小于一定秒数就拒绝提交。不要依赖用户侧提交的时间,直接在服务端判断处理。

CSRF是很多人会忽略的。删留言是一个状态变更操作,攻击者可以诱导管理员浏览器访问一条带删除ID的URL,从而删掉任意留言。防御办法是给删除操作加一个由服务端生成的token:

$_SESSION['csrf_token'] = bin2hex(random_bytes(16)); // 表单里带上这个token // 后台删除时校验

这是安全开发里“业务逻辑防绕过”的一部分,代码看着不多,但在真实渗透测试里特别容易成为突破口。

5. 第三方插件引入:安全开发的心腹大患

5.1 第三方插件究竟引入哪些风险

留言板功能如果只是一个空模板,用户会觉得太简陋,所以很多人会把“富文本编辑器”“图表组件”“文件上传组件”“验证码组件”这类第三方插件直接引进来。这里就涉及到今天标题里的另一个大头:第三方插件引用的安全。

第三方插件的风险在于,你等于把一部分业务代码执行权交给了陌生人。插件里可能藏着后门、旧版本有已知漏洞、或者它发布时本身自带上传接口但没有做好权限控制。很多开发者的习惯是搜索到某个组件、看到能跑就拖进来用,这个行为本身就是在给项目埋雷。现实中遇到的目标站点,很多主站代码写得很安全,最后却是被某个老版本的富文本编辑器拿下的。

5.2 引入插件前必须做的五项检查

我给自己定了一个死规矩:没有经过检查的插件不许入库。五步检查法是:一看来源,只从官方仓库或官方站点下载,不要在不知名的下载站拿“汉化版”“破解版”;二看版本和更新日期,超过一年没更新的组件要非常谨慎,说明维护者可能已经不再跟进安全问题;三看下载次数和社区讨论,异常庞大的下载量加活跃的issue区是加分项,无人讨论的组件要警惕;四看文件构成,很多恶意插件会藏加密文件、base64串、eval调用,我拿到压缩包先扫一遍可疑代码模式;五看权限边界,插件需要访问文件系统和执行命令的权限是否超过了它的功能范围。

对于PHP项目来说,把所有第三方文件集中在某个“插件目录”里,而不是散落到主题、模块各个位置,方便做权限控制和更新替换。实际这五步对大型项目也适用,只是一些企业会升级成专门的“组件依赖清单”和漏洞扫描工具。

5.3 实际验证过的经典插件漏洞案例

练习时我在本地环境专门模拟过三类第三方插件漏洞,可以体会一下损坏范围。第一类是富文本编辑器图片上传。编辑器自带的upload匿名上传接口如果信任了Content-Type,攻击者构造一个合法的图片头,但数据段填充PHP代码,再配合路径可控,就能直接拿到WebShell。第二类是后台主题编辑功能。很多CMS后台提供在线编辑PHP模板的功能,本质是拿后台管理员权限换任意代码执行,现在不少云WAF识别到“主题编辑”“插件编辑”相关路径访问就会告警,因为它基本上是给管理员开了一扇后门。第三类是特定版本的文件管理器组件,删除功能只是个显形入口,底层可能存在文件名拼接和路径遍历问题。

悄悄说一句,真实渗透测试里遇到站点装了旧版本文件管理组件,我优先看的不是主页功能,而是这个组件的版本号。版本号一旦落进存在漏洞的区间,整个后台基本等于白给。这些案例在安全开发课程里被称为“引狼入室”,因为代码本身不是你写的,出问题后连应对方案都不知道去哪找。

5.4 插件引入之后的维护纪律

引入插件并不是一劳永逸。安全开发的一个完整流程里必须包含“持续维护”这一步,否则漏洞补丁出来你都不知情。我之前见过一个站点,插件版本停留在三年前,公开漏洞早就有一大把,但业务没人管更新,最后被扫描器扫一下就直接出问题。这里说的维护范围包括:每周关注插件更新日志、升级前先在测试环境全量回归、升级后对比核心文件是否有异常变更、不需要的功能模块直接禁用。

另一个比较实用的准则是“最小化使用”。如果一个插件为了方便,把它的路由对外全部暴露,那你应该通过配置开关把不需要的接口全部关掉,只留业务真正用到的部分。很多组件设计的时候是通用产品,接口开得又多又杂,这些多余接口里大概率藏着几个在旧版本里没有修复干净的洞。

6. 写完留言板之后踩的坑和常用排查姿势

6.1 报错全开把注入位置暴露给攻击者

这个坑是我在第一次本地测试时自己踩进去的。为了调试方便,我把display_errors和error_reporting(E_ALL)全部开到了最满,然后故意用SQL注入去打那个留言id参数。结果页面直接把SQL错误信息完整暴露出来,包括表名、字段名、SQL语法结构。那一刻我才明白一个问题:攻击者不需要拿到你的源码,光是报错信息就足以把他的注入调整得极具针对性。

之后我把项目改成这样:

// 开发环境临时开,生产环境一定关 ini_set('display_errors', '0'); ini_set('log_errors', '1'); error_reporting(E_ALL);

同时try/catch捕获PDOException,只把简洁提示返回给页面。建议大家在部署任何PHP项目前都检查一下这个配置,线上环境报错全开的行为等于送弹药。

6.2 转义顺序出错,XSS照样能弹

我修留言内容XSS的时候,一开始是在入库前把用户名和内容都过滤了一遍,比如用htmlspecialchars()转义后再存库。测试的时候发现,直接浏览留言列表确实没弹窗了。但后来我加了一个搜索功能,把关键词回显到页面上,搜索关键词从GET参数进入,页面输出时忘了再做一次实体编码,反射型XSS又出现了。原因就是我前面说的:数据经过多个执行场景时,你不能只在存储层做一次编码就以为万事大吉。

后来我把思路改成“存储层保留原始数据,输出层统一编码”,搜索关键词、留言内容、昵称、错误提示,所有动态输出都走同一个e()函数。这样虽然代码多了几个括号,但排查问题时的心理负担小了很多,只要看到输出点都会条件反射地检查有没有走编码函数。

6.3 习惯了拼接SQL,切成预处理后反而写错

用原生SQL拼接写多了,切换成PDO预处理时我还在犯一个低级错误:把SQL模板里的冒号占位符写错,比如先写了:id,execute数组里又写成了'id'。PDO在大多数情况下会报错,但有时会因为部分PHP版本的配置问题静默失败,导致数据写入不生效,排查了半天还以为是业务逻辑问题。

我的建议是:在关键操作后打印一下预处理语句的调试信息,或者暂时打开ATTR_ERRMODE为异常模式:

$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

这能让所有PDO异常第一时间抛出来,不至于埋到深层逻辑里。预处理写多了之后,你会自然地把“SQL模板”和“参数数组”两件事分开看,思路反而更清晰。

6.4 常见问题速查表

把整个留言板开发过程中比较典型的问题整理成一张速查表,方便大家在本地测试时对照排查:

问题现象可能原因快速验证方法处理方向
留言内容提交后弹窗输出未做HTML实体编码输入<img src=x onerror=alert(1)>再查看源码所有动态输出统一过 htmlspecialchars
改ID参数后出现SQL报错使用了字符串拼接查询输入1'看是否报错改为PDO预处理
页面源码出现PHP警告信息生产环境错误显示未关闭浏览页面搜“Warning”关键字关闭display_errors,开启log_errors
明明登录了,后台操作仍失败Session未启动或作用域配置错误查看cookie是否存在对应SessionID检查session_start()和session.cookie_secure配置
用第三方编辑器上传图片失败或异常插件版本过旧或权限目录不当查看上传返回的HTTP状态码和服务端日志升级组件,配置上传目录禁止执行PHP
两个管理员互相踢下线Session文件存储问题查看Session ID是否频繁更换检查Cookie域名、路径配置

这张表只是最初级的,真实项目里问题会更复杂,但排查方向基本是一致的:先确认入口数据是什么,再确认数据流经过哪些环节,最后确认输出点是否做了对应安全处理。安全开发的能力不是背几条命令,而是养成这条“数据流排查”的习惯。

按照这套笔记把这个留言板完整跑下来,你会发现自己再去看那些开源PHP项目时,眼睛会不自觉地盯向几个位置:超全局变量有没有被直接使用、SQL是拼接还是预处理、HTML输出有没有统一编码、后台操作有没有鉴权、插件目录是不是版本老旧。我个人在开发这个项目时的最大体会是:安全和功能从来不是对立的,提前把入口、出口、执行路径这三件事想清楚,后面基本都是在既定框架里补逻辑而已。如果你也正在学安全开发方向,建议找一台本地环境,把这个留言板从有漏洞版改到相对安全版,完整操作一遍,比看多少篇笔记都有用。

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

RAG检索增强生成实战指南:从原理到落地解决大模型幻觉

如果你最近试过让大模型回答需要结合最新资讯的问题&#xff0c;比如"某某新功能这个季度上线了吗""某个产品文档里的参数到底怎么配"&#xff0c;大概率会遇到两种情况&#xff1a;要么模型直接说不知道&#xff0c;要么非常自信地编一个看起来专业、实则…

作者头像 李华
网站建设 2026/10/21 6:05:33

知识工作插件体系:从信息捕获到沉淀复用的流水线设计

写这篇文章之前&#xff0c;我想先说说这个东西的来龙去脉。我自己日常的工作状态&#xff0c;基本就是被各种零碎信息推着走——浏览器里躺着二十几个标签页&#xff0c;平时看到好文章先收藏再说&#xff0c;真正要用的时候根本想不起来在哪&#xff1b;任务管理工具换了好几…

作者头像 李华
网站建设 2026/10/21 6:05:02

知识工作插件:从记录到复用的效率闭环设计

1. 为什么要给知识工作单独做一套插件我做过一个内部项目&#xff0c;名字就叫 knowledge-work-plugins。它不 fancy&#xff0c;说穿了就是一组插件&#xff0c;把平时散落在浏览器、文档、聊天记录和脑子里的信息&#xff0c;统一收进一个可以检索、可以复用、可以输出的工作…

作者头像 李华
网站建设 2026/10/21 6:03:32

动态规划在强化学习中的应用:策略迭代与价值迭代

如果你刚学完状态价值函数和贝尔曼方程&#xff0c;进入第4讲动态规划&#xff08;Dynamic Programming&#xff0c;DP&#xff09;的时候&#xff0c;通常会有一种明显的感觉&#xff1a;前面几讲都在搭概念框架&#xff0c;这一讲终于开始“算数”了。强化学习入门最容易卡住…

作者头像 李华
网站建设 2026/10/21 6:03:06

PaddleOCR模型转ONNX部署实战:动态shape与精度对齐全记录

最近在做一个OCR识别模块的迁移部署&#xff0c;目标平台不方便直接跑完整的PaddlePaddle推理环境&#xff0c;我得把文本检测和文本识别模型统一转成ONNX&#xff0c;再交给跨平台推理引擎去执行。整个过程比我预想的复杂一些——模型本身的转换不算太难&#xff0c;真正花时间…

作者头像 李华
网站建设 2026/10/21 6:02:57

C++缺省参数与重载函数(超详细!)

前言 缺省参数&#xff08;default argument&#xff0c;也叫默认实参&#xff09;和函数重载&#xff08;function overloading&#xff09;经常被放在一起讲&#xff0c;因为它们在调用形式上很像&#xff1a;都允许「少写几个参数」。但它们是两套完全不同的机制。缺省参数是…

作者头像 李华