Ssrf Server Side in Fiber with Dynamodb
Ssrf Server Side in Fiber with Dynamodb
Server-side request forgery (SSRF) in a Fiber application that interacts with DynamoDB typically occurs when the app accepts a user-supplied URL or host and uses it to drive both an external HTTP request and a downstream DynamoDB operation. Because DynamoDB operations often include metadata such as remote service endpoints, S3 bucket URLs, or custom API addresses, an attacker can supply a malicious endpoint that causes the server to read or write data to unintended internal resources while the DynamoDB calls appear legitimate.
In a black-box scan, middleBrick tests this combination by probing endpoints that accept a target URL, then observing whether DynamoDB-related parameters are influenced by attacker input. For example, an endpoint that accepts a table_url parameter and uses it to construct a DynamoDB condition or to decide which table to query can be abused to redirect requests to internal AWS metadata service (169.254.169.254) or to an internal ElasticSearch or Redis host that should never be reachable from the application.
Consider a Fiber handler that retrieves a DynamoDB table name from a user-supplied URL path segment without strict validation:
package main
import (
"github.com/gofiber/fiber/v2"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/dynamodb"
)
func main() {
app := fiber.New()
app.Get("/table/:tableUrl", func(c *fiber.Ctx) error {
tableURL := c.Params("tableUrl")
sess := session.Must(session.NewSession())
svc := dynamodb.New(sess)
input := &dynamodb.GetItemInput{
TableName: aws.String(tableURL),
Key: map[string]*dynamodb.AttributeValue{
"id": {S: aws.String("user-123")},
},
}
result, err := svc.GetItem(input)
if err != nil {
return c.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
return c.JSON(result)
})
app.Listen(":3000")
}
If an attacker controls tableUrl, they can supply http://169.254.169.254/latest/meta-data/iam/security-credentials/ as the table name, causing the application to make an SSRF request to the instance metadata service while the DynamoDB client attempts to use that value as a table name. Although the DynamoDB call will fail, the scanner can detect that the application performed an outbound HTTP request to a sensitive internal endpoint based on attacker input, which constitutes a high-severity SSRF vector.
Another scenario involves an endpoint that accepts a remote URL to fetch a schema or mapping and then stores or logs that URL in a DynamoDB item without validation:
package main
import (
"net/http"
"github.com/gofiber/fiber/v2"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/dynamodb"
)
func main() {
app := fiber.New()
app.Post("/import", func(c *fiber.Ctx) error {
var req struct {
SourceURL string `json:"source_url"`
}
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).SendString("invalid body")
}
sess := session.Must(session.NewSession())
svc := dynamodb.New(sess)
// Store the user-supplied URL into DynamoDB without validation
input := &dynamodb.PutItemInput{
TableName: aws.String("AuditLog"),
Item: map[string]*dynamodb.AttributeValue{
"id": {S: aws.String("audit-001")},
"url": {S: aws.String(req.SourceURL)},
"kind": {S: aws.String("import")},
},
}
if _, err := svc.PutItem(input); err != nil {
return c.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
return c.SendStatus(fiber.StatusOK)
})
app.Listen(":3000")
}
An attacker could provide a URL that resolves to an internal service, causing the backend to make a request to that service and persist the malicious URL in DynamoDB, which may later be used in other internal workflows. middleBrick flags such patterns as high-severity findings because the application exposes an SSRR vector via uncontrolled external input that influences both network requests and DynamoDB operations.
Dynamodb-Specific Remediation in Fiber
Remediation focuses on strict input validation, avoiding direct use of attacker-controlled data in DynamoDB parameters, and ensuring that URLs used for external requests are never derived from untrusted sources. For the table name example, validate against a whitelist or enforce a strict naming pattern before using the value in a DynamoDB operation:
package main
import (
"regexp"
"github.com/gofiber/fiber/v2"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/dynamodb"
)
var tableNameRegex = regexp.MustCompile(`^[a-zA-Z0-9_-]{1,255}$`)
func safeTableName(c *fiber.Ctx) string {
candidate := c.Params("table")
if !tableNameRegex.MatchString(candidate) {
return "DefaultTable"
}
return candidate
}
func main() {
app := fiber.New()
app.Get("/table/:table", func(c *fiber.Ctx) error {
table := safeTableName(c)
sess := session.Must(session.NewSession())
svc := dynamodb.New(sess)
input := &dynamodb.GetItemInput{
TableName: aws.String(table),
Key: map[string]*dynamodb.AttributeValue{
"id": {S: aws.String("user-123")},
},
}
result, err := svc.GetItem(input)
if err != nil {
return c.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
return c.JSON(result)
})
app.Listen(":3000")
}
For the import endpoint, avoid storing raw user URLs in DynamoDB. If you must persist URLs, validate and normalize them, and store only the domain or a sanitized path component. Use an allowlist of known domains or a strict URL pattern:
package main
import (
"net/url"
"regexp"
"github.com/gofiber/fiber/v2"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/dynamodb"
)
var allowedDomains = map[string]bool{"api.example.com": true, "schemas.example.com": true}
func isValidSourceURL(raw string) bool {
parsed, err := url.Parse(raw)
if err != nil || parsed.Scheme != "https" {
return false
}
if !allowedDomains[parsed.Host] {
return false
}
return true
}
func main() {
app := fiber.New()
app.Post("/import", func(c *fiber.Ctx) error {
var req struct {
SourceURL string `json:"source_url"`
}
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).SendString("invalid body")
}
if !isValidSourceURL(req.SourceURL) {
return c.Status(fiber.StatusBadRequest).SendString("invalid source URL")
}
sess := session.Must(session.NewSession())
svc := dynamodb.New(sess)
// Store only the validated, sanitized URL or its domain
input := &dynamodb.PutItemInput{
TableName: aws.String("AuditLog"),
Item: map[string]*dynamodb.AttributeValue{
"id": {S: aws.String("audit-001")},
"url": {S: aws.String(req.SourceURL)},
"kind": {S: aws.String("import")},
},
}
if _, err := svc.PutItem(input); err != nil {
return c.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
return c.SendStatus(fiber.StatusOK)
})
app.Listen(":3000")
}
Additionally, enforce least privilege on the IAM role associated with the Fiber application so that it cannot reach sensitive internal endpoints like the EC2 metadata service. Use DynamoDB condition expressions or fine-grained IAM policies to restrict which tables and attributes can be accessed, reducing the impact of any SSRF-induced DynamoDB interaction.