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


猜你喜欢:

37 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中国 中国电信

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

  4.  Level 6
    Google Chrome 142 Google Chrome 142 Mac OS X 10.15 Mac OS X 10.15 jp日本–东京都 Digital_VM

    这种应该不算攻击,就是有爬虫爬到了程序,然后后面的大量的爬虫跟着一起要攻克这几个文件吧,应该不是主动打的。 在服务器的防火墙里面增加基础的限制,访问这个超过几文件的爬虫ip直接ban掉就行。 这些目录和文件每天很多爬虫再爬,只要是外部链接多的wp老博客,几乎都能检查到,但是更新后台版本这些其实无所谓的,wp新版都修复这些,纯纯针对不升级的老系统攻击多。

      1.  Level 6
        Google Chrome 142 Google Chrome 142 Mac OS X 10.15 Mac OS X 10.15 jp日本–东京都 Digital_VM

        我这个措辞 可能不太准确,应该是漏洞爬虫。主要是伪装后爬各种漏洞文件,然后黑进系统,应该在waf或者cdn层面就能直接过滤掉,写个过滤规则就行。爬你多,可能是你的外链多,摸着链接就来了,各处评论区都能看到你,你的外部链链至少是我的一百倍。。。。。

        1. 公主 Queen 
          Google Chrome 134 Google Chrome 134 Mac OS X 10.15 Mac OS X 10.15 cn中国–山东–青岛 移动

          恶意 ip
          这些 ip 其实感觉都不像家宽,前两个是同一个机房的,后面的是有个什么安全云 cdn,还有个联通的。但是这这些 ip 基本都是联通的。
          这种扫描方式,如果是家宽或者境外的可以理解,但是国内的 ip 用来干这些事情有些傻逼了。

          1.  Level 6
            Google Chrome 107 Google Chrome 107 Android 15 Android 15 cn中国 中国联通

            那就很离谱了,机房ip干这个,难道机房被漏洞拿下?不然不可能这样大规模的来流量干这种吃力不讨好事,这个多少有点离谱。有没有可能是哪家训练的ai爬虫被滥用了😳理解不了。

            1. 公主 Queen 
              Google Chrome 134 Google Chrome 134 Mac OS X 10.15 Mac OS X 10.15 cn中国–山东–青岛 移动

              以 221.204.26.162 这个ip 为例,这个东西,最开始感觉还是正常的,不外乎就是各种下载图片,包括 09 年的图片都在下载,但是,最后就变成了疯狂的 post,去掉了其他的所有的请求。
              如果说是扫描器或者 ai。通常不会伪装的这么彻底,请求没有系统信息,没有 ua 信息。没有任何请求信息,出了 post。并且请求频率比较稳定,怀疑是存在学习能力,在探测那种攻击效果更好,逐步停止掉那些无效攻击和请求,最后只保留这个 post,降低了请求量,但是数据库会消耗掉所有的 cpu 资源,这种攻击是效率最高的。
              post

              1.  Level 6
                Google Chrome 107 Google Chrome 107 Android 15 Android 15 cn中国 中国联通

                确实这种效率高,因为见过一次类似的日志。Cpu会拉爆,量大了服务器网站就会被干瘫痪,mysql会被查到暂停,这种简单粗暴的,就是纯攻击了,和爬虫无关了。你配置足够大,小点服务器早瘫了。

                1. 公主 Queen 
                  Google Chrome 140 Google Chrome 140 Android 10 Android 10 cn中国–山东–青岛 联通

                  我刚翻了下日志 发现自己可能错了 由于前端套了cdn所以nginx记录的这个ip是错的,大概率是eo的节点ip。大意了,日志记录没改,现在没法溯源了。
                  至于源ip大概率还是比较分散的。

            2. Level 2
              Google Chrome 142 Google Chrome 142 Ubuntu 24.04 Ubuntu 24.04 cn中国–北京–北京 阿里云

              国内攻击IP很多都是机房啦,通过脚本批量获取试用机,月抛机进行攻击攻击成本几乎趋近于0

              1. 公主 Queen 
                Google Chrome 140 Google Chrome 140 Android 10 Android 10 cn中国–山东–青岛 联通

                嗯嗯 也有可能 不过 现在我发现自己可能错了 ngjnx记录没改,记录的可能是cdn节点ip😂

        2. 公主 Queen 
          Google Chrome 134 Google Chrome 134 Mac OS X 10.15 Mac OS X 10.15 cn中国–山东–青岛 移动

          ddos
          从昨天到现在已经 39 万次了,如果是普通扫描器早就停了。

  5. Level 2
    Google Chrome 142 Google Chrome 142 Windows 10 Windows 10 cn中国–香港 CDN520加速节点(BGP)数据中心

    我的404响应记录里,95%都是wp相关的请求,还好我没用wp

    1. 公主 Queen 
      Google Chrome 134 Google Chrome 134 Mac OS X 10.15 Mac OS X 10.15 cn中国–山东–青岛 移动

      那这就是被扫描器扫啦,沙雕脚本小子太多了,防不胜防

  6. Level 6
    Google Chrome 109 Google Chrome 109 Windows 10 Windows 10 cn中国–上海–上海 腾讯云

    所以灵妹妹好久下定决心换掉WP?

  7. Level 1
    Google Chrome 141 Google Chrome 141 Windows 11 Windows 11 cn中国 中国移动

    哈哈,你这个比较少见,现在面板都是自带防火墙可以限制访问频率,所以反而小白基本遇不到单ip缓存穿透的情况 laugh 说起来我前天也遇到一个神人,挑了个凌晨给我eo刷流量,就一个3m的gif给我刷了33G…

  8. Level 4
    Google Chrome 120 Google Chrome 120 Windows 10 Windows 10 cn中国–广西–北海 电信

    有个地区一直在慢慢刷我流量直接禁了,都禁了半年了今天放出来,一看后台还在慢慢刷流量,这你不得不佩服,这种干其他的干啥干不成

  9. Level 6
    Google Chrome 142 Google Chrome 142 Windows 10 Windows 10 cn中国–云南–丽江 电信

    我就一个人自己记录的wp博客都被国外的攻击了很多次,闲得很他们

发表回复

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