Prototype Pollution in Echo Go with Dynamodb
Prototype Pollution in Echo Go with Dynamodb — how this specific combination creates or exposes the vulnerability
Prototype pollution in an Echo Go application using DynamoDB typically arises when user-controlled input is merged into server-side objects that later influence how data is stored or queried. In Go, this often occurs through libraries that manipulate map structures representing request payloads before persisting them to DynamoDB. An attacker can inject properties such as __proto__, constructor.prototype, or other special keys that affect object behavior in JavaScript if the data is ever rendered client-side, or can manipulate server-side logic that interprets map keys as configuration flags.
When an Echo Go handler deserializes JSON into an interface{} or map[string]interface{} and then conditionally writes only selected fields into a DynamoDB item, pollution can occur if the untrusted keys are not explicitly filtered. For example, code that copies the entire request body into a DynamoDB attribute without validation may store polluted objects that later affect retrieval or conditional checks. DynamoDB itself does not execute prototype chains, but the vulnerability matters if the stored data is later used by a frontend or another service that does. Additionally, if the application uses the polluted data to construct expressions for ConditionExpression or filter values, it may lead to unintended authorization bypass or logic manipulation.
The Echo framework does not sanitize incoming JSON, so responsibility lies with the developer to validate and sanitize data before persistence. Common patterns that are risky include using json.NewDecoder to bind directly to a map[string]interface{} and then passing that map to DynamoDB via the AWS SDK without removing or rejecting keys like constructor or prototype.
Dynamodb-Specific Remediation in Echo Go — concrete code fixes
To prevent prototype pollution when storing data in DynamoDB from Echo Go, validate and sanitize all incoming JSON before constructing DynamoDB attribute values. Use strict allowlists for expected fields and reject or strip keys that could influence object prototypes. Below are concrete, working examples using the AWS SDK for Go v2 and Echo.
Example 1: Explicit field mapping with rejection of dangerous keys
Define a struct for expected data and unmarshal into it rather than using a generic map. This prevents unexpected keys from being stored.
//go:generate mockgen -source=main.go -destination=mocks/main_mock.go
type Item struct {
ID string `json:"id" validate:"required"`
Name string `json:"name" validate:"required,alphanum,printascii,len=1..100"`
Value string `json:"value"`
}
func createItem(c echo.Context) error {
req := new(Item)
if err := c.Bind(req); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "invalid payload")
}
if err := validator.Validate(req); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
av, err := dynamodbattribute.MarshalMap(req)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "failed to marshal item")
}
_, err = dynamoClient.PutItem(c.Request().Context(), &dynamodb.PutItemInput{
TableName: aws.String("Items"),
Item: av,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "failed to store item")
}
return c.JSON(http.StatusCreated, req)
}
Example 2: Whitelist-based sanitization for dynamic data
If you must accept dynamic attributes, explicitly copy only safe keys and reject known pollution keys.
var dangerousKeys = map[string]bool{
"constructor": true,
"__proto__": true,
"__defineGetter__": true,
"__defineSetter__": true,
"hasOwnProperty": true,
"isPrototypeOf": true,
"propertyIsEnumerable": true,
}
func sanitizeForDynamo(data map[string]interface{}) map[string]interface{} {
sanitized := make(map[string]interface{})
for k, v := range data {
if dangerousKeys[k] {
continue // drop polluted keys
}
sanitized[k] = v
}
return sanitized
}
func dynamicItem(c echo.Context) error {
var raw map[string]interface{}
if err := c.Bind(&raw); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "invalid JSON")
}
safeItem := sanitizeForDynamo(raw)
av, err := dynamodbattribute.MarshalMap(safeItem)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "failed to marshal")
}
_, err = dynamoClient.PutItem(c.Request().Context(), &dynamodb.PutItemInput{
TableName: aws.String("DynamicItems"),
Item: av,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "failed to store")
}
return c.JSON(http.StatusCreated, safeItem)
}
Example 3: Using condition expressions safely
When building ConditionExpression, avoid concatenating user input directly. Use expression attribute values instead.
func conditionalPut(item map[string]interface{}) error {
// Ensure no polluted keys affect condition logic
conditionExpr := aws.String("attribute_exists(id)")
av, err := dynamodbattribute.MarshalMap(item)
if err != nil {
return err
}
_, err = dynamoClient.PutItem(context.TODO(), &dynamodb.PutItemInput{
TableName: aws.String("SafeTable"),
Item: av,
ConditionExpression: conditionExpr,
ExpressionAttributeNames: map[string]string{"#id": "id"},
ExpressionAttributeValues: map[string]types.AttributeValue{
":idVal": &types.AttributeValueMemberS{Value: item["id"].(string)},
},
})
return err
}
General recommendations
- Prefer static structs over
map[string]interface{}when the shape of data is known. - If using dynamic data, implement a strict allowlist and drop keys like
constructor,prototype, and other JavaScript-specific properties. - Validate and sanitize on input, and avoid using raw user input in condition expressions or key names.
- Use middleware in Echo to centralize validation, for example by wrapping handlers with a validation layer that rejects polluted keys before reaching DynamoDB logic.