我正在使用OpenSSL为NodeJS编写本机DTLS模块。它使用内存BIO,因此节点自己的套接字可用于控制数据流。一切似乎都正常,但我在DOS缓解方面遇到了一些问题。
根据规范,发送给服务器的初始ClientHello应该被拒绝,并且服务器将发送HelloVerifyRequest,其中包含要从客户端重新发送的cookie。一切正常,但是当客户端发回第二个ClientHello时,由于某种原因DTLSv1_listen()调用导致我的cookie生成方法而不是cookie验证方法再次触发。奇怪的是,如果我发回第二个HelloVerifyRequest(长度和内容与第一个完全相同),我将得到一个ClientHello,它似乎触发了验证方法。
这是我写的一个小测试,用来说明我正在做的事情(不完全是,跳过了诸如导入证书/密钥,在调用握手后检查读/写的结果代码,释放内存等结果代码之类的东西)
TEST(New, Test) {
// Init context
auto ctx = SSL_CTX_new(DTLS_method());
SSL_CTX_set_cipher_list(ctx, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, [](int ok, X509_STORE_CTX * context) { return 1; });
SSL_CTX_set_cookie_generate_cb(ctx, [](SSL * ssl, unsigned char * cookie, unsigned int * cookie_len) {
return 1;
});
SSL_CTX_set_cookie_verify_cb(ctx, [](SSL * ssl, const unsigned char * cookie, unsigned int cookie_len) {
return 1;
});
// Init connections
auto client = SSL_new(ctx);
auto client_rbio = BIO_new(BIO_s_mem());
auto client_wbio = BIO_new(BIO_s_mem());
SSL_set_bio(client, client_rbio, client_wbio);
SSL_set_connect_state(client);
auto server = SSL_new(ctx);
auto server_rbio = BIO_new(BIO_s_mem());
auto server_wbio = BIO_new(BIO_s_mem());
SSL_set_bio(server, server_rbio, server_wbio);
SSL_set_accept_state(server);
std::vector<unsigned char> data;
// Client Hello, no cookie
SSL_do_handshake(client);
auto data_len = BIO_ctrl_pending(client_wbio);
data.resize(data_len);
BIO_read(client_wbio, data.data(), data.size());
ASSERT_EQ(data[13], 1);
// Hello Verify Request
BIO_write(server_rbio, data.data(), data.size());
DTLSv1_listen(server, NULL);
data_len = BIO_ctrl_pending(server_wbio);
data.resize(data_len);
BIO_read(server_wbio, data.data(), data.size());
ASSERT_EQ(data[13], 3);
// Client Hello, with cookie
BIO_write(client_rbio, data.data(), data.size());
SSL_do_handshake(client);
data_len = BIO_ctrl_pending(client_wbio);
data.resize(data_len);
BIO_read(client_wbio, data.data(), data.size());
ASSERT_EQ(data[13], 1);
// Should be pass...?
BIO_write(server_rbio, data.data(), data.size());
ASSERT_EQ(DTLSv1_listen(server, NULL), 1);
}
最后一个断言失败-在此示例中为-1,在我的实际代码中为0(随后的BIO_read为我获取了data [13] = 3 aka HelloVerifyRequest),但此处要注意的重要一点是,如果您附加了调试器,并在验证lambda上放置一个断点,它将不会被击中。
答案 0 :(得分:0)
答案 1 :(得分:0)
根据我的经验,您必须向BIO_addr
提供DTLSv1_listen
。就您而言,您正在传递NULL
。我还认为这会起作用,但是a,那没有用!
答案 2 :(得分:0)
让我尝试消除CLIENT_HELLO-HELLO_VERIFY_REQUEST的神秘感
RFC6347 - Denial-of-Service Countermeasures
说明,尽管可能会欺骗CLIENT_HELLO,但使用HELLO_VERIFY_REQUEST来证明客户端确实在监听地址。因此,服务器使用来自CLIENT_HELLO的数据,客户端IP地址和端口以及服务器机密来创建cookie。
Cookie = HMAC(秘密,客户端IP,客户端参数)
服务器不会存储该Cookie,否则大规模欺骗将需要大量内存。因此,服务器每次接收到CLIENT_HELLO时都会重新计算cookie。如果CLIENT_HELLO包含cookie,则将其与新计算的cookie进行比较。 现在,如果CLIENT_HELLO更改了Cookie的任何其他内容,则该新计算的Cookie可能会有所不同。这包括客户端IP(地址+端口)也必须保持不变。如果服务器根据RFC6347的建议更新其秘密,则在某些极少数情况下,cookie可能也会有所不同。
此方案的一种潜在攻击是攻击者收集一个 来自不同地址的Cookie数量,然后将其重复使用以 攻击服务器。服务器可以通过以下方式防御此攻击 经常更改Secret值,从而使这些cookie无效。
这样,检查您的CLIENT_HELLO是否已更改(可能只是源端口已更改),或者服务器更新密钥的频率太高。 如果您可以提供一些Wireshark的捕获信息,我可以为您提供帮助。