Spring4shell in Fiber
How Spring4shell Manifests in Fiber
Spring4Shell (CVE-2022-22965) is a Java‑specific data‑binding flaw that lets an attacker set dangerous properties on a Spring bean and achieve remote code execution. While the vulnerability lives in the Spring Framework, the underlying concept — blindly binding user‑supplied data to internal objects that contain dangerous capabilities — can appear in any language that offers automatic object mapping from request payloads. In the Go web framework Fiber, the analogous risk shows up when developers use c.BodyParser() or c.Bind() to unmarshal JSON directly into a struct that contains exported fields of types that can cause side effects (e.g., *exec.Cmd, *os.File, template.HTML, or channels). If such a struct is reachable from a handler, an attacker can inject values that trigger unintended behavior.
package main
import (
"github.com/gofiber/fiber/v2"
"os/exec"
)
type Payload struct {
// Exported field – dangerous because it holds an executable command
Cmd *exec.Cmd `json:"cmd"`
}
func vulnerableHandler(c *fiber.Ctx) error {
var p Payload
if err := c.BodyParser(&p); err != nil {
return c.Status(400).SendString("invalid json")
}
// If the attacker managed to set p.Cmd, the following line runs it
if p.Cmd != nil {
_ = p.Cmd.Run() // potential RCE
}
return c.SendString("ok")
}
func main() {
app := fiber.New()
app.Post("/process", vulnerableHandler)
app.Listen(":3000")
}
In the example above, the JSON key "cmd" maps to the exported Cmd field. An attacker could POST:
{
"cmd": {
"Path": "/bin/sh",
"Args": ["-c", "rm -rf /tmp/test; echo hacked > /tmp/pwned"]
}
}
Because Fiber’s binder uses reflection to set only exported fields, the malicious values are accepted, the *exec.Cmd is populated, and Run() executes the attacker’s command. This mirrors the Spring4Shell mechanism: a trusted framework automatically populates a dangerous object from user input.
Fiber-Specific Detection
Detecting this class of flaw requires looking for endpoints that automatically bind request bodies to structs containing dangerous types. middleBrick’s Input Validation check (one of its 12 parallel scans) inspects the OpenAPI/Swagger specification (if available) and samples live responses to identify:
- Handlers that use
c.BodyParser()orc.Bind()without explicit field allow‑lists. - Struct definitions that expose fields of types such as
*exec.Cmd,*os.File,io.Closer,func(), or channels. - Lack of validation or sanitization on those fields before they are used.
When middleBrick scans a Fiber service, it reports a finding similar to:
{
"category": "Input Validation",
"severity": "high",
"title": "Unsafe direct binding to dangerous type",
"description": "Endpoint /process binds JSON to struct containing *exec.Cmd, which could lead to RCE if attacker controls the payload.",
"remediation": "Define a dedicated DTO with only the needed fields, validate inputs, and avoid exporting dangerous types."
}
You can trigger this scan from the CLI:
middlebrick scan https://api.example.com/process
Or integrate it into CI:
# .github/workflows/api-security.yml
name: API Security
on: [push]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install middleBrick
run: npm i -g middlebrick
- name: Scan staging API
run: middlebrick scan https://staging.api.example.com --fail-below B
The action will fail the build if the returned score drops below the threshold you set (e.g., grade B). This gives you early visibility of the unsafe binding pattern before code reaches production.
Fiber-Specific Remediation
The fix follows the same principle that mitigated Spring4Shell: never let the framework automatically populate objects that carry executable power. In Fiber, you can achieve this by:
- Creating a lightweight DTO (data‑transfer object) that contains only the fields you truly need, all of which are primitive or safe types.
- Explicitly mapping the DTO to your internal model after validation.
- Using Fiber’s built‑in validator or a third‑party library (e.g.,
go-playground/validator/v10) to enforce constraints. - Ensuring that any struct with dangerous capabilities (
*exec.Cmd, file handles, etc.) is never exported or is initialized only through controlled constructors.
Here is a safe version of the previous handler:
package main
import (
"github.com/gofiber/fiber/v2"
"github.com/go-playground/validator/v10"
"os/exec"
)
type CmdDTO struct {
Path string `json:"path" validate:"required,isallowedpath"`
Args []string `json:"args" validate:"dive,required"`
}
// custom validator to restrict allowed executables
func isAllowedPath(fl validator.FieldLevel) bool {
allowed := map[string]bool{
"/bin/echo": true,
"/usr/bin/date": true,
}
return allowed[fl.Field().String()]
}
type InternalCmd struct {
Cmd *exec.Cmd
}
func safeHandler(c *fiber.Ctx) error {
var dto CmdDTO
if err := c.BodyParser(&dto); err != nil {
return c.Status(400).SendString("invalid json")
}
validate := validator.New()
_ = validate.RegisterValidation("isallowedpath", isAllowedPath)
if err := validate.Struct(&dto); err != nil {
return c.Status(400).SendString("validation failed: " + err.Error())
}
// Build the command only after validation
internal := InternalCmd{
Cmd: exec.Command(dto.Path, dto.Args...),
}
// Optionally, you could run it here – but only after strict validation
// _ = internal.Cmd.Run()
return c.SendString("command prepared safely")
}
func main() {
app := fiber.New()
app.Post("/process", safeHandler)
app.Listen(":3000")
}
Key points:
- The DTO
CmdDTOcontains only strings and a slice — no dangerous types. - A custom validator ensures the
Pathis limited to a whitelist of safe binaries. - The actual
*exec.Cmdis created inside the handler after validation, never directly from user input. - If you truly need to accept arbitrary commands, you must redesign the feature (e.g., use a job queue with strict sandboxing) rather than relying on automatic binding.
By applying this pattern across all endpoints, you eliminate the class of vulnerability that Spring4Shell exemplifies, even when using Fiber’s convenient binding helpers.