0

我正在使用 OpenSSL 为 NodeJS 编写本机 DTLS 模块。它使用内存 BIO,因此节点自己的套接字可用于控制数据流。一切似乎都在工作,但我在 DOS 缓解方面遇到了一些问题。

根据规范,发送到服务器的初始 ClientHello 应该被拒绝,并且服务器将发送一个包含 cookie 的 HelloVerifyRequest 以从客户端重新发送回。这一切都很好,但是当客户端发回第二个 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 获取我的数据 [13] = 3 aka HelloVerifyRequest)但这里要注意的重要一点是,如果您附加调试器并将验证 lambda 上的断点不会被命中。

4

3 回答 3

1

让我试着揭开 CLIENT_HELLO - HELLO_VERIFY_REQUEST 的神秘面纱

RFC6347 - 拒绝服务对策

解释说,尽管 CLIENT_HELLO 可能被欺骗,但 HELLO_VERIFY_REQUEST 用于证明客户端确实在监听地址。因此,服务器使用来自 CLIENT_HELLO 的数据、客户端 IP 地址和端口以及服务器机密创建一个 cookie。

Cookie = HMAC(秘密、客户端 IP、客户端参数)

服务器不存储该 cookie,否则大规模欺骗将需要大量内存。因此,每次收到 CLIENT_HELLO 时,服务器都会重新计算 cookie。如果 CLIENT_HELLO 包含 cookie,则将其与新计算的 cookie 进行比较。如果 CLIENT_HELLO 更改了其他任何内容,那么新计算的 cookie 现在可能会有所不同。这包括,客户端 IP(地址+端口)也必须保持不变。如果服务器根据 RFC6347 的建议更新其秘密,则 cookie 在极少数情况下也可能不同

对这种方案的一种潜在攻击是攻击者从不同地址收集大量 cookie,然后重用它们来攻击服务器。服务器可以通过频繁更改 Secret 值来防御这种攻击,从而使这些 cookie 无效。

这样,检查您的 CLIENT_HELLO 是否已更改(可能只是源端口已更改),或者服务器是否过于频繁地更新密钥。如果您可以提供一些wireshark 捕获,我可以为您提供帮助。

于 2019-09-16T06:50:01.783 回答
0

您可以尝试使用nodejs-dtls

示例用法可以在 IOTBroker 中找到。云nodejs客户端

看看 CoAP 和 MQTT-SN 协议

BR

玉莲欧法

莫比乌斯软件

于 2019-01-07T18:15:06.583 回答
0

根据我的经验,您必须提供一个BIO_addrto DTLSv1_listen。就您而言,您正在通过NULL. 我也认为这会奏效,但唉,它没有!

于 2019-09-07T20:15:01.047 回答