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/