123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204 |
- using System;
- using System.IO;
- using System.IO.Compression;
- using System.Linq;
- using System.Net;
- using System.Net.Security;
- using System.Net.Sockets;
- using System.Runtime.ConstrainedExecution;
- using System.Security.Cryptography;
- using System.Security.Cryptography.X509Certificates;
- using System.Text;
- using System.Threading;
- using DnsClient;
- //////////////////////////////////
- #region __________ INIT __________
- using Microsoft.Extensions.Logging.Console;
- var builder = WebApplication.CreateBuilder(args);
- //控制台日志格式
- builder.Services.AddLogging(opt =>
- {
- opt.AddSimpleConsole(p =>
- {
- p.TimestampFormat = "[dd HH:mm:ss] ";
- p.SingleLine = true;
- p.ColorBehavior = LoggerColorBehavior.Enabled;
- });
- });
- using var host = builder.Build();
- builder.WebHost.UseUrls("http://*:0");
- await host.StartAsync();
- var isRunning = true;
- var cts = new CancellationTokenSource();
- Console.CancelKeyPress += (_, _) =>
- {
- isRunning = false;
- cts.Cancel(false);
- };
- var logger = host.Services.GetRequiredService<ILogger<Program>>();
- logger.LogInformation("Hello, World!");
- //Main
- try
- {
- await RealMain();
- }
- catch (Exception ex)
- {
- logger.LogError(ex, "Main");
- }
- finally
- {
- logger.LogInformation("Bye!");
- await host.StopAsync();
- Console.WriteLine();
- Console.Write("Press ENTER to exit...");
- Console.ReadLine();
- }
- #endregion __________ INIT __________
- //////////////////////////////////
- async Task RealMain()
- {
- const string url = "http://example.com/index.html";
- const string host = "example.com";
- const string path = "/index.html";
- const string dnsServerName = "reliable-dns-server-in-hosts";
- var dnsServerIp = Dns.GetHostEntry(dnsServerName).AddressList.FirstOrDefault();
- var lookup = new LookupClient(dnsServerIp);
- var result = await lookup.QueryAsync(host, QueryType.A);
- var record = result.Answers.ARecords().FirstOrDefault();
- var ip = record?.Address;
- var tcpClient = new TcpClient();
- tcpClient.Connect(new IPEndPoint(ip, 443)); // stuck if was ip gfw-ed
- var ssl = new SslStream(tcpClient.GetStream());
- var sslOptions = new SslClientAuthenticationOptions
- {
- TargetHost = string.Empty, // Leave this empty to avoid sending SNI
- RemoteCertificateValidationCallback = (_, certificate, chain, errs) =>
- {
- if (errs == SslPolicyErrors.None) return true;
- if (errs != SslPolicyErrors.RemoteCertificateNameMismatch) return false;
- if (certificate is not X509Certificate2 cert2) return false;
- // 先验证证书的有效期
- if (DateTime.Now < cert2.NotBefore || DateTime.Now > cert2.NotAfter) return false;
- // 然后比较证书名称和主机名称 (任意一个匹配)
- var names = GetAllSubjectAlternativeNames(cert2);
- var flagNameMatched = false;
- foreach (var certName in names)
- {
- if (certName.StartsWith("*."))
- {
- if (!host.EndsWith(certName[2..], StringComparison.OrdinalIgnoreCase)) continue;
- flagNameMatched = true;
- break;
- }
- if (certName.Equals(host, StringComparison.OrdinalIgnoreCase))
- {
- flagNameMatched = true;
- break;
- }
- }
- if (flagNameMatched == false) return false;
- // 最后检查信任链
- if (chain == null) return false;
- chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; //检测吊销耗时太长,忽略
- chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain;
- chain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 0, 10);
- chain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;
- var isValidChain = chain.Build(cert2);
- if (isValidChain) return true;
- foreach (X509ChainStatus chainStatus in chain.ChainStatus)
- {
- // 仅处理会影响安全性的错误状态
- if (chainStatus.Status == X509ChainStatusFlags.RevocationStatusUnknown ||
- chainStatus.Status == X509ChainStatusFlags.OfflineRevocation ||
- chainStatus.Status == X509ChainStatusFlags.NoError)
- {
- continue;
- }
- // 其他任何错误状态都认为证书无效
- return false;
- }
- return true;
- }
- };
- await ssl.AuthenticateAsClientAsync(sslOptions, cts.Token);
- ssl.Write(Encoding.ASCII.GetBytes($"GET {path} HTTP/1.1\r\n"));
- ssl.Write(Encoding.ASCII.GetBytes($"Host: {host}\r\n"));
- ssl.Write(Encoding.ASCII.GetBytes($"\r\n"));
- var reader = new StreamReader(ssl);
- var lines = new List<string>(); // <- HTTP/1.1 200 OK -- PoC SUCC!
- do
- {
- var line = reader.ReadLine();
- if (line == "") break;
- lines.Add(line);
- } while (true);
- int bp = 0;
- }
- IReadOnlyList<string> GetAllSubjectAlternativeNames(X509Certificate2 cert)
- {
- var names = new HashSet<string>();
- foreach (var extension in cert.Extensions)
- {
- if (extension is X509SubjectAlternativeNameExtension sanExtension)
- {
- foreach (var name in sanExtension.EnumerateDnsNames()) names.Add(name);
- }
- else if (extension.Oid?.Value == "2.5.29.17") // Subject Alternative Name OID
- {
- var asnData = new AsnEncodedData(extension.Oid, extension.RawData);
- var sanString = asnData.Format(true);
- var sanParts = sanString.Split(new[] { ", ", "DNS Name=", " " }, StringSplitOptions.RemoveEmptyEntries);
- foreach (var part in sanParts)
- {
- if (!string.IsNullOrEmpty(part) && !part.StartsWith("IPAddress") && !part.StartsWith("Uri"))
- {
- names.Add(part);
- }
- }
- }
- }
- // 添加证书的CN(通用名称)
- var certName = cert.GetNameInfo(X509NameType.DnsName, false);
- if (!string.IsNullOrEmpty(certName)) names.Add(certName);
- return [.. names];
- }
|