4

我有一个用 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 次,服务器宕机。

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

编辑:recvtimeout

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);
    }
4

5 回答 5

4

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

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

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

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

于 2012-05-28T15:46:38.850 回答
2

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

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

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

例如令人讨厌的攻击,请阅读wikipedia 上的Slow Loris

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

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

于 2012-05-28T15:43:33.067 回答
1

这并不是 DOS 攻击的万灵药,但使用非阻塞套接字肯定有助于提高可扩展性。如果你可以扩大规模,你可以减轻许多 DOS 攻击。此设计更改包括将接受调用中使用的侦听套接字和客户端连接套接字设置为非阻塞。

然后,您不会阻塞 recv()、send() 或 accept() 调用,而是阻塞 poll、epoll 或 select 调用 - 然后尽可能多地处理该连接的事件。使用合理的超时时间(例如 30 秒),这样您就可以从轮询呼叫中醒来,以清除并关闭任何似乎没有通过您的协议链进行的连接。

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

您可以在此处查看我的项目代码中的非阻塞套接字服务器示例。(在第 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;
}
于 2012-05-28T16:50:55.087 回答
0

您的代码在成功时会泄漏文件句柄,这最终会使您用尽 fds 来分配,从而accept()导致失败。

close()完成后的套接字。

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

于 2012-05-28T15:49:05.270 回答
0

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

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

于 2012-05-28T15:51:55.290 回答