最近在整理一批应急响应记录时,我把过去大半年用过的WebShell分析手段重新梳理了一遍,最终攒下了30个高频技巧。这里说的WebShell分析,围绕的是一个非常具体的问题:当一台Web服务器可能被上传了恶意脚本时,如何通过文件、日志、行为和流量四条线索,尽快把“有没有、是不是、干了什么”这三个问题查清楚。标题写“懂一半就能算高手”,我觉得真不算夸张——这30个技巧不是让你背下来的知识点,而是按“定位、研判、验证、收尾”四个阶段排好的操作手册。
这套东西适合谁用?安全运营、应急响应工程师、开发兼运维的同学,甚至是被临时拉去救火的后端都适用。能熟练用上三分之一,日常应急响应基本不抓瞎;能熟练用上一半,已经可以独立带队做一轮完整的WebShell专项排查了。下面我就把这30个技巧按使用场景拆开讲,每个技巧都尽量说明原理和适用场景,最后再带你走一遍完整案例,看完就能直接上手。
1. 先定调:WebShell分析到底在分析什么
很多人第一次接触WebShell分析,上来就翻日志、查文件,折腾半天却没有结论,原因是没有把目标拆清楚。WebShell分析的最终目标其实只有三句话:判断服务器上有没有异常脚本、这个脚本是不是恶意的、它进入系统后到底做了什么。围绕这三个诉求,分析工作可以拆成两条主线:静态分析和动态分析。
1.1 回答三个问题:有没有、是不是、干什么
“有没有”解决的是定位问题。在没有明确告警的情况下,怎么从成百上千个文件里把可疑对象筛出来。这个阶段依赖文件系统层面的观察,比如文件修改时间、目录变化、扩展名异常、与历史基线对比。很多人上来就用关键词搜整个磁盘,其实效率很低,正确做法是先通过时间和目录缩小范围。“是不是”解决的是定性问题,也就是在找到可疑文件之后,判断它是真的WebShell,还是正常业务代码、第三方组件、开发留下的调试脚本。这个阶段依赖代码层静态特征,包括风险函数、编码方式、混淆程度、代码结构和上下文语义。“干什么”解决的是行为问题,确定样本确实有问题之后,要进一步还原它的行为链路:访问了哪些接口、读取了哪些敏感配置、有没有尝试连接外部主机、有没有添加自启动任务。
这三个问题有严格的先后顺序。没有完成定位就急着做行为分析,容易在无关文件上浪费时间;没有完成定性就直接按WebShell处置,又容易误伤业务。实际项目中最常见的翻车案例,都是因为跨越阶段,直接在待定文件上执行了命令,既污染了现场,又丢失了判断依据。
1.2 三种分析对象:落地文件、混淆文件、内存样例
WebShell在服务器上的存在形态,决定了分析手段的选择。第一种是落地文件型,也是最常见的形态,通常是PHP、JSP、ASPX脚本文件直接被写进web目录、上传目录或临时目录。这类样本特征相对明显,静态扫描命中率高。第二种是加密混淆型,脚本经过编码、拼接、压缩函数组合,表面上看起来是一堆乱码字符串,但会在运行到关键阶段时还原出真正的执行逻辑。这类样本需要结合字符串解码、熵值分析和动态模拟来识别。第三种是内存驻留型,文件系统里不一定能找到独立的脚本文件,代码可能通过类加载器注入、扩展模块加载或运行期生成的方式存在于内存中。处理这类样本要转向进程层面、网络连接层面和日志层面的综合判断。
分清形态的意义在于,不同形态对应不同的分析优先级。文件落地型优先做目录遍历和静态扫描,加密混淆型优先做解码尝试和沙箱验证,内存驻留型优先做进程快照和连接追踪。顺序搞反了,会漏掉真正的风险点。
2. 30个技巧分组速查:从定位到清除一次讲透
下面这30个技巧我按使用阶段分成四组,每一组解决一个阶段的核心问题。每个技巧单独拎出来都不复杂,但组合在一起就是一套完整的分析流程。
2.1 定位可疑文件:信息收集阶段的8个技巧
在没有告警、没有明确线索的情况下,第一步永远是缩小范围,而不是大海捞针。这个阶段的目标是快速找出最值得检查的文件,再用后续手段验证。
按时间窗口优先排查:WebShell落地后一定会留下时间痕迹,文件创建时间、修改时间、访问时间都会集中在攻击者操作的时间段。最有效的一招是先用最近7天或最近24小时修改过的脚本文件圈定范围,再结合可疑请求日志反向验证。时间属性可以被人为伪造,所以不要把时间作为唯一依据,但它一定能帮助你砍掉90%的无关文件。
按扩展名白名单反查:不要只搜.php、.jsp这类常见脚本后缀,而是要列出业务运行必须的扩展名白名单,在web目录里找出不在白名单中的异常文件。很多攻击者会把脚本后缀伪装成.png、.jpg、.txt甚至无后缀,文件名看着正常,但放在不合理的目录里。反查思路比正向搜后缀更能发现伪装样本。
重点排查上传目录和临时目录:从攻击者的角度看,上传入口是最便捷的投递通道。检查/home/upload、/tmp、/var/tmp、静态资源目录、头像目录这些本不该出现脚本文件的位置。如果业务系统根本没有上传功能,却在uploads目录里出现了PHP文件,这本身就是一个强信号。
关注文件命名异常:恶意文件通常会避开人工审查,但也会暴露出命名上的慌乱。比如出现随机字符串文件名(类似a3f9k2.php)、双扩展名文件(x.php.jpg)、与正常文件高度相似但多了一个字符的副本名、或者突然出现大量连续编号文件。看起来越“普通”的命名,越要确认它是否在版本库或备份中有对应记录。
检查文件数量和目录体积突变:一个原本只有几十个文件的目录,突然多出上百个文件;一个正常业务目录的体积在几天内翻了数倍——这些突变往往伴随着批量投毒或备用WebShell的存放。用du和ls配合历史快照对比,几分钟就能定位异常。
寻找隐藏文件和高权限文件:隐藏文件(以点开头)在Windows和Linux下的意义不同,但攻击者都爱用。检查web目录下的隐藏文件、设置了setuid/setgid权限的脚本文件、属主突然变成root或www的特殊文件。正常业务文件很少刻意隐藏,出现即可疑。
与备份或Git仓库做差异对比:这是效率极高的定位手段。对比当前web目录和最近一次备份的差异,凡是新增、修改的文件全部提取出来单独排查。前提是备份本身没有被污染,所以在做对比前要先确认备份的时间点是否早于可疑事件发生时间。
建立文件指纹基线:把全站文件路径、大小、内容哈希固化到一张表里,之后每次排查只做差异比对。这个技巧适合长期维护的系统,初期建立基线的成本不高,但后续每次应急都能把排查时间从小时级压缩到分钟级。
2.2 辨别样本真伪:静态特征研判的12个技巧
定位阶段筛出来的可疑文件,不一定都是WebShell。这一步要做的是静态特征研判,通过代码特征、结构异常和语义判断来确定样本性质。这12个技巧覆盖了当前主流的恶意脚本特征面。
标记高危函数调用:脚本执行命令、读取配置、访问网络、操作文件系统的函数都是关注重点。真实业务代码也可能调用这些函数,所以这里只能作为初筛条件,不能作为最终定性依据,关键是看函数调用的上下文。一个正常业务的图片上传接口,不太可能同时出现命令执行和请求外部地址的逻辑。
识别动态执行代码结构:动态执行函数、可变函数名、字符串拼接后执行等结构,是WebShell最典型的代码组织方式。看到代码把一段字符串拆成多个部分拼接、通过变量动态调用函数,基本就值得进一步深挖。正常的业务逻辑也会用动态调用做插件化设计,但要怀疑那些看不出业务含义的动态执行代码。
警惕编码字符串:Base64、十六进制、URL编码、自定义字符替换,都是隐藏代码内容的常用手段。静态扫描时可以自动检测超长编码串,用解码工具做一轮批量还原。编码串本身不可怕,可怕的是解码后出现了命令、文件读写、配置读取等敏感操作。
关注压缩与解码函数组合:gzdecode、str_rot13、pack、unpack等函数,单独看可能只是数据处理,但组合使用时往往是代码还原链。攻击者会把真正的执行代码压缩编码后存储,运行时再解码执行,静态扫描时看到的只有一段无意义的乱码。这类组合出现时,需要把解码后的内容拉出来看,而不是停留在表面。
检查文件操作函数链:WebShell落地后常伴随一系列文件系统操作,例如将上传的临时文件移动到web目录、修改文件权限、写入自启动脚本、删除自身等。静态代码里如果同时出现文件写入、目录切换、权限修改、原文件删除这几个动作,基本可以判断不是正常业务。
提取外部地址特征:在脚本代码里搜索可疑的外部地址,包括硬编码的URL、IP和域名。攻击者的C2地址有时会直接写在脚本里,但更隐蔽的做法是编码后存放,所以要把解码后的字符串也纳入检查范围。这里要注意,不要只搜http协议开头的字符串,很多脚本用自定义协议或者只写域名不写协议。
识别图片头伪装:最常见的伪装手法是在文件头部加上GIF89a或PNG头,让file命令识别成图片,但代码内容仍然是PHP。检查方法是直接读取文件内容而不是依赖file命令,通过文本特征和实际执行环境判断。实际操作中可以用strings或十六进制查看工具,跳过文件头直接看中部内容。
检查业务注释伪装:攻击者也懂“大隐隐于市”,会在一堆正常业务注释中间夹带核心载荷,或者把恶意逻辑放在看似人畜无害的类名、函数名里。应对办法是不要只看关键字,要完整阅读关键函数的上下文逻辑,尤其是那些名称正常但实现奇怪的方法。
识别混淆变量名与垃圾代码:正常开发者会尽量让变量名和函数名有意义,而混淆代码的常见特征是无意义变量名(如$a、$b、$c但不能一概而论)、大量冗余变量赋值、无实际作用的算术表达式、重复调用的空函数。这些垃圾代码的目的不是实现功能,而是提高人类阅读成本、绕过静态规则。
计算内容熵值:中文业务代码的熵值通常比较低,英文代码略高,但经过加密或压缩的代码数据分布接近随机,熵值会显著偏高。写一个小脚本遍历可疑文件计算香农熵,可以快速找出那些看起来“过于随机化”的脚本。这个方法特别适合识别加密混淆型WebShell。
观察字符串长度分布:正常代码里字符串长度分布相对均匀,短字符串多、超长字符串少。如果某个文件中频繁出现几十KB的连续字符串、大量重复的长串文本,说明内容很可能被编码或压缩过。这条配合熵值分析,能覆盖多数“看起来不寻常”的样本。
搜索敏感信息获取函数:WebShell的核心目的之一是通过读取敏感信息扩大战果,涉及数据库配置、环境变量、服务账号等内容的操作函数都在此列。静态扫描时对这些函数做加权打分,结合出现频率和上下文,可以量化判断文件的可疑程度,而不是单纯看是否命中某一个特征。
2.3 确认行为意图:动态行为验证的6个技巧
静态分析只能告诉我们“这个文件像什么东西”,想要确认“它到底能干什么”,还得靠动态行为验证。这一阶段的红线是:绝对不能直接在受害服务器上执行可疑样本,一切验证必须在隔离环境中完成。
离线隔离复现:准备一台断网的虚拟机,恢复与受害服务器相同版本的系统环境和应用版本,再把可疑文件完整拷贝进去复现。没有现成环境时,用容器临时搭建也可以,但必须断开外部网络连接。联网环境下的动态分析可能导致恶意行为外联,让风险扩大。
触发前先做快照:在触发样本之前,先记录系统状态,包括进程列表、网络连接、文件列表、注册表或crontab内容,然后执行疑似触发请求,再对比前后差异。快照隔离了从“干净状态”到“感染后”的变化,能让行为链路清晰呈现,避免被大量无关数据干扰。
分析进程树行为:WebShell触发执行后,通常会产生子进程,例如调用命令执行功能、启动物理新进程。观察父子进程关系,能快速确认脚本是否具备命令执行能力。一个正常的图片展示页面,不应该衍生出解析命令的子进程。
监控网络连接变化:在隔离环境里用抓包工具监控样本执行前后所有对外连接请求,重点关注连接外部地址的行为,特别是非标准端口的TCP连接、DNS解析异常域名、HTTP回调请求。不能只看IP,要看连接方向和数据内容,有的样本会先做DNS查询再连接目标,DNS日志里也有痕迹。
检查持久化行为:WebShell如果仅仅是单次执行,危害可控;一旦添加了计划任务、自启动项、服务注册、启动脚本等手段,就具备持久化能力。动态分析时要特意检查这些位置在样本执行后有没有新增内容,发现异常就要立即提取并记录。
评估数据读取范围:判断样本是否读取了数据库配置文件、口令文件、密钥文件、环境变量等敏感数据。可以在隔离环境里放置诱饵配置文件,记录有哪些文件被读取。这一步的输出,直接决定后续影响的定级和清理范围。
2.4 收尾闭环:溯源取证与清理加固的4个技巧
分析工作做到行为确认还不够,真正的闭环是把攻击入口、影响范围和清理措施全部落地。最后4个技巧解决的是收尾问题。
日志关联定位攻击入口:拿到样本哈希和特征后,回到受害服务器的访问日志、应用日志、认证日志里反向搜索,找出样本文件第一次出现的时间点、对应请求、来源IP、使用的参数。一个样本可能被多次访问,但只有第一次上传时的请求能揭示攻击入口。这一步不能省,否则下一次还会以同样方式被突破。
重建攻击时间线:把文件创建时间、日志请求时间、入侵告警时间、恶意行为外联时间放在一条时间轴上对比。时间线能让整个事件从“孤立样本”变成“完整故事”,进而帮助确认攻击者还做了哪些操作、还有没有问题没有被发现。重建时间线时注意时区问题,日志与文件时间可能用了不同时区,先统一再对齐。
证据留痕规范化:发现恶意样本后,不要直接删除文件,而是先计算MD5/SHA256哈希、复制原文件到隔离目录、记录文件属性和路径,再把样本打包加密归档。很多团队在清理阶段手太快,把样本删了,导致后续溯源和追踪完全没有依据。证据保存的优先级高于清理。
清理后复查与基线固化:删除恶意文件后,立刻做一次全盘复查,检查临时目录里有没有残留、计划任务里有没有被新写入口、其他主机有没有同类样本。确认干净后,把样本特征(哈希、文件名、特征码、外联地址)整理进威胁情报表,并更新文件基线。一次清理不是终点,基线和IOC的沉淀才是下一次应急提速的关键。
3. 实操演示:从一条疑似上传的日志到完整清理复盘
技巧讲再多,不如走一遍完整的实操。下面用一个模拟项目作为案例,把“日志发现—文件定位—静态验证—动态确认—影响评估—清理加固”的完整链路呈现出来。为了叙述方便,我把目标服务器称为某业务系统,系统基于PHP开发,部署在一台Linux服务器上,web根目录为/webroot,访问日志位于/var/log/nginx/access.log。
3.1 现场线索:一条异常上传请求
某天在进行日志例行检查时,我发现了一条POST请求非常扎眼:请求路径是/upload/image.php,但请求体不是正常的图片数据,而是几个表单字段,其中一个字段的键名是cmd。正常的图片上传接口不会提交这类字段,更不会让图片文件路径直接指向PHP脚本。第一反应是记录请求时间、来源IP和完整请求行,把它们作为后续排查时间窗口的锚点。
这里要提醒一点,发现异常请求后不要立刻去web目录翻文件,而是先固定日志证据。把请求时间前后10分钟的访问日志单独导出,重点看同一来源IP还访问了哪些路径。很多时候攻击者在成功上传WebShell前后,会先探测目标站点结构,这些探测请求会为后续定位攻击入口提供线索。
3.2 按图索骥:定位文件并采集静态证据
以那条请求时间为起点,我在本地对/webroot目录执行了按修改时间筛选的操作,命令大致是这样的:
find /webroot -type f \( -name "*.php" -o -name "*.jsp" -o -name "*.asp" -o -name "*.aspx" \) -mtime -7 -printf "%TY-%Tm-%Td %TH:%TM %s %p\n" | sort -r | head -100这行命令的意思是:在/webroot下找最近7天内修改过的常见脚本文件,按修改时间倒序输出前100条。执行结果中,/webroot/upload/images/2024/11/18/u_98421.php排在最前面,修改时间与那条异常请求完全吻合。注意这个文件位于图片上传目录,扩展名却是.php,这已经是一个明显的异常信号。
接下来做三件事固定证据:计算文件哈希、复制原始文件到取证目录、用只读方式查看文件内容。用stat命令查看文件属性,用file命令识别文件类型,用strings提取文件中可读字符串。这一步我没有在原始目录里执行任何可能修改文件的操作,保证现场不被破坏。
sha256sum /webroot/upload/images/2024/11/18/u_98421.php > /evidence/sample_1.sha256 cp -p /webroot/upload/images/2024/11/18/u_98421.php /evidence/sample_1.php stat /webroot/upload/images/2024/11/18/u_98421.php file /webroot/upload/images/2024/11/18/u_98421.php strings /webroot/upload/images/2024/11/18/u_98421.php | head -50静态分析结果很清晰:这个文件不是图片,而是一个纯粹的PHP脚本,没有图片头伪装,代码只有16行,其中包含了几个非常敏感的关键字:命令执行函数、配置读取、文件上传临时路径检测。文件命名方式(无意义字符串、出现在upload/images下)和代码内容互相印证,初步判断高度疑似WebShell。
3.3 谨慎复现:隔离环境动态确认
静态分析只能给出高置信度的猜测,不能直接下结论。我把样本文件拷贝进一台断网的虚拟机,虚拟机里用相同版本的PHP运行时搭建了一个最小化环境,并在执行前用抓包工具和文件监控工具打了快照。然后构造了一个无害的测试请求,观察访问该页面时系统的行为变化。
结果很清楚:第一次请求后,监控工具记录到进程派生出一个子进程,执行系统命令;同时抓包工具记录到一次对预设外部地址的DNS查询请求。正常图片展示逻辑绝不会产生这两类行为,到此可以判定这不是误报。动态确认后,我对样本的功能有了明确认知:这是一个集命令执行、文件读取、外联通信于一体的恶意脚本,攻击者在成功上传后大概率已经通过它执行过探测命令。
3.4 清理加固:复查、恢复与基线化
确认恶意样本后,处置顺序很重要。我没急着删除文件,而是先做了一次影响面排查:把所有web目录和临时目录的文件按时间再筛一遍,确认只有一个可疑文件;把access.log里该样本路径的所有访问记录导出,发现从上传到今天共有4次访问,全部来自同一个来源IP;检查该IP的登录记录和错误日志,确认没有其他账号被爆破成功;最后检查计划任务和启动目录,没有发现新的持久化配置。
清理动作分三步走。第一步,在保存证据后删除恶意文件,并更新页面缓存与临时文件缓存。第二步,对上传接口增加文件内容校验,禁止脚本类型文件落地到图片目录,同时限制该来源IP的访问权限。第三步,把样本哈希、文件名规则、外联地址、来源IP整理成IOC记录,加入检测规则和文件基线表。处置完成后继续观察了三天,确认没有复发后关闭事件。
4. 新手高发:9个实战中容易踩的坑
有了这30个技巧,是不是就能顺畅完成一次应急分析了?未必。下面这9个坑是我在实际项目里亲眼见过、也亲自踩过的,按阶段分成三组,每一组都值得拿笔记住。
4.1 现场操作类:最容易破坏证据的三个误区
第一个误区:在受害服务器上直接执行可疑样本。这是所有坑里最严重的。一旦在真实环境里执行样本,攻击者可能立刻收到外联信号,要么触发二次破坏,要么销毁现场痕迹。正确做法是样本先哈希、复制、再移入隔离环境,一切动态验证都在断网的虚拟机里完成。
第二个误区:只查web目录,不查备份、缓存和日志轮转目录。攻击者比你想的更了解服务器目录结构,备份目录里的旧版本WebShell、日志目录里被混淆脚本写入的隐藏文件、缓存目录里生成的可执行文件,都容易成为漏网之鱼。排查范围至少覆盖web目录、临时目录、备份目录、日志目录四个位置。
第三个误区:删文件不留原始样本和证据。很多同学看到恶意文件,第一反应是恨不能马上删掉。但删除动作本身会破坏溯源链条。应该先计算哈希、保留副本、记录文件属性和发现时间,再执行清理。没有样本,后续的溯源分析、IOC提取和同类扫描都无从谈起。
4.2 判断分析类:容易误报漏报的三个盲区
第四个误区:看到eval就把文件判死刑。动态执行函数在很多正常框架和模板引擎里都有使用,看到eval/assert就直接定性,会造成大量误报。正确的做法是看上下文:eval的参数是固定字符串还是用户可控输入,函数调用周围有没有明显的混淆代码,整体逻辑是否符合业务场景。上下文决定性质,单点关键字只能作为线索。
第五个误区:忽略业务方对代码的确认。静态分析出现灰色地带时,最快的确定办法是找业务负责人确认这段代码是否存在、由谁编写。有些接口代码写得确实很绕,但也确实是业务逻辑的一部分。跳过了业务确认这个环节,很容易把一个老旧的第三方组件误判成WebShell,浪费排查时间的同时还影响业务信心。
第六个误区:只做静态不做动态,放过了混淆样本。静态特征能看出“可疑”,但识别不了加密压缩后的真实行为。有些混淆型样本静态扫描时几乎没有警告,运行后才还原出完整恶意逻辑。只要条件允许,高嫌疑样本都要进隔离环境做一次动态行为确认,彻底解码后再下结论。
4.3 收尾固化类:做一半留一半的三个问题
第七个误区:清理完成后不做复核。删除文件不等于处置完成。攻击者可能已经在同一目录或临时目录里备用了多份脚本,清理后做一次快速的全盘复核,按样本哈希再扫一遍全站文件,确认没有相同内容的副本,才是真正安全。我见过不少案例是在第一次清理后第三天又发现了同款样本,原因就是没有做哈希全盘扫描。
第八个误区:不关联日志就草草结束。如果只删文件不查日志,等于没有回答“攻击者从哪里进来的”这个问题。正确收尾至少要从日志里还原攻击入口、攻击时间、攻击次数、影响路径四个维度。缺少日志关联,下次攻击者换个文件名进来,你还是防不住。
第九个误区:没有将结果沉淀成IOC和基线。一次完整的应急分析应该输出:样本哈希、攻击IP、攻击路径、URL特征、外联域名、检测规则建议。这些信息录入威胁情报库并更新到监控规则里,才是这次排查真正的长期价值。否则每次碰到同类样本,都要从头分析一遍,效率低还容易漏。
5. 把技巧沉淀成工具:命令组合与体检脚本
技巧要变成日常可用的能力,最好落成一组命令和一个轻量脚本。下面放出的三个工具,可以直接用于日常巡检和应急初筛。
5.1 一条find命令圈定近期改动文件
快速定位最近几天被修改过的脚本文件,是WebShell分析最常用的起手式:
find /webroot -type f \( -name "*.php" -o -name "*.jsp" -o -name "*.asp" -o -name "*.aspx" \) -mtime -7 -printf "%TY-%Tm-%Td %TH:%TM %s %p\n" | sort -r | head -200注意几个细节:-mtime -7表示最近7天内修改过的文件;printf参数里的%s是文件大小,%p是完整路径;sort -r按时间倒序排列,让最新修改的文件排在最前面。你可以把这个命令设置成每周自动执行一次,输出结果作为疑似文件清单,再结合备份差异进一步筛选。如果服务器上文件量大,还可以追加 -size +10k 过滤掉太小的文件,减少干扰。
5.2 一条awk命令聚合异常请求
日志分析阶段,最常做的是按来源IP聚合请求数量,再结合时间线定位异常访问:
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20这行命令按访问频率统计出TOP 20来源IP,正常情况下访问量最高的IP往往是搜索引擎、监控系统或办公出口。如果看到一个陌生IP请求量异常高、集中在短时间内且大量访问上传接口,就需要进一步拉出该IP的完整请求列表。更精准的做法是把请求路径里包含upload、image、attach等敏感路径的行再过滤一次:
grep -E "upload|image|attach" /var/log/nginx/access.log | grep -E "\.php|\.jsp|\.asp"5.3 一个Python体检脚本做批量初筛
单文件人工分析没法覆盖大量文件,我日常会用一个小脚本做批量初筛。它的逻辑不复杂:遍历指定目录下的脚本文件,计算文件大小、修改时间、内容熵值,再命中几个高危关键字权重打分,最后输出一个CSV报告。下面是简化版实现:
import os import re import math import csv import sys from collections import Counter RISK_KEYWORDS = [ (r"eval\s*\(", 10), (r"assert\s*\(", 8), (r"base64_decode\s*\(", 8), (r"gzdecode\s*\(", 6), (r"str_rot13\s*\(", 4), (r"shell_exec\s*\(", 10), (r"system\s*\(", 8), (r"exec\s*\(", 6), (r"passthru\s*\(", 8), (r"\$_REQUEST|\$_POST|\$_GET|\$_COOKIE", 4), (r"(file_put_contents|fwrite)\s*\(", 6), (r"move_uploaded_file\s*\(", 4), ] def calc_entropy(data: bytes) -> float: if not data: return 0.0 length = len(data) counter = Counter(data) return -sum((count / length) * math.log2(count / length) for count in counter.values()) def scan_file(path: str): try: with open(path, "rb") as f: data = f.read() except Exception: return None score = 0 matched = [] try: text = data.decode("utf-8", errors="ignore") except Exception: text = "" for pattern, weight in RISK_KEYWORDS: if re.search(pattern, text, re.IGNORECASE): score += weight matched.append(pattern) return { "path": path, "size": len(data), "entropy": round(calc_entropy(data), 2), "score": score, "keywords": ";".join(matched[:5]), "mtime": os.path.getmtime(path), } def main(root_dir: str, output_csv: str): script_exts = (".php", ".jsp", ".asp", ".aspx") results = [] for base, _, files in os.walk(root_dir): for name in files: if name.lower().endswith(script_exts): item = scan_file(os.path.join(base, name)) if item and item["score"] > 0: results.append(item) results.sort(key=lambda x: x["score"], reverse=True) with open(output_csv, "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=["path", "size", "entropy", "score", "keywords", "mtime"]) writer.writeheader() writer.writerows(results) print(f"report saved: {output_csv}, matched {len(results)} files") if __name__ == "__main__": if len(sys.argv) != 3: print(f"usage: {sys.argv[0]} <root_dir> <output.csv>") sys.exit(1) main(sys.argv[1], sys.argv[2])这个脚本的定位是“初筛器”,不是“判定器”。分数高的文件不代表一定是WebShell,分数低也不代表绝对干净。它存在的意义是把上百个文件压缩成几行高优先级报告,让你把有限的时间花在最值得研究的对象上。脚本本身没有任何执行恶意代码的能力,可以放心跑在生产服务器上。
使用的时候,建议先跑在web根目录,再把数据库配置文件、日志目录、备份目录一起纳入。输出的CSV按分数倒序排列,优先处理score大于20的文件;score介于10到20之间的文件结合静态特征人工过一遍;score小于10的基本可以先放一放。如果某次扫描突然出现一个分数特别高的文件,先不要动它,直接进入“证据固定——哈希记录——隔离复现”的流程,用第3章的链路走完全程再下结论。
我个人的习惯是每个月初把这条巡检脚本跑一遍,输出结果和上个月的基线做对比。新增的高分文件永远是排查重点,因为攻击者一旦上传了WebShell,文件变动轨迹会自然暴露在时间线上。稳扎稳打地把这套流程用熟,比我在这里写再多技巧都更有用,毕竟WebShell分析这门手艺,最终是靠在实战中一次次复盘练出来的。