On September 7, 2017, Equifax announced that attackers had accessed the personal records of roughly 143 million U.S. consumers. Names, Social Security numbers, birth dates, addresses, and in some cases driver's license numbers. The number was later revised upward to 147.7 million. The attack window was mid-May 2017 through July 29, 2017. Seventy-six days of unimpeded access to one of the three largest credit bureaus in the United States.
The headlines focused on executives selling stock. The interesting story was a Java library.
The single link in the chain
The attackers walked in through CVE-2017-5638, a remote code execution vulnerability in Apache Struts 2's Jakarta Multipart parser. The bug was disclosed by Apache and patched in Struts 2.3.32 and 2.5.10.1 on March 7, 2017. The CVSS v3 score was 10.0.
Content-Type: %{#context['com.opensymphony.xwork2.dispatcher.HttpServletResponse'].addHeader('X-Test','vuln')}
A crafted Content-Type header containing an OGNL expression was evaluated server-side. Full RCE as the application user. By March 8, public exploits were on GitHub. By March 9, Cisco Talos reported mass exploitation in the wild.
Equifax's online dispute portal, equifax.com/ondisputeportal/, was running Apache Struts 2.3.x and had not been patched. The Department of Homeland Security's US-CERT notice about CVE-2017-5638 was sent to Equifax on March 8, 2017. Equifax's own internal scanning on March 15 failed to detect vulnerable Struts instances, for reasons still partially unexplained in the subsequent GAO report.
First confirmed attacker access: May 13, 2017.
Why this is a supply chain story, not a patching story
Almost every post-mortem framed Equifax as a patch management failure. That framing is incomplete. Struts was a transitive concern for Equifax's web application teams. They were not Struts developers. They inherited Struts as part of an older Java EE stack, and they inherited the operational responsibility for knowing which of their internal applications used it, at which version, on which hosts.
That inventory did not exist. The March 15 internal scan used a signature-based network scanner that looked for HTTP fingerprints of Struts. The dispute portal responded in a way the scanner did not recognize, possibly because of a reverse proxy in front of it. The portal was therefore reported as not running Struts.
This is the core supply chain failure: when you consume a dependency, the signature of "am I running it" is not trivial. Equifax had an asset inventory. They had a CVE feed. They had a patch policy. What they did not have was an authoritative, build-time map from source code to deployed binary to running host.
Every organization with a deployment older than three years has some version of this gap.
The dependency archaeology
The 2018 GAO report (GAO-18-559) and the 2019 Senate Homeland Security and Governmental Affairs Committee investigation surfaced details that rarely made the trade press:
- The dispute portal had been acquired from a legacy product line. Its original developers were not the team operating it in 2017.
- The Struts version in use was 2.3.x, in the 2.3.14 to 2.3.29 range based on build artifacts recovered. All were vulnerable.
- The portal depended on a shared Java runtime managed by a different team than the application team, which complicated patching even once the vulnerability was identified on July 29.
- An expired TLS certificate on an internal inspection appliance meant that encrypted exfiltration from May through July was not decrypted and inspected. The attackers moved approximately 76 days' worth of data out in roughly 9,000 queries across 51 databases.
The TLS certificate detail is the one that stays with me. Equifax had the right control in place. It had lapsed. The failure mode was not "we lacked defense in depth", it was "our defense in depth silently stopped working".
What the attackers did once inside
Public indictments unsealed in February 2020 attributed the breach to four members of China's People's Liberation Army Unit 54th Research Institute. The TTP sequence was:
- CVE-2017-5638 against the dispute portal on May 13, 2017.
- A webshell dropped under the application user, used to enumerate internal network services.
- Credential harvesting from a configuration file stored unencrypted on the same host. Those credentials gave access to 48 unrelated internal databases.
- 9,000 separate queries over 76 days, tunneled out through the portal's TLS connection, which was unmonitored because of the expired cert.
- Exfiltration of the dataset in roughly 600 megabyte chunks.
The credentials in the config file are the second supply chain angle. That file was a template that had been cloned across environments when the portal was stood up. The developers who cloned it are almost certainly not employed by Equifax in 2017. Their decisions were live in production, four, five, six years later, unreviewed.
The lessons that are actually about supply chain
Inventory is adversarial. A CVE feed plus an asset DB is not an inventory. A real inventory knows which CVEs apply to which running processes and is continuously validated. The Struts 2.3.x instance on the dispute portal was only invisible because the inventory depended on network signatures. A build-time SBOM would have named it in seconds.
Transitive dependencies outlive their authors. The Equifax dispute portal was running code whose original developers had long since moved on. That is not unusual. The mitigation is to treat every inherited dependency as requiring the same vulnerability response protocol as first-party code, because from an attacker's perspective it is first-party code.
Secrets belong to the build. The credentials in the config file were not in a vault because vaults were not ubiquitous in 2012. They are in 2017. Any dependency-update process that does not also audit embedded secrets is missing half the problem.
Monitoring has a supply chain too. The expired TLS inspection certificate was a vendor appliance failure, compounded by an internal process that did not catch the expiry. The attackers were inside the environment because of Struts. They exfiltrated for 76 days because of a lapsed vendor cert.
What Equifax owed, and what it cost
The FTC, CFPB, and 50 state attorneys general settlement finalized in July 2019 assessed 575 million dollars in penalties and restitution, later expanded to up to 700 million. Equifax's stock lost roughly 35 percent of its value in the week following disclosure. The CEO, CIO, and CISO all departed within weeks.
Three indictments, one consent decree with the FTC running through 2024, and a generation of credit consumers whose data sits in forums to this day.
The cost of the patch, had it been applied on March 7, 2017, was one Maven version bump.
How Safeguard Helps
Equifax's failure mode was not being unable to patch. It was not knowing where the dependency lived. Safeguard inverts that problem: every build you ship through our pipeline produces a CycloneDX or SPDX SBOM that resolves transitive dependencies like org.apache.struts:struts2-core down to the host and container they run on. Reachability analysis then tells you whether the vulnerable parser is actually called in your code paths, which is how you separate 20 real findings from 2,000 theoretical ones. Griffin AI turns each finding into a pull-request-ready patch suggestion. Our TPRM module catches inherited applications from acquisitions before they become an uninventoried dispute portal, and policy gates prevent any new release from shipping a Struts version with a public RCE.