AWD场景下Seay源代码审计系统实战:规则调优与结果收敛

发布时间:2026/10/29 11:32:51
AWD场景下Seay源代码审计系统实战:规则调优与结果收敛 简介Seay源代码审计系统是一款面向Windows平台、基于C#开发的PHP代码安全审计工具适合网络安全初学者、CTF/AWD参赛者及渗透测试人员用于快速排查SQL注入、代码执行、命令执行、文件包含、文件上传、XSS、信息泄露等常见漏洞。压缩包共30个文件整体约42.16MB以exe程序、dll运行库为主并包含rar/zip源码与插件资料、配置及说明文档便于直接部署、扩展规则和查看示例。目前已有805人学习下载具备较高参考热度。其价值在于支持一键审计、代码调试、函数定位、插件扩展、自定义规则与编码转换等功能可帮助读者在AWD场景中快速定位风险点、理解审计流程并积累排错与规则配置经验。1. AWD 开局先翻源码Seay 源代码审计系统到底解决什么问题攻防对抗演练的开局十分钟很多人第一反应是登堡垒机看日志、跑扫描器打一遍自己的服务。我习惯反过来先把靶场源码拉到本地用 Seay 源代码审计系统过一遍。它是一款 Windows 下的白盒代码审计工具以 PHP 项目为主核心能力是把项目里的危险函数、超全局变量来源、可疑拼接模式按规则匹配出来落到具体文件的具体行号上双击就能跳到代码。AWD 场景里它的价值不是自动挖洞而是把几千个文件压缩成二三十个值得人眼看的入口点。适合做防守方排查自己服务、也适合赛前熟悉靶场代码结构的安全从业者和打比赛的队伍。下面按装、配、用、排错的顺序拆。2. 装通再谈效率运行环境、编码与项目导入的三道关2.1 运行环境与目录约定先对齐这个工具是 C# WinForm 程序依赖 .NET Framework 运行时Windows 平台原生跑最省事。有人在 Linux 下用兼容层硬跑界面能出来但文件遍历和正则匹配会出各种怪问题我不建议在比赛现场折腾这个要么物理机要么虚拟机里备一套干净的 Windows。安装路径有个血泪经验不要放在 Program Files 下不要带中文和空格。工具运行时要读写自己的规则库和配置文件放在受保护目录里会出现规则改了不生效扫描到一半退出这类玄学问题。我一般固定用短英文路径# 我自己的目录约定赛前一次性建好后面所有靶场都按这个来 mkdir -p /d/audit/tools/seay # 工具本体 mkdir -p /d/audit/src # 靶场源码一个靶场一个子目录 mkdir -p /d/audit/out # 每次扫描的结果存档 mkdir -p /d/audit/rules # 自己维护的规则集备份 # 确认目录结构靶场源码按 靶场名/日期 分层 tree -L 2 /d/audit/src把工具目录整个复制一份到rules下面做冷备也很重要。规则库是你一条条调出来的误删一次就得从头再来这个后悔药不贵五分钟的事。第一次启动大概率会被杀毒软件拦下来甚至直接删掉主程序。原因不复杂这个工具的行为特征就是遍历磁盘上的所有源码文件、加载一大堆包含eval、system、assert这类字符串的正则规则在启发式引擎眼里和恶意脚本高度相似。解决方式是给工具目录加信任不要图省事关防护也不要因为它报毒就以为工具本身有问题。2.2 导入项目编码选错后面全是白扫新建项目时最重要的一个参数是源码编码。国内很多老靶场、老 CMS 源码是 GBK 的如果你按默认 UTF-8 导入界面上的中文注释全是乱码更致命的是正则会匹配不上——因为多字节字符在错误解码下会破坏行内字节序列原本能命中的规则直接漏掉。这类问题不会报错你只会觉得这个项目怎么这么干净然后被现实打脸。稳妥流程是导入前先探测一遍编码别靠猜。下面这段脚本遍历目录下所有 PHP 文件按文件统计 UTF-8 解码是否失败输出编码分布import os from collections import Counter root rD:\audit\src\mock_project # 靶场源码根目录 stat Counter() bad_files [] for dirpath, dirnames, filenames in os.walk(root): # 排除第三方依赖和缓存目录这些不需要审计 dirnames[:] [d for d in dirnames if d not in (vendor, node_modules, .git, cache, runtime)] for name in filenames: if not name.endswith((.php, .inc)): continue path os.path.join(dirpath, name) raw open(path, rb).read() try: raw.decode(utf-8) stat[utf-8] 1 except UnicodeDecodeError: stat[非utf-8] 1 bad_files.append(path) print(stat) print(疑似GBK文件数:, len(bad_files)) for p in bad_files[:10]: print( , p)这段脚本的逻辑很直白用utf-8严格模式解码抛异常就说明不是纯 UTF-8。参数上root换成你的靶场路径排除列表按项目实际情况加比如 ThinkPHP 项目一定要排vendor和runtime否则扫描时间会被第三方库吃掉八成。输出的bad_files就是需要单独处理的那批。如果探测结果是全部 GBK导入时直接选 GBK 就行。如果是混合编码——这种情况在老项目里非常常见主程序 GBK、新加的功能模块 UTF-8——不要试图一次导入按子目录拆成两个项目分别扫最后人工合并结果。我试过用脚本统一转码再导入转码过程中一旦有字符集转换失败文件内容会被静默替换成问号审计结果就失真了得不偿失。顺带说一个文件类型的问题只导入.php、.inc、.phtml这几类就够了。模板文件.html、.tpl里也可能有漏洞但它们通常被主程序include进来审计入口还是 PHP 文件。全类型导入只会让结果列表变长不会让结论变准。2.3 第一次自动审计结果面板每个字段怎么读项目导入完成后先别急着点自动审计。我一般先手动翻三个文件入口文件index.php、路由/分发文件、公共函数库common.php、functions.php。这三处决定了整个项目的参数流向看完再扫结果列表里哪些命中值得点开心里就有谱了。自动审计跑完结果面板的信息量很大。每一行基本包含这几个字段命中的规则名称、危险等级、文件相对路径、行号、命中的代码片段。读的时候按这个顺序处理先按危险等级排序把高危的全过一遍。高危规则一般是命令执行、代码执行、文件包含这几类函数直接出现误报率相对低命中量也少。然后看文件路径/admin/、/api/、/upload/这几个目录下的命中优先级高于/install/、/demo/后者很多是安装脚本或者示例代码实际不可达。最后才看代码片段本身判断这个变量到底可不可控。这里要建立一个心理预期一个中型项目扫出几千条命中是正常的。工具本身是基于正则的轻量匹配没有完整的抽象语法树和污点传播分析它只负责这里出现了可疑写法不负责这里一定可利用。你要做的是用规则和排除目录把几千条压到几十条而不是指望它一步到位。第一次扫描的结果建议原样存档命名带上靶场名和日期。后面每次改规则重扫都可以和这份基线对比看新增了哪些命中——比赛现场改代码是常态基线对比比重新看一遍全量结果快得多。3. 规则库改造把满屏命中压到二十条可验证线索3.1 内置规则的三类命中逻辑摸清内置规则的分类才知道该在哪儿动刀。按我的使用经验工具内置的规则大致落在三种模式上第一种是危险函数直匹配。规则就是一个函数名加词边界比如代码执行类的eval、assert命令执行类的system、exec、shell_exec、passthru、popen文件操作类的file_get_contents、unlink、fopen。这类规则命中率高、误报率也高因为大量正常业务代码也在用这些函数操作文件。第二种是变量来源匹配。规则盯的是$_GET、$_POST、$_REQUEST、$_COOKIE、$_FILES这几个超全局变量的出现位置判断用户输入有没有被直接使用。这类规则的价值在于快速定位所有外部输入入口缺点是它不知道这个变量后面经过了什么过滤。第三种是组合模式匹配也就是把来源和汇点写进同一条正则比如超全局变量出现在 SQL 查询函数参数附近。这类规则命中量最少但精度相对高是我在 AWD 现场最先看的一批。理解这三类的区别规则调优就有的放矢了第一类做减法排除目录、排除已验证的安全调用第二类做聚焦只保留出现在敏感上下文里的第三类做加法按项目实际框架自己补。提示这个工具的规则语法走的是 .NET 正则和 PCRE 有细微差异比如不支持某些内联修饰符写法、命名分组语法是(?name...)而不是(?Pname...)。从网上抄规则时先在小样本文件上试一遍。3.2 自定义规则从能匹配到能收敛自己加规则是这个工具真正提效的地方。界面里新增一条规则通常要填四项规则名称、正则表达式、危险等级、备注说明。名称我习惯按漏洞类型-框架-场景三段式起比如SQL注入-原生拼接-GET参数扫完看结果列表时一眼就知道该找什么。写正则有几条固定套路。第一加词边界\b否则ass这种子串也会命中assert。第二排除注释行PHP 里//和#开头的行大量出现函数名但那是文档不是代码。第三排除函数定义行function eval_something(这样的自定义函数会被误判。下面这张表是我自己维护的一小部分规则覆盖 AWD 场景里最常见的几类漏洞类型正则片段主要误报来源代码执行\beval\s*\(\s*\$模板引擎内部调用、缓存文件生成命令执行\b(system|exec|shell_exec)\s*\(\s*\$运维脚本、导出功能文件包含\b(include|require)(_once)?\s*\(?\s*\$框架的自动加载器、模板渲染SQL 注入\b(mysql_query|mysqli_query)\s*\(.*\$_(GET|POST|REQUEST)已做转义的旧代码文件上传move_uploaded_file\s*\(.*\$_(FILES)上传模块自身需结合后缀校验判断反序列化\bunserialize\s*\(\s*\$_(GET|POST|COOKIE)缓存读取、session 处理正则写完不要直接扔进项目里跑。先在 Python 里验证一遍逻辑对不对能省掉大量来回import re # 待验证的规则先按 .NET 语法写Python 里基本兼容 pattern re.compile(r\b(include|require)(_once)?\s*\(?\s*\$, re.IGNORECASE) samples [ include $tpl;, # 应命中 require_once($file);, # 应命中 include ./config.php;, # 不应命中参数是常量 // include $user_input;, # 不应命中是注释 function my_include($x) {, # 不应命中是定义 ] for line in samples: hit bool(pattern.search(line)) print(f{HIT if hit else MISS} | {line})跑一遍看输出五个样本的期望结果对不对。这一步的意义在于把规则的正确性问题隔离在单个文件之外否则你改一版规则、全项目扫一次、翻结果列表一轮下来十分钟就没了。参数上re.IGNORECASE保持开启PHP 函数名不区分大小写INCLUDE和include一样危险。注释行的排除在 Python 里好写在工具里就得靠正则本身。一个可行的补偿办法是给规则加负向断言把行首是//、#、*的情况排掉虽然不算完美但能砍掉相当一部分噪音。3.3 结果收敛用脚本把命中变成可读清单工具扫完的原始结果适合人眼扫不适合逐条核对。我一般会用脚本对源码做一次交叉验证把命中的文件、行号和上下文抽出来生成一份可以直接贴进协作文档的清单。import os import re root rD:\audit\src\mock_project out_md rD:\audit\out\hit_list.md # 汇点函数字典函数名 - 漏洞类型 SINKS { eval: 代码执行, assert: 代码执行, system: 命令执行, shell_exec: 命令执行, include: 文件包含, require: 文件包含, unserialize: 反序列化, } sink_re re.compile(r\b( |.join(SINKS) r)\s*\(, re.IGNORECASE) # 同一行出现超全局变量视为外部输入可达 taint_re re.compile(r\$_(GET|POST|REQUEST|COOKIE|FILES)\b) rows [] for dirpath, dirnames, filenames in os.walk(root): dirnames[:] [d for d in dirnames if d not in (vendor, node_modules, .git, cache, runtime)] for name in filenames: if not name.endswith((.php, .inc)): continue path os.path.join(dirpath, name) try: lines open(path, encodingutf-8, errorsignore).read().splitlines() except OSError: continue for idx, line in enumerate(lines, 1): m sink_re.search(line) if not m: continue # 只保留同一行或前两行存在外部输入变量的命中降低噪音 ctx .join(lines[max(0, idx - 3):idx]) if not taint_re.search(ctx): continue rows.append({ file: os.path.relpath(path, root), line: idx, type: SINKS[m.group(1).lower()], code: line.strip()[:160], }) rows.sort(keylambda r: (r[type], r[file])) with open(out_md, w, encodingutf-8) as f: f.write(| 类型 | 文件 | 行号 | 代码 |\n|---|---|---|---|\n) for r in rows: f.write(f| {r[type]} | {r[file]} | {r[line]} | {r[code]} |\n) print(命中条数:, len(rows))逻辑分三步先用汇点函数字典做第一层过滤再用超全局变量做第二层过滤要求汇点往前三行内出现过输入来源最后按类型和文件排序输出 Markdown 表格。参数上errorsignore是为了绕过混合编码项目里的解码异常代价是极少数行可能丢字符对定位不影响。ctx取前三行是经验值取太少会漏掉变量赋值在上一行的情况取太多噪音又会回来。这个脚本不能替代工具它是补充。工具的价值在于规则库覆盖广、界面跳转快脚本的价值在于批量输出和二次过滤。两者结合一份几千条的扫描结果能压到几十条剩下的就是人眼判断的事了。4. AWD 场景落地从拿到源码到出一份可疑点清单4.1 赛前基线给每个靶场建一份命中底稿攻防演练通常会在赛前给出靶场或者往届题目很多人拿到源码直接扔一边开赛了再翻。我的做法是赛前就把每个靶场都扫一遍结果存档形成基线。目录结构按靶场名/日期/组织每次扫描输出三个文件原始结果、收敛后的清单、自己写的备注。备注里记三件事——哪些命中已经确认是安全写法、哪些目录是第三方依赖可以整体忽略、哪些文件是入口文件需要优先看。下一次再看这个靶场直接读备注不用重新分析。这个习惯在比赛中能省出大量时间的根本原因在于攻防演练里你防的往往不止一个服务四五个靶场同时开着每个都需要快速判断。没有基线每次都要从零开始理解代码结构有基线你只是在确认上次标记的那几个点现在有没有变化。基线还有个隐性作用能帮你发现代码有没有被人动过。如果某个文件的行数、内容和你存档的版本不一致说明它被改过这在防守场景里是重要信号。4.2 开局前十分钟的固定动作比赛开始后的时间极其宝贵我会把前十分钟拆成固定动作按顺序执行不做任何临时发挥。第一分钟到第三分钟确认服务可用性把每个靶场的首页、登录页、后台入口各访问一次记录响应状态。这一步和审计无关但决定了后面所有工作的前提。第三分钟到第六分钟打开已经建好的项目跑一次增量扫描。重点看两处——所有涉及文件上传的处理逻辑以及所有拼接 SQL 的地方。这两类在攻防演练里被利用的频率最高也最容易在短时间内确认。第六分钟到第十分钟从收敛清单里挑出前十条逐条点开看上下文判断参数是否可控。判断标准就一条从$_GET/$_POST到这个汇点之间有没有出现过有效的过滤函数。没有就是可疑有也要看过滤是否写对了比如intval之后又拼进字符串那还是安全的addslashes之后拼进LIKE语句那就不一定。十分钟结束手里应该有一份五到十条的可疑点清单按可能性排序。这就是后续所有工作的输入。不要在这个阶段追求挖出完整的利用链先有清单再谈验证。4.3 静态命中怎么交给动态验证静态审计的结论永远只是可疑最终要落到请求上才算数。验证环节一定要在自己有授权的靶场环境里做本地起一个 PHP 服务就够# 在靶场源码目录下起一个本地服务端口避开常用端口 cd /d/audit/src/mock_project php -S 127.0.0.1:8899 -t . # 另开一个窗口按清单里的入口构造请求验证 curl -s http://127.0.0.1:8899/api/export.php?file../../etc/passwd -o /dev/null -w %{http_code}\n参数说明-t .指定文档根目录-S是内置服务器。curl那条命令里的-w %{http_code}只输出状态码方便快速判断请求是否被正常处理-o /dev/null丢弃响应体避免刷屏。如果目标是需要登录的接口先用浏览器登录拿到 Cookie再用-b PHPSESSIDxxx带上。验证的时候有个原则一次只改一个变量。同一个接口先传正常参数看返回再传一次可疑参数对比差异。差异出现在报错信息、页面长度、响应时间这三个维度上都算线索。很多人验证失败是因为一次改了好几个参数出问题了不知道是哪一处引起的。静态清单和动态验证之间还有一层工作常被忽略确认代码可达性。审计工具标出来的文件可能根本没被任何路由引用是历史遗留的废弃文件。验证之前先在项目里全局搜一下这个文件名看看有没有include或路由配置引用它。不可达的命中直接划掉能省掉大量无效验证。5. 排查清单五个把我坑过的现场5.1 编码与文件层面的三个坑坑一双击启动直接闪退什么提示都没有。现象是双击主程序窗口一闪就没了任务管理器里也看不到进程。原因是两个一是机器上没装对应的 .NET Framework 运行时二是工具放在了带中文或空格的路径下加载自身配置时路径解析失败。解决方式先看系统里有没有装 4.x 运行时没有就装上再看路径改成D:\audit\tools\seay这种纯英文短路径。这两个都排除还闪退就去看工具目录下有没有生成的日志文件通常能直接看到异常堆栈。坑二界面中文全是问号规则怎么改都不命中。现象是打开 PHP 文件中文注释显示成乱码同一个项目用别的编辑器看是正常的。原因是导入项目时编码选错了GBK 源码按 UTF-8 解码。解决方式是删掉项目重新新建导入时选 GBK。已经扫过的结果没有保留价值直接丢弃重扫。如果项目是混合编码按 2.2 节的办法拆成多个项目分别导入。坑三扫描到一半卡死或者提示内存不足。现象是进度条走到某个位置不动了或者直接弹异常。原因通常是项目里混进了超大文件——压缩包、日志文件、导出的 SQL 备份工具试图把整个文件读进内存做正则匹配。解决方式是扫描前先清理把非源码文件移出目录或者按子目录分批扫描一个模块一个项目。清理脚本很简单# 扫描前清掉明显不是源码的大文件先看再删 find /d/audit/src/mock_project -type f -size 2M -not -name *.php -print # 确认无误后再执行删除注意路径一定要写全 find /d/audit/src/mock_project -type f -size 2M -not -name *.php -delete-size 2M是阈值一般 PHP 源文件超过 2MB 就已经很反常了。-not -name *.php保证不会误删源码。这两条命令我建议先跑第一条看输出确认列表里确实都是垃圾文件再跑第二条血泪经验别问我怎么知道的。5.2 规则与结果层面的两个坑坑四结果列表里同一个位置重复出现十几条。现象是某一行代码被多条规则同时命中列表里刷出一大片重复项翻起来很烦。原因是内置规则之间存在重叠比如system和命令执行函数两条规则覆盖了同一个函数。解决方式有两个方向一是在规则配置里把重复的规则禁用掉只留精度最高的那条二是不动规则在导出结果后用脚本按文件行号去重。我一般选后者因为规则禁用之后换项目可能又漏去重是无损的。去重逻辑就是按(file, line)建字典保留危险等级最高的那条。坑五导出结果后发现条数比界面上少很多。现象是界面里显示几千条导出成文件只有几百条。原因是大项目导出时存在缓冲或截断也可能是导出格式选择了精简模式。解决方式是分批导出——按目录分批扫描、分批导出每个批次控制在几百个文件以内。另外导出后一定要核对条数界面右下角通常会显示当前结果总数导出文件的表格行数应该和它对得上对不上就重导。顺带提一个不算坑但很容易踩的工具扫描时会遍历整个目录树如果源码目录里挂着网络驱动器或者同步盘的挂载点扫描会慢到无法接受。扫描前把源码复制到本地磁盘别在原位置扫。6. 进阶让审计结果可检索、可复用的一套习惯工具用久了真正的效率差距不在界面操作而在结果管理。我现在的做法是每次扫描完立刻做三件事五分钟内完成长期收益很大。第一件把收敛后的清单按靶场名_日期.md存进统一的输出目录文件名里不要有空格和中文方便后续用命令行检索。第二件给清单加一段固定格式的头部写清楚这次的规则版本、排除目录、扫描时间下次翻回来能立刻知道当时的环境。第三件用ripgrep对源码做一次反向抽查验证审计结果没有大范围遗漏# 抽查审计结果是否漏掉了明显的危险函数调用 rg -n --type php -e \b(eval|assert|system|shell_exec|passthru)\s*\( \ /d/audit/src/mock_project \ --glob !vendor/** --glob !runtime/** \ | tee /d/audit/out/spot_check.txt # 用审计清单里的文件名去比对找出抽查有、清单没有的行 rg -F -f (cut -d| -f3 /d/audit/out/hit_list.md | tr -d ) \ /d/audit/out/spot_check.txt第一条命令做全量抽查--type php限定文件类型两个--glob排除第三方目录和缓存tee同时输出到屏幕和文件。第二条把审计清单里的文件路径当作模式文件去匹配抽查结果-F表示按字面量匹配不走正则避免路径里的特殊字符被解释。输出为空说明两者一致输出有内容就说明有漏网的行需要人工看一眼。这套流程跑顺之后一个陌生靶场从拿到源码到出可疑点清单能压到十五分钟以内。规则库是慢慢长出来的我现在的习惯是每遇到一次误报就把那条规则加一个排除条件而不是关掉它每遇到一次漏报就补一条新规则并写清楚触发场景。规则文件单独版本管理换机器直接拷过去不用重新调。最后说一个教训。早期我图快扫完不存结果觉得结论都在脑子里。结果比赛打到一半去处理别的服务回头再看这个靶场前面分析过的哪些点已经排除、哪些还没验证全忘了只能重扫一遍。从那以后我每次扫描都强制走一遍存档、写备注、抽查这三步哪怕时间再紧也不跳过。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询