基于 wp-cron.php 的拒绝服务攻击

这几天不知道是发生什么事了,说是不知道什么事情,但是大概率是被打了。只是这次打的挺高级的,外层的 eo 貌似也没什么反应。只是那个访问量通过 umami 看,直接爆炸了。

平常几百的访问量,昨天的时候,结果到了 2000 多,当然这不是最奇怪的,奇怪的是服务器过了会儿卡死了。之前都是因为请求太多 php-fpm 耗尽 cpu 资源卡死了,这次以为还是同样的问题。然而,并不是,发现 mysql 把 cpu 跑满了,查看日志的时候发现大量的 wp-cron.php 的请求,这尼玛,请求直接透传过来了。

另外还有一大堆 bot 的请求,包括 bing 以及一些乱起八糟的爬虫遍历。

最开始没想到什么好办法,简单粗暴的把 wp-cron.php 改名了,暂时解决了这个问题。

不过这个方法的确是高明,带着参数透传过来,wp 就是疯狂的执行,一条没执行完就到了下一条。然而,对于这种事情直接改名的确是可以解决办法,不过后来想了一下还是直接从 eo 下手吧。

尽管 eo 防住了 22 万次的攻击,但是,这些透传的请求,直接让 mysql 耗尽了 cpu 资源,也是个不错的办法,甚至请求频率都不用太高。流量到了 144g,这也不知道是哪个哥们又闲的蛋疼了,如果真的蛋疼来找姐姐啊,姐姐帮你治疗,直接给你割下来,塞你自己嘴里!

昨天晚上发现这个情况的时候,本来是想去处理下的,结果对象在用电脑,自己又不想去开笔记本,就用手机处理了一下,简单的改下了文件名。

今天早上才处理了一下,加到了 eo 的访问规则里:

尽管如此,还是对这几天的访问记录比较好奇,想看看请求了多少次。去拉 nginx 日志的时候发现文件已经 1.5G 了。直接截取这几天的记录,用 goaccess 跑了一下,但是比较奇怪的是这个 wp-cron.php 的请求竟然没有。

暂时放弃 goaccess 直接使用 ngxtop 进行数据分析:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
使用ngxtop分析Nginx日志中的POST请求
提供交互式菜单和多种分析选项
"""

import subprocess
import sys
import os
from pathlib import Path


def run_ngxtop(cmd_args):
    """运行ngxtop命令"""
    venv_python = Path(__file__).parent / "venv" / "bin" / "python"
    ngxtop_script = Path(__file__).parent / "venv" / "bin" / "ngxtop"
    
    if not ngxtop_script.exists():
        print("错误: ngxtop未安装,请先运行: source venv/bin/activate && pip install ngxtop")
        sys.exit(1)
    
    try:
        result = subprocess.run(
            [str(ngxtop_script)] + cmd_args,
            capture_output=True,
            text=True,
            check=False
        )
        print(result.stdout)
        if result.stderr and "error" in result.stderr.lower():
            print(result.stderr, file=sys.stderr)
        return result.returncode == 0
    except Exception as e:
        print(f"错误: {e}", file=sys.stderr)
        return False


def show_menu():
    """显示菜单"""
    print("\n" + "="*60)
    print("Nginx日志POST请求分析 - ngxtop工具")
    print("="*60)
    print("1. POST请求总览")
    print("2. 按URL统计POST请求 (Top 20)")
    print("3. 按IP统计POST请求 (Top 20)")
    print("4. 按状态码统计POST请求")
    print("5. POST请求中状态码为404的URL")
    print("6. POST请求中状态码为200的URL")
    print("7. 可疑POST请求 (xmlrpc, wp-login等)")
    print("8. POST请求详情示例")
    print("9. 自定义查询")
    print("0. 退出")
    print("="*60)


def analyze_post_requests(log_file):
    """分析POST请求"""
    if not os.path.exists(log_file):
        print(f"错误: 日志文件 {log_file} 不存在")
        return
    
    base_args = ["-l", log_file, "--no-follow", "-i", 'request.startswith("POST")']
    
    while True:
        show_menu()
        choice = input("\n请选择分析选项 (0-9): ").strip()
        
        if choice == "0":
            print("退出分析")
            break
        elif choice == "1":
            print("\n【POST请求总览】")
            print("-" * 60)
            run_ngxtop(base_args + ["--limit", "0"])
        elif choice == "2":
            print("\n【按URL统计POST请求 (Top 20)】")
            print("-" * 60)
            run_ngxtop(base_args + ["--group-by", "request_path", "--limit", "20"])
        elif choice == "3":
            print("\n【按IP统计POST请求 (Top 20)】")
            print("-" * 60)
            run_ngxtop(base_args + ["--group-by", "remote_addr", "--limit", "20"])
        elif choice == "4":
            print("\n【按状态码统计POST请求】")
            print("-" * 60)
            run_ngxtop(base_args + ["--group-by", "status", "--limit", "0"])
        elif choice == "5":
            print("\n【POST请求中状态码为404的URL (Top 10)】")
            print("-" * 60)
            run_ngxtop(["-l", log_file, "--no-follow", 
                       "-i", 'request.startswith("POST") and status == 404',
                       "--group-by", "request_path", "--limit", "10"])
        elif choice == "6":
            print("\n【POST请求中状态码为200的URL (Top 10)】")
            print("-" * 60)
            run_ngxtop(["-l", log_file, "--no-follow",
                       "-i", 'request.startswith("POST") and status == 200',
                       "--group-by", "request_path", "--limit", "10"])
        elif choice == "7":
            print("\n【可疑POST请求统计】")
            print("-" * 60)
            run_ngxtop(["-l", log_file, "--no-follow",
                       "-i", 'request.startswith("POST") and (request_path == "/xmlrpc.php" or request_path == "/wp-login.php" or request_path.startswith("/wp-admin"))',
                       "--group-by", "request_path", "--limit", "0"])
        elif choice == "8":
            print("\n【POST请求详情示例 (前10条)】")
            print("-" * 60)
            run_ngxtop(base_args + ["print", "remote_addr", "time_local", "request", "status", "bytes_sent", "--limit", "10"])
        elif choice == "9":
            print("\n【自定义查询】")
            print("-" * 60)
            print("示例查询:")
            print("  - 查看特定URL: ngxtop -l <file> -i 'request.startswith(\"POST\") and request_path == \"/wp-cron.php\"'")
            print("  - 查看特定IP: ngxtop -l <file> -i 'request.startswith(\"POST\") and remote_addr == \"114.66.247.160\"'")
            print("  - 查看错误请求: ngxtop -l <file> -i 'request.startswith(\"POST\") and status >= 400'")
            print("\n请输入自定义ngxtop命令参数 (用空格分隔):")
            custom_args = input("> ").strip().split()
            if custom_args:
                run_ngxtop(["-l", log_file, "--no-follow"] + custom_args)
        else:
            print("无效的选择,请重试")
        
        input("\n按回车键继续...")


def main():
    """主函数"""
    if len(sys.argv) < 2:
        # 查找默认日志文件
        log_files = list(Path(".").glob("*.txt"))
        if log_files:
            default_log = str(log_files[0])
            print(f"未指定日志文件,使用默认: {default_log}")
            log_file = default_log
        else:
            print("用法: python analyze_with_ngxtop.py <日志文件路径>")
            print("示例: python analyze_with_ngxtop.py 11-08_org.txt")
            sys.exit(1)
    else:
        log_file = sys.argv[1]
    
    analyze_post_requests(log_file)


if __name__ == "__main__":
    main()

运行命令:

python3 analyze_with_ngxtop.py 11-08_org.txt

分析结果:

【按URL统计POST请求 (Top 20)】
------------------------------------------------------------

running for 7 seconds, 23670 records processed: 3508.50 req/sec

Summary:
|   count |   avg_bytes_sent |   2xx |   3xx |   4xx |   5xx |
|---------+------------------+-------+-------+-------+-------|
|   23670 |         2381.924 |  4678 |    21 | 18574 |   397 |

Detailed:
| request_path                    |   count |   avg_bytes_sent |   2xx |   3xx |   4xx |   5xx |
|---------------------------------+---------+------------------+-------+-------+-------+-------|
| /wp-cron.php                    |   16454 |          731.309 |  3413 |     0 | 13034 |     7 |
| /xmlrpc.php                     |    3102 |          416.754 |   248 |     0 |  2853 |     1 |
| /wp-login.php                   |    2519 |        15204.250 |     0 |     0 |  2519 |     0 |
| /wp-admin/admin-ajax.php        |    1017 |          542.043 |   971 |     0 |    44 |     2 |
| /wp-comments-post.php           |     401 |         2551.357 |     0 |    14 |     0 |   387 |
| /xmrpc.php                      |      41 |          915.000 |     0 |     0 |    41 |     0 |
| /tslogin                        |      20 |        30543.150 |    16 |     4 |     0 |     0 |
| /alfacgiapi/perl.alfa           |      11 |        51292.455 |     0 |     0 |    11 |     0 |
| /ALFA_DATA/alfacgiapi/perl.alfa |      11 |        51323.636 |     0 |     0 |    11 |     0 |
| /index.php                      |      10 |        34570.900 |    10 |     0 |     0 |     0 |
| /wp-plain.php                   |       9 |         1331.000 |     0 |     0 |     9 |     0 |
| /                               |       9 |        28609.556 |     7 |     0 |     2 |     0 |
|                                 |       8 |          415.000 |     8 |     0 |     0 |     0 |
| /flow.php                       |       7 |          915.000 |     0 |     0 |     7 |     0 |
| /wp-admin/async-upload.php      |       5 |          736.000 |     5 |     0 |     0 |     0 |
| /php-cgi/php-cgi.exe            |       4 |        33911.500 |     0 |     0 |     4 |     0 |
| /graphql                        |       4 |        33469.750 |     0 |     0 |     4 |     0 |
| /wp-admin/post.php              |       3 |            5.000 |     0 |     3 |     0 |     0 |
| /member/success.aspx            |       2 |        16784.500 |     0 |     0 |     2 |     0 |
| /e/aspx/upload.aspx             |       2 |        16628.500 |     0 |     0 |     2 |     0 |

【按IP统计POST请求 (Top 20)】
------------------------------------------------------------
running for 7 seconds, 23670 records processed: 3586.40 req/sec

Summary:
|   count |   avg_bytes_sent |   2xx |   3xx |   4xx |   5xx |
|---------+------------------+-------+-------+-------+-------|
|   23670 |         2381.924 |  4678 |    21 | 18574 |   397 |

Detailed:
| remote_addr    |   count |   avg_bytes_sent |   2xx |   3xx |   4xx |   5xx |
|----------------+---------+------------------+-------+-------+-------+-------|
| 221.204.26.162 |    4407 |          696.960 |  1125 |     1 |  3279 |     2 |
| 221.204.26.233 |    4291 |          738.947 |  1054 |     1 |  3235 |     1 |
| 101.71.101.44  |    3168 |          686.088 |   911 |     4 |  2252 |     1 |
| 101.71.101.106 |    2564 |          868.693 |   183 |     2 |  2379 |     0 |
| 43.174.53.229  |    2094 |         7795.611 |     6 |     0 |  2088 |     0 |
| 43.174.53.236  |    2090 |         7811.496 |     4 |     0 |  2086 |     0 |
| 114.66.247.160 |    1810 |          743.818 |   520 |     1 |  1288 |     1 |
| 114.66.246.149 |    1123 |          507.375 |   538 |     1 |   582 |     2 |
| 101.71.105.47  |     104 |          574.404 |    57 |     0 |    47 |     0 |
| 43.175.19.192  |      29 |         5430.241 |     1 |     0 |    15 |    13 |
| 43.175.17.169  |      26 |         2520.500 |     0 |     0 |     8 |    18 |
| 43.175.18.81   |      25 |         2049.720 |     1 |     0 |     6 |    18 |
| 43.175.18.253  |      25 |         1835.800 |     1 |     0 |     8 |    16 |
| 43.175.18.195  |      25 |         5997.720 |     0 |     0 |     8 |    17 |
| 43.175.18.137  |      25 |         2101.840 |     1 |     0 |     5 |    19 |
| 43.175.17.87   |      24 |         2210.208 |     0 |     0 |     5 |    19 |
| 43.175.17.47   |      23 |         7488.043 |     0 |     0 |     9 |    14 |
| 43.175.18.51   |      22 |         3213.455 |     0 |     0 |     8 |    14 |
| 43.175.17.205  |      21 |         7011.381 |     1 |     0 |    10 |    10 |
| 43.175.169.137 |      16 |         1386.562 |     3 |     0 |     6 |     7 |

而至于这些 IP 地址,多数都是国内的,这个倒是也在意料之内,毕竟国外的被拦截的概率会更高一些。

然而,goaccess 就无法分析吗?也可以,添加忽略请求参数的参数就可以了:

#!/bin/bash
# 使用goaccess的--no-query-string参数移除查询参数
# 不需要修改日志文件!

LOG_FILE="${1:-11-08_org.txt}"
OUTPUT_FILE="${2:-goaccess_no_query_report.html}"

if [ ! -f "$LOG_FILE" ]; then
    echo "错误: 日志文件 $LOG_FILE 不存在"
    exit 1
fi

echo "=========================================="
echo "使用GoAccess分析(移除查询参数)"
echo "=========================================="
echo "日志文件: $LOG_FILE"
echo "输出文件: $OUTPUT_FILE"
echo ""
echo "使用参数: --no-query-string (或 -q)"
echo "这将移除URL中的查询参数,只保留路径"
echo ""

# 使用--no-query-string参数
goaccess "$LOG_FILE" \
  --log-format='%h %^[%d:%t %^] "%r" %s %b "%R" "%u"' \
  --date-format='%d/%m/%Y' \
  --time-format='%H:%M:%S' \
  --no-query-string \
  -o "$OUTPUT_FILE"

if [ $? -eq 0 ]; then
    echo ""
    echo "✅ 报告生成成功: $OUTPUT_FILE"
    echo ""
    echo "现在wp-cron.php应该能正确合并统计了!"
    echo ""
    echo "在浏览器中打开报告查看:"
    echo "  open $OUTPUT_FILE    # macOS"
    echo "  xdg-open $OUTPUT_FILE  # Linux"
    echo ""
    echo "在交互界面中使用:"
    echo "  goaccess $LOG_FILE \\"
    echo "    --log-format='%h %^[%d:%t %^] \"%r\" %s %b \"%R\" \"%u\"' \\"
    echo "    --date-format='%d/%m/%Y' \\"
    echo "    --time-format='%H:%M:%S' \\"
    echo "    --no-query-string"
else
    echo "❌ 报告生成失败"
    exit 1
fi

主要就是:–no-query-string参数。

实际效果:

文件没改名之前:

文件改名之后:

虽然加起来之后不到两万次,但是却让 mysql 把 cpu 资源耗尽了,这的确不失为一个低成本的攻击方式。

爬虫占比:

这几天也不知道爬虫是发什么疯

今天的访问量:

百度的统计:

咱就是说,有点时间干点正事不好吗?真是闲的。

 


☆版权☆

* 网站名称:obaby@mars
* 网址:https://da.bi/
* 个性:https://oba.by/
* 本文标题: 《基于 wp-cron.php 的拒绝服务攻击》
* 本文链接:https://www.da.bi/2025/11/21970
* 短链接:https://oba.by/?p=21970
* 转载文章请标明文章来源,原文标题以及原文链接。请遵从 《署名-非商业性使用-相同方式共享 2.5 中国大陆 (CC BY-NC-SA 2.5 CN) 》许可协议。


猜你喜欢:

11 comments

  1. Level 5
    Google Chrome 142 Google Chrome 142 Windows 11 Windows 11 cn中国 中国联通

    感觉被攻击都是优秀的站点。哈哈。
    不错。哪天被攻击了可以试试。

  2.  Level 3
    Google Chrome 142 Google Chrome 142 Windows 11 Windows 11 cn中国–上海–上海 电信

    WP最容易被攻击吧,我用小众程序就很少遇到这种情况。

  3.  Level 6
    WebView 4 WebView 4 Android 15 Android 15 cn中国 中国电信

    这种统计很好玩,我的网站好久没人打过了,可能对原创个人程序别人是不会打的,打之前扫描不到典型文件

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注