HttpsScanMiddleware.cs 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  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<HttpsScanOptions> 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<HttpsScanOptions> 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.Timeout;
  54. using var timeoutTokenSource = new CancellationTokenSource(timeout);
  55. using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(timeoutTokenSource.Token, context.CancellationToken);
  56. var httpClient = this.httpClientFactory.CreateClient(nameof(FastGithub));
  57. using var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, linkedTokenSource.Token);
  58. VerifyHttpsResponse(context.Domain, response);
  59. context.Available = true;
  60. await next();
  61. }
  62. catch (Exception ex)
  63. {
  64. context.CancellationToken.ThrowIfCancellationRequested();
  65. this.logger.LogTrace($"{context.Domain} {context.Address} { GetInnerMessage(ex)}");
  66. }
  67. }
  68. /// <summary>
  69. /// 验证响应内容
  70. /// </summary>
  71. /// <param name="domain"></param>
  72. /// <param name="response"></param>
  73. /// <exception cref="HttpRequestException"></exception>
  74. /// <exception cref="ValidationException"></exception>
  75. private static void VerifyHttpsResponse(string domain, HttpResponseMessage response)
  76. {
  77. response.EnsureSuccessStatusCode();
  78. if (domain == "github.com" || domain.EndsWith(".github.com"))
  79. {
  80. if (response.Headers.Server.Any(item => IsGithubServer(item)) == false)
  81. {
  82. throw new ValidationException("伪造的github服务");
  83. }
  84. }
  85. static bool IsGithubServer(ProductInfoHeaderValue headerValue)
  86. {
  87. var value = headerValue.Product?.Name;
  88. return string.Equals("github.com", value, StringComparison.OrdinalIgnoreCase);
  89. }
  90. }
  91. private static string GetInnerMessage(Exception ex)
  92. {
  93. while (ex.InnerException != null)
  94. {
  95. return GetInnerMessage(ex.InnerException);
  96. }
  97. return ex.Message;
  98. }
  99. }
  100. }