GitHub Advisoryadvisory · Go

GHSA-p46v-f2x8-qp98 pREST has a Systemic SQL Injection Vulnerability

CRITICAL Published 2025-09-08 Modified 2025-09-10 Official page ↗
Aliases
CVE-2025-58450
Ecosystem
Go
Packages
github.com/prest/prest/v2
Severity
Critical
CVSS vector
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N
Weaknesses
CWE-89
Reviewed
2025-09-08

Affected packages

• Go github.com/prest/prest/v2: < 2.0.0-rc2 (no fix yet)

Description

Summary pREST provides a simple way for users to expose access their database via a REST-full API. The project is implemented using the Go programming language and is designed to expose access to Postgres database tables. During an independent review of the project, Doyensec engineers found that SQL injection is a systemic problem in the current implementation (version `v2.0.0-rc2`). Even though there are several instances of attempts to sanitize user input and mitigate injection attempts, we have found that on most code-paths, the protection is faulty or non-existent. Core Endpoints The main functionality providing REST operations on the data stored in the Postgres database is exposed via the following endpoints: • `GET /{database}/{schema}/{table}` • `POST /{database}/{schema}/{table}` • `PUT|PATCH /{database}/{schema}/{table}` • `DELETE /{database}/{schema}/{table}` Handlers for the above endpoints execute very similar logic. At a high-level they: 1. Perform authentication and authorization 2. Build the SQL query based on the incoming request 3. Execute the query on the database 4. Return the data to the user The query construction logic uses data from the request (e.g query, body or path parameters) and incorporates them in the SQL query. As an example, let us look at the `GET` request or the read operation. After completing the authentication and authorization steps, the `SelectFromTables` function will first compile a list of all columns/fields, that will be returned in the HTTP response. ```go cols, err := config.PrestConf.Adapter.FieldsPermissions(r, table, "read", userName) // ---snip--- selectStr, err := config.PrestConf.Adapter.SelectFields(cols) ``` The `SelectFields` function will validate the requested columns using the `chkInvalidIdentifier` function, and will ultimately return the beginning of the generated SQL statement. Assuming the request specifies that only the `id` and `task` columns should be returned, the generated SQL will look something like: ```sql SELECT "id", "task" FROM ``` The next step involves generating the table name, from which the data will be queried. ```go query := config.PrestConf.Adapter.SelectSQL(selectStr, database, schema, table) // ... func (adapter *Postgres) SelectSQL(selectStr string, database string, schema string, table string) string { return fmt.Sprintf(`%s "%s"."%s"."%s"`, selectStr, database, schema, table) } ``` The `SelectSQL` function will receive the `database`, `schema` and `table` values directly from the request and use them to construct the next part of the SQL statement using simple string concatenation. If we assume that the `GET` request is made to the following path `/db001/api/todos`, the resulting query will look similar to: ```sql SELECT "id", "name" FROM "api"."todos" ``` This step performs processing on values, specifically `schema` and `table`, which do not undergo any input validation, and ultimately allow for SQL injection. --- The description above is only a single instance of this issue. The list below contains code paths that we believe is a comprehensive list of all code paths affected by this issue: • https://github.com/prest/prest/blob/main/adapters/postgres/postgres.go#L243 • https://github.com/prest/prest/blob/main/adapters/postgres/postgres.go#L245 • https://github.com/prest/prest/blob/main/adapters/postgres/postgres.go#L559 • https://github.com/prest/prest/blob/main/adapters/postgres/postgres.go#L643 • https://github.com/prest/prest/blob/main/adapters/postgres/postgres.go#L1538 • https://github.com/prest/prest/blob/main/adapters/postgres/postgres.go#L1559 • https://github.com/prest/prest/blob/main/adapters/postgres/postgres.go#L1581 • https://github.com/prest/prest/blob/main/adapters/postgres/postgres.go#L1583 • https://github.com/prest/prest/blob/main/adapters/postgres/postgres.go#L1585 • https://github.com/prest/prest/blob/main/adapters/postgres/postgres.go#L1601 • https://github.com/prest/prest/blob/main/adapters/postgres/postgres.go#L1606 • https://github.com/prest/prest/blob/main/adapters/postgres/postgres.go#L1611 • https://github.com/prest/prest/blob/main/adapters/postgres/postgres.go#L1616 • https://github.com/prest/prest/blob/main/controllers/tables.go#L394 • https://github.com/prest/prest/blob/main/controllers/tables.go#L465 Reproduction The reproduction steps require a working environment which can be set up using the instructions below. With that, the issue can be verified using the following HTTP request: ```http GET /db001/api"."todos"%20where%20(select%201%20from%20pg_sleep(5))=1)%20s--/todos HTTP/1.1 Host: localhost:3000 ``` The value provided as the `schema` path parameter contains the injection payload and contains SQL which will be added to the existing SQL statement and will inject a nested query that calls the `pg_sleep()` function, delaying the response by 5 seconds. The statement shown below will be the one that is ultimately executed on the database server. ```sql SELECT * FROM "db001"."api"."todos" where (select 1 from pg_sleep(5))=1 ``` Missing Validation on tsquery Predicates Users with permission to read data from tables have the ability to specify `tsquery` predicates, allowing them to perform more complex filtering on the data. An example usage of `tsquery` can be seen below: ```http GET /databases?datname:tsquery=prest HTTP/1.1 Host: localhost:3000 ``` pREST will parse the request, and if it detects that a `tsquery` needs to be generated, the following code will be executed: ```go case "tsquery": tsQueryField := strings.Split(keyInfo[0], "$") tsQuery := fmt.Sprintf(`%s @@ to_tsquery('%s')`, tsQueryField[0], value) if len(tsQueryField) == 2 { tsQuery = fmt.Sprintf(`%s @@ to_tsquery('%s', '%s')`, tsQueryField[0], tsQueryField[1], value) } whereKey = append(whereKey, tsQuery) ``` In this example, the value of the `value` variable is used directly from the request without any validation, which ultimately allows another path to perform SQL injection. Reproduction The reproduction steps require a working environment which can be set up using the instructions below. With that, the issue can be verified using make the following HTTP request: ```http GET /databases?datname:tsquery=db001')+and+((select+'1'+from+pg_sleep(5))%3d'1 HTTP/1.1 Host: localhost:3000 ``` As with the previous example, the request above will use Postgres' `pg_sleep()` function to delay the response for 5 seconds, proving the injection was successful. Script Templates pREST users can define templates for complex SQL queries, that can be reached using the `/_QUERIES/{queriesLocation}/{script}` endpoint. The scripts are read directly from the file system. Their content is passed to the `text/template` Go library, which will render any dynamic data, sourced from the request, directly on to the script template and return the result. ```go func ExecuteScriptQuery(rq *http.Request, queriesPath string, script string) ([]byte, error) { config.PrestConf.Adapter.SetDatabase(config.PrestConf.PGDatabase) sqlPath, err := config.PrestConf.Adapter.GetScript(rq.Method, queriesPath, script) //---snip--- templateData := make(map[string]interface{}) extractHeaders(rq, templateData) extractQueryParameters(rq, templateData) sql, values, err := config.PrestConf.Adapter.ParseScript(sqlPath, templateData) //---snip--- sc := config.PrestConf.Adapter.ExecuteScriptsCtx(rq.Context(), rq.Method, sql, values) //---snip--- return sc.Bytes(), nil } //... func (adapter *Postgres) ParseScript(scriptPath string, templateData map[string]interface{}) (sqlQuery string, values []interface{}, err error) { _, tplName := filepath.Split(scriptPath) funcs := &template.FuncRegistry{TemplateData: templateData} tpl := gotemplate.New(tplName).Funcs(funcs.RegistryAllFuncs()) tpl, err = tpl.ParseFiles(scriptPath) //---snip--- var buff bytes.Buffer err = tpl.Execute(&buff, funcs.TemplateData) //---snip--- sqlQuery = buff.String() return } ``` The `text/template` li…

Related

Alias · CVE (1)

  • CVE-2025-58450 pREST (PostgreSQL REST), is an API that delivers an application on top of a Postgres database. vulnerability

Weakness · CWE (1)

  • CWE-89 Improper Neutralization of Special Elements used in an SQL Command weakness

References