In April 2022, a critical vulnerability was disclosed in curl, a widely used command-line tool and library for transferring data with URLs. This bug, identified as CVE-2022-27780, revolves around the way curl parses URLs—specifically, its handling of percent-encoded separators in host names. This flaw can let attackers bypass common security filters by changing host names in unexpected ways. In this article, I’ll break down this vulnerability for all skill levels, show how you can trigger it, and discuss how it can be abused.

Understanding the Bug

First, let’s see what curl *should* do. Normally, URLs are split into different parts like the protocol, host, path, etc. Special characters like / are used as separators, and percent-encoding (e.g., %2F for /) is usually allowed for the *path* but not the host.

The Problem: Curl’s old URL parser would misinterpret percent-encoded separators (like %2F for /) in the host section of the URL.

In plain words: If you trick curl with a percent-encoded slash in the *host* part, curl treats it like a real slash. This can swap parts of the URL, causing curl to make requests to a different host than the one you intended.

Exploiting the Vulnerability

Let’s look at a real-world example. Suppose an application uses curl to fetch data from URLs and filters requests so only certain hosts are allowed—maybe to block access to internal addresses or prevent attacks.

Example Vulnerable Code

import subprocess

def fetch_url(url):
    # Only allow URLs starting with example.com
    if url.startswith("http://example.com";):
        output = subprocess.check_output(["curl", url])
        return output
    else:
        raise ValueError("Host not allowed")

# Attacker's input:
malicious_url = "http://example.com%2F127...1/";
print(fetch_url(malicious_url))

What’s Going On Under the Hood?

- The application’s check (url.startswith("http://example.com";)) passes, because the URL *looks* like it’s going to example.com.
- But: When curl parses the URL, it decodes %2F to /. So http://example.com%2F127...1/ becomes http://example.com/127...1/.
- Curl interprets example.com as the “userinfo” part, and 127...1 as the actual *host*. The request ends up going to 127...1!  

That’s a big problem, because it allows a user to sneak a forbidden host past your URL filter.

Try this command:

curl -v "http://example.com%2F127...1/";

What happens?
Curl connects to 127...1, not example.com. You can test with a local server to confirm.

Why Is This Dangerous?

- Filter Bypass: If your service uses curl to proxy requests to “safe” hosts, this bug lets attackers bypass your filter to access internal resources, cloud metadata, or other illegal endpoints.
- SSRF (Server-Side Request Forgery): Attackers can potentially access services like localhost or 169.254.169.254 by sneaking internal IPs through encoded hostnames.
- Log/Detection Evasion: Security tools checking logs for requests to bad hosts might miss the attack, because the logged URL looks innocent.

References

- CVE-2022-27780 - MITRE CVE Entry
- curl Security Advisory
- GitHub Issue Discussion

How to Fix

Update immediately.  
Curl fixed this bug in version 7.83.1 by rejecting percent-encoded separators in the host name.

- If you run a server using curl/libcurl, upgrade to at least 7.83.1.
- If you filter or validate URLs, always parse URLs using a *robust library*, not just string checks or regex.

Conclusion

CVE-2022-27780 is a great example of how tiny URL parsing quirks can have massive real-world impacts. If you’re building anything that fetches URLs—even if you trust your inputs—make sure your libraries are up-to-date and your code is doing *real* validation, not just naive string checks. Otherwise, an attacker might just sneak right past your security… one percent sign at a time.

Timeline

Published on: 06/02/2022 14:15:00 UTC
Last modified on: 06/22/2022 13:48:00 UTC