|
一个PHP文件,里面写着 <?php eval($_POST['cmd']); ?>,上传到服务器上,就成了webshell。就这么简单。很多人第一次接触这个概念会觉得——这也太容易了吧?没错,就是容易。攻击者拿到一个上传点,传一个php文件上去,接下来整台服务器的控制权就在他手里了。 我见过一个案例。某电商网站的商品图片上传功能没有做文件类型校验,只在前端用JS限制了后缀。攻击者抓包改一下,把 shell.php 传上去,路径直接回显在响应里。访问一下,连上了。整个过程不超过两分钟。服务器上跑着MySQL,配置文件里写着数据库密码,/etc/passwd 随便读,phpinfo() 一执行,disable_functions 列表一览无余。这就是webshell的威力——它不是一个漏洞,它是漏洞的放大器。 PHP为什么成了重灾区 PHP的设计哲学是“让开发者快速构建Web应用”,大量功能直接暴露给语言层面。eval()、system()、exec()、passthru()、shell_exec()、popen(),这些函数在PHP里用起来毫无门槛。对比一下Java,你想执行系统命令得走 Runtime.getRuntime().exec(),至少还有个类型系统的心理负担。PHP这边,一行代码就能调用 /bin/bash。 更麻烦的是PHP的部署方式。大量老站点跑在共享主机上,一个虚拟主机目录下几百个站点共用同一个PHP进程池。你拿下了一个站点的webshell,open_basedir 没配好的话,隔壁站点的文件也能翻。我见过一台服务器上跑了三十多个站,其中一个WordPress插件有漏洞被传了shell,结果三十多个站的数据库配置全部泄露。 免杀与隐藏 直接写 eval($_POST['cmd']) 的webshell活不过三天。安全软件的特征库早就把这种明文模式收录了。于是有了各种变形: 字符串拼接——$a='ev';$b='al';$c=$a.$b;$c($_POST['x']); Base64编码——eval(base64_decode($_POST['x'])); 利用回调函数——array_map('assert', $_POST); 还有更隐蔽的,把webshell藏在图片的EXIF信息里,用 include 配合 php://filter 协议去包含。或者干脆写一个看起来完全正常的业务逻辑文件,只在特定参数下才触发恶意代码。我见过一个webshell伪装成 wp-config.php 的备份文件,文件名是 wp-config.php.bak,里面混着正常的数据库配置和一段经过混淆的payload。管理员扫目录的时候看到 .bak 后缀,大概率会忽略。 检测这件事,比想象中难 静态特征匹配靠不住。攻击者改一个变量名、加一层编码,特征就失效了。动态检测呢?监控 eval 调用?PHP的 eval 是语言结构不是函数,disable_functions 都禁不掉它。用 Suhosin 补丁?那玩意儿早就不维护了。 实际运维中比较有效的办法是文件完整性监控。对Web目录做哈希基线,发现新增或修改的PHP文件就告警。但这也只能防“写文件”型的webshell,内存马呢?通过 auto_prepend_file 或者 .user.ini 加载的webshell,磁盘上可能根本找不到恶意文件。 还有日志。访问日志里一个POST请求,URL是正常的 /images/logo.php,但POST body里是 cmd=whoami。如果你只看URL,什么都发现不了。得看请求体,得看响应大小异常,得看请求频率。一个正常用户不会每分钟往同一个PHP文件POST二十次不同的系统命令。 写webshell的人在想什么 我翻过一些被入侵站点的日志。攻击者上传webshell之后的第一件事通常是 whoami 和 uname -a,确认权限和系统版本。然后是 ls /home,看看有没有其他站点。接着翻数据库配置文件。最后才是留后门——可能是另一个webshell,可能是一个cron任务,可能是一个SSH公钥。 整个过程冷静、机械、有章法。这不是什么高深的技术对抗,更像是一个熟练工在流水线上作业。PHP webshell之所以长期存在,不是因为它多精妙,而是因为Web应用的攻击面实在太大了。一个文件上传、一个文件包含、一个反序列化,任何一个入口没堵住,后面就是连锁反应。 你问怎么防?别让用户上传PHP文件,别用 eval,别把 phpinfo() 留在生产环境,open_basedir 和 disable_functions 配好,日志看紧一点。都是老生常谈。但老生常谈之所以成为老生常谈,就是因为总有人不做。 |