阅读视图

彻底放弃收费 SSL 证书

在一年前的文章中我就质疑过收费 SSL 证书的必要性。到了今年年底,公司的官网证书再次到期,我终于有机会将之前的设想付诸实现。

DigiCert Logo

今年我就没有直接在 DigiCert 官网询价了,不过按照它每年费用的涨幅,我估计今年的价格妥妥要破 2000 刀。说实话花 2000 美元买一个一年多就过期的 SSL 证书,对我们这种不涉及金融等高保密领域的小公司而言还是需要一点勇气的。

DV,OV 还是 EV?

如果你申请过 SSL 证书,一定知道这三者代表证书的三个验证级别,从低到高分别是:

  1. DV(Domain Validation,域名型证书)
  2. OV(Organization Validation,组织型证书)
  3. EV(Extended Validation,扩展验证证书)

DV 下证书最简单,只需要验证你有域名的控制权,一般几分钟就可以拿到证书。OV 麻烦一点,需要验证你的组织是否存在,我们公司之前就是使用的 OV 证书,需要提供 DUNS 码(现在可以直接提供境内的中文企业名称),等待人工审核。EV 我也没用过,据说是更加严格,可能需要人工介入来验证更多的材料。

从 OV 证书往上,由于验证了组织信息,所以你的企业名称也会在证书中体现出来,当然这也是逼格的体现,大公司可以追求这个,小公司实属没有必要,毕竟普通用户也不会有事没事去查看你的证书信息,这种事只有闲着没事干的程序员才干得出来。

百度的 SSL 证书

而 EV 证书,在各大浏览器厂商去掉了地址栏的企业绿标后,再去申请它实属钱多得没地方花了。对于我们公司,在去年我就意识到了连申请 OV 证书都是一种浪费,今年我直接瞄准了最便宜的 DV 证书。

有没有便宜的选择?

Sectigo Logo

有的,在我多方探索之后,发现 Sectigo 这个公司的收费证书非常便宜,当然你别去它的官网申请,一般 SSL 证书产业由于利润丰厚,会有强大的代理体系。直接在淘宝搜索 Sectigo 可以找到一堆国内的代理公司,大致的价格可以做到 DV 的泛域名证书一年 600 元人民币的价格,这相比之前上千刀的价格已经非常便宜了。

但是最终我还是没选择它,原因有二:

  1. 如果是个人用无所谓,但是公司的证书去找一家不知名的代理商来签发,心里还是有点无法接受。
  2. 既然已经便宜到 600 块了,咱干脆一步到位,一分钱都别花岂不是更好。

免费证书

对于免费证书,有一点需要注意,大名鼎鼎的 Let's Encrypt 已经将它的证书有效时间从 90 天进一步下降到了 45 天,这对我们来说是个小麻烦,因此后来选择了更友好的 ZeroSSL

在去年我还在用 certbot 这样的上古工具,今年已经有了国人开发的更好选择 acme.sh。相比 certbot 它不仅做到了申请证书的自动化,还把部署证书自动化也集成了进来,而且由于是使用了 Shell 脚本语言,不需要依赖任何运行时环境。我们公司的域名解析服务商是 dnspod,这在 acme.sh 里得到很好的支持,可以做到全自动 dns 验证下发证书。

当前我把证书自动申请和部署的工作流,放到了 Github Actions 里。做成了一个定时 Cron 任务,每周更新一次证书,这样证书的申请情况可以实时记录,如果失败了也有邮件提醒。而放到 Github 上还有个好处,我可以利用它完善的生态来补充 acme.sh 的能力,比如我们现在部分存储还放在又拍云上,而 acme.sh 并没有针对又拍云的官方部署脚本,又拍云本身也没有开放 SSL 证书的部署 API,但有好心的开发者已经开发出了使用模拟登录的方式来更新证书的 Actions,拿来使用可以极大减轻我们的工作量。

最终效果

几天前我就已经把公司的官方网站,CDN 等等各种域名证书切换到了免费证书上,目前几天使用下来没有任何不适,也没有遇到任何兼容性的问题。虽然证书信息中的公司名称不再显示,但相比这省下来的真金白银还是挺香的。

SegmentFault 的 SSL 证书

  •  

付费的 SSL 证书是不是智商税

最近要给公司网站的 SSL 证书续费了,以前一直用的是高大上的 DigiCert 泛域名 OV 证书服务。为啥要选这个服务商,理由很简单,当时看到 GitHub 用的就是它家的证书,所以就无脑为信仰充值了(按理说 DigiCert 应该给 GitHub 不少广告费才对)。年复一年,每次续费似乎已经成了习惯动作,直到最近我瞟了一眼账单。

IMG_9658.PNG

好嘛,从 2017 年的 1k 刀出头,直接来到了一千七百多刀,结合汇率的涨幅,这个使用成本直接高出了一倍。我寻摸着 SSL 证书这玩意也没啥技术和资源投入吧,咋涨这么狠呢?当然也不能怪 DigiCert 心太黑,其实 OV 证书本来价格就比较高,更何况还是泛域名。证书快到期的这段时间里,国内供应商的销售电话轰炸就没停过,我看了看他们的报价1,其实也没有更良心。

IMG_9680.PNG

本来我是决定吃下这个哑巴亏的,但是由于我是在周五提交的续费申请,OV 证书还有一个人工审核步骤。后面我的证书就一直卡在这一步下发不了,跟他们客服咨询后告诉我,中国区的代理都提前下班去搞团建了,最快也要等到下周一才能审核。好吧,外资公司的福利果然让人羡慕😂。

但是我的证书周日就要过期了,这是我不能忍受的。所以我决定用免费证书先顶上💡,之前一直听说免费证书可能有兼容性问题,但这次也顾不了那么多了,有总比挂了好。

使用免费的 SSL 证书

2023-12-15T08:51:06.png

免费证书当然是 Let's Encrypt 的最出名,直接 certbot 一把梭

certbot certonly --manual -d xxxxxx.com -d *.xxxxxx.com --agree-tos --manual-public-ip-logging-ok --preferred-challenges dns-01 --server https://acme-v02.api.letsencrypt.org/directory --register-unsafely-without-email

我直接选择了 DNS 验证,因为不用再去改服务器配置,缺点是三个月过期以后2还要去 DNS 那里重新配置一下,不过我就是临时用一下,这样反而是最方便的。命令输入后系统会提示你给 DNS 记录里加上两条 _acme-challenge.xxxxxx.comTXT 记录用于验证(因为同时申请了根域名和泛域名所以是两条)。添加完毕后确认验证通过以后,证书就已经下发到本地了,下发的证书存储在目录

/etc/letsencrypt/live/xxxxxx.com/

里面有这么几个文件:

cert.pem
chain.pem
fullchain.pem
privkey.pem

privkey.pem 是你的私钥,cert.pem 是你的证书,但是为了提高兼容性我建议你使用带有完整证书链的证书 fullchain.pemcertbot是基于 Python 的,你可能还需要搞定运行环境,现在貌似 acme.sh 更流行,这个直接在 shell 里就可以运行,兼容性更高。

不管怎么样现在我已经拿到了一个可用的 SSL 证书,大多数云厂商都提供了很方便的证书更新服务,把这个证书上传上去然后等待更新完毕即可。我在周五下班前,花了半个小时干完了上面说的所有事情。然后在所有用户都不知情的情况下,网站已经运行在了一个成本为 0 的安全证书上。

三天以后周一,DigiCert 通知我新的证书已经下发下来了,而在这期间我们没有接到任何的关于安全证书方面的用户反馈,网页,手机端,小程序都没有。于是我决定再等几天,看看会发生什么事情。一周下来,什么事情都没有发生🤷‍♂️。当然最后我还是把 DigiCert 的证书换上去了,毕竟钱已经花了🤦‍♂️。

最后的思考

其实免费证书已经广泛商用了,很多 CDN 服务商在你使用自己的域名时已经不要求必须上传证书了,它们会跟我一样为客户生成一个免费证书。而像 Cloudflare 这样的厂商,干脆就自己签发免费证书了。而我自己的个人网站,也一直在使用免费证书。

所以我就在想,我使用收费证书到底能带来什么?

兼容性?根据我的实践,没遇到这方面的问题。提高安全性?这就是骗骗小白的话术,实际上从加密手段上来讲这两者没啥区别。更方便?确实能省一点时间,免费证书大多过期时间在三个月,但收费证书的最长过期时间也从两年缩短到了一年多一点,而从我的实践来看,每次更新证书也花不了多少时间,所以你觉得省的这些时间值这么多钱么?提高置信度?这可能有点玄学了,以前如果你买更贵的 EV 证书,在浏览器上会显示一个 Green Bar,告诉用户这个网站属于哪个企业,但现在也已经取消了,现在你要查询证书里的验证信息,还得查看证书详情才看得到,我想没有人吃饱了撑的去靠这个来验证安全性。

唯一我能想到值得一点的就是稳定性,毕竟 Let's Encrypt 作为一个免费服务是没有任何商业保证的,它哪一天突然不干了不提供服务了也是有可能的,这就是你需要权衡的。

2023-12-15T10:15:58.png

但况且就算你要买 OV 证书也有更便宜的选择,另一大免费 SSL 证书提供商 ZeroSSL 也提供收费的 OV EV 证书选择,但是费用要便宜得多,需要的用户可以考虑一下。

所以对我来说,收费 SSL 证书提供的附加值对我价值有限(某些公司可能有特殊的安全认证要求,必须要收费证书),我的用户也不是金融用户,相反免费证书可以帮我省掉一比费用。所以接下来我会考虑这些做法:

  1. 逐步提高免费证书的使用比率,比如在一些次级域名使用,进一步验证兼容性。
  2. 考查更便宜的泛域名证书选择,三个月更新一次还是有点麻烦,而且我可能不会选择 OV 证书了,普通的就行。对于重要的服务还是需要有商业保证的证书,但是有便宜的选择最好。

收费证书不完全是智商税,有它的必要性,但是付出这么多费用如果你没有任何这方面的回报,那就是不划算的。


  1. 赛门铁克证书服务已经被 DigiCert 收购
  2. Let's Encrypt 免费 SSL 证书的最长有效期是三个月
  •  

努力要趁早

年轻就是最宝贵财富年轻就意味着无限可能性

一个二十岁的农民工,想成为程序员,有可能吗?有。一个二十岁的服务员,想成为摄影师,有可能吗?有。年轻就有尝试的资本,有学习的空间。年轻人犯错,社会是可以包容的,年轻人想要尝试,社会是愿意给机会的。

人到中年之后,就不一样了。没有人在意中年人的梦想,没有人会包容中年人的错误,也没有人会相信中年人还有什么可能性。一个四十岁的民工,说他想成为优秀的程序员,又有多少人会相信呢,又有多少人愿意帮助他呢。

努力要趁早。

  •  

努力要趁早

年轻就是最宝贵财富年轻就意味着无限可能性

一个二十岁的农民工,想成为程序员,有可能吗?有。一个二十岁的服务员,想成为摄影师,有可能吗?有。年轻就有尝试的资本,有学习的空间。年轻人犯错,社会是可以包容的,年轻人想要尝试,社会是愿意给机会的。

人到中年之后,就不一样了。没有人在意中年人的梦想,没有人会包容中年人的错误,也没有人会相信中年人还有什么可能性。一个四十岁的民工,说他想成为优秀的程序员,又有多少人会相信呢,又有多少人愿意帮助他呢。

努力要趁早。

  •  

基于 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 资源耗尽了,这的确不失为一个低成本的攻击方式。

爬虫占比:

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

今天的访问量:

百度的统计:

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

 

  •