SV.BFC.USING_STRUCT

不安全地绑定套接字

不安全地绑定套接字可能使恶意用户能够“劫持”服务器套接字,从而控制连接并窃取用户信息。

大多数系统会将 SO_REUSEADDR 套接字选项的设置和对 bind() 的调用相结合,从而允许将任意进程绑定至已经与绑定了前一个流程的 INADDR_ANY 所绑定的端口。使用 INADDR_ANY 使服务器可以接收来自所有主机的网络接口的数据。在绑定到特定地址时,bind() 函数并不会检查以确保在同一端口上没有已经绑定到 INADDR_ANY 的套接字。因此,恶意代码可以绑定到已经与非特权端口上的 INADDR_ANY 所绑定的服务器的特定地址,并且可以访问其 UDP 数据包或 TCP 连接。

针对此漏洞,SV.BFC.USING_STRUCT 检查器会标记出相关的 INADDR_ANY 实例(实例位于与对 bind 函数的调用相关联的 struct sockaddr 的 sin_addr.s_addr 字段中)。

漏洞与风险

通过对 socket 函数的调用来创建套接字时,该套接字会存在于命名空间中,但是不会为其分配名称。bind 函数会通过给未命名的套接字分配本地名称来建立套接字的本地关联。

恶意应用程序会通过调用 setsockopt() 来将套接字选项设置为 SO_REUSEADDR,然后强制绑定到已经用于标准网络协议服务的端口,从而禁止对这些服务进行访问。

对于 TCP 连接,如果攻击者将其它套接字绑定至已经使用的端口,则无法保证该端口上的所有入站 TCP 连接请求都能通过正确的套接字进行处理:行为无法确定。不确定行为的例外情况是多播套接字。如果同一个多播组的两个套接字成员绑定至相同的网络接口和端口,则会将数据同时传递到这两个套接字,而不是随机传递到其中的一个套接字。

在这些情况下,来自各种网络服务的数据包可能遭窃,服务也可能受骗。恶意用户也可能对服务器发动拒绝服务 (DoS) 攻击。

缓解与预防

虽然使用 INADDR_ANY 比指定确切的地址更加容易,但是这样做将导致其他程序也可以侦听同一端口以获得计算机上的具体地址,而且恶意程序也可以截获从客户端发往服务器的数据。因此最好在代码中使用具体的地址。

漏洞代码示例

复制
  #include <sys/types.h>
  
  #include <sys/socket.h>
  #include <netinet/in.h>
  #include <unistd.h>
  #include <stdio.h>
  #include <arpa/inet.h>
  
  void bind_socket(void) {
 
      int server_sockfd;
      int server_len;
      struct sockaddr_in server_address;
 
      unlink("server_socket");
      server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
 
      server_address.sin_family = AF_INET;
      server_address.sin_port = 21;
      server_address.sin_addr.s_addr = htonl(INADDR_ANY);
 
      server_len = sizeof(struct sockaddr_in);
 
      bind(server_sockfd, (struct sockaddr *) &server_address, server_len);
 }

Klockwork 标记了第 24 行,该行中的代码绑定至存在相关 INADDR_ANY 关键字的 struct sockaddr。server_address.sin_addr.s_addr 字段中的 INADDR_ANY 关键字表示 opportunistic 代码可以绑定到同一端口,而且可能导致数据包遭窃或服务受骗。

修正代码示例

复制
  #include <sys/types.h>
  
  #include <sys/socket.h>
  #include <netinet/in.h>
  #include <unistd.h>
  #include <stdio.h>
  #include <arpa/inet.h>
  
  void bind_socket(void) {
 
      int server_sockfd;
      int server_len;
      struct sockaddr_in server_address;
 
      unlink("server_socket");
      server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
 
      server_address.sin_family = AF_INET;
      server_address.sin_port = 21;
      server_address.sin_addr.s_addr = inet_addr("192.168.1.10");
 
      server_len = sizeof(struct sockaddr_in);
 
      bind(server_sockfd, (struct sockaddr *) &server_address, server_len);
 }

在经修正的代码示例中,INADDR_ANY 关键字已替换为具体的 IP 地址。