简介Seay源代码审计系统是一款基于C#开发、主要运行于Windows平台的PHP代码安全审计工具面向网络安全初学者、CTF/AWD参赛选手及渗透测试人员用于快速发现PHP项目中的SQL注入、代码执行、命令执行、文件包含、文件上传、XSS跨站、信息泄露、任意URL跳转等常见漏洞。资源包共30个文件以exe主程序与升级组件、dll依赖库、rar源码与插件示例压缩包为主另含ini配置、md说明及少量bin、zip等辅助文件整体约42.16MB解压后即可直接运行。工具支持一键审计、代码调试、函数定位、插件扩展、自定义规则配置、代码高亮与编码转换、数据库执行监控等功能并附带2.1版源码与插件示例便于读者理解审计引擎实现、按需扩展检测规则。目前已有807人学习下载适合在AWD对抗与代码审计入门阶段作为实战工具使用。1. 从一场 AWD 被打穿说起Seay 源代码审计系统到底解决什么问题打过 AWD 的人大概都有过这种体验防守方刚开局别人还在读题你这边 flag 已经被轮着交了。事后复盘漏洞往往不是什么高深的东西——一个没过滤的$_GET直接进了mysql_query一个include拼了用户可控的路径一个上传点只校验了后缀没校验内容。这类问题肉眼可见但代码几千个文件靠人一行行翻翻到天亮也翻不完。Seay 源代码审计系统就是干这件事的把一份 PHP 源码目录丢进去它按规则批量扫出可疑的危险函数调用、变量覆盖、文件包含、SQL 拼接点把「哪一行、什么函数、参数从哪来」列成清单你再人工确认。它面向的是做代码审计、CTF 出题、AWD 赛前自查、以及接手遗留 PHP 项目要快速摸底的人。这一篇就按「它怎么扫 → 怎么跑起来 → 规则和引擎怎么改」的顺序拆把这份工具的落地路径讲透顺带把代码审计里那些血泪经验一起说了。2. 引擎与规则拆解一次扫描从目录遍历到危险函数命中的完整链路2.1 整体架构三层结构规则驱动这类审计工具的核心设计思路其实很朴素词法/语法分析层 规则匹配层 结果聚合层。Seay 的工程结构大致也是这个路子常见做法是分成这么几块层次职责典型实现文件采集层递归遍历源码目录按扩展名过滤跳过二进制和第三方库目录递归 后缀白名单解析层把 PHP 源码切成 token 或做正则预处理定位函数调用、变量赋值、字符串拼接tokenizer / 正则规则层维护危险函数表、敏感变量表、正则规则集配置文件或内置数组结果层记录文件路径、行号、命中规则、上下文片段输出可读报告列表 / 导出这个分层的好处是规则和引擎解耦。你想加一条新规则比如检测assert($_POST[...])这种一句话木马特征只需要往规则表里加一条不用动扫描主循环。这也是后面二次开发最常动的地方。从工程目录看一个典型的 PHP 审计工具会长成这样不同版本略有差异但骨架一致seay-audit/ ├── index.php # 入口接收扫描参数 ├── config/ │ ├── rules.php # 危险函数、敏感变量规则表 │ └── config.php # 扫描选项、超时、忽略目录 ├── core/ │ ├── Scanner.php # 扫描主控遍历 调度 │ ├── TokenParser.php # 源码解析切 token │ └── Reporter.php # 结果聚合与输出 ├── lib/ # 第三方依赖 └── runtime/ # 扫描缓存、临时结果提示如果你拿到的版本目录名和上面不完全一致不用慌找config里放规则的文件、找主循环里scandir/RecursiveDirectoryIterator的位置基本就能定位到引擎入口。2.2 文件采集递归遍历与过滤策略扫描第一步是把目标目录里的 PHP 文件捞出来。这一步看着简单但过滤策略直接决定扫描速度和误报量。常见做法是维护一个后缀白名单和一个忽略目录黑名单?php // core/Scanner.php 片段递归采集待扫描文件 function collectFiles(string $root, array $exts [php, inc, phtml], array $ignore []): array { $result []; $iterator new RecursiveIteratorIterator( new RecursiveDirectoryIterator($root, FilesystemIterator::SKIP_DOTS) ); foreach ($iterator as $file) { if (!$file-isFile()) { continue; } // 忽略目录第三方库、缓存、上传目录通常不扫 $path $file-getPathname(); foreach ($ignore as $dir) { if (strpos($path, DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR) ! false) { continue 2; } } // 后缀白名单避免把图片、压缩包当源码解析 if (in_array(strtolower($file-getExtension()), $exts, true)) { $result[] $path; } } return $result; }逻辑说明RecursiveDirectoryIterator负责递归SKIP_DOTS跳过.和..$ignore里放vendor、cache、uploads这类目录因为第三方库的漏洞不是你的责任扫进来只会淹没真实结果$exts用白名单而不是黑名单是因为黑名单永远漏。参数上$root是源码根目录绝对路径$exts建议至少包含php、inc、phtml有些老项目还用.php3、.php5按需加。这一步的坑在于符号链接和超大文件。如果目录里有软链指向自身递归会死循环如果有几十 MB 的压缩包被误判成源码解析会卡死。稳妥做法是加一个文件大小上限比如 2MB和isLink()判断。2.3 解析与规则匹配危险函数表怎么建真正出结果的是规则匹配。最直接的方式是正则扫危险函数调用但纯正则误报高所以成熟一点的做法是先用token_get_all把源码切成 token再在 token 流里找「函数名 token 左括号 变量 token」这种模式。?php // core/TokenParser.php 片段基于 token 定位危险函数调用 function findDangerCalls(string $code, array $dangerFuncs): array { $tokens token_get_all($code); $hits []; $count count($tokens); for ($i 0; $i $count; $i) { $tok $tokens[$i]; if (!is_array($tok) || $tok[0] ! T_STRING) { continue; } $name strtolower($tok[1]); if (!in_array($name, $dangerFuncs, true)) { continue; } // 往后找左括号确认是函数调用而不是函数定义 $j $i 1; while ($j $count is_array($tokens[$j]) $tokens[$j][0] T_WHITESPACE) { $j; } if ($j $count $tokens[$j] () { $hits[] [ func $name, line $tok[2], // token 自带行号 ]; } } return $hits; }逻辑说明token_get_all返回的数组里字符串 token如(、)是裸字符串关键字和标识符是[类型, 内容, 行号]三元组。我们只关心T_STRING且名字在危险函数表里的再往后跳过空白找(确认是调用而非定义function eval()这种非法写法不会命中但function mysql_query()定义会被误判所以实际实现里还要判断前一个非空白 token 是不是T_FUNCTION。$tok[2]直接给出行号省去自己数换行。危险函数表是规则的核心常见的一批长这样?php // config/rules.php 片段 return [ danger_funcs [ eval, assert, system, exec, shell_exec, passthru, popen, proc_open, call_user_func, call_user_func_array, create_function, include, include_once, require, require_once, unserialize, file_put_contents, fwrite, move_uploaded_file, ], sql_funcs [mysql_query, mysqli_query, PDO::query], sensitive_vars [$_GET, $_POST, $_REQUEST, $_COOKIE, $_FILES], ];参数说明danger_funcs是命令执行/代码执行/文件包含类命中即高危sql_funcs单独列是因为 SQL 注入要看参数是否拼接了敏感变量sensitive_vars是污点源用来判断「危险函数的参数是不是来自用户输入」。真正有价值的不是「用了eval」而是「eval的参数直接来自$_POST」所以规则匹配之后还要做一层污点传播判断——这也是这类工具从「玩具」到「能用」的分水岭。2.4 结果聚合从命中列表到可读报告扫出来的原始命中是一堆{文件, 行号, 函数}直接丢给用户没法看。结果层要做的是按文件分组、按危险等级排序、附上上下文代码片段?php // core/Reporter.php 片段按文件聚合附上下文 function buildReport(array $hits, array $fileLines): array { $report []; foreach ($hits as $hit) { $file $hit[file]; $line $hit[line]; // 取命中行前后各 2 行做上下文方便人工确认 $start max(1, $line - 2); $end $line 2; $snippet array_slice($fileLines[$file], $start - 1, $end - $start 1, true); $report[$file][] [ line $line, func $hit[func], level in_array($hit[func], [eval, assert, system], true) ? high : medium, snippet $snippet, ]; } return $report; }逻辑说明$fileLines是预先按行切好的源码数组file()函数即可array_slice取命中行上下各两行保留原始键名第四个参数true以便显示真实行号。level用简单映射区分高危和中危实际项目里可以做成规则表里的字段。报告最终按文件分组输出前端渲染成可折叠列表点开看上下文——这个交互对人工确认效率影响很大比纯文本报告好用得多。3. 跑起来环境准备、参数配置与第一次扫描3.1 运行环境与依赖这类 PHP 审计工具对环境的胃口不大常见要求是 PHP 7.x 以上部分老版本只兼容 PHP 5.6跑之前先看入口文件有没有用新语法需要开启tokenizer扩展token_get_all依赖它Web 方式运行还需要一个 Apache/Nginx PHP 的环境。命令行方式更省事直接php跑入口脚本即可。先确认扩展php -v php -m | grep -i tokenizer如果tokenizer没输出说明没编译进去Debian/Ubuntu 系装php-tokenizer包Windows 下在php.ini里取消extensiontokenizer的注释。这一步不做扫描会直接报Call to undefined function token_get_all()属于最常见的开局翻车。3.2 配置扫描目标与规则开关配置文件一般管三件事扫描根目录、忽略目录、规则开关。以config/config.php为例?php // config/config.php 片段 return [ scan_root /data/www/project, // 待审计源码绝对路径 ignore_dirs [vendor, node_modules, cache, uploads, runtime], max_size 2 * 1024 * 1024, // 单文件上限 2MB exts [php, inc, phtml], enable_taint true, // 是否开启污点传播判断 timeout 300, // 单次扫描超时秒 ];参数说明scan_root必须是绝对路径相对路径在不同工作目录下会解析错ignore_dirs按目录名匹配注意别把业务目录误伤max_size防止大文件拖垮解析enable_taint关掉后只做函数名匹配速度快但误报多适合先粗筛再人工看timeout在 Web 模式下尤其重要PHP 默认max_execution_time是 30 秒大项目必超时要么调大要么改命令行跑。注意Web 方式跑大项目很容易撞上max_execution_time和memory_limit我一般直接命令行跑把结果写文件再用浏览器看报告省得跟超时较劲。3.3 命令行与 Web 两种启动方式命令行方式最直接# 在项目根目录执行指定目标目录和输出文件 php index.php --path/data/www/project --out/tmp/audit_report.json如果入口脚本不支持参数就改配置文件里的scan_root再跑php index.php。Web 方式则是把整个目录丢到 Web 根下浏览器访问http://localhost/seay-audit/在页面里填路径、勾规则、点扫描。两种方式底层调的是同一套Scanner区别只是参数来源和结果渲染。第一次扫描建议拿一个已知有漏洞的小 demo试手比如自己写一个带include($_GET[page])的页面看能不能命中、行号对不对。确认引擎工作正常再上真实项目。这一步别省直接上大项目扫出一堆误报你会怀疑工具坏了其实是规则没调好。3.4 结果解读哪些命中值得人工确认扫出来的清单不是让你逐条改而是按优先级排。我的习惯是这么分层看命中类型危险等级处理方式eval/assert参数含$_GET/$_POST高立即人工确认基本就是后门或一句话include/require路径含用户变量高看是否有白名单限制没有就是文件包含mysql_query拼接了$_REQUEST高确认是否用了预处理没用的就是注入unserialize参数可控中高看类里有没有__destruct/__wakeup魔术方法单纯出现system但参数是常量低多半是正常业务扫一眼放过这张表的价值在于把有限的人工时间花在高危命中上。工具再准也只是筛子最终判断还得靠人。AWD 赛前自查时我一般只盯前两类因为比赛里被打穿的基本都是这两类。4. 避坑与排查扫描结果不对时先看这几处4.1 扫出来全是误报命中几百条没法看现象一份正常业务代码扫出几百条高危点开一看全是include config.php这种常量路径。原因规则只匹配了函数名没做参数来源判断include后面跟字符串常量也被当成危险调用。解决开启污点传播enable_taint true或者在规则里加一条「参数必须是变量且变量来自敏感源」的过滤。临时办法是在结果层按snippet里是否含$_GET/$_POST做二次过滤先把常量参数的命中压下去。4.2 明明有漏洞却扫不出来现象手工确认某文件有eval(base64_decode($_POST[x]))工具没报。原因常见有三种——文件后缀不在白名单比如.php5、文件在ignore_dirs里被跳过、或者eval前面有命名空间/反斜杠导致 token 匹配失败。解决先确认文件是否被采集到在collectFiles里打日志再确认后缀白名单最后检查 token 匹配逻辑对\eval这种写法是否兼容。命名空间下的函数调用 token 流会多出T_NAME_QUALIFIED老版本解析器可能不认。4.3 扫描到一半卡死或内存爆掉现象进度条停在某个文件不动或者直接Allowed memory size exhausted。原因遇到超大文件压缩包、日志、SQL 导出被当源码读进内存或者目录里有软链死循环。解决加max_size上限采集阶段就跳过加isLink()判断防死循环解析阶段用流式读取而不是file_get_contents一次性读入。大项目建议分目录扫别一次扫全量。4.4 Web 页面点扫描没反应日志也没报错现象浏览器点「开始扫描」转圈几秒后白屏PHP 错误日志里只有一条超时。原因max_execution_time默认 30 秒大目录遍历没跑完就被杀了前端拿不到结果。解决改php.ini调大max_execution_time和memory_limit或者干脆走命令行 结果文件的方式。Web 模式适合小项目快速看大项目别硬扛。4.5 报告里行号对不上现象报告说第 120 行有eval打开文件一看是空行。原因源码文件用了 CRLF 换行或者解析时对多行字符串/heredoc 的行号计算有偏差。解决确认token_get_all返回的行号是准确的它按\n计数CRLF 也认偏差多半出在自己做行号累加的逻辑上。统一用 token 自带行号别自己数。5. 二次开发加一条自定义规则并验证它真的生效规则引擎最大的价值是可扩展。假设你要加一条检测「一句话木马常见特征」的规则比如$_POST[x]直接进assert或者preg_replace带/e修饰符老版本 PHP 的代码执行点。改造路径分三步加规则、改匹配、写验证。第一步在config/rules.php里加规则组?php // 新增一句话木马与 preg_replace /e 特征 custom_rules [ [ id CUSTOM-001, regex /preg_replace\s*\(\s*[\][^\]*\/[a-z]*e[a-z]*[\]/i, level high, desc preg_replace /e 修饰符可导致代码执行, ], [ id CUSTOM-002, regex /assert\s*\(\s*\$_(POST|GET|REQUEST|COOKIE)\s*\[/i, level high, desc assert 直接接收超全局变量疑似一句话木马, ], ],第二步在TokenParser的匹配流程里挂上正则规则。token 解析适合结构化匹配正则适合特征串两者互补?php // core/TokenParser.php 片段在 token 匹配后追加正则规则 function applyCustomRules(string $code, array $rules): array { $hits []; foreach ($rules as $rule) { if (preg_match_all($rule[regex], $code, $m, PREG_OFFSET_CAPTURE)) { foreach ($m[0] as $match) { // 由字节偏移换算行号 $line substr_count(substr($code, 0, $match[1]), \n) 1; $hits[] [ id $rule[id], line $line, level $rule[level], desc $rule[desc], ]; } } } return $hits; }逻辑说明PREG_OFFSET_CAPTURE让preg_match_all返回匹配的字节偏移substr_count数偏移之前的换行数再加一就是行号。参数上$rule[regex]要写成完整分隔符形式/.../i$rule[level]决定报告里的排序权重。注意正则别写太宽assert\s*\(这种会命中大量正常断言加上\$_(POST|...)才收得住。第三步写验证用例。这一步最容易被跳过但恰恰是规则开发里最该做的?php // tests/rule_test.php最小验证脚本 require __DIR__ . /../config/rules.php; require __DIR__ . /../core/TokenParser.php; $sample ?php assert($_POST[cmd]); ?; $hits applyCustomRules($sample, $rules[custom_rules]); // 期望命中 CUSTOM-002行号 1 var_dump($hits);跑一遍确认CUSTOM-002命中且行号是 1再拿一个正常文件比如assert($x 0)确认不误报。正例和反例都要测只测正例的规则上线后必然误报满天飞。改造时还有两个经验点值得说。一是规则优先级自定义规则和内置规则可能重复命中同一行报告层要做去重按文件行号函数做唯一键。二是性能正则规则是逐文件全文匹配规则多了会明显拖慢扫描建议把正则规则和 token 规则分开跑正则规则只对 token 阶段已标记「含敏感变量」的文件生效能砍掉一大半无效匹配。从那以后我每次加规则都强制先写一个正例一个反例跑通再合并不然规则库越加越乱最后连自己都不敢信扫描结果。这套「加规则 → 挂匹配 → 写验证」的流程放在任何规则驱动的审计工具上都通用希望帮到你。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
原生Canvas图片翻转实战:像素级操作与性能优化 简介:这份资源面向Web前端初学者与交互效果爱好者,围绕JavaScript实现图片翻转这一常见交互场景,提供可直接运行的示例工程,帮助理解如何通过脚本控制DOM样式完成视觉翻转。压缩包共36个文件,以26个gif与7个jpg图片素材… · 2026/11/6 2:12:36
Qt网络安全开发:SSL/TLS加密与证书管理实战 1. 项目概述Qt作为一个跨平台的C应用程序开发框架,在网络通信领域有着广泛的应用。随着网络安全问题日益突出,Qt框架中的网络安全功能模块成为开发者必须掌握的核心技能。本专题将深入剖析Qt网络模块的安全机制,从基础概念到实战应用… · 2026/11/6 2:12:36
现代IM系统架构设计与关键技术解析 1. 即时通信技术的前世今生2003年我刚入行时,还在用Delphi写局域网聊天工具,谁能想到二十年后的今天,微信每天要处理450亿条消息。IM(Instant Messaging)技术从最初的ICQ雏形发展到如今支撑起整个社交生态,… · 2026/11/6 2:12:36
5G小基站部署仿真技术与应用解析 1. 5G网络仿真中的小基站部署概述在5G网络规划与优化过程中,小基站(Small Cell)部署已经成为提升网络容量和覆盖的关键策略。与传统宏基站相比,小基站具有体积小、功耗低、部署灵活等特点,能够有效解决高密度用户场景下… · 2026/11/6 2:16:37
Python Selenium自动化测试实战与反爬技巧 1. Python3 Selenium库深度实战指南作为从业八年的自动化测试工程师,我见证了Selenium从一个小众测试工具成长为Web自动化领域的瑞士军刀。不同于官方文档的刻板教条,本文将分享我在金融、电商领域实战中积累的二十余个关键技巧,包括如何绕过… · 2026/11/6 2:16:37
2025企业网盘选型指南:5款主流产品深度横评 1. 企业网盘选型的关键考量因素2025年的企业数据管理环境正面临前所未有的挑战。随着远程办公常态化、数据合规要求趋严,传统文件共享方式已无法满足现代企业的需求。我们团队在过去三年实测了37家企业网盘产品,最终筛选出当前最具代表性的5款解决方案进… · 2026/11/6 2:16:37
从本地开发到云端协同:Sealos实践与团队效率提升 1. 从地狱级环境调试到云端协同开发:我的团队转型实录上周五下午五点半,我在群里发的那条"环境问题还没解决,大家先别走"的消息,成了我职业生涯最尴尬的时刻之一。五个同事从期待到绝望的表情变化,至今仍清晰… · 2026/11/6 2:16:37
5G网络仿真在物联网开发中的关键技术与应用 1. 5G网络仿真与物联网的天然契合5G网络仿真技术正在成为物联网应用开发和测试的关键基础设施。作为一名长期从事无线通信系统设计的工程师,我深刻体会到传统物理测试环境的局限性——高昂的设备成本、复杂的测试场景搭建、难以复现的边缘案例等问题,严重… · 2026/11/6 2:16:37
生成式AI如何悄悄削弱批判性思维?基于936个真实工作场景的认知行为图谱 简介:本资源为一篇聚焦生成式AI对知识工作者思维能力影响的实证研究论文,面向人工智能从业者、教育研究者、人因工程与认知科学学习者,以及关注AI伦理与人机协同效能的管理者。论文基于对319名知识工作者的问卷调查与936个真实工作场景用例分… · 2026/11/6 2:15:37
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:27:59
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:28:03
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:28:07
三菱PLC数据采集到数据库的时序设计:Python+MC协议实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/6 1:02:11
ASIC主模式RTL设计:状态机与三态驱动的流片级实现 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/6 1:02:11
STM32嵌入式C++开发:GDB调试与Renode仿真实战指南 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/6 1:02:11
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:27:59
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:28:03
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:28:07