在使用OpenSSL内存BIO的第二个ClientHello之后无法触发验证方法

时间:2018-12-31 07:01:11

标签: c++ openssl dtls

我正在使用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上放置一个断点,它将不会被击中。

3 个答案:

答案 0 :(得分:0)

您可以尝试使用nodejs-dtls

可以在以下位置找到样品用法 IOTBroker. cloud nodejs client

看看CoAP和MQTT-SN协议

BR

尤利安·伊法

Mobius Software

答案 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的捕获信息,我可以为您提供帮助。