Xml External Entities in Rails with Basic Auth
Xml External Entities in Rails with Basic Auth — how this specific combination creates or exposes the vulnerability
XML External Entity (XXE) injection occurs when an application processes XML input that references external entities, allowing an attacker to force the parser to disclose local files, trigger SSRF, or consume system resources. In Ruby on Rails, this typically arises when you use libraries such as Nokogiri with unsafe parsing options or when Rails directly parses XML payloads from request bodies (e.g., SOAP or legacy web services). When Basic Authentication is used, an attacker may need to first obtain or bypass credentials to reach the endpoint; however, if an endpoint accepts XML input and performs unsafe parsing, the presence of Basic Auth does not mitigate XXE—it only changes how an attacker reaches the vulnerable code path.
Consider a Rails controller that receives an XML payload to configure a document import feature. If the controller parses the XML with default or unsafe settings, external entities like &xxe SYSTEM "file:///etc/passwd" can be expanded, leading to data exposure. Basic Auth might be enforced via an Authorization header, but if the endpoint is reachable and the parser is unsafe, the authentication layer does not prevent malicious entity expansion. In some configurations, attackers may probe for unauthenticated XML endpoints or use credentialed paths where Basic Auth is misconfigured (e.g., credentials sent in URLs or logs), which can widen the attack surface. Moreover, XXE can be chained with other issues like SSRF to make internal services reachable, bypassing network ACLs that assume Basic Auth is sufficient protection.
Rails does not parse XML by default in controllers, so developers must explicitly add XML parsing. When they do, using Nokogiri with options such as LoadExternalDTD or without disabling entity resolution introduces the risk. An example of a vulnerable setup is:
require 'nokogiri'
# Unsafe: loads external DTDs and expands entities
xml = params[:document]
doc = Nokogiri::XML(xml, nil, nil, Nokogiri::XML::ParseOptions::LoadExternalDTD | Nokogiri::XML::ParseOptions::NoEntityResolution) # dangerous flags
# or simply:
doc = Nokogiri::XML(xml) # still may resolve entities depending on libxml2 configuration
Even when Basic Auth is enforced via before actions, the parsing call remains unsafe. Attackers who obtain or guess credentials can submit malicious XML; without safe parsing, the server may read sensitive files or make internal network requests. Therefore, the combination of Basic Auth and XXE-prone XML parsing creates a scenario where authentication does not protect against data exposure or SSRF via entity expansion.
Basic Auth-Specific Remediation in Rails — concrete code fixes
Securing a Rails API that uses Basic Auth and XML input requires both correct authentication practices and secure XML parsing. For authentication, avoid embedding credentials in URLs and use standard Authorization headers. Validate credentials securely and avoid logging them. For XML processing, disable DTD loading and external entity resolution entirely. Below are concrete, safe patterns.
1. Safe XML parsing in Rails
Always configure Nokogiri to prevent external entity expansion. The safest approach is to avoid loading external DTDs and to disable network access during parsing. Use the following options:
# Safe parsing: disables external entities and DTDs
def parse_xml_safely(xml_string)
Nokogiri::XML(xml_string, nil, nil, Nokogiri::XML::ParseOptions::NONET | Nokogiri::XML::ParseOptions::NOENT).tap do |doc|
# Further validation can be applied here
end
end
The NONET flag prevents network access, and NOENT handles numeric character entities without loading external DTDs. Avoid passing LoadExternalDTD or enabling entity resolution.
2. Basic Auth in controllers with secure credential handling
Use HTTP Basic Auth headers and ensure credentials are not exposed in logs or URLs. Here is a typical, secure pattern:
class ApiController < ApplicationController
before_action :authenticate_with_basic_auth
private
def authenticate_with_basic_auth
authenticate_or_request_with_http_basic do |username, password|
# Use secure comparison and avoid timing attacks
ActiveSupport::SecurityUtils.secure_compare(
Digest::SHA256.hexdigest(username),
Digest::SHA256.hexdigest(Rails.application.credentials.api_user)
) &&
ActiveSupport::SecurityUtils.secure_compare(
Digest::SHA256.hexdigest(password),
Digest::SHA256.hexdigest(Rails.application.credentials.api_password)
)
end
end
end
This approach stores credentials in rails credentials and uses constant-time comparison to reduce timing attack risks. Avoid using plain string equality for credentials.
3. Example endpoint combining safe auth and safe parsing
class DocumentsController < ApplicationController
before_action :authenticate_with_basic_auth
def import
xml_payload = params[:xml]&.read
if xml_payload.blank?
render plain: 'Missing XML', status: :bad_request
return
end
doc = parse_xml_safely(xml_payload)
# Continue with validated data processing
render json: { message: 'Import validated', root: doc.root&.name }
end
private
def authenticate_with_basic_auth
authenticate_or_request_with_http_basic do |username, password|
# secure compare against stored credentials
ActiveSupport::SecurityUtils.secure_compare(
Digest::SHA256.hexdigest(username),
Digest::SHA256.hexdigest(Rails.application.credentials.api_user)
) &&
ActiveSupport::SecurityUtils.secure_compare(
Digest::SHA256.hexdigest(password),
Digest::SSHA256.hexdigest(Rails.application.credentials.api_password)
)
end
end
def parse_xml_safely(xml_string)
Nokogiri::XML(xml_string, nil, nil,
Nokogiri::XML::ParseOptions::NONET | Nokogiri::XML::ParseOptions::NOENT
)
end
end
Additional recommendations: disable XML external parameter entities in your parser configuration, validate and sanitize all XML content, and monitor for unexpected file or network access patterns. Even with authentication, always treat XML input as untrusted.
Frequently Asked Questions
Does Basic Auth alone prevent XXE in Rails APIs?
How can I test if my Rails XML parsing is vulnerable to XXE?
&xxe SYSTEM "file:///etc/passwd") to your endpoint using a tool like curl, ensuring you include valid Basic Auth credentials if required. If the response contains file contents or errors revealing local paths, the parser is vulnerable; use safe Nokogiri parsing options (NONET and NOENT) and retest to confirm the issue is resolved.