curl中的连接和代理以及证书穿透


curl中的连接和代理以及证书穿透

1.curl中的主机名解析与技巧

1.1编辑hosts文件,改变域名指向
vim /etc/hosts
182.61.200.6 www.baidu.com
1.2指定Host头
HTTP客户端一般会通过Host:标头告诉HTTP服务器它要连接到哪个服务器,因为通常同一个HTTP服务器实例会有多个名字
    curl -H "Host: www.example.com" http://182.61.200.6/
注意点: HTTPS服务器通信时,只使用Host:标头是不够的。TLS协议中有一个单独的扩展字段,称作SNI(Server Name Indication,服务器名称指示),客户端用它来告诉服务器它想要与哪台服务器通信。curl只会从指定的URL中提取SNI。
简述: 不能使用这种方式访问https连接,curl会直接拿主机名2.2.2.2与服务器发来的域名证书进行匹配,结果可能不通过。curl 可以使用-k忽略证书校验
    curl -H "Host: www.baidu.com" https://182.61.200.6
1.3提供自定义的IP地址
使用--resolve将地址插入curl的DNS缓存中,以便curl相信那就是自己解析得到的地址。
如果使用的是HTTPS,那么将发送URL中的SNI,并且curl会验证服务器端的响应,以确保使用的是URL中的名字。
    curl --resolve www.baidu.com:443:182.61.200.6 https://www.baidu.com/
注意点: --resolve两个作用:
    一个dns缓存,不需要修改本地hosts;
    一个是tls交互提供sni扩展;详情抓包见客户端的client-hello部分
    Extension: server_name (len=18)
        Type: server_name (0)
        Length: 18
        Server Name Indication extension
            Server Name list length: 16
            Server Name Type: host_name (0)
            Server Name length: 13
            Server Name: www.baidu.com
1.4提供替换名
--connect-to选项提供了一个小变体。当需要连接特定主机名和端口时,你可以通过这个选项为curl指定替换主机名和端口。
假设你有一个名为www.example.com的站点,这个站点实际由三个HTTP服务器实例提供服务:load1、load2和load3。在正常的流程中,curl解析主站点地址,并与其中一个服务器实例通信(它得到一个地址列表并从中选择了一个地址),一切都很正常
    curl --connect-to www.example.com:80:load1.example.com:80 http://www.example.com

2.curl中的代理与证书穿透测试

这里本意想测试客户端是否可以穿过代理,自己设定域名的解析的ip地址,做证书校验。
2.1 http代理
HTTP代理是客户端用来通过HTTP完成传输的代理。默认情况下,curl假设你使用-x或--proxy选项指定的主机就是HTTP代理
    curl -x 192.168.0.1:8080 http:/example.com/
注意:
    代理接收你的请求,将它转发给真实的服务器,然后从服务器获取响应,再将响应返回给客户端
2.2https代理
HTTPS旨在为客户端和服务器(以及后端)提供安全的端到端隐私。为了在使用HTTP代理时仍然能够提供这种安全隐私,HTTP协议提供了一种特殊的请求,curl可以用它设置一个通道,这个通道经过代理,并可以对流量进行加密和验证。这个HTTP方法就是CONNECT
    curl -x proxy.example.com:80 https://example.com/
注意:
    用CONNECT方法设置好通道后,数据流经通道时就会被加密,代理在不破坏加密的情况下是无法查看或修改流量的:
2.3关于https代理模式dns解析的实验
默认情况http的代理都是代理端服务则域名解析。怎么让客户端指定呢,测试下
测试代理服务器
 docker run -d --name tinyproxy --restart always --network=host  -p 8888:8888 stilleshan/tinyproxy

测试访问:
 curl --proxy http://10.206.0.15:8888   https://www.baidu.com --resolve www.baidu.com:443:182.61.200.6
 curl --proxy http://10.206.0.15:8888   --resolve www.baidu.com:443:182.61.200.6 https://182.61.200.6/
通过抓包发现:
    1.客户端先与代理建立connect连接,在通过代理建立与服务器的连接通道。
    2.连接通道建立后客户端端与服务器建立连接,比如tls验证,数据转发
    3.通过resolve增加dns解析的方式在这种方式下没有生效,域名的地址还是由代理进行了解析
    4.通过https://182.61.200.6方式会提示证书校验错误,出现这种错误的根本原因,是curl命令在tls握手过程中无法添加sni头,虽然可以获取一个服务器端发来的默认整数,但是curl会直接提取url中的host进行校验
结论:
    1.客户端使用https代理时,无法控制域名解析的地址,即使指定了https://ip地址的访问方式,也会因为curl命令证书校验不了ip地址而失败。当然可以通过-k忽略证书
    2.对其他语言而言,个人认为可以通过sni方式验证,待实验
测试java包可以实现提供给大家参考,抓包就不提供了。

package org.example;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.ProxySelector;
import java.net.SocketAddress;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Collections;
import java.util.List;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SNIHostName;

public class HttpClientWithSNIAndProxy {
    public static void main(String[] args) throws Exception {
        System.setProperty("jdk.httpclient.allowRestrictedHeaders", "connection,content-length,expect,host,upgrade");

        // 配置代理
        ProxySelector proxySelector = new ProxySelector() {
            @Override
            public List<Proxy> select(URI uri) {
                return List.of(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("10.206.0.15", 8888)));
            }

            @Override
            public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
                // 处理连接失败的情况
                ioe.printStackTrace();
            }
        };

        // 配置SSL上下文和SNI
        SNIHostName serverName = new SNIHostName("www.baidu.com");
        SSLParameters sslParams = new SSLParameters();
        sslParams.setServerNames(Collections.singletonList(serverName));

        // 使用默认的SSL上下文
        SSLContext sslContext = SSLContext.getDefault();

        HttpClient client = HttpClient.newBuilder()
                .proxy(proxySelector)
                .sslContext(sslContext)
                .sslParameters(sslParams)
                .build();

        // 构建请求
        HttpRequest request = HttpRequest.newBuilder()
                .uri(new URI("https://180.101.50.188"))
                .header("Host", "www.baidu.com")
                .build();

        // 发送请求并获取响应
        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
        System.out.println(response.body());
    }
}

3.补充知识

1.sni概念:
    Server Name Indication (SNI) 是TLS协议(以前称为SSL协议)的扩展,该协议在HTTPS中使用。它包含在TLS/SSL握手流程中,以确保客户端设备能够看到他们尝试访问的网站的正确SSL证书。该扩展使得可以在TLS握手期间指定网站的主机名或域名 ,而不是在握手之后打开HTTP连接时指定。
2.TLS的SNI扩展有什么作用?
    Web服务器通常负责多个主机名–或域名。如果网站使用HTTPS 则每个主机名将具有其自己的SSL证书。
    在HTTPS中,先有TLS握手,然后才能开始HTTP对话。如果没有SNI,客户端将无法向服务器指示正在与之通信的主机名。
    如果服务器可能为错误的主机名生成SSL证书。那么SSL证书上的名称与客户端尝试访问的名称不匹配,则客户端浏览器将返回错误信息,并通常会终止连接。
    通过 SNI,拥有多虚拟机主机和多域名的服务器就可以正常建立 TLS 连接了。

4.参考文件

1.curl必知必会 3.5章
2.HTTP权威指南 6-8章
2.SSL/TLS协议解析: https://zhuanlan.zhihu.com/p/446371370
3.什么是SNI: https://www.cloudflare.com/zh-cn/learning/ssl/what-is-sni/