【问题标题】:How to correctly send binary data over HTTPS POST?如何通过 HTTPS POST 正确发送二进制数据?
【发布时间】:2023-03-25 13:41:02
【问题描述】:

我将二进制数据从客户端 (Debian 6.0.3) 发送到服务器 (Windows Server 2003)。为了绕过大多数防火墙,我使用 HTTPS POST。客户端和服务器使用 Boost.AsioOpenSSL 实现。首先,我实现了最简单的版本,它运行良好。

HTTP 标头:

POST / HTTP/1.1
User-Agent: my custom client v.1

[binary data]

(如果这很重要,[binary data] 不是 base64 编码的)

然后,在另一台客户端机器上它失败了(连接到同一台服务器机器)。行为不稳定。连接始终建立良好(端口 443)。大多数时候我通过 SSL 握手很好,但服务器没有收到数据(几乎没有数据,有时实际上收到一两个数据包)。有时我会收到 SSL 握手错误“短读”。有时我会收到无效数据。

客户端连接到服务器,握手,发送 HTTP POST 标头,然后无限发送二进制数据,直到出现问题。对于测试,我使用自定义生成的 SSL 证书。

服务器代码:

namespace ssl = boost::asio::ssl;
ssl::context context(io_service, ssl::context::sslv23);
context.set_options(ssl::context::default_workarounds | ssl::context::no_sslv2);
context.use_certificate_chain_file("server.pem");
context.use_private_key_file("server.pem", boost::asio::ssl::context::pem);

ssl::stream<tcp::socket> socket(io_service, context);

// standard connection accepting

socket.async_handshake(ssl::stream_base::server, ...);
...
boost::asio::async_read_until(socket, POST_header, "\r\n\r\n", ...);
...

客户端代码:

ssl::context context(io_service, ssl::context::sslv23);
context.load_verify_file("server.crt");
socket.reset(new ssl::stream<tcp::socket>(io_service, context));
socket->set_verify_mode(ssl::verify_none);

// standard connection

socket.async_handshake(ssl::stream_base::client, ...);
...

(错误处理连同无关代码一起省略)

如您所见,这是最简单的 SSL 连接。怎么了?原因可能是防火墙吗?

我在同一个 443 端口上尝试了不带 SSL 的简单 TCP,效果很好。

编辑:

尝试添加“Content-Type: application/octet-stream”,没有帮助。

编辑 2:

通常我会收到 HTTP POST 标头。然后我以chunk-size(4 bytes)chunk(chunk-size bytes)... 发送数据块。服务器收到chunk-size 很好,但是什么也没有。客户端不通知服务器问题(无错误)并继续发送数据。有时服务器可以接收一两个块,有时它接收到无效的chunk-size,但大多数时候什么都没有。

编辑 3:

比较在客户端和服务器上捕获的流量,没有发现任何差异。

解决方案

我从一开始就被这个问题误导了。将其缩小到令人惊讶的细节:

如果我在 Boost v.1.48(目前最新的一个)中使用 Boost.Asio 多缓冲区,则通过 SSL 套接字发送会失败。示例:

// data to send, protocol is [packet size: 4 bytes][packet: packet_size bytes]
std::vector<char> packet = ...;
uint32_t packet_size = packet.size();
// prepare buffers
boost::array<boost::asio::const_buffer, 2> bufs = {{boost::asio::buffer(&packet_size, sizeof(packet_size)), boost::asio::buffer(packet)}};
// send multi buffers by single call
boost::asio::async_write(socket, bufs, ...);

在此示例中分别发送packet_sizepacket 可以解决此问题。我绝不会将任何可疑行为称为错误,尤其是当它与 Boost 库有关时。但这一个看起来真的像一个错误。在 Boost v.1.47 上试过 - 工作正常。尝试使用通常的 TCP 套接字(不是 SSL 套接字) - 工作正常。在 Linux 和 Windows 上都一样。

我将在 Asio 邮件列表中找到有关此问题的任何报告,如果没有找到,我将报告。

【问题讨论】:

  • 不使用 SSL 是否可以工作?您确定二进制数据不包含嵌入的换行符吗?
  • @AlanStokes:它可以工作,至少在长时间测试期间在另一台客户端机器上工作。嵌入式数据可以包含任何东西,这有关系吗?
  • @AlanStokes:抱歉,前面的评论不准确:它在这台机器上不使用 SSL,在另一台机器上与 SSL 一起工作。我只是无法理解有什么区别。这些机器位于不同的网络中。这就是为什么我怀疑防火墙,但重新检查并没有发现。
  • 我建议你在工作和不工作上尝试 Wireshark 并进行比较。 (当然,关闭 SSL 会更容易。)
  • @AlanStokes:是的,尝试分析流量。在双方我看到相同:“客户端->服务器:TLSv1应用程序数据,应用程序数据,...”,“服务器->客户端:TCP https ->一些端口[ACK] ...”

标签: c++ ssl https boost-asio


【解决方案1】:

如果你不必在web服务器前操作,你就没有 使用 HTTPS 协议。从防火墙的角度来看 HTTPS 看起来还像 另一个 SSL 连接,它不知道发生了什么。所以如果 您唯一需要的只是传递数据 - 而不是实际的 Web 服务器,使用 只是通过 443 端口的 SSL 连接。

所以只需对您的 SSL 连接进行故障排除,该问题与 HTTP 无关。


如果您想使用 HTTP Web 服务器而不是自定义客户端:

两点:

  1. 您需要指定 Content-Length。
  2. 如果您使用的是 HTTP/1.1,则需要指定 Host 标头。

最简单的应该是

POST /url HTTP/1.0
User-Agent: my custom client v.1
Content-Type: application/octet-stream
Content-Length: NNN

Actual Content

或者对于 HTTP/1.1

POST /url HTTP/1.1
Host: www.example.com
User-Agent: my custom client v.1
Content-Type: application/octet-stream
Content-Length: NNN

Actual Content

注意:您不能发送无限数据。 HTTP 协议需要固定的内容长度 大多数情况下,Web 服务器会先加载数据,然后再将其传递给 后端。

所以你必须按块传输数据。

【讨论】:

  • 一些防火墙使用transparent proxies来控制SSL连接,从而模拟HTTPS。客户端和服务器都是我实现的。我不知道content-length,因为我的数据是无穷无尽的,但我会尝试指定一些较大的值来检查这是否重要。通常我会在服务器上收到 HTTP POST 标头和第一个数据块大小。为问题添加了一些信息。
  • 如果您事先不知道流的大小,那么根本不要使用Content-Length 标头。它的值必须与发送的总大小相匹配。您将需要使用带有 Transfer-Encoding: chunked 标头的 HTTP 1.1 请求以块的形式发送流数据。
  • @Andy T 这意味着透明代理知道你的私钥......或者 SSL 客户端忽略了中间人攻击或不验证 SSL 证书。
  • @RemyLebeau-TeamB:已解决问题,请查看我的回答或更新的问题
【解决方案2】:

我从一开始就被这个问题误导了。将其缩小到令人惊讶的细节:

如果我在 Boost v.1.48(目前最新的一个)中使用 Boost.Asio 多缓冲区,则通过 SSL 套接字发送会失败。示例:

// data to send, protocol is [packet size: 4 bytes][packet: packet_size bytes]
std::vector<char> packet = ...;
uint32_t packet_size = packet.size();
// prepare buffers
boost::array<boost::asio::const_buffer, 2> bufs = {{boost::asio::buffer(&packet_size, sizeof(packet_size)), boost::asio::buffer(packet)}};
// send multi buffers by single call
boost::asio::async_write(socket, bufs, ...);

在此示例中分别发送 packet_sizepacket 可以解决此问题。我绝不会将任何可疑行为称为错误,尤其是当它与 Boost 库有关时。但这一个看起来真的像一个错误。在 Boost v.1.47 上试过 - 工作正常。尝试使用通常的 TCP 套接字(不是 SSL 套接字) - 工作正常。在 Linux 和 Windows 上都一样。

我将在 Asio 邮件列表中找到有关此问题的任何报告,如果没有找到,我将报告。

【讨论】:

  • 一年多之后 - 有什么发现吗?
【解决方案3】:

(编辑:我最初删除了这个,因为我意识到它并没有真正使用 HTTP。在你认为你可能有 MITM 代理并且应该使用正确的 HTTP 的评论之后,我正在取消删除/编辑。)

POST / HTTP/1.1
User-Agent: my custom client v.1

[binary data]

无论是否是二进制数据,在纯 HTTP 或 SSL/TLS 中,您都需要 Content-Length 标头或使用 chunked transfer encoding。这是 HTTP 规范的 this sectionContent-Type 标头也很有用。

分块传输编码适用于您不一定知道流长度的情况。 (发送数据时总是需要某种形式的分隔符,如果只是为了可靠地检测到它何时结束。)

话虽如此,如果您获得的证书不是您的服务器,您应该能够确定您是否使用了 MITM 代理,该代理在 SSL/TLS 之上查看应用层。如果您仍然与您赢得的服务器证书成功握手,那么就没有这样的代理。即使是 HTTP 代理也会使用 CONNECT 并中继所有内容,而不会更改 SSL/TLS 连接(因此不会更改您最初的伪 HTTP)。

【讨论】:

  • 尝试添加“Content-Length: 1000000\r\n”,没有任何改变
  • Content-Length 需要与您发送的内容的确切大小相匹配,因此分块编码通常可能是合适的。话虽如此,不幸的是,这似乎不是您的问题的原因。
  • 解决了问题,请看我的回答或更新的问题
猜你喜欢
  • 2011-12-13
  • 2011-03-04
  • 2012-02-18
  • 1970-01-01
  • 2012-01-02
  • 1970-01-01
  • 1970-01-01
  • 2010-12-06
  • 2019-11-24
相关资源
最近更新 更多