Xml External Entities in Grape with Dynamodb
Xml External Entities in Grape with Dynamodb — how this specific combination creates or exposes the vulnerability
XML External Entity (XXE) injection occurs when an XML parser processes external entity references in a way that can disclose files, trigger SSRF, or consume resources. Grape is a REST-like API micro-framework for Ruby, and when it parses incoming XML payloads using a library such as nokogiri with default settings, it can be tricked into loading external entities. If the parsed data is then used to build a DynamoDB request (for example, as a table name, key attribute value, or condition expression), the server-side interaction with AWS DynamoDB can reflect or be influenced by the attacker-controlled data, turning an XXE into a vector for data exposure or SSRF against internal AWS metadata services.
Consider a Grape endpoint that accepts an XML upload and uses the parsed values to query DynamoDB:
class App < Grape::API
format :xml
helpers do
def dynamodb_client
Aws::DynamoDB::Client.new(region: 'us-east-1')
end
end
post '/items' do
parsed = Hash.from_xml(request.body.read)
table_name = parsed.dig('Item', 'TableName')
response = dynamodb_client.get_item(
table_name: table_name,
key: { 'id' => { s: parsed.dig('Item', 'Id') } }
)
response.to_h
end
end
If the XML contains an external entity like "<!ENTITY x SYSTEM 'file:///etc/passwd'>" and the parser is not hardened, the attacker can cause the server to read /etc/passwd. When the extracted data is passed to DynamoDB operations (such as get_item or query), the workflow exposes internal behavior: the file read may happen before the DynamoDB call, and SSRF against the AWS metadata service (169.254.169.254) can occur if entity expansion is used to probe internal endpoints. Moreover, if the attacker can influence table names or key values via XML content, they may force queries against unexpected tables, aiding data exfiltration or reconnaissance within AWS permissions granted to the application.
In practice, the risk depends on how the XML is parsed and how its content reaches DynamoDB. Default Nokogiri behavior can resolve external DTDs, so an attacker can force DNS or HTTP requests to external servers. Because DynamoDB operations often include table names and key attribute values derived directly from request data, an XXE payload can manipulate these parameters to probe internal AWS endpoints or extract sensitive configuration. This combination—user-supplied XML parsed with vulnerable defaults and fed into AWS SDK calls—creates a realistic path for sensitive data exposure or SSRF within the application’s AWS environment.
To mitigate, ensure XML parsing is configured to disable external entities and DTDs. When using Nokogiri, prefer Nokogiri::XML::Document.parse with a secure, non-validating setup and avoid passing untrusted data directly into DynamoDB request parameters. Validate and sanitize inputs, use allowlists for table names, and avoid reflecting XML content in AWS SDK calls without strict checks.
Dynamodb-Specific Remediation in Grape — concrete code fixes
Remediation focuses on disabling external entity processing in XML parsing and strictly validating data before it reaches DynamoDB operations. Below are concrete, safe patterns for a Grape API.
1. Secure XML parsing with Nokogiri
Always parse XML with external entities and DTDs disabled. Use Nokogiri::XML::Document.parse with a non-validating, non-network-resolving parser:
def safe_parse_xml(xml_string)
# Disable external entities and DTDs to prevent XXE
Nokogiri::XML::Document.parse(xml_string, nil, nil, Nokogiri::XML::ParseOptions::NOENT | Nokogiri::XML::ParseOptions::NONET | Nokogiri::XML::ParseOptions::DTDLOAD | Nokogiri::XML::ParseOptions::DTDATTR)
end
Alternatively, with stricter options (recommended):
def safe_parse_xml(xml_string)
# NOENT: replace general entities with their values; NONET: prohibit network access; DTDLOAD/DTDATTR disabled by default in some setups, be explicit
Nokogiri::XML(xml_string) do |config|
config.options = Nokogiri::XML::ParseOptions::NOENT | Nokogiri::XML::ParseOptions::NONET
end
end
These options prevent the parser from fetching external resources and reduce the risk of SSRF via entity expansion.
2. Validate and restrict table names
DynamoDB table names should never be taken directly from user input. Use an allowlist or strict regex validation:
VALID_TABLES = %w[users orders products].freeze
def safe_table_name(input)
raise ArgumentError, 'Invalid table name' unless input.is_a?(String) && input.match?(\A[a-zA-Z0-9_]{1,255}\z)
raise ArgumentError, 'Table not allowed' unless VALID_TABLES.include?(input)
input
end
Then use it in your endpoint:
class App < Grape::API
format :xml
helpers do
def dynamodb_client
Aws::DynamoDB::Client.new(region: 'us-east-1')
end
def safe_table_name(input)
VALID_TABLES = %w[users orders products]
raise Grape::Exceptions::ValidationErrors, 'Invalid or not allowed table name' unless input.is_a?(String) && input.match?(\A\w{1,100}\z) && VALID_TABLES.include?(input)
input
end
end
post '/items' do
xml = safe_parse_xml(request.body.read)
item_table = safe_table_name(xml.at_xpath('//TableName')&.content)
item_id = xml.at_xpath('//Id')&.content or raise Grape::Exceptions::ValidationErrors, 'Missing Id'
response = dynamodb_client.get_item(
table_name: item_table,
key: { 'id' => { s: item_id } }
)
response.to_h
end
end
3. Avoid using XML content directly in DynamoDB conditionals or expressions
If you must use dynamic values, prefer JSON-based inputs and strict schema validation. If XML is required, sanitize string values and avoid concatenating them into expression attribute names or values. Use placeholders and bound parameters instead.
4. Infrastructure and scanning
Use the middleBrick CLI to verify that your API does not expose XXE issues in unauthenticated scans:
middlebrick scan https://api.example.com/openapi.yaml
For ongoing assurance, consider the Pro plan to enable continuous monitoring and fail CI/CD pipelines via the GitHub Action when security scores drop. The MCP Server can also integrate checks into your IDE to catch risky patterns early.