From d8cab72fbdc72e87b39d3ac81bd2cf075d4f3566 Mon Sep 17 00:00:00 2001 From: Genteure Date: Sat, 9 Mar 2024 21:02:44 +0800 Subject: [PATCH] feat(web): block open access from internet by default. (#571) --- BililiveRecorder.Cli/Program.cs | 8 ++ .../DisableOpenAccessWarningConfig.cs | 7 ++ .../OpenAccessWarningMiddleware.cs | 112 ++++++++++++++++++ BililiveRecorder.Web/Startup.cs | 5 + 4 files changed, 132 insertions(+) create mode 100644 BililiveRecorder.Web/DisableOpenAccessWarningConfig.cs create mode 100644 BililiveRecorder.Web/OpenAccessWarningMiddleware.cs diff --git a/BililiveRecorder.Cli/Program.cs b/BililiveRecorder.Cli/Program.cs index 41d5852..1be32da 100644 --- a/BililiveRecorder.Cli/Program.cs +++ b/BililiveRecorder.Cli/Program.cs @@ -59,6 +59,7 @@ namespace BililiveRecorder.Cli new Option(new []{ "--http-bind", "--bind", "-b" }, () => null, "Bind address for http service"), new Option(new []{ "--http-basic-user" }, () => null, "Web interface username"), new Option(new []{ "--http-basic-pass" }, () => null, "Web interface password"), + new Option(new []{ "--http-open-access" }, () => false, "Allow open access from the internet"), new Option(new []{ "--enable-file-browser" }, () => true, "Enable file browser located at '/file'"), new Option(new []{ "--loglevel", "--log", "-l" }, () => LogEventLevel.Information, "Minimal log level output to console"), new Option(new []{ "--logfilelevel", "--flog" }, () => LogEventLevel.Debug, "Minimal log level output to file"), @@ -77,6 +78,7 @@ namespace BililiveRecorder.Cli new Option(new []{ "--http-bind", "--bind", "-b" }, () => null, "Bind address for http service"), new Option(new []{ "--http-basic-user" }, () => null, "Web interface username"), new Option(new []{ "--http-basic-pass" }, () => null, "Web interface password"), + new Option(new []{ "--http-open-access" }, () => false, "Allow open access from the internet"), new Option(new []{ "--enable-file-browser" }, () => true, "Enable file browser located at '/file'"), new Option(new []{ "--loglevel", "--log", "-l" }, () => LogEventLevel.Information, "Minimal log level output to console"), new Option(new []{ "--logfilelevel", "--flog" }, () => LogEventLevel.Debug, "Minimal log level output to file"), @@ -228,6 +230,10 @@ namespace BililiveRecorder.Cli { services.AddSingleton(new BasicAuthCredential(sharedArguments.HttpBasicUser ?? string.Empty, sharedArguments.HttpBasicPass ?? string.Empty)); } + + if (!sharedArguments.HttpOpenAccess && Environment.GetEnvironmentVariable("BREC_HTTP_OPEN_ACCESS") is not null){ + services.AddSingleton(new DisableOpenAccessWarningConfig()); + } }) .ConfigureWebHost(webBuilder => { @@ -522,6 +528,8 @@ namespace BililiveRecorder.Cli public string? HttpBasicPass { get; set; } = null; + public bool HttpOpenAccess { get; set; } = false; + public bool EnableFileBrowser { get; set; } public string? CertPemPath { get; set; } = null; diff --git a/BililiveRecorder.Web/DisableOpenAccessWarningConfig.cs b/BililiveRecorder.Web/DisableOpenAccessWarningConfig.cs new file mode 100644 index 0000000..03810fe --- /dev/null +++ b/BililiveRecorder.Web/DisableOpenAccessWarningConfig.cs @@ -0,0 +1,7 @@ +namespace BililiveRecorder.Web +{ + public class DisableOpenAccessWarningConfig + { + public readonly bool WarningDisabled = true; + } +} diff --git a/BililiveRecorder.Web/OpenAccessWarningMiddleware.cs b/BililiveRecorder.Web/OpenAccessWarningMiddleware.cs new file mode 100644 index 0000000..200343a --- /dev/null +++ b/BililiveRecorder.Web/OpenAccessWarningMiddleware.cs @@ -0,0 +1,112 @@ +using System; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Net.Http.Headers; + +namespace BililiveRecorder.Web +{ + public class OpenAccessWarningMiddleware + { + private readonly RequestDelegate next; + + public OpenAccessWarningMiddleware(RequestDelegate next) + { + this.next = next ?? throw new ArgumentNullException(nameof(next)); + } + + public Task InvokeAsync(HttpContext context) + { + if (context.RequestServices.GetService() is not null) + { + // 启用了身份验证,不需要警告 + return this.next(context); + } + + if (sourceIpNotLan(context) || haveReverseProxyHeaders(context) || haveCustomHostValue(context)) + { + context.Response.StatusCode = 412; + var accept = context.Request.Headers[HeaderNames.Accept]; + if (accept.Contains("text/html")) + { + context.Response.ContentType = "text/html; charset=utf-8"; + return context.Response.WriteAsync("Access Denied" + + "

Access Denied

Open access from the internet detected. Please enable basic authentication " + + "or disable this warning by setting the environment variable \"BREC_HTTP_OPEN_ACCESS\".

" + + "

检测到非局域网无密码访问。请设置用户名密码或通过设置环境变量 \"BREC_HTTP_OPEN_ACCESS\" 禁用此警告。

" + + "

录播姬 BililiveRecorder " + GitVersionInformation.FullSemVer + "

\n"); + } + else + { + context.Response.ContentType = "text/plain; charset=utf-8"; + return context.Response.WriteAsync("Access Denied.\nOpen access from the internet detected. Please enable " + + "basic authentication or disable this warning by setting the environment variable \"BREC_HTTP_OPEN_ACCESS\".\n" + + "检测到非局域网无密码访问。请设置用户名密码或通过设置环境变量 \"BREC_HTTP_OPEN_ACCESS\" 禁用此警告。\n" + + "录播姬 BililiveRecorder " + GitVersionInformation.FullSemVer + "\n"); + } + } + else + { + return this.next(context); + } + } + + private static bool sourceIpNotLan(HttpContext context) + { + var ip = context.Connection.RemoteIpAddress; + if (ip is null) return true; + return !isLocalIpv4Address(ip) && !ip.IsIPv6LinkLocal && !ip.IsIPv6UniqueLocal; + } + + private static bool isLocalIpv4Address(IPAddress ip) + { + if (ip.IsIPv4MappedToIPv6) + ip = ip.MapToIPv4(); + + if (ip.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork) + return false; + + var bytes = ip.GetAddressBytes(); + if (bytes.Length != 4) + return false; + switch (bytes[0]) + { + case 10: // 10.0.0.0/8 + return true; + case 127: // 127.0.0.0/8 + return true; + case 172: // 172.16.0.0/12 + return bytes[1] >= 16 && bytes[1] <= 31; + case 192: // 192.168.0.0/16 + return bytes[1] == 168; + default: + return false; + } + } + + private static bool haveReverseProxyHeaders(HttpContext context) + { + return + context.Request.Headers.ContainsKey("X-Real-IP") || + context.Request.Headers.ContainsKey("X-Forwarded-For") || + context.Request.Headers.ContainsKey("X-Forwarded-Host") || + context.Request.Headers.ContainsKey("Via"); + } + + private static bool haveCustomHostValue(HttpContext context) + { + // check if the host header is set to a custom value such as a domain name + if (IPAddress.TryParse(context.Request.Host.Host, out var ip)) + { + // the host header is an IP address + // check if the IP address matches the server's IP address + return ip.Equals(context.Connection.LocalIpAddress); + } + // the host header is not an IP address + return true; + } + } +} diff --git a/BililiveRecorder.Web/Startup.cs b/BililiveRecorder.Web/Startup.cs index c40d8e0..f7a00de 100644 --- a/BililiveRecorder.Web/Startup.cs +++ b/BililiveRecorder.Web/Startup.cs @@ -133,6 +133,11 @@ namespace BililiveRecorder.Web { const string PAGE404 = "/404.html"; + if (app.ApplicationServices.GetService() is null) + { + app.UseMiddleware(); + } + app .UseCors() .UseMiddleware()