博客 (249)

问题复现

使用 .NET Framework 开发的网站项目,用 QQ 浏览器访问无法登录成功,用其它浏览器(如 Edge)没有问题。

只有访问 https 地址时出现问题。


原因

在 HTTPS 协议下,现代浏览器(特别是 QQ 浏览器)会强制执行安全策略。根据规范,当 Cookie 设置了 SameSite=None 时,必须同时设置 Secure 属性,否则浏览器会静默拒绝(丢弃)该 Cookie。


解决方案

方法一:如果您是通过 web.config 配置的,请确保 <system.web> 节点下的 <httpCookies> 设置正确,并且您的 .NET Framework 版本支持这些属性(4.7.2+)。

<system.web>
    <httpCookies sameSite="None" requireSSL="true" />
    <sessionState ... />
</system.web>

方法二:在 Global.asax 文件的 Application_PostAuthenticateRequest 或 Application_EndRequest 事件中,强制为 Cookie 添加 Secure 属性。

protected void Application_PostAuthenticateRequest(object sender, EventArgs e)
{
    // 仅在 HTTPS 环境下处理
    if (Request.IsSecureConnection)
    {
        HttpCookie sessionCookie = Response.Cookies["ASP.NET_SessionId"];
        if (sessionCookie != null)
        {
            // 强制设置 SameSite=None 和 Secure
            sessionCookie.SameSite = SameSiteMode.None;
            sessionCookie.Secure = true; 
            Response.Cookies.Set(sessionCookie);
        }
    }
}


检验

确认 Set-Cookie 的值变为:

ASP.NET_SessionId=...; path=/; HttpOnly; SameSite=None; Secure


xoyozo 5 天前
39

报道称 NGINX 被曝一组高危漏洞,已潜伏约 18 年,威胁全球约三分之一的网络服务器。

本次曝光的漏洞如下:

CVE-2026-42945 - (9.2 Critical)

CVE-2026-42946 - (8.3 High)

CVE-2026-40701 - (6.3 Medium)

CVE-2026-42934 - (6.3 Medium)

攻击者无需登录认证,只需发送一条特制 HTTP 请求,就能让 NGINX 工作进程崩溃;在合适条件下,还可能拿到服务器远程代码执行权限。


修复方案一

NGINX 升级到 1.31.0 或 1.30.1


修复方案二

若你使用的是 OpenResty 或宝塔面板,没有更新的 nginx 版本选择,那么手动修复此漏洞。

将 rewrite 规则中的未命名正则捕获改成命名捕获。

示例:

原来的写法:

rewrite ^(.*)$ /index.php?s=$1 last;

修改为:

rewrite ^(?<a>.*)$ /index.php?s=$a last;


参考文献:

  1. NGINX Rift: Achieving NGINX Remote Code Execution via an 18-Year-Old Vulnerability

  2. NGINX 曝 9.2 分高危漏洞:潜伏 18 年,威胁全球约 1/3 服务器

xoyozo 半个月前
319

以下是 Spectre.Console 的核心功能示例,涵盖最常用的输出和交互场景:


1. 基础富文本输出

image.png

using Spectre.Console;

// 使用 Markup 语法(类似 BBCode)
AnsiConsole.Markup("[bold green]成功![/] 文件已保存。\n");
AnsiConsole.Markup("[red]错误:[/] 无法连接到服务器。\n");

// 混合样式
AnsiConsole.Markup("[underline blue]https://example.com[/]\n");

// 自动换行写
AnsiConsole.Write(new Panel("[yellow]警告[/] 磁盘空间不足")
    .Header("系统通知")
    .Border(BoxBorder.Rounded));


2. 表格

image.png

var table = new Table();
table.AddColumn("[u]ID[/]");
table.AddColumn(new TableColumn("[u]名称[/]").Centered());
table.AddColumn("[u]状态[/]");

table.AddRow("1", "订单服务", "[green]运行中[/]");
table.AddRow("2", "支付网关", "[red]离线[/]");
table.AddRow("3", "消息队列", "[yellow]警告[/]");

AnsiConsole.Write(table);


3. 进度条 / 状态指示

image.pngimage.png

// 带进度条的循环任务
await AnsiConsole.Progress()
    .StartAsync(async ctx =>
    {
        var task1 = ctx.AddTask("[green]下载文件[/]", maxValue: 100);
        var task2 = ctx.AddTask("[green]处理数据[/]", maxValue: 100);

        while (!ctx.IsFinished)
        {
            task1.Increment(1.5);
            task2.Increment(0.8);
            await Task.Delay(50);
        }
    });

// 不确定时长的旋转状态
await AnsiConsole.Status()
    .StartAsync("正在连接...", async ctx =>
    {
        await Task.Delay(3000); // 模拟工作
        AnsiConsole.MarkupLine("[green]连接成功![/]");
    });


4. 交互式提示

image.png

// 确认
if (AnsiConsole.Confirm("是否继续安装?"))
{
    // 执行安装
}

// 文本输入(带验证)
var name = AnsiConsole.Prompt(
    new TextPrompt<string>("请输入用户名:")
        .ValidationErrorMessage("[red]用户名不能为空[/]")
        .Validate(input => !string.IsNullOrWhiteSpace(input)));

// 选择列表
var fruit = AnsiConsole.Prompt(
    new SelectionPrompt<string>()
        .Title("请选择最喜欢的水果")
        .AddChoices(new[] { "苹果", "香蕉", "橙子", "葡萄" }));

AnsiConsole.MarkupLine($"你选择了:[green]{fruit}[/]");

// 多选
var colors = AnsiConsole.Prompt(
    new MultiSelectionPrompt<string>()
        .Title("请选择颜色")
        .AddChoices(new[] { "红色", "绿色", "蓝色", "黄色" }));


5. 树形结构

image.png

var root = new Tree("[yellow]项目结构[/]");
var src = root.AddNode("src");
src.AddNode("Program.cs");
src.AddNode("Services");
src.AddNode("Models");

var tests = root.AddNode("tests");
tests.AddNode("UnitTests");

AnsiConsole.Write(root);


6. 布局 / 网格

image.png

var layout = new Layout("Root")
    .SplitColumns(
        new Layout("Left").Size(30),
        new Layout("Right")
            .SplitRows(
                new Layout("Top"),
                new Layout("Bottom")));

layout["Left"].Update(new Panel("导航栏"));
layout["Top"].Update(new Panel("主内容区"));
layout["Bottom"].Update(new Panel("日志输出"));

AnsiConsole.Write(layout);


7. 实时更新

image.png

var table = new Table().AddColumn("Time").AddColumn("Message");
table.AddRow("10:00", "系统启动");

await AnsiConsole.Live(table)
    .StartAsync(async ctx =>
    {
        for (int i = 1; i <= 5; i++)
        {
            await Task.Delay(1000);
            table.AddRow($"10:0{i}", $"事件 {i}");
            ctx.Refresh(); // 刷新显示
        }
    });


8. 带样式的异常显示

image.png

try
{
    throw new InvalidOperationException("操作失败");
}
catch (Exception ex)
{
    AnsiConsole.WriteException(ex, ExceptionFormats.ShortenPaths);
}


9. 日历

image.png

var calendar = new Calendar(2026, 4);
calendar.AddCalendarEvent(2026, 4, 22); // 标记日期
calendar.HighlightStyle(Style.Parse("yellow bold"));
AnsiConsole.Write(calendar);


10. 条形图 / 柱状图

image.png

AnsiConsole.Write(new BarChart()
    .Width(60)
    .Label("[green bold]项目进度[/]")
    .CenterLabel()
    .AddItem("后端", 80, Color.Green)
    .AddItem("前端", 65, Color.Blue)
    .AddItem("测试", 40, Color.Red));


11. 分解图

image.png

AnsiConsole.Write(new BreakdownChart()
    .Width(60)
    .AddItem("CPU", 45, Color.Red)
    .AddItem("内存", 30, Color.Blue)
    .AddItem("磁盘", 25, Color.Green));


12. 规则线

image.png

AnsiConsole.Write(new Rule("[red]警告区域[/]").RuleStyle("red").LeftJustified());
AnsiConsole.WriteLine("内容");
AnsiConsole.Write(new Rule().RuleStyle("green"));


13. 文本样式

image.png

// 大字标题
AnsiConsole.Write(new FigletText("Hello").Color(Color.Green));
// Emoji
AnsiConsole.Markup(":check_mark_button: 成功 :cross_mark: 失败");


14. 网格

image.png

var grid = new Grid();
grid.AddColumn(new GridColumn().NoWrap().PadRight(4));
grid.AddColumn();

grid.AddRow("[b]名称[/]", "Spectre.Console");
grid.AddRow("[b]版本[/]", "0.49.1");
grid.AddRow("[b]许可[/]", "MIT");

AnsiConsole.Write(grid);






xoyozo 1 个月前
240

安装虚拟声卡 VB-Cable 搞定。

若重启后未生效,在服务中启动 Windows Audio、Windows Audio Endpoint Builder 和 Multimedia Class Scheduler(如果有),并设置为自动启动。


如何希望使用 OBS 录制系统声音,在 OBS 菜单 - 文件 - 设置 - 音频 - 桌面音频 中选择正确的音源(如“远程音频”)。

xoyozo 1 个月前
204

明明已登录 clawhub.ai 网站,但是 clawhub login 的时候仍然提示:

CLI login

Missing state.

Run the CLI again to start a fresh login.


解决办法:

把 PowerShell 中的那个网址复制出来,在浏览器上打开即可。网址格式类似:

https://clawhub.ai/cli/auth?redirect_uri=http%3A%2F%2F127.0.0.1%3A64172%2Fcallback&label_b64=******&state=******


xoyozo 3 个月前
540

nginx 配置文件中不能随意嵌套 if,而且 if 中也没有 and 和 or 逻辑,不过可以借用变量来实现:

set $a "";
if ($request_uri ~* "action=postreview") 
{
   set $a "${a}1";
}
if ($http_referer !~* "^https?://([^/]+\.)?domains\.com([:/?#]|$)") 
{
   set $a "${a}1"; 
}
if ($a = '11')
{
    return 403;
}

fastcgi...

上例中实现了 url 中包含 action=postreview 且来源不是本站的情况拒绝访问。

一般放在 fastcgi 所在 location,譬如宝塔面板的 enable-php-xx.conf 文件中。

xoyozo 4 个月前
486

在MySQL中,没有单条SQL语句能直接优化和修复所有表。不过,有以下两种常用方法可以实现这个需求:

1. 使用MySQL命令行工具:

mysqlcheck -u root -p --auto-repair --optimize --all-databases

这条命令会提示输入密码,然后自动修复并优化所有数据库中的所有表。

2. 如果确实需要在MySQL客户端内执行,可以生成批处理语句:

SELECT CONCAT('OPTIMIZE TABLE ', table_schema, '.', table_name, '; REPAIR TABLE ', table_schema, '.', table_name, ';') 
FROM information_schema.tables 
WHERE table_schema NOT IN ('information_schema','mysql','performance_schema','sys');

执行此查询后,复制结果中的所有语句再执行。

注意:执行表优化和修复操作需要相应权限,且在高负载生产环境中应谨慎操作,最好在低峰期进行。

xoyozo 4 个月前
453

今天发现在 .NET Framework 和 .NET 9 中使用 System.Uri.EscapeDataString() 方法对字符串进行编码,会产生不同的结果。

譬如“(”符号,前者视其为非保留字符,不进行转义,后者视为保留字符,转义为“%28”。

原因是 .NET Framework 4.8 主要遵循 RFC 2396,而 .NET 9 遵循 RFC 3986。


在跨平台签名验证场景中,对 URL 编码的一致性要求极高,任何细微差别都会导致签名校验失败。

以下是以 RFC 3986 标准为核心、优先使用各平台内置的高一致性方案。


对于 .NET 9,直接使用 Uri.EscapeDataString()。

string encodedData = System.Uri.EscapeDataString(dataToEncode);


对于 .NET Framework,以下是一个遵循 RFC 3986 严格标准的自定义编码方法示例。

static string Rfc3986EscapeDataString(string input)
{
    // 定义 RFC 3986 中明确的未保留字符集(不编码)
    var unreservedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";

    var result = new StringBuilder();
    byte[] data = Encoding.UTF8.GetBytes(input); // 统一转换为 UTF-8 字节

    foreach (byte b in data)
    {
        char currentChar = (char)b;
        // 如果是未保留字符,直接输出
        if (unreservedChars.IndexOf(currentChar) != -1)
        {
            result.Append(currentChar);
        }
        else
        {
            // 否则,进行百分号编码(%XX,大写)
            result.Append('%').Append(b.ToString("X2"));
        }
    }
    return result.ToString();
}


对于 PHP,直接使用内置的 rawurlencode() 函数。这个函数的设计初衷就是严格遵循 RFC 3986 标准。

$encoded_data = rawurlencode($data_to_encode);


对于 JavaScript,encodeURIComponent 函数严格遵循 RFC 3986 标准。

let encodedData = encodeURIComponent(dataToEncode);


重要提示:无论使用哪种语言,务必在编码前明确指定字符串使用 UTF-8 编码。编码不一致是导致乱码和签名失败最常见的原因之一 。

xoyozo 6 个月前
1,062
using System.Runtime.InteropServices;

/// <summary>
/// Windows 资源管理器的文件名排序规则(允许在非 Windows 平台使用)
/// <para>调用 Windows API(StrCmpLogicalW 函数)实现,仅在 Windows 平台环境中使用</para>
/// <para>基本规则如下:</para>
/// <para>将文件名中的数字作为数值来处理。</para>
/// <para>不区分大小写。</para>
/// <para>字符类型的大致优先级顺序为:特殊符号 → 数字 → 字母。</para>
/// <para>对于中文文件名,排序方式取决于系统的区域设置:拼音(默认)、笔画。</para>
/// </summary>
public class FileLogicalComparer : IComparer<string>
{
    [DllImport("Shlwapi.dll", CharSet = CharSet.Unicode)]
    private static extern int StrCmpLogicalW(string psz1, string psz2);
    private static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);

    public int Compare(string? x, string? y)
    {
        // 处理 null 值情况
        if (x == null && y == null) return 0;
        if (x == null) return -1;
        if (y == null) return 1;

        try
        {
            if (IsWindows)
            {
                // Windows 平台:使用原生 API
                return StrCmpLogicalW(x, y);
            }
            else
            {
                // 非 Windows 平台:使用 C# 实现的回退方案
                return NaturalCompareFallback(x, y);
            }
        }
        catch (Exception) // 捕获 DllNotFound、EntryPointNotFoundException 等异常
        {
            // 异常时使用默认的字符串比较器
            return string.Compare(x, y, StringComparison.Ordinal);
        }
    }

    /// <summary>
    /// C# 实现的自然排序回退方案(代码来自 AI,未测试!)
    /// </summary>
    private static int NaturalCompareFallback(string x, string y)
    {
        if (x == y) return 0;

        int i = 0, j = 0;

        while (i < x.Length && j < y.Length)
        {
            if (char.IsDigit(x[i]) && char.IsDigit(y[j]))
            {
                // 提取连续数字并进行数值比较
                string num1 = ExtractNumber(x, ref i);
                string num2 = ExtractNumber(y, ref j);

                if (long.TryParse(num1, out long n1) && long.TryParse(num2, out long n2))
                {
                    if (n1 != n2)
                        return n1.CompareTo(n2);
                }
                else
                {
                    // 解析失败时按字符串比较
                    int strCompare = string.Compare(num1, num2, StringComparison.Ordinal);
                    if (strCompare != 0)
                        return strCompare;
                }
            }
            else
            {
                // 非数字字符直接比较
                if (x[i] != y[j])
                    return x[i].CompareTo(y[j]);

                i++;
                j++;
            }
        }

        return x.Length.CompareTo(y.Length);
    }

    /// <summary>
    /// 从字符串中提取连续的数字序列
    /// </summary>
    private static string ExtractNumber(string str, ref int index)
    {
        int start = index;
        while (index < str.Length && char.IsDigit(str[index]))
        {
            index++;
        }
        return str.Substring(start, index - start);
    }
}
xoyozo 8 个月前
2,422

早前记录过在 CentOS 中部署 ASP.NET Core 网站,现在宝塔面板已经直接支持创建 .NET 网站了,再次记录一下。


本文环境:VS2022、.NET 9、宝塔面板v11。


一、在 VS 中创建一个 .NET 9 网站,项目名称例:WebApplication1

发布选项:

image.png

关于“生成单个文件”选项,若勾选,发布后的项目启动文件不带后缀名(.dll),尝试在宝塔面板 v11.0.0 中无法顺利启动。


二、在 Linux 服务器上创建目录:/www/wwwroot/WebApplication1/

将发布后的文件上传到这个目录上,最终的文件结构如下:

image.png


三、进入宝塔面板,在 网站-Net项目 中安装 .Net环境管理,这里以 9.0.201 为例:

image.png


四、添加项目

项目名称:随意

运行路径:项目所在目录,本例中为:

/www/wwwroot/WebApplication1

启动命令:使用 dotnet 命令,dotnet 命令可以不使用全路径(/www/server/dotnet/9.0.201/dotnet),第一个参数是项目启动文件,--urls 是反向代理的服务器和端口。服务器名可以是*也可以是localhost。例:

dotnet WebApplication1.dll --urls=http://*:5000

项目端口:与启动命令中的端口号一致

开机启动:一般会勾选

启动用户:尽量用 www,最小权限原则。

image.png

正常情况下,网站已启动,如果未启动,检查配置,根据报错内容排查问题。


五、配置网站

添加域名、SSL 证书等。


友情提示:

  • 网站版本更新重新发布后,需要手动重启网站才能生效,习惯于发布到 IIS(会自动生效)的同学要特别注意。

  • 新手尽量以空项目或默认项目来部署,避免因特殊原因导致一系列其它问题。

  • nginx 反向代理并不会转发所有 Header,需要手动配置。

xoyozo 9 个月前
7,469