如何避免此代码中的 DOS 攻击

How to avoid DOS attack in this code?

本文关键字:DOS 攻击 代码 何避免      更新时间:2023-10-16

我有一个用 C/C++ 编写的代码,看起来像这样:

    while(1)
{
    //Accept
    struct sockaddr_in client_addr;
    int client_fd = this->w_accept(&client_addr);
    char client_ip[64];
    int client_port = ntohs(client_addr.sin_port);
    inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip));
    //Listen first string
    char firststring[512];
    memset(firststring,0,512);
    if(this->recvtimeout(client_fd,firststring,sizeof(firststring),u->timeoutlogin) < 0){
        close(client_fd);
    }
    if(strcmp(firststring,"firststr")!=0)
    {
        cout << "Disconnected!" << endl;
        close(client_fd);
        continue;
    }
    //Send OK first string
    send(client_fd, "OK", 2, 0);

    //Listen second string
    char secondstring[512];
    memset(secondstring,0,512);
    if(this->recvtimeout(client_fd,secondstring,sizeof(secondstring),u->timeoutlogin) < 0){
        close(client_fd);
    }
    if(strcmp(secondstring,"secondstr")!=0)
    {
        cout << "Disconnected!!!" << endl;
        close(client_fd);
        continue;
    }
    //Send OK second string
    send(client_fd, "OK", 2, 0);

}
    }

所以,这是可配药的。我在perl中编写了一个非常简单的dos脚本,可以关闭服务器。

#Evildos.pl
use strict;
use Socket;
use IO::Handle;
sub dosfunction
{
my $host = shift || '192.168.4.21';
my $port = 1234;
my $firststr = 'firststr';
my $secondstr = 'secondstr';
my $protocol = getprotobyname('tcp');
$host = inet_aton($host) or die "$host: unknown host";
socket(SOCK, AF_INET, SOCK_STREAM, $protocol) or die "socket() failed: $!";
my $dest_addr = sockaddr_in($port,$host);
connect(SOCK,$dest_addr) or die "connect() failed: $!";
SOCK->autoflush(1);
print SOCK $firststr;
#sleep(1);
print SOCK $secondstr;
#sleep(1);
close SOCK;
}
my $i;
for($i=0; $i<30;$i++)
{
&dosfunction;
}

循环 30 次后,服务器出现故障。

问题是:有没有一种方法、一个系统、一个解决方案可以避免这种类型的攻击?

编辑:接收超时

int recvtimeout(int s, char *buf, int len, int timeout)
     {

fd_set fds;
int n;
struct timeval tv;
// set up the file descriptor set
FD_ZERO(&fds);
FD_SET(s, &fds);
// set up the struct timeval for the timeout
tv.tv_sec = timeout;
tv.tv_usec = 0;
// wait until timeout or data received
n = select(s+1, &fds, NULL, NULL, &tv);
if (n == 0){
    return -2; // timeout!
}
if (n == -1){
    return -1; // error
}
// data must be here, so do a normal recv()
return recv(s, buf, len, 0);
    }

我认为一般来说,DOS攻击没有任何100%有效的软件解决方案;无论你做什么,总有人会在你的网络接口上抛出比它所能处理的更多的数据包。

但是,在这种特殊情况下,看起来您的程序一次只能处理一个连接 - 也就是说,在连接 #1 完成其事务(或超时)之前,不会处理传入连接 #2。 所以这是一个明显的瓶颈 - 攻击者所要做的就是连接到你的服务器,然后什么都不做,你的服务器实际上被禁用了(无论你的超时期限有多长)。

为了避免这种情况,您需要重写服务器代码以一次处理多个 TCP 连接。 您可以通过切换到非阻塞 I/O(通过将O_NONBLOCK标志传递给 fcntl()并使用 select() 或 poll() 等来同时等待多个套接字上的 I/O,或者通过生成多个线程或子进程来并行处理传入连接,或者通过使用异步 I/O 来做到这一点。 (我个人更喜欢第一种解决方案,但都可以在不同程度上起作用)。 在第一种方法中,在接受来自该IP地址的新套接字之前,强制关闭来自给定IP地址的任何现有套接字也是实用的,这意味着任何给定的攻击计算机一次最多只能占用服务器上的一个套接字,这将使该人更难DOS您的计算机,除非他可以访问许多客户端计算机。

您可以阅读本文,了解有关同时处理多个 TCP 连接的更多讨论。

DOS和DDOS攻击的主要问题是它们利用了您的弱点:即可用于提供服务的内存/端口数量/处理资源有限。即使您使用像亚马逊农场这样的东西具有无限的可扩展性(或接近),您可能也希望限制它以避免账单通过屋顶。

在服务器级别,您主要担心的应该是通过施加自我保护限制来避免崩溃。例如,您可以设置您知道可以处理的最大连接数,并简单地拒绝任何其他连接。

完整的策略将包括专门的材料,例如防火墙,但总有一种方法可以玩它们,您将不得不忍受它。

例如讨厌的攻击,请阅读维基百科上的慢懒猴。

Slowloris 试图保持与目标 Web 服务器的许多连接保持打开状态,并尽可能长时间地保持打开状态。它通过打开与目标 Web 服务器的连接并发送部分请求来实现此目的。它将定期发送后续 HTTP 标头,添加(但永远不会完成)请求。受影响的服务器将保持这些连接打开,填满其最大并发连接池,最终拒绝来自客户端的其他连接尝试。

DOS攻击有许多变种,因此很难找到具体的答案。

你的代码在成功时会泄漏文件句柄,这最终会让你用完要分配的 fds,使accept()失败。

完成后close()套接字。

另外,要直接回答您的问题,除了纠正错误代码之外,没有其他解决方案可以解决由错误代码引起的DOS。

这不是DOS攻击的万能药,但使用非阻塞套接字肯定会有助于可扩展性。如果可以扩大规模,则可以缓解许多DOS攻击。此设计更改包括将接受呼叫中使用的侦听套接字和客户端连接套接字设置为非阻塞。

然后,您不会阻止 recv()、send() 或 accept() 调用,而是阻止轮询、epoll 或选择 call - 然后尽可能多地处理该连接的事件。 使用合理的超时(例如 30 秒),以便您可以从轮询调用中唤醒,以扫描并关闭任何似乎没有通过协议链进行的连接。

这基本上要求每个套接字都有自己的"连接"结构,该结构跟踪该连接相对于您实现的协议的状态。这可能还意味着保留所有套接字的(哈希)表,以便它们可以映射到其连接结构实例。 这也意味着"发送"也是非阻塞的。发送和接收无论如何都可以返回部分数据量。

您可以在此处查看我的项目代码中的非阻塞套接字服务器示例。 (环顾第 360 行,了解 Run 方法中主循环的开始)。

将套接字设置为非阻塞状态的示例:

int SetNonBlocking(int sock)
{
    int result = -1;
    int flags = 0;
    flags = ::fcntl(sock, F_GETFL, 0);
    if (flags != -1)
    {
        flags |= O_NONBLOCK;
        result = fcntl(sock , F_SETFL , flags);
    }
    return result;
}

我会使用 boost::asio 函数中的boost::asio::async_connector来创建多个连接处理程序(适用于单线程和多线程环境)。在单线程情况下,您只需要不时运行boost::asio::io_service::run以确保通信有时间处理

你想使用 asio 的原因是因为它非常擅长处理异步通信逻辑,所以如果连接被阻塞,它不会阻塞(就像你的情况一样)。您甚至可以安排要投入多少处理来打开新连接,同时继续为现有连接提供服务