Cross Site Request Forgery in Grape with Dynamodb
Cross Site Request Forgery in Grape with DynamoDB — how this specific combination creates or exposes the vulnerability
Cross Site Request Forgery (CSRF) in a Grape API that uses DynamoDB typically arises when the API relies on static or easily predictable identifiers to target DynamoDB items and does not enforce per-request origin verification. In this stack, CSRF is less about traditional cookie-based session replay (since APIs are often token-based) and more about unauthorized actions being triggered via authenticated user sessions in browsers or malicious sites that can make authenticated requests on behalf of a user.
Consider a Grape endpoint that deletes a DynamoDB item using a path parameter such as id:
resource :widgets do
desc 'Delete a widget' # No per-request CSRF protection
params do
requires :id, type: String, desc: 'DynamoDB item ID'
end
delete ':id' do
dynamodb = Aws::DynamoDB::Client.new(region: 'us-east-1')
dynamodb.delete_item(table_name: ENV['DYNAMODB_TABLE'], key: { id: { s: params[:id] } })
{ status: 'deleted' }
end
end
If this endpoint accepts requests with an authenticated session (e.g., via an access token placed in an Authorization header) and the token is stored in cookies or can be triggered by a simple image tag or form POST from another origin, a CSRF attack becomes feasible. An attacker can craft a request that leverages the victim’s credentials to invoke the delete action on a specific DynamoDB key, leading to unauthorized data deletion. Because DynamoDB operations are performed server-side with the permissions attached to the provided AWS credentials, the API must ensure each request’s intent is explicitly authorized and tied to the correct user context.
The risk is elevated when combined with these factors:
- Use of HTTP methods that change state (DELETE, PUT, POST) without requiring a per-request anti-CSRF token or origin check.
- DynamoDB keys that are predictable or derivable, allowing an attacker to guess valid item identifiers.
- Missing scope or ownership checks so that a request can act on resources belonging to other users.
For example, an attacker could host a page with a form that submits to the Grape endpoint:
<form action="https://api.example.com/widgets/known-id" method="DELETE">
<button type="submit">Click for a prize!</button>
</form>
If the user’s browser includes the necessary authentication cookies or tokens, the request will be processed by the Grape API against the specified DynamoDB item. This illustrates the importance of coupling API design (Grape) with secure data access patterns (DynamoDB) to enforce user-specific authorization and anti-CSRF controls.
Dynamodb-Specific Remediation in Grape — concrete code fixes
To mitigate CSRF when using Grape with DynamoDB, implement per-request authorization tied to the authenticated user and ensure that operations include explicit identity checks. Below are concrete code examples demonstrating these patterns.
1. Require an anti-CSRF token or use same-site/secure cookies
For state-changing requests, require a custom header (e.g., X-CSRF-Token) that cannot be set by cross-origin forms. Combine this with secure cookie settings if using cookie-based sessions.
helpers do
def verified_request?
# Example: compare token from header with an expected value (e.g., from session or custom header)
request.headers['X-CSRF-Token'] == session_csrf_token
end
end
before do
error!('Invalid CSRF token', 403) unless verified_request?
end
resource :widgets do
desc 'Delete a widget with CSRF protection'
params do
requires :id, type: String, desc: 'DynamoDB item ID'
end
delete ':id' do
halt 403, { error: 'Invalid CSRF token' } unless verified_request?
dynamodb = Aws::DynamoDB::Client.new(region: 'us-east-1')
result = dynamodb.delete_item(table_name: ENV['DYNAMODB_TABLE'], key: { id: { s: params[:id] } })
{ status: 'deleted' }
end
end
2. Enforce ownership checks against DynamoDB item attributes
Before performing an operation, retrieve the item and confirm that it belongs to the requesting user. This prevents acting on arbitrary IDs even if the request is not forged cross-origin.
resource :widgets do
desc 'Delete a widget with ownership verification'
params do
requires :id, type: String, desc: 'DynamoDB item ID'
end
delete ':id' do
dynamodb = Aws::DynamoDB::Client.new(region: 'us-east-1')
# Fetch item to verify ownership
get_resp = dynamodb.get_item(table_name: ENV['DYNAMODB_TABLE'], key: { id: { s: params[:id] } })
item = get_resp.item
halt 404, { error: 'Not found' } unless item
halt 403, { error: 'Forbidden: ownership mismatch' } unless item['user_id'] == current_user_id
dynamodb.delete_item(table_name: ENV['DYNAMODB_TABLE'], key: { id: { s: params[:id] } })
{ status: 'deleted' }
end
end
3. Use least-privilege IAM roles for DynamoDB access
Ensure the AWS credentials used by the Grape service have permissions scoped to specific table actions and, where possible, to specific item-level patterns. While this does not prevent CSRF, it limits the impact of compromised credentials.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:GetItem",
"dynamodb:DeleteItem"
],
"Resource": "arn:aws:dynamodb:us-east-1:123456789012:table/widgets"
}
]
}
By combining these approaches—CSRF tokens or secure headers, per-request ownership validation, and scoped IAM permissions—you significantly reduce the risk of unauthorized actions against DynamoDB through the Grape API.