CVE-2026-31069
PHP vulnerability analysis and mitigation

Overview

CVE-2026-31069 is a SQL Injection vulnerability in BillaBear's EventRepository component, classified as CWE-89. It affects all versions of BillaBear up to and including 2025.01.03 (the latest GitHub release at time of disclosure). The vulnerability was discovered on January 13, 2026, reported to the vendor the same day, and publicly disclosed on April 13, 2026; the GitHub Advisory (GHSA-xp6r-8pcc-xv5p) was published May 19, 2026. It carries a CVSS v3.1 base score of 8.8 (High) (Github Advisory, Researcher Report).

Technical details

The root cause (CWE-89) lies in src/BillaBear/Repository/Usage/EventRepository.php, where user-controlled metric filter names and aggregation properties are directly interpolated into raw SQL strings via PHP's sprintf() without sanitization or identifier quoting. While filter values are properly bound as parameterized query inputs, filter identifiers (keys/names) are concatenated directly into the SQL string across five methods: createCountSql(), createSumSql(), createMaxSql(), createLatestSql(), and createCountUnique(). The CreateMetricFilter DTO applies only NotBlank and Type('string') constraints on the name field — no regex or character restrictions — leaving the injection path fully open. This is a second-order (stored) SQL injection: the malicious payload is stored in the database when a metric is created via POST /app/metric, and executes later when any usage calculation is triggered (invoice generation, billing cycle processing, or usage report viewing) (Researcher Report, Researcher PoC).

Impact

Successful exploitation grants an authenticated attacker with ROLE_ACCOUNT_MANAGER permissions full read, write, and delete access to the underlying PostgreSQL database. This includes access to admin credentials (password hashes), all customers' payment data and PII, API keys, secrets, and system configuration — far exceeding the intended access boundary of the account manager role. In multi-tenant SaaS deployments, a malicious customer granted ROLE_ACCOUNT_MANAGER for their own organization could exfiltrate data belonging to other tenants, achieving complete tenant isolation bypass. Availability impact includes potential database destruction or denial-of-service via resource exhaustion, with regulatory implications under GDPR and PCI-DSS (Researcher Report, Github Advisory).

Exploitation steps

  1. Reconnaissance: Identify a BillaBear instance running version 2025.01.03 or earlier. Obtain or compromise credentials for an account with ROLE_ACCOUNT_MANAGER permissions.
  2. Craft malicious metric: Send an authenticated POST request to /app/metric with a filter name field containing a SQL injection payload. Example:
curl -X POST "http://TARGET/app/metric" \
  -H "Content-Type: application/json" \
  -H "Cookie: PHPSESSID=<session>" \
  -d '{
    "name": "Malicious Metric",
    "code": "malicious_test",
    "aggregation_method": "count",
    "filters": [{"name": "x''); SELECT CASE WHEN (1=1) THEN pg_sleep(5) ELSE pg_sleep(0) END; --", "value": "anything", "type": "inclusive"}]
  }'
  1. Store payload (second-order injection): The malicious filter name is stored in the database without sanitization, awaiting execution during a usage calculation.
  2. Trigger execution: Wait for or trigger a usage calculation event — such as viewing a usage report (GET on usage reports endpoint), generating an invoice, or initiating a billing cycle for a subscription associated with the malicious metric.
  3. Achieve SQL execution: The EventRepository methods (createCountSql(), createSumSql(), etc.) interpolate the stored filter name directly into the SQL query via sprintf(), executing the injected SQL against the PostgreSQL database.
  4. Exfiltrate data: Use UNION-based or time-based blind injection payloads to extract sensitive data, e.g.:
"name": "x' UNION SELECT version()::text, NULL, NULL FROM pg_database LIMIT 1; --"

(Researcher Report, Researcher PoC)

Indicators of compromise

  • Network: Authenticated POST requests to /app/metric or /app/metric/{id}/update with unusually long or syntactically complex name fields in filter objects containing SQL keywords (SELECT, UNION, pg_sleep, OR 1=1, --).
  • Logs: Application or web server access logs showing POST /app/metric requests from ROLE_ACCOUNT_MANAGER accounts followed by delayed responses (indicative of time-based blind injection via pg_sleep); PostgreSQL query logs showing unexpected SQL constructs originating from the application's database user.
  • Database: Presence of metric records in the database with filter names containing SQL syntax characters (', ;, --, UNION, SELECT); unexpected queries in PostgreSQL's pg_stat_activity or slow query logs referencing pg_sleep or UNION SELECT.
  • Application Behavior: Unusual delays during invoice generation, billing cycle processing, or usage report rendering that correlate with specific metric configurations; unexpected database errors logged in the BillaBear application log related to SQL syntax in filter name fields. (Researcher Report, Researcher PoC)

Mitigation and workarounds

The GitHub Advisory lists no patched version as of publication — affected versions include all releases up to and including 2025.01.03, and the advisory notes "Patched versions: None" (Github Advisory). Operators should upgrade to a BillaBear release published after January 2026 once available. As immediate workarounds: (1) apply whitelist regex validation (/^[a-zA-Z_][a-zA-Z0-9_]*$/) to the name field in CreateMetricFilter.php; (2) use $this->connection->quoteIdentifier() in EventRepository.php before interpolating filter names into SQL; or (3) use parameterized JSON path queries (PostgreSQL 12+). Additionally, restrict ROLE_ACCOUNT_MANAGER assignments to the minimum necessary users and monitor database access for anomalous query patterns (Researcher Report).

Community reactions

The vulnerability was discovered and disclosed by researcher nedlir, who published detailed technical write-ups and reproduction steps via GitHub Gists after receiving no vendor response following initial contact on January 13, 2026. The disclosure followed a responsible disclosure timeline with public release on April 13, 2026, approximately 90 days after discovery. No significant vendor statement, broader media coverage, or notable community discussion has been identified beyond the researcher's own publications (Researcher Report, Github Advisory).

Additional resources


SourceThis report was generated using AI

Related PHP vulnerabilities:

CVE ID

Severity

Score

Technologies

Component name

CISA KEV exploit

Has fix

Published date

CVE-2026-48030CRITICAL9.9
  • PHP logoPHP
  • pheditor/pheditor
NoYesJul 27, 2026
CVE-2026-55579CRITICAL9.8
  • PHP logoPHP
  • pheditor/pheditor
NoYesJul 27, 2026
CVE-2026-55578HIGH8.8
  • PHP logoPHP
  • pheditor/pheditor
NoYesJul 27, 2026
CVE-2026-54540HIGH8.8
  • PHP logoPHP
  • pheditor/pheditor
NoYesJul 27, 2026
GHSA-cmwh-g2h8-c222HIGH8.1
  • PHP logoPHP
  • poweradmin/poweradmin
NoYesJul 24, 2026

Free Vulnerability Assessment

Benchmark your Cloud Security Posture

Evaluate your cloud security practices across 9 security domains to benchmark your risk level and identify gaps in your defenses.

Request assessment

Get a personalized demo

Ready to see Wiz in action?

"Best User Experience I have ever seen, provides full visibility to cloud workloads."
David EstlickCISO
"Wiz provides a single pane of glass to see what is going on in our cloud environments."
Adam FletcherChief Security Officer
"We know that if Wiz identifies something as critical, it actually is."
Greg PoniatowskiHead of Threat and Vulnerability Management