PAC-X002 · shexpmatch-trailing-asterisk

Trailing * in a host pattern matches attacker-controlled suffixes

high · Security and fail-safety · Top 20 #3

shExpMatch(host, "example.com*") matches example.com.evil.test and example.comms.test. Anyone who controls a domain can append the trusted name as a subdomain label and inherit the rule.

Why it matters

shExpMatch is a glob over the whole string. A trailing * after a hostname does not mean “this domain”; it means “any string that starts with these characters”. example.com* matches example.com.evil.test (lab, 2026-05-20), example.com-cdn.test and example.comms.test. Registering such names requires no cooperation from the owner of example.com. If the rule grants DIRECT or a privileged proxy, an attacker gets the same treatment for a host they control. A trailing * after an IP prefix (192.168.1.1*) has the same problem and additionally matches 192.168.1.10 through 192.168.1.19.

Trailing * is legitimate in URL patterns after a slash (http://intranet.corp.example/*) where it stands for the path.

How to fix

Match the domain exactly (host == "example.com") and its subdomains with "*.example.com" or dnsDomainIs(host, ".example.com"); never end a host pattern with *.

Examples

Bad

function FindProxyForURL(url, host) {
  if (shExpMatch(host, "example.com*")) {
    return "DIRECT";
  }
  return "PROXY proxy.corp.example:8080";
}
Open bad example in checker

Good

function FindProxyForURL(url, host) {
  if (host == "example.com" || shExpMatch(host, "*.example.com")) {
    return "DIRECT";
  }
  return "PROXY proxy.corp.example:8080";
}
Open good example in checker

Engine behaviour

EngineBehaviourSource
pacparser"example.com*" matches example.com.evil.testlab, verified 2026-05-20

Source: code = read in the engine's source, doc = vendor documentation, lab = observed in a lab run, expert = practitioner knowledge, not yet verified.

Related rules

References