HttpsScanMiddleware.cs 4.3 KB

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