Implementation of obtaining SSL fingerprint and ByPass by web container

preface

Purchase discount www.fenfaw.com cn

Some time ago, I was very interested in the acquisition and implementation of SSL fingerprint. From the surface to the in-depth and then to the implementation, I have a deeper understanding of SSL design. This article introduces:

  • How to obtain SSL fingerprints under the web container (Kestrel) and implement a Middleware to facilitate integration into the web project (the source code address is attached below).
  • Parsing the routine of ClientHello and how to generate SSL fingerprint
  • Test SSL fingerprints of different clients (java curl Fiddler python csharp chrome edge)

This research on SSL fingerprint is the end of this series

  1. Walk out of the misunderstanding of SSL
  2. Function performance of SSL certificate
  3. Start of text

Let's talk about what this SSL fingerprint is used for

  • waf is usually useful
  • To tell you the truth, I can only do script Boy at present

For example, I've seen before why httpheaders with the same request address and parameters are normal when accessed by the browser, and those sent in python will be intercepted by waf

Normal user access: [screenshot from guy A of parrot security]

script Boy sends a Request in Python and is directly intercepted by waf

[screenshot from guy A of parrot security]

There are many inquiry posts like this

  • https://stackoverflow.com/questions/60407057/python-requests-being-fingerprinted
  • https://stackoverflow.com/questions/63343106/how-to-avoid-request-fingerprinted-in-python

The conclusion is that python's tls handshake has characteristics, and the unique fingerprint is recognized by waf!

SSL fingerprint identification principle

The giant's shoulders are here: https://github.com/salesforce/ja3

It is to parse the ClientHello message sent by the TLS handshake client and obtain

  • SSLVersion version
  • Cipher client supported encryption suite
  • Sslexception is an extended content collection for SSL
  • [supported_groups] (CurveP256,CurveP384,CurveP521,X25519) in the extended content of EllipticCurve SSL
  • [sec_point_formats] (uncompressed, ANSI x962_compressed_prime, ANSI x962_compressed_char2) in the extended parameter of EllipticCurvePointFormat SSL

Arrange the versions, encryption suites, extensions and other contents parsed above in order, and then calculate the hash value to get the TLS FingerPrint of a client. The waf protection rule is actually to sort out and extract the fingerprints of some common non browser client requests and curl, and then identify and intercept them when the client initiates https requests!

Hands on practice

This technical implementation is based on aspnet5 0. web container is a high-performance Kestrel built by Microsoft for aspnetcore! Thanks to Kestrel's middleware design, we can easily specify our own middleware to intercept ClientHello when configuring Kestrel (thanks to David fowl, the great God of Microsoft)

webBuilder.UseKestrel(options =>
{
   var logger = options.ApplicationServices.GetRequiredService<ILogger<Program>>();

   options.ListenLocalhost(5002, listenOption =>
   {
       var httpsOptions = new HttpsConnectionAdapterOptions();
       //Local test certificate
       var serverCert = new X509Certificate2("server.pfx", "1234");
       httpsOptions.ServerCertificate = serverCert;
       //Register tls interception Middleware
       listenOption.Use(async (connectionContext, next) =>
       {
           await TlsFilterConnectionMiddlewareExtensions
           .ProcessAsync(connectionContext, next, logger);
       });
       listenOption.UseHttps(httpsOptions);
   });
});

The next step is to do analysis in our customized middleware

public static async Task ProcessAsync(ConnectionContext connectionContext, Func<Task> next, ILogger<Program> logger)
{
    var input = connectionContext.Transport.Input;
    var minBytesExamined = 0L;
    while (true)
    {
        var result = await input.ReadAsync();
        var buffer = result.Buffer;

        if (result.IsCompleted)
        {
            return;
        }

        if (buffer.Length == 0)
        {
            continue;
        }

        //Start processing ClientHello message
        if (!TryReadHello(buffer, logger, out var abort))
        {
            minBytesExamined = buffer.Length;
            input.AdvanceTo(buffer.Start, buffer.End);
            continue;
        }

        //We read the stream above. We need to return here
        var examined = buffer.Slice(buffer.Start, minBytesExamined).End;
        input.AdvanceTo(buffer.Start, examined);

        if (abort)
        {
            // Close the connection.
            return;
        }

        break;
    }

    await next();
}

Parsing ClientHello message

private static bool TryReadHello(ReadOnlySequence<byte> buffer, ILogger logger, out bool abort)
{
    abort = false;

    if (!buffer.IsSingleSegment)
    {
        throw new NotImplementedException("Multiple buffer segments");
    }
    var data = buffer.First.Span;

    TlsFrameHelper.TlsFrameInfo info = default;
    if (!TlsFrameHelper.TryGetFrameInfo(data, ref info))
    {
        return false;
    }

    //Resolved version
    logger.LogInformation("Protocol versions: {versions}", info.SupportedVersions);

    //Resolve the Host requested by the client
    //Here is a trick. A simple ByPass means of waf defense is to ByPass the domain name and directly access Ip. If the server adds a Host white list here, it can prevent bypassing.
    logger.LogInformation("SNI: {host}", info.TargetName);
    
    //Other fields omitted
    Console.WriteLine("ClientHello=>" + info);
    return true;
}

ClientHello message analysis

There is nothing special about parsing the message, that is, according to the RCF document:

 public enum ExtensionType : ushort
    {
        server_name = 0,
        max_fragment_length = 1,
        client_certificate_url = 2,
        trusted_ca_keys = 3,
        truncated_hmac = 4,
        status_request = 5,
        user_mapping = 6,
        client_authz = 7,
        server_authz = 8,
        cert_type = 9,
        supported_groups = 10,//  Elliptic curve points
        ec_point_formats = 11, // Elliptic curve point formats
        srp = 12,
        signature_algorithms = 13,
        use_srtp = 14,
        heartbeat = 15,
        application_layer_protocol_negotiation = 16,
        status_request_v2 = 17,
        signed_certificate_timestamp = 18,
        client_certificate_type = 19,
        server_certificate_type = 20,
        padding = 21,
        encrypt_then_mac = 22,
        extended_master_secret = 23,
        token_binding = 24,
        cached_info = 25,
        tls_lts = 26,
        compress_certificate = 27,
        record_size_limit = 28,
        pwd_protect = 29,
        pwd_clear = 30,
        password_salt = 31,
        session_ticket = 35,
        pre_shared_key = 41,
        early_data = 42,
        supported_versions = 43,
        cookie = 44,
        psk_key_exchange_modes = 45,
        certificate_authorities = 47,
        oid_filters = 48,
        post_handshake_auth = 49,
        signature_algorithms_cert = 50,
        key_share = 51,
        renegotiation_info = 65281
    }

tips: parsing the routine of extension:

The first two bytes of the byte array are the length, followed by the content, and then parsed according to the struct of the RFC

struct enumeration is directly copied from tls of Go SDK

The comments in GoSDK are very detailed, and the related information of RCF is also in the comments. I like it!

This part of the code is a little too much. I put it in my github. If you want to study it, you can click to view it

Finally, the data is spliced according to the principle to generate SSL fingerprint in md5
public string getSig()
{
    StringBuilder sb = new StringBuilder();
    //edition
    sb.Append((int)Header.Version);
    sb.Append(",");
    //cipher suite 
    if (_ciphers != null)
    {
        sb.Append(string.Join("-", _ciphers.Select(r => (int)r)));
    }
    sb.Append(",");
    //SSL extension field
    if (_extensions != null)
    {
        sb.Append(string.Join("-", _extensions.Select(r => (int)r)));
    }
    sb.Append(",");
    //Elliptic curve points
    if (_supportedgroups != null)
    {
        sb.Append(string.Join("-", _supportedgroups.Select(r => (int)r)));
    }
    sb.Append(",");
    // Elliptic curve point formats
    if (_ecPointFormats != null)
    {
        sb.Append(string.Join("-", _ecPointFormats.Select(r => (int)r)));
    }
    String str = sb.ToString();
    using var md5 = MD5.Create();
    var result = md5.ComputeHash(Encoding.ASCII.GetBytes(str));
    var strResult = BitConverter.ToString(result);
    //Consistent with the implementation of other languages
    var sig = strResult.Replace("-", "").ToLower();
    return sig;
}

Try the effect at a wonderful moment

Test with different clients to see the collected fingerprints

1. chrome
fingerprint:
192,
0-4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,
0-23-65281-10-11-35-16-5-13-18-51-45-43-27-21,
29-23-24,
0
2. edge
fingerprint:
192,
0-4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,
0-23-65281-10-11-35-16-5-13-18-51-45-43-27-17513-21,
29-23-24,
0

The new version of edge also uses the core of chromium, and the extension is extended by one more 17513

3. HttpClient of CSharp
fingerprint:
3072,
49196-49195-49200-49199-159-158-49188-49187-49192-49191-49162-49161-49172-49171-157-156-61-60-53-47-10,
0-10-11-13-35-23-65281,
29-23-24,
0
4. Fiddler
fingerprint:
3072,
49196-49195-49200-49199-159-158-49188-49187-49192-49191-49162-49161-49172-49171-157-156-61-60-53-47-10,
0-10-11-13-35-23-65281,
29-23-24,
0

Because Fiddler is written by csharp, it should be implemented with Microsoft's encapsulated ssl. So the fingerprint is the same as the HttpClient of csharp.

5. HttpsURLConnection of Java JDK
fingerprint:
3072,
49187-49191-60-49189-49193-103-64-49161-49171-47-49156-49166-51-50-49195-49199-156-49197-49201-158-162-49160-49170-10-49155-49165-22-19-255,
10-11-13,
23-1-3-19-21-6-7-9-10-24-11-12-25-13-14-15-16-17-2-18-4-5-20-8-22,
0

Obviously, we can see that there are a lot more ellipticcurves!

6. Apache HttpClient
fingerprint:
3072,
49188-49192-61-49190-49194-107-106-49162-49172-53-49157-49167-57-56-49187-49191-60-49189-49193-103-64-49161-49171-47-49156-49166-51-50-49196-49195-49200-157-49198-49202-159-163-49199-156-49197-49201-158-162-255,
10-11-13-23,
23-24-25,
0

Compared with the above, the EllipticCurve is obviously different!

7. curl
fingerprint:
192,
4866-4867-4865-49196-49200-159-52393-52392-52394-49195-49199-158-49188-49192-107-49187-49191-103-49162-49172-57-49161-49171-51-157-156-61-60-53-47-255,
0-11-10-13172-16-22-23-49-13-43-45-51-21,
29-23-30-25-24,
0-1-2

8. Request for Python 3
fingerprint:
192,
4866-4867-4865-49196-49200-49195-49199-52393-52392-163-159-162-158-52394-49327-49325-49188-49192-49162-49172-49315-49311-107-106-57-56-49326-49324-49187-49191-49161-49171-49314-49310-103-64-51-50-157-156-49313-49309-49312-49308-61-60-53-47-255,
0-11-10-35-22-23-13-43-45-51-21,
29-23-30-25-24,
0-1-2

Haha, practice is the only criterion to test the truth. It's not hard to see why Ali's waf can kill curl and python scripts so easily

ByPass has a way?? Certainly.

You can communicate by private mail

 

 

I'm Zhengdong. The more I learn, the more I don't know. This official account is my experimental field. I will share some of my open source tools (welcome to comment), and new technologies that are fun to use. If you like tossing technology as much as I do, please pay attention!

Added by mattm591 on Mon, 31 Jan 2022 14:16:58 +0200