Skip to main content
XXE in Nokogiri: What Went WrongIncident
4 min readFor Security Engineers

XXE in Nokogiri: What Went Wrong

On January 11th, the Snyk security research team disclosed XML External Entity (XXE) vulnerabilities in Nokogiri, a widely-used Ruby XML parsing library. Versions prior to 1.5.4 were vulnerable by default to XXE attacks, allowing attackers to read local files, perform server-side request forgery, or trigger denial of service.

This issue highlights a persistent problem: the gap between knowing a vulnerability exists and actively preventing it in your dependency chain.

Timeline

The disclosure timeline from Snyk to the Nokogiri maintainers began on January 11th. However, the vulnerability existed in all versions prior to 1.5.4, leaving applications running older versions exposed long before the research team's findings.

The root cause was traced to libxml2, the C library that Nokogiri uses. When Nokogiri parsed XML documents, it inherited libxml2's default behavior of processing external entities—a feature intended for legitimate document composition but exploited by attackers to exfiltrate data.

Which Controls Failed or Were Missing

Three control failures contributed to this exposure:

1. Insecure defaults in the XML parser
Nokogiri versions before 1.5.4 did not disable external entity processing by default. The library passed XML input directly to libxml2 without setting the NOENT and DTDLOAD flags to prevent entity expansion and DTD loading.

2. Missing dependency version constraints
Applications that specified Nokogiri in their Gemfile without a minimum version constraint (gem 'nokogiri' instead of gem 'nokogiri', '>= 1.5.4') would accept any version, including vulnerable ones. This meant a bundle update on an older project could pull in a patched version but wouldn't enforce it.

3. Lack of input validation before XML parsing
Applications accepted XML input without validating whether it contained external entity declarations. Even with a patched library, defense-in-depth suggests rejecting documents with <!ENTITY declarations unless your application specifically requires them.

What the Relevant Standards Require

PCI DSS v4.0.1 Requirement 6.3.2 mandates that custom software be developed based on industry standards and incorporate information security throughout the software development life cycle. This includes secure coding practices that prevent injection vulnerabilities like XXE.

OWASP ASVS v4.0.3 Section 5.5.2 (Verification Level 2) requires that XML parsers be configured to prevent XXE attacks by disabling DTDs, external entities, and entity expansion.

ISO/IEC 27001:2022 Control 8.28 addresses secure coding, requiring organizations to apply secure coding principles to software development, including configuring third-party libraries safely.

NIST 800-53 Rev 5 Control SI-10 (Information Input Validation) requires that information systems check the validity of input. For XML parsers, this means both validating the structure of incoming XML and configuring the parser to reject potentially malicious constructs like external entities.

The gap here wasn't regulatory—the standards clearly address injection vulnerabilities and secure configuration. The gap was operational: translating "configure parsers securely" into "pin Nokogiri >= 1.5.4 and set NONET and DTDLOAD flags."

Lessons and Action Items for Your Team

Audit your current Nokogiri version
Run bundle show nokogiri in each Ruby application. If you see anything below 1.5.4, update your Gemfile to enforce gem 'nokogiri', '>= 1.5.4' and run bundle update nokogiri.

Set explicit parser options
Even with a patched version, configure your XML parser defensively:

Nokogiri::XML::Document.parse(xml_string) do |config|
  config.nonet.strict
end

This disables network access during parsing and enforces strict mode. If your application doesn't need DTDs, add config.options = Nokogiri::XML::ParseOptions::NOENT | Nokogiri::XML::ParseOptions::DTDLOAD to prevent entity expansion.

Implement dependency version pinning
Your Gemfile should specify minimum versions for security-critical libraries. This isn't just about Nokogiri—any library that handles untrusted input should have a floor version that excludes known vulnerabilities.

Add XXE test cases to your security test suite
Create a test XML document with an external entity reference:

<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<root>&xxe;</root>

Your parser should reject this or return an empty entity. If it returns file contents, you're vulnerable.

Monitor vulnerability disclosures for your dependency tree
Tools like Dependabot, Snyk, or OWASP Dependency-Check can automate this, but someone on your team needs to triage the alerts and schedule updates.

Document your parser configuration decisions
When you disable DTD loading or external entities, document why in your code or architecture decision records. This satisfies the "security throughout the SDLC" requirement in PCI DSS Requirement 6.3.2 and creates an audit trail for compliance reviews.

The Nokogiri XXE vulnerability wasn't sophisticated. It was a default configuration that trusted input it shouldn't have. The fix was a version bump and a few configuration flags. The lesson: your security posture depends as much on how you configure your dependencies as on the code you write yourself.

Topics:Incident

You Might Also Like