zoukankan      html  css  js  c++  java
  • web容器获取SSL指纹实现和ByPass

    前言

    前段时间对SSL指纹的获取实现很感兴趣,从表面到深入再到实现让我更加深刻理解SSL设计。 本篇介绍:

    • SSL指纹在web容器(Kestrel)下如何获取,并实现一个Middleware来很方便集成到web工程里面(下文附源码地址)。
    • 解析ClientHello的套路以及如何生成SSL指纹
    • 测试不同的客户端的SSL指纹(java curl Fiddler python csharp chrome edge)

    本次对SSL指纹的研究就算是完结篇了, 本次系列

    1. 从SSL的理解误区走出
    2. SSL证书的作用表现
    3. 正文开始

    先来说说这个SSL指纹用来干嘛

    • waf里面一般会有用到
    • 说实话在目前的话也只能干干script Boy

    举个例子,之前看过说为什么同样的请求地址一样的参数 一样的httpHeader,在浏览器访问就正常,用python发送的就会被waf拦截

    正常用户访问: image [截图出自ParrotSecurity的A佬]

    script Boy 用Python 发 Request,直接就被waf拦截了

    image

    [截图出自ParrotSecurity的A佬]

    类似这样的求问帖有很多

    结论就是 python的tls握手有特征,被waf识别到独特的指纹了!

    image

    SSL指纹识别原理

    巨人的肩膀在这里:https://github.com/salesforce/ja3

    image

    就是解析TLS握手客户端发送的ClientHello报文并获取

    • SSLVersion 版本
    • Cipher 客户端支持的加密套件
    • SSLExtention SSL的扩展内容集合
    • EllipticCurve SSL的扩展内容里面的【supported_groups】(CurveP256,CurveP384,CurveP521,X25519)
    • EllipticCurvePointFormat SSL的扩展参数里面的【sec_point_formats】(uncompressed,ansiX962_compressed_prime,ansiX962_compressed_char2)

    把上面解析出来的版本,加密套件,扩展等内容按顺序排列然后计算hash值,便可得到一个客户端的TLS FingerPrint,waf防护规则其实就是整理提取一些常见的非浏览器客户端requests,curl的指纹然后在客户端发起https请求时进行识别并拦截!

    动手实践

    本次技术实现基于aspnet5.0,web容器是微软为aspnetcore打造的高性能Kestrel! 得益于Kestrel的中间件设计,我们可以很容易的 在配置Kestrel的时候指定自己的中间件去拦截ClientHello (感谢微软大神davidfowl的指点)

    webBuilder.UseKestrel(options =>
    {
       var logger = options.ApplicationServices.GetRequiredService<ILogger<Program>>();
    
       options.ListenLocalhost(5002, listenOption =>
       {
           var httpsOptions = new HttpsConnectionAdapterOptions();
           //本地测试证书
           var serverCert = new X509Certificate2("server.pfx", "1234");
           httpsOptions.ServerCertificate = serverCert;
           //注册tls拦截中间件
           listenOption.Use(async (connectionContext, next) =>
           {
               await TlsFilterConnectionMiddlewareExtensions
               .ProcessAsync(connectionContext, next, logger);
           });
           listenOption.UseHttps(httpsOptions);
       });
    });
    
    

    接下来就是在我们自定义的中间件做解析

    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;
            }
    
            //开启处理ClientHello报文
            if (!TryReadHello(buffer, logger, out var abort))
            {
                minBytesExamined = buffer.Length;
                input.AdvanceTo(buffer.Start, buffer.End);
                continue;
            }
    
            //上面我们读了流这里要归位
            var examined = buffer.Slice(buffer.Start, minBytesExamined).End;
            input.AdvanceTo(buffer.Start, examined);
    
            if (abort)
            {
                // Close the connection.
                return;
            }
    
            break;
        }
    
        await next();
    }
    
    

    解析ClientHello报文

    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;
        }
    
        //解析的版本
        logger.LogInformation("Protocol versions: {versions}", info.SupportedVersions);
    
        //解析客户端请求的Host
        //这里有一个小技巧,waf防御的一个简单的ByPass手段就是绕过域名直接访问Ip进行访问,如果服务端在这里增加一个Host白名单,就能防止绕过。
        logger.LogInformation("SNI: {host}", info.TargetName);
        
        //其他字段省略
        Console.WriteLine("ClientHello=>" + info);
        return true;
    }
    
    

    ClientHello报文解析

    解析报文没啥特别的,就是根据RCF文档,:

     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:解析Extention的套路:

    byte数组 前2个byte为长度 后面的是内容 然后根据RFC的struct进行解析

    image

    struct枚举 我从Go SDK的tls直接复制过来用的

    image

    image

    GoSDK里面的注释很详细 RCF的相关也在注释里面,点赞!

    这部分代码有点多,我都放在了我的github,想研究的可以 点击查看

    最后就是按照原理将数据进行拼接在md5生成SSL指纹
    public string getSig()
    {
        StringBuilder sb = new StringBuilder();
        //版本
        sb.Append((int)Header.Version);
        sb.Append(",");
        //加密套件
        if (_ciphers != null)
        {
            sb.Append(string.Join("-", _ciphers.Select(r => (int)r)));
        }
        sb.Append(",");
        //SSL扩展字段
        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);
        //和其他语言的实现保持一致
        var sig = strResult.Replace("-", "").ToLower();
        return sig;
    }
    
    

    精彩时刻 来试试效果

    用不同的客户端来测试下看看收集的指纹

    1. chrome
    指纹:
    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
    指纹:
    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
    

    新版edge也是用的chromium的内核,Extention扩展多了一个17513

    3. csharp的HttpClient
    指纹:
    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
    指纹:
    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
    

    因为Fiddler是csharp写的,应该用的都是微软的封装的ssl实现吧。 所以和csharp的HttpClient是一样的指纹。

    5. java JDK自带的HttpsURLConnection
    指纹:
    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
    
    

    明显可以看出来 EllipticCurve 多了很多!

    6. Apache HttpClient
    指纹:
    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
    
    

    相比上面几个 在 EllipticCurve 上面有明显不一样!

    7. curl
    指纹:
    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. python3的Request
    指纹:
    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
    

    哈哈,实践是检验真理的唯一标准, 不难看出来为什么阿里的waf这么容易就能干掉curl 和 python脚本

    ByPass 有办法??当然可以

    可以私信交流

    我是正东,学的越多不知道也越多。这个公众号是我的实验小天地,我会分享一些我开源的工具(欢迎你来提意见),好玩好用的新技术。如果你也和我一样喜欢折腾技术请关注 !


    如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!欢迎各位转载,转载文章之后须在文章页面明显位置给出作者和原文连接,谢谢。
  • 相关阅读:
    转来的:Diablo中的七大套装背景
    我的单元测试认识之路(下)
    《You can do it!》的chapter 2笔记
    一个枚举的简单应用
    仿百度的搜索下拉提示
    简单的 菜单当前选中 解决思路
    单元测试框架NUnit 之 Attributes特性(一)
    单元测试框架NUnit 之 constraints 约束
    分享个自己的ajax封装
    开源单元测试工具 Nunit
  • 原文地址:https://www.cnblogs.com/yudongdong/p/14855507.html
Copyright © 2011-2022 走看看