HttpsScanMiddleware.cs 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. using Microsoft.Extensions.DependencyInjection;
  2. using Microsoft.Extensions.Logging;
  3. using Microsoft.Extensions.Options;
  4. using System;
  5. using System.ComponentModel.DataAnnotations;
  6. using System.Linq;
  7. using System.Net.Http;
  8. using System.Net.Http.Headers;
  9. using System.Threading;
  10. using System.Threading.Tasks;
  11. namespace FastGithub.Scanner.ScanMiddlewares
  12. {
  13. /// <summary>
  14. /// https扫描中间件
  15. /// </summary>
  16. [Service(ServiceLifetime.Singleton)]
  17. sealed class HttpsScanMiddleware : IMiddleware<GithubContext>
  18. {
  19. private readonly IOptionsMonitor<GithubOptions> options;
  20. private readonly IHttpClientFactory httpClientFactory;
  21. private readonly ILogger<HttpsScanMiddleware> logger;
  22. /// <summary>
  23. /// https扫描中间件
  24. /// </summary>
  25. /// <param name="options"></param>
  26. /// <param name="logger"></param>
  27. public HttpsScanMiddleware(
  28. IOptionsMonitor<GithubOptions> options,
  29. IHttpClientFactory httpClientFactory,
  30. ILogger<HttpsScanMiddleware> logger)
  31. {
  32. this.options = options;
  33. this.httpClientFactory = httpClientFactory;
  34. this.logger = logger;
  35. }
  36. /// <summary>
  37. /// https扫描
  38. /// </summary>
  39. /// <param name="context"></param>
  40. /// <param name="next"></param>
  41. /// <returns></returns>
  42. public async Task InvokeAsync(GithubContext context, Func<Task> next)
  43. {
  44. try
  45. {
  46. context.Available = false;
  47. var request = new HttpRequestMessage
  48. {
  49. Method = HttpMethod.Head,
  50. RequestUri = new Uri($"https://{context.Address}"),
  51. };
  52. request.Headers.Host = context.Domain;
  53. var timeout = this.options.CurrentValue.Scan.HttpsScanTimeout;
  54. using var cancellationTokenSource = new CancellationTokenSource(timeout);
  55. var httpClient = this.httpClientFactory.CreateClient(nameof(FastGithub));
  56. using var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationTokenSource.Token);
  57. VerifyHttpsResponse(context.Domain, response);
  58. context.Available = true;
  59. await next();
  60. }
  61. catch (TaskCanceledException)
  62. {
  63. this.logger.LogTrace($"{context.Domain} {context.Address}连接超时");
  64. }
  65. catch (Exception ex)
  66. {
  67. var message = GetInnerMessage(ex);
  68. this.logger.LogTrace($"{context.Domain} {context.Address} {message}");
  69. }
  70. }
  71. /// <summary>
  72. /// 验证响应内容
  73. /// </summary>
  74. /// <param name="domain"></param>
  75. /// <param name="response"></param>
  76. /// <exception cref="HttpRequestException"></exception>
  77. /// <exception cref="ValidationException"></exception>
  78. private static void VerifyHttpsResponse(string domain, HttpResponseMessage response)
  79. {
  80. response.EnsureSuccessStatusCode();
  81. if (domain == "github.com" || domain.EndsWith(".github.com"))
  82. {
  83. if (response.Headers.Server.Any(item => IsGithubServer(item)) == false)
  84. {
  85. throw new ValidationException("伪造的github服务");
  86. }
  87. }
  88. static bool IsGithubServer(ProductInfoHeaderValue headerValue)
  89. {
  90. var value = headerValue.Product?.Name;
  91. return string.Equals("github.com", value, StringComparison.OrdinalIgnoreCase);
  92. }
  93. }
  94. private static string GetInnerMessage(Exception ex)
  95. {
  96. while (ex.InnerException != null)
  97. {
  98. return GetInnerMessage(ex.InnerException);
  99. }
  100. return ex.Message;
  101. }
  102. }
  103. }