User Controlled Primary Key
ID |
go.user_controlled_primary_key |
Severity |
high |
Remediation Complexity |
medium |
Remediation Risk |
medium |
Remediation Effort |
medium |
Resource |
Misconfiguration |
Language |
Go |
Tags |
ASVS50:v8.2.1, CWE:566, NIST.SP.800-53, PCI-DSS:6.5.6 |
Description
This vulnerability arises when an application uses user-supplied input to control primary key values for database queries or operations, which may lead to unauthorized data access or modification.
Rationale
When user input is directly used to build database queries, including primary keys, it can result in unauthorized access or manipulation of protected data.
This problem is particularly dangerous when primary keys are integers or predictable values, as it enables users to perform unintended actions like accessing records that belong to other users, potentially exposing or corrupting sensitive information.
For example, consider this Golang code snippet:
package main
import (
"database/sql"
"fmt"
"log"
"net/http"
_ "github.com/go-sql-driver/mysql"
)
var db *sql.DB
func init() {
var err error
db, err = sql.Open("mysql", "username:password@tcp(localhost:3306)/dbname")
if err != nil {
log.Fatal(err)
}
}
func userControlledPrimaryKey(w http.ResponseWriter, r *http.Request) {
pk := r.URL.Query().Get("pk")
if pk == "" {
http.Error(w, "Bad Request: Missing 'pk' parameter", http.StatusBadRequest)
return
}
query := "SELECT employee.userid, first_name, last_name FROM employee, roles WHERE employee.userid=?"
rows, err := db.Query(query, pk) // FLAW
if err != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
defer rows.Close()
for rows.Next() {
var userid, firstName, lastName string
if err := rows.Scan(&userid, &firstName, &lastName); err != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
fmt.Fprintf(w, "UserID: %s, First Name: %s, Last Name: %s\n", userid, firstName, lastName)
}
}
func main() {
http.HandleFunc("/user", userControlledPrimaryKey)
log.Fatal(http.ListenAndServe(":8080", nil))
}
In this example, the userId parameter is user controlled, which may allow a user to access data of any user simply by changing the userId value.
Remediation
To mitigate the risks of CWE-566, it’s essential to avoid constructing database queries using user-supplied input directly or allow user input to control database primary key values. Best practices include:
If the SQL query is constructed without relying on user-controlled input, such as through the use of parameterized SQL, attackers cannot access unauthorized records.
When the identifier legitimately comes from the request, authorize it against the caller: check that the record belongs to the authenticated principal, and prefer doing so before the lookup, so an unauthorized identifier never reaches the database.
Whether a given method of your own performs that authorization is project knowledge, not something recoverable from the code, so the rule cannot infer it. If you delegate the check to a helper, declare that helper as a neutralizer in a custom library metadata file (conf.custom/libraries/<technology>/*.yml), naming the argument that carries the identifier and the primary_controlled_key kind:
# schema: library_schema.json
name: acme-authorization
technology: java
modules:
- name: acme
classes:
- name: com.acme.security.AccessGuard
functions:
- name: validateUser
neutralizations:
- argPos: [0]
kind: primary_controlled_key
Identifiers validated through the declared helper are then no longer reported. An inline check that is not declared this way stays reported, because nothing in the code distinguishes it from an unrelated comparison.
Configuration
The detector has the following configurable parameters:
-
sources, that indicates the source kinds to check. -
neutralizations, that indicates the neutralization kinds to check.
Unless you need to change the default behavior, you typically do not need to configure this detector.
References
-
CWE-566 : Authorization Bypass Through User-Controlled SQL Primary Key.