{"uuid": "c4c7323a-e9e5-4e9c-92d0-141d1ab208f2", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2025-45769", "type": "seen", "source": "https://gist.github.com/feranmi2002/a79078da33b34369681222b0a03ab04d", "content": "# Security Audit Report \u2014 GoshenWells Admin Web Services\n\n**Date:** 2026-05-12\n**Auditor:** Claude (Expert DevSecOps / SAST mode) \u2014 updated after cross-review\n**Codebase:** PHP (native) + Slim 3 Framework\n**Branch Audited:** `dev`\n\n---\n\n## Summary\n\n**39 distinct vulnerability classes** were identified across the admin panel, author portal, legacy `web_services` endpoints, Slim 3 API, Docker configuration, committed binary artifacts, and dependency manifests. The codebase contains **11 Critical** and **16 High** severity findings. Several issues represent active compromise risks regardless of network exposure \u2014 secrets are already committed, a destructive unauthenticated endpoint is live, SQL injection is pervasive, passwords are stored in plaintext in multiple flows, and uploaded files can contain executable scripts in a public directory.\n\n**This codebase must not be exposed to the internet in its current state.**\n\n---\n\n## Vulnerability Summary Table\n\n| # | Vulnerability | Severity |\n|---|---|---|\n| 1 | Hardcoded Database Credentials in VCS | **Critical** |\n| 2 | Hardcoded Third-Party API Keys and SMTP Passwords | **Critical** |\n| 3 | Firebase FCM Server Keys and Apple Push Certificates Committed | **Critical** |\n| 4 | SMTPJS Credentials Exposed in Browser JavaScript | **Critical** |\n| 5 | Hardcoded JWT Secret \u2014 Known Example Value | **Critical** |\n| 6 | Unauthenticated User Deletion Endpoint | **Critical** |\n| 7 | SQL Injection \u2014 Pervasive Throughout Codebase | **Critical** |\n| 8 | Admin and Author Passwords Stored in Plaintext | **Critical** |\n| 9 | `reset.php` Writes New Password to DB Without Hashing | **Critical** |\n| 10 | Request-Controlled File Deletion via `unlink()` | **Critical** |\n| 11 | CVE-2021-46743 \u2014 `firebase/php-jwt` v5.0.0 | **Critical** |\n| 12 | Use of Removed `mysql_*` Extension | **High** |\n| 13 | Unsafe `extract()` on User-Supplied Superglobals | **High** |\n| 14 | Weak SHA-1 Password Hashing Without Salt | **High** |\n| 15 | Insecure File Upload \u2014 No Server-Side MIME Validation | **High** |\n| 16 | JWT Token Expires in 10 Seconds \u2014 Broken Auth | **High** |\n| 17 | Full User Object Returned on Login \u2014 Sensitive Data Exposure | **High** |\n| 18 | Missing `exit()` After Session Redirect \u2014 Auth Bypass | **High** |\n| 19 | IDOR \u2014 Book Purchase Accepts Arbitrary `user_id` | **High** |\n| 20 | Broken API Authorization \u2014 Token Does Not Prove User Ownership | **High** |\n| 21 | Social Login Trusts Request-Supplied Identity | **High** |\n| 22 | Missing Authentication on `common/*.php` AJAX Endpoints | **High** |\n| 23 | `phpinfo()` Pages Publicly Exposed | **High** |\n| 24 | Session ID Echoed in Admin Panel HTML | **High** |\n| 25 | TLS Verification Disabled in cURL Notification Scripts | **High** |\n| 26 | Docker: Database and phpMyAdmin Ports Exposed; EOL PHP Runtime | **High** |\n| 27 | Apache `Options Indexes` Enabled \u2014 Directory Listing | **High** |\n| 28 | Session Fixation \u2014 No `session_regenerate_id()` After Login | **High** |\n| 29 | No CSRF Protection on State-Changing Admin Forms | **Medium** |\n| 30 | DB Error Messages Exposed to HTTP Response | **Medium** |\n| 31 | SMTP Debug Mode Enabled in Production | **Medium** |\n| 32 | Predictable Password Reset Token \u2014 `uniqid()` | **Medium** |\n| 33 | Stored XSS \u2014 Unescaped User Data in Admin Panel | **Medium** |\n| 34 | Reflected XSS \u2014 Raw `$_GET`/`$_REQUEST` Output in HTML | **Medium** |\n| 35 | JWT Middleware `\"secure\" =&gt; false` | **Medium** |\n| 36 | Missing HTTP Security Headers | **Medium** |\n| 37 | Database Dumps and Sensitive Artifacts Committed to Repository | **Medium** |\n| 38 | Outdated Dependencies \u2014 Slim 3, PHPMailer, Frontend Libraries | **Medium** |\n| 39 | `md5()` Used in Security-Sensitive Context | **Low** |\n\n---\n\n## Critical Findings\n\n---\n\n## [1] Hardcoded Database Credentials in Version-Controlled Files\n\n* **Severity:** Critical\n* **CWE/OWASP Reference:** CWE-798, OWASP A05:2021 \u2013 Security Misconfiguration\n* **Location:**\n  * `connection.php` at lines `2\u20138`\n  * `web_services/credData.php` at lines `2\u20134`\n  * `web_services/new/src/config/config.php` at lines `3\u20135`\n* **Description:** Production database hostname, username, and password (`Gh&amp;8J%3sd@a12d$tg)jY%1)`) are hard-coded as PHP constants in plain text and committed to the Git repository. The credential appears identically in at least three separate files. Any repository reader \u2014 developer, contractor, or attacker \u2014 has immediate full database access.\n* **Impact:** Complete database compromise. All user, book-purchase, and payment data can be dumped, modified, or destroyed. The credential's presence in Git history means rotating it in the working tree is insufficient without also purging history.\n* **Remediation:**\n  1. Immediately rotate the database password on the server.\n  2. Move all secrets to a `.env` file and add `.env` to `.gitignore`.\n  3. Use `getenv()` or `vlucas/phpdotenv` to load values at runtime.\n  4. Purge all past commits containing credentials using `git filter-repo` or BFG Repo-Cleaner.\n* **Code Fix Example:**\n```php\n// .env (never committed)\n// DB_HOST=localhost\n// DB_USER=appoo\n// DB_PASS=\n// DB_NAME=ebookapp\n\n// connection.php\ndefine(\"DB_HOST\", getenv('DB_HOST') ?: '');\ndefine(\"DB_USER\", getenv('DB_USER') ?: '');\ndefine(\"DB_PASS\", getenv('DB_PASS') ?: '');\ndefine(\"DB_NAME\", getenv('DB_NAME') ?: '');\n```\n\n---\n\n## [2] Hardcoded Third-Party API Keys and SMTP Passwords\n\n* **Severity:** Critical\n* **CWE/OWASP Reference:** CWE-798, OWASP A02:2021 \u2013 Cryptographic Failures\n* **Location:**\n  * `web_services/new/src/libs/auth.php` at line `116` \u2014 SendGrid API key\n  * `web_services/new/src/libs/functions.php` at lines `16\u201317` \u2014 AWS SES IAM key and secret\n  * `function.php` at lines `562\u2013563`, `595\u2013596` \u2014 GoDaddy SMTP password\n  * `web_services/function.php` at lines `16\u201317`, `562\u2013563` \u2014 duplicates of the above\n* **Description:** Multiple live third-party credentials are hard-coded across source files:\n  * SendGrid API key: `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`\n  * AWS SES IAM key: `AKIAI34UUS63AJH5EPXQ` / `Agw5szbfEs4F/5uFwyP/BltE5Od1wGnki59jYlvmxPi/`\n  * GoDaddy SMTP password: `G0shenWellsUserAcc0unt`\n  * AuthSMTP credentials: `ac54642` / `zfbqyec9qxbfnz`\n* **Impact:** Unauthorized email sending (phishing, spam), AWS billing fraud, full mailing infrastructure compromise, SendGrid account ban.\n* **Remediation:** Immediately revoke and rotate all listed keys in their respective provider dashboards. Move all keys to environment variables (see Finding #1). Never commit secrets even in commented-out code, as Git history retains them.\n* **Code Fix Example:**\n```php\n$mail-&gt;Username = getenv('SENDGRID_USERNAME');\n$mail-&gt;Password = getenv('SENDGRID_API_KEY');\n```\n\n---\n\n## [3] Firebase FCM Server Keys and Apple Push Certificates Committed to Repository\n\n* **Severity:** Critical\n* **CWE/OWASP Reference:** CWE-321, OWASP A02:2021 \u2013 Cryptographic Failures\n* **Location:**\n  * `common/notification_abhinew.php` and related notification scripts \u2014 Firebase Cloud Messaging server key\n  * `common/goshenwellspushcert.p12` \u2014 Apple push certificate (binary)\n  * `common/goshenwellspushcert.pem` \u2014 Apple push certificate (PEM)\n  * `common/goshenwellspushcert_old.pem` \u2014 previous Apple push certificate\n* **Description:** Live Firebase Cloud Messaging server keys are embedded in notification scripts. Apple push certificate files (`.p12` and `.pem`) \u2014 which act as private keys authenticating the app to Apple Push Notification Service (APNs) \u2014 are committed as binary files in the repository.\n* **Impact:** Any repository reader can send arbitrary push notifications to all registered iOS and Android users, enabling phishing, social engineering, and reputational damage. Compromised Apple certificates require formal revocation through Apple Developer and redeployment of new certificates to all servers.\n* **Remediation:**\n  1. Revoke the Apple push certificates immediately via Apple Developer and generate new ones.\n  2. Rotate the Firebase server key in the Firebase console.\n  3. Remove the certificate files and FCM keys from the repository and purge from Git history.\n  4. Store push credentials in a secret manager or environment variable, never as committed files.\n* **Code Fix Example:**\n```php\n// Load FCM key from environment, never hardcode\n$fcm_key = getenv('FCM_SERVER_KEY');\n\n// Load push cert path from environment, store file outside webroot\n$push_cert_path = getenv('APNS_CERT_PATH'); // e.g., /etc/ssl/private/push.pem\n```\n\n---\n\n## [4] SMTPJS Credentials Exposed in Browser-Side JavaScript\n\n* **Severity:** Critical\n* **CWE/OWASP Reference:** CWE-312, OWASP A02:2021 \u2013 Cryptographic Failures\n* **Location:**\n  * `admin/auth_panel/view_books_test.php`\n  * `admin/auth_panel/view_books_local.php`\n* **Description:** These files use SMTPJS \u2014 a client-side JavaScript library that sends email directly from the browser. This requires embedding SMTP credentials (host, username, password or API token) in the JavaScript that is delivered to the browser. Any visitor who opens DevTools or views page source can extract and reuse these credentials.\n* **Impact:** SMTP credentials are effectively public. Anyone can authenticate to the mail server and send email as the application \u2014 for spam, phishing, or credential theft campaigns.\n* **Remediation:** Remove all SMTPJS usage. Email must only be sent from server-side PHP code using credentials that are never exposed to the browser. If client-triggered email is needed, the browser calls an authenticated server endpoint, which then sends the email.\n* **Code Fix Example:**\n```php\n// Server-side endpoint (email_send.php) \u2014 browser POSTs to this\nsession_start();\nif (empty($_SESSION['admin_id'])) { http_response_code(403); exit; }\n\n$to      = filter_input(INPUT_POST, 'to',      FILTER_SANITIZE_EMAIL);\n$subject = filter_input(INPUT_POST, 'subject', FILTER_DEFAULT);\n// ... send via PHPMailer with server-side credentials from getenv()\n```\n\n---\n\n## [5] Hardcoded JWT Secret \u2014 Known Example Value\n\n* **Severity:** Critical\n* **CWE/OWASP Reference:** CWE-321, OWASP A02:2021 \u2013 Cryptographic Failures\n* **Location:** `web_services/new/src/config/config.php` at line `9`\n* **Description:** The JWT signing secret is set to `\"supersecretkeyyoushouldnotcommittogithub\"` \u2014 copied verbatim from the `firebase/php-jwt` README example. This string is in public wordlists and is trivially known. Any attacker can use it to forge arbitrary JWT tokens selecting any user ID.\n* **Impact:** Complete authentication bypass. Forged tokens grant access to all protected API endpoints as any user, including impersonating administrators.\n* **Remediation:** Generate a cryptographically random secret (`openssl rand -base64 64`), store it in an environment variable, and redeploy. All currently issued tokens become invalid and all users must re-authenticate.\n* **Code Fix Example:**\n```php\ndefine(\"SECRET\", getenv('JWT_SECRET') ?: throw new RuntimeException('JWT_SECRET not set'));\n```\n\n---\n\n## [6] Unauthenticated User Deletion Endpoint\n\n* **Severity:** Critical\n* **CWE/OWASP Reference:** CWE-306, OWASP A01:2021 \u2013 Broken Access Control\n* **Location:** `web_services/new/src/routes/api.php` at lines `17\u201343` (middleware) and `433\u2013456` (handler)\n* **Description:** The JWT middleware's `passthrough` list includes `/api/v1/deleteUser` (line 31), so no token is required. The handler accepts a bare `id` parameter and issues a DELETE query with no authentication, role check, or ownership verification. The SQL is also vulnerable to injection (see Finding #7).\n* **Impact:** Any unauthenticated internet user can delete any account by iterating `id=1`, `id=2`, etc., destroying the entire user base.\n* **Remediation:** Remove `/api/v1/deleteUser` from the passthrough list. Require authentication and an admin role check inside the handler. Use a parameterized query.\n* **Code Fix Example:**\n```php\n// Remove from passthrough \u2014 and add role guard in handler:\n$app-&gt;post('/deleteUser', function (Request $request, Response $response) {\n    $token = $request-&gt;getAttribute('jwt');\n    if (!isset($token-&gt;header-&gt;role) || $token-&gt;header-&gt;role !== 'admin') {\n        return $response-&gt;withJson(['status' =&gt; 0, 'message' =&gt; 'Forbidden'], 403);\n    }\n    $id   = (int) $request-&gt;getParam('id');\n    $conn = PDOConnection::getConnection();\n    $stmt = $conn-&gt;prepare(\"DELETE FROM appUsers WHERE id = :id\");\n    $stmt-&gt;bindParam(\":id\", $id, PDO::PARAM_INT);\n    $stmt-&gt;execute();\n    // ...\n});\n```\n\n---\n\n## [7] SQL Injection \u2014 Pervasive Throughout the Codebase\n\n* **Severity:** Critical\n* **CWE/OWASP Reference:** CWE-89, OWASP A03:2021 \u2013 Injection\n* **Location (representative sample):**\n  * `web_services/login.php` at lines `6`, `16`\n  * `web_services/forgetpassword.php` at lines `8`, `13`\n  * `web_services/book_purchase.php` at line `9`\n  * `response.php` and `authors_admin/response.php` \u2014 login queries\n  * `function.php` \u2014 entire `database` class: `logincheck()`, `insert_entry()`, `update_entry()`, `select_single_row()`, `run_result_query()`, etc.\n  * `common/addAudioVideo.php`, `common/book_ajax.php`, `common/assignBooksToUser.php`, `common/delete_ajax.php`, `common/approve_books_ajax.php`, `common/update_books_ajax.php`\n  * `web_services/new/src/routes/api.php` at lines `84`, `96`, `159`, `314`, `321`, `441`\n  * `authors_admin/function.php`, `authors_admin/forget.php`, `authors_admin/reset.php`, `authors_admin/export_payments.php`\n* **Description:** User-supplied data flows directly into SQL query strings via string concatenation across dozens of files and the central `database` helper class. The helper's generic `insert_entry()` and `update_entry()` methods build queries from arbitrary key-value arrays with no escaping, meaning any caller that passes user data is vulnerable. `addslashes()` used in some places is not a security control.\n* **Impact:** Authentication bypass, full database read/write/delete, potential Remote Code Execution via `SELECT ... INTO OUTFILE` if MySQL file privileges are set.\n* **Remediation:** Replace all concatenated queries with PDO prepared statements. Remove or make private any helper that accepts raw SQL strings.\n* **Code Fix Example:**\n```php\n// VULNERABLE:\n$qry1 = \"select * from appUsers where email ='\".$email.\"'\";\n\n// SECURE:\n$stmt = $pdo-&gt;prepare(\"SELECT * FROM appUsers WHERE email = :email\");\n$stmt-&gt;bindParam(\":email\", $email, PDO::PARAM_STR);\n$stmt-&gt;execute();\n$row = $stmt-&gt;fetch(PDO::FETCH_ASSOC);\n```\n\n---\n\n## [8] Admin and Author Passwords Stored in Plaintext\n\n* **Severity:** Critical\n* **CWE/OWASP Reference:** CWE-312, OWASP A02:2021 \u2013 Cryptographic Failures\n* **Location:**\n  * `admin/auth_panel/change_password.php` at line `16`\n  * `function.php` \u2014 `logincheck()` compares raw password in SQL WHERE clause\n  * `response.php` \u2014 direct password comparison against DB value\n  * `authors_admin/response.php` \u2014 same pattern\n  * `common/author_ajax.php` \u2014 creates author accounts with raw passwords\n* **Description:** Admin and author passwords are stored as plaintext in the database and compared via SQL conditions or direct equality checks. There is no hashing at any point in these flows.\n* **Impact:** Any database read \u2014 via SQL injection, a backup, or direct access \u2014 immediately yields all plaintext admin and author credentials, enabling full account takeover.\n* **Remediation:** Migrate all passwords to `password_hash($password, PASSWORD_DEFAULT)`. Verify with `password_verify()`. Force a password reset for all existing accounts.\n* **Code Fix Example:**\n```php\n// Store:\n$insData['password'] = password_hash($new_password, PASSWORD_DEFAULT);\n\n// Verify \u2014 fetch by ID or email, never by password value:\n$admin = $obj-&gt;select_single_row('users', 'id', $_SESSION['admin_id']);\nif (!password_verify($old_password, $admin['password'])) {\n    $err_msg = 'Old password is wrong';\n}\n```\n\n---\n\n## [9] `reset.php` Writes New Password to Database Without Hashing\n\n* **Severity:** Critical\n* **CWE/OWASP Reference:** CWE-312, OWASP A02:2021 \u2013 Cryptographic Failures\n* **Location:**\n  * `reset.php`\n  * `authors_admin/reset.php`\n* **Description:** The password reset flows update `users.password` directly with the user-submitted value. No hashing is applied. Even if passwords were hashed at registration, the reset flow overwrites the hash with plaintext.\n* **Impact:** Every user who resets their password has their new password stored in plaintext, compounding Finding #8 and ensuring any database disclosure yields cleartext credentials for all users who have ever used the reset flow.\n* **Remediation:** Hash the new password before writing it. Also enforce token expiry and one-time-use semantics on reset codes.\n* **Code Fix Example:**\n```php\n// reset.php \u2014 BEFORE:\n$sql = \"UPDATE users SET password='\".$_POST['new_password'].\"' WHERE id=...\";\n\n// AFTER:\n$hashed = password_hash($_POST['new_password'], PASSWORD_DEFAULT);\n$stmt = $pdo-&gt;prepare(\"UPDATE users SET password = :pass, resetCode = NULL WHERE id = :id\");\n$stmt-&gt;bindParam(':pass', $hashed);\n$stmt-&gt;bindParam(':id',   $userId, PDO::PARAM_INT);\n$stmt-&gt;execute();\n```\n\n---\n\n## [10] Request-Controlled File Deletion via `unlink()`\n\n* **Severity:** Critical\n* **CWE/OWASP Reference:** CWE-73, OWASP A01:2021 \u2013 Broken Access Control\n* **Location:** `common/adit_book_ajax.php`\n* **Description:** The file edit handler calls `unlink($_POST['oldImage'])` using a path supplied directly from the POST body. An authenticated attacker (or an unauthenticated one if the session guard is bypassed \u2014 see Finding #18) can supply any server path as `oldImage` to delete arbitrary files, including application PHP files, configuration files, or other uploaded content.\n* **Impact:** Denial of service (deleting application files), privilege escalation (deleting auth guards), or destruction of user data. Combined with path traversal, an attacker could delete files outside the intended directory.\n* **Remediation:** Never use user input to construct file paths for deletion. Look up the canonical filename from the database by the record's primary key and delete only that server-side path.\n* **Code Fix Example:**\n```php\n// BEFORE:\nunlink($_POST['oldImage']);\n\n// AFTER \u2014 look up the filename from DB by trusted ID:\n$book_id = (int) $_POST['book_id'];\n$stmt = $pdo-&gt;prepare(\"SELECT image FROM books WHERE id = :id\");\n$stmt-&gt;bindParam(':id', $book_id, PDO::PARAM_INT);\n$stmt-&gt;execute();\n$book = $stmt-&gt;fetch(PDO::FETCH_ASSOC);\n\n$safe_path = realpath('/var/uploads/books/' . basename($book['image']));\nif ($safe_path &amp;&amp; str_starts_with($safe_path, '/var/uploads/books/')) {\n    unlink($safe_path);\n}\n```\n\n---\n\n## [11] CVE-2021-46743 \u2014 `firebase/php-jwt` v5.0.0 (Key/Algorithm Confusion)\n\n* **Severity:** Critical\n* **CWE/OWASP Reference:** CWE-347, OWASP A02:2021 \u2013 Cryptographic Failures\n* **Location:** `web_services/new/composer.lock` \u2014 `firebase/php-jwt` pinned at `5.0.0`\n* **Description:** `composer audit --locked` reports CVE-2021-46743 / GHSA-8xf4-w7qw-pjjw against `firebase/php-jwt` 5.0.0: a key and algorithm type confusion vulnerability. An attacker can craft a JWT that is accepted as valid by supplying a public key as the HMAC secret, bypassing signature verification entirely. A second advisory (CVE-2025-45769 / GHSA-2x45-7fc3-mxwq) covers weak encryption in the same version.\n* **Impact:** JWT signature verification is bypassed. Combined with the committed JWT secret (Finding #5), tokens are completely forgeable, meaning the API's authentication layer provides no real protection.\n* **Remediation:** Upgrade `firebase/php-jwt` to the current major version and update `tuupola/slim-jwt-auth` to a compatible release. Run `composer audit` in CI on every build.\n* **Code Fix Example:**\n```bash\n# In web_services/new/\ncomposer require firebase/php-jwt:^6.0\ncomposer update tuupola/slim-jwt-auth\ncomposer audit\n```\n\n---\n\n## High Findings\n\n---\n\n## [12] Use of Removed `mysql_*` Extension\n\n* **Severity:** High\n* **CWE/OWASP Reference:** CWE-477, OWASP A06:2021 \u2013 Vulnerable and Outdated Components\n* **Location:** `function.php` throughout; `web_services/function.php` throughout; `config.php` at line `14`\n* **Description:** The entire legacy `database` class relies on the `mysql_*` extension (`mysql_query`, `mysql_fetch_assoc`, `mysql_affected_rows`, `mysql_connect`), which was deprecated in PHP 5.5 and removed in PHP 7.0. It provides no native prepared statement capability, making SQL injection mitigations impossible without a full rewrite.\n* **Impact:** All SQL injection attacks are possible with zero mitigation. The server must be running PHP 5.x to support this code, exposing it to a decade of unpatched PHP CVEs.\n* **Remediation:** Replace all `mysql_*` calls with PDO using prepared statements (the `web_services/new/` stack already does this \u2014 extend that pattern to the legacy code).\n* **Code Fix Example:**\n```php\n// BEFORE:\n$res = mysql_query(\"SELECT * FROM admin WHERE name='$email'\");\n$row = mysql_fetch_assoc($res);\n\n// AFTER:\n$stmt = $pdo-&gt;prepare(\"SELECT * FROM admin WHERE name = :name\");\n$stmt-&gt;bindParam(\":name\", $email, PDO::PARAM_STR);\n$stmt-&gt;execute();\n$row = $stmt-&gt;fetch(PDO::FETCH_ASSOC);\n```\n\n---\n\n## [13] Unsafe `extract()` on User-Supplied Superglobals\n\n* **Severity:** High\n* **CWE/OWASP Reference:** CWE-74, OWASP A03:2021 \u2013 Injection\n* **Location:**\n  * `web_services/login.php` at line `5`; `web_services/register.php` at line `6`; `web_services/forgetpassword.php` at line `7`; `web_services/book_purchase.php` at line `6`; `web_services/social_login.php` at line `6`\n  * `common/addAudioVideo.php`, `common/book_ajax.php`, `common/book_ajax_local.php`, `common/book_ajax_admin.php`, `common/adit_author_ajax.php`, `common/edit_audio_video_ajax.php`, `common/update_book_ajax.php`, and 10+ additional files in `common/`\n* **Description:** `extract($_POST)` and `extract($_REQUEST)` import all user-controlled keys into the local variable scope unconditionally. An attacker can overwrite any local variable \u2014 including `$obj`, `$json`, `$ids`, `$qry`, authentication flags \u2014 by submitting a POST key matching that variable name.\n* **Impact:** Variable hijacking, business logic bypass, and second-order injection across every affected endpoint.\n* **Remediation:** Remove all `extract()` calls on superglobals and explicitly access each required parameter by name with type casting.\n* **Code Fix Example:**\n```php\n// BEFORE:\nextract($_POST);\n\n// AFTER:\n$email    = filter_input(INPUT_POST, 'email',    FILTER_SANITIZE_EMAIL);\n$password = filter_input(INPUT_POST, 'password', FILTER_DEFAULT);\n```\n\n---\n\n## [14] Weak Password Hashing \u2014 SHA-1 Without Salt\n\n* **Severity:** High\n* **CWE/OWASP Reference:** CWE-916, OWASP A02:2021 \u2013 Cryptographic Failures\n* **Location:** `web_services/login.php` at line `12`; `web_services/register.php` at line `15`; `web_services/registerTest.php` at line `20`\n* **Description:** App user passwords are hashed with bare `sha1($password)`. SHA-1 is not designed for password storage, has no salt, and is trivially reversible via GPU cracking or rainbow tables.\n* **Impact:** Database disclosure immediately yields all user passwords.\n* **Remediation:** Replace with `password_hash()` / `password_verify()`. The `/register` route in the new API stack already does this correctly \u2014 back-port it to the legacy endpoints.\n* **Code Fix Example:**\n```php\n// Store:\n$postArr['password'] = password_hash($password, PASSWORD_BCRYPT);\n\n// Verify:\nif (password_verify($password, $isLogin1['password'])) { /* success */ }\n```\n\n---\n\n## [15] Insecure File Upload \u2014 No Server-Side MIME or Extension Validation\n\n* **Severity:** High\n* **CWE/OWASP Reference:** CWE-434, OWASP A04:2021 \u2013 Insecure Design\n* **Location:** `common/book_ajax_admin_local.php` at lines `9\u201323`; `common/addAudioVideo.php`; `common/book_ajax.php`; `common/update_book_ajax.php`; `common/edit_audio_video_ajax.php`\n* **Description:** File uploads are handled with no server-side validation. Only client-side JavaScript extension checks exist, which are trivially bypassed. Files are moved to public `uploads/` directories using the original client-supplied extension.\n* **Impact:** An attacker can upload a PHP webshell to the publicly accessible upload directory and achieve Remote Code Execution.\n* **Remediation:** Validate MIME type server-side with `finfo`. Use an extension allowlist. Store files outside the web root. Generate random server-side filenames. Deny PHP execution in upload directories via `.htaccess`.\n* **Code Fix Example:**\n```php\n$allowed = ['application/pdf' =&gt; 'pdf', 'image/jpeg' =&gt; 'jpg', 'image/png' =&gt; 'png'];\n$finfo   = finfo_open(FILEINFO_MIME_TYPE);\n$mime    = finfo_file($finfo, $_FILES['book_img']['tmp_name']);\nfinfo_close($finfo);\n\nif (!array_key_exists($mime, $allowed)) {\n    echo json_encode(['id' =&gt; -3, 'message' =&gt; 'Invalid file type.']);\n    exit;\n}\n$safe_name = bin2hex(random_bytes(16)) . '.' . $allowed[$mime];\nmove_uploaded_file($_FILES['book_img']['tmp_name'], '/var/uploads/books/' . $safe_name);\n```\n\n---\n\n## [16] JWT Token Expires in 10 Seconds \u2014 Effectively Broken Authentication\n\n* **Severity:** High\n* **CWE/OWASP Reference:** CWE-613, OWASP A07:2021 \u2013 Identification and Authentication Failures\n* **Location:** `web_services/new/src/libs/auth.php` at line `29`\n* **Description:** Token expiry is computed as `now + 10 seconds` using a `mktime()` call that adds 10 to the seconds component, making every issued token expire almost immediately. The manual `verifyToken()` re-implements expiry checking instead of relying on the library's built-in claim validation, introducing further edge-case errors.\n* **Impact:** Legitimate users cannot stay authenticated. The broken check may also cause intermittent bypasses under clock drift.\n* **Remediation:** Use a Unix timestamp `exp` claim with a proper TTL (2\u201324 hours) and delegate expiry enforcement to the JWT library.\n* **Code Fix Example:**\n```php\npublic static function getToken(int $id, string $user): string\n{\n    $payload = [\n        'iat'  =&gt; time(),\n        'exp'  =&gt; time() + (int)(getenv('JWT_TTL_SECONDS') ?: 7200),\n        'id'   =&gt; $id,\n        'user' =&gt; $user,\n    ];\n    return JWT::encode($payload, SECRET, 'HS256');\n}\n\npublic static function verifyToken(string $token): bool\n{\n    try {\n        JWT::decode($token, SECRET, ['HS256']); // library enforces exp automatically\n        return true;\n    } catch (\\Exception $e) {\n        return false;\n    }\n}\n```\n\n---\n\n## [17] Full User Object Returned on Login \u2014 Sensitive Data Exposure\n\n* **Severity:** High\n* **CWE/OWASP Reference:** CWE-200, OWASP A02:2021 \u2013 Cryptographic Failures\n* **Location:** `web_services/new/src/routes/api.php` at line `375`\n* **Description:** The login handler returns the entire database row: `$data['data'] = $query`. This exposes the password hash, `resetCode`, `confirmCode`, `remember_token`, `socialId`, and all internal fields to the client in the JSON response.\n* **Impact:** Reset codes in the response allow account takeover without email access. Password hashes can be cracked offline.\n* **Remediation:** Return only the fields the client legitimately needs.\n* **Code Fix Example:**\n```php\n$data['data'] = [\n    'id'        =&gt; $query-&gt;id,\n    'firstname' =&gt; $query-&gt;firstname,\n    'lastname'  =&gt; $query-&gt;lastname,\n    'email'     =&gt; $query-&gt;email,\n    'token'     =&gt; $query-&gt;token,\n];\n```\n\n---\n\n## [18] Missing `exit()` After Session Redirect \u2014 Full Auth Bypass\n\n* **Severity:** High\n* **CWE/OWASP Reference:** CWE-284, OWASP A01:2021 \u2013 Broken Access Control\n* **Location:** `admin/auth_panel/av_header.php` at lines `3\u201310`; `admin/auth_panel/av_header_test.php`\n* **Description:** The session guard emits a JavaScript `window.location` redirect when the session is absent but does not call `exit()`. PHP continues executing the rest of the page. Any HTTP client that does not run JavaScript (curl, Burp Suite, Python requests) receives the full authenticated admin page.\n```php\n} else {\n    echo 'window.location=\"/admin\";';\n    // execution continues \u2014 entire admin panel rendered below\n}\n```\n* **Impact:** The entire admin panel is accessible without authentication to any non-browser client.\n* **Remediation:** Replace the JavaScript redirect with `header()` + `exit`.\n* **Code Fix Example:**\n```php\nif (empty($_SESSION['admin_id'])) {\n    header('Location: ' . ADMIN_URL . 'admin/');\n    exit;\n}\n```\n\n---\n\n## [19] IDOR \u2014 Book Purchase Accepts Arbitrary `user_id` from Client\n\n* **Severity:** High\n* **CWE/OWASP Reference:** CWE-639, OWASP A01:2021 \u2013 Broken Access Control\n* **Location:** `web_services/book_purchase.php` at lines `4\u20139`\n* **Description:** `purchase_user_id` is accepted directly from `$_REQUEST` with no authentication or ownership check. An attacker can credit a purchase to any user's account. Both `$book_id` and `$purchase_user_id` are also injected directly into the SQL query.\n* **Impact:** Free content for any user; arbitrary library manipulation; SQL injection.\n* **Remediation:** Derive `purchase_user_id` exclusively from the authenticated server-side session. Never accept it from the client.\n* **Code Fix Example:**\n```php\nsession_start();\nif (empty($_SESSION['user_id'])) {\n    echo json_encode(['response' =&gt; ['id' =&gt; -4, 'message' =&gt; 'Unauthorized']]);\n    exit;\n}\n$purchase_user_id = (int) $_SESSION['user_id'];\n$book_id          = (int) ($_POST['book_id'] ?? 0);\n```\n\n---\n\n## [20] Broken API Authorization \u2014 JWT Token Does Not Prove User Ownership\n\n* **Severity:** High\n* **CWE/OWASP Reference:** CWE-639, OWASP A01:2021 \u2013 Broken Access Control\n* **Location:** `web_services/new/src/routes/api.php` \u2014 `/getAudioVideo`, `/getAudioVideoClone`, and other user-scoped routes\n* **Description:** Several API routes accept `user_id` as a request parameter and query data for that ID after only checking that a valid auth token exists in the database \u2014 not that the token belongs to the user represented by `user_id`. A valid token issued to user A can be submitted alongside `user_id=B` to access user B's data.\n```php\n// token is validated against DB, but user_id comes from request param unchecked\n$user_id = $request-&gt;getParam('user_id');\n$sql = \"SELECT email FROM appUsers WHERE id='\" . $user_id . \"'\";\n```\n* **Impact:** Any authenticated user can access or manipulate any other user's library, purchases, or profile data.\n* **Remediation:** Extract the user ID from the verified JWT payload, never from request parameters.\n* **Code Fix Example:**\n```php\n// Decode token and pull user ID from it \u2014 not from the request\n$decoded = JWT::decode($authToken, SECRET, ['HS256']);\n$user_id = (int) $decoded-&gt;id; // authoritative, not client-controlled\n```\n\n---\n\n## [21] Social Login Trusts Request-Supplied Identity Without Provider Verification\n\n* **Severity:** High\n* **CWE/OWASP Reference:** CWE-287, OWASP A07:2021 \u2013 Identification and Authentication Failures\n* **Location:** `web_services/social_login.php`; corresponding route in `web_services/new/src/routes/api.php`\n* **Description:** The social login endpoint accepts `socialId`, `email`, `firstname`, and `lastname` directly from the POST body and creates or updates accounts based on those values. No OAuth access token from Google, Facebook, or Apple is validated against the provider's API. An attacker can submit any `socialId` and `email` to log in as or create any account.\n* **Impact:** Complete identity impersonation. An attacker can log in as any existing social-linked account by crafting a POST request with the target's `socialId` or email.\n* **Remediation:** Require a provider-issued OAuth access token or ID token. Validate it server-side against the provider's token verification endpoint before trusting any user identity fields.\n* **Code Fix Example:**\n```php\n// Google example \u2014 validate ID token server-side:\n$id_token = $_POST['google_id_token'];\n$response = file_get_contents(\n    'https://oauth2.googleapis.com/tokeninfo?id_token=' . urlencode($id_token)\n);\n$payload = json_decode($response, true);\nif (!$payload || $payload['aud'] !== getenv('GOOGLE_CLIENT_ID')) {\n    http_response_code(401); exit('Invalid token');\n}\n$email    = $payload['email'];\n$socialId = $payload['sub'];\n```\n\n---\n\n## [22] Missing Authentication on `common/*.php` AJAX Endpoints\n\n* **Severity:** High\n* **CWE/OWASP Reference:** CWE-306, OWASP A01:2021 \u2013 Broken Access Control\n* **Location:** `common/addAudioVideo.php`, `common/book_ajax.php`, `common/assignBooksToUser.php`, `common/delete_ajax.php`, `common/deactivate_book_ajax.php`, `common/approve_books_ajax.php`, `common/update_books_ajax.php`, and others\n* **Description:** These AJAX backend handlers perform high-privilege state-changing operations (book approval, deletion, deactivation, assignment to users, audio/video upload, and user modification) but many call only `session_start()` with no subsequent role or session validity check. An unauthenticated or low-privilege HTTP client can call these endpoints directly.\n* **Impact:** Unauthenticated attackers can approve, deactivate, delete, or modify content and user records without going through the admin UI. Combined with the SQL injection in these files, the impact is total data compromise.\n* **Remediation:** Add a shared authorization guard \u2014 a single include or function \u2014 that validates the session and admin role before any processing. Return HTTP 401/403 and exit on failure.\n* **Code Fix Example:**\n```php\n// common/auth_guard.php\nfunction require_admin_session(): void {\n    session_start();\n    if (empty($_SESSION['admin_id'])) {\n        http_response_code(401);\n        echo json_encode(['error' =&gt; 'Unauthorized']);\n        exit;\n    }\n}\n\n// Top of every common/*.php handler:\nrequire_once __DIR__ . '/auth_guard.php';\nrequire_admin_session();\n```\n\n---\n\n## [23] `phpinfo()` Pages Publicly Exposed\n\n* **Severity:** High\n* **CWE/OWASP Reference:** CWE-200, OWASP A05:2021 \u2013 Security Misconfiguration\n* **Location:** `demo.php`; `web_services/info.php`; `common/phpinfo.php`\n* **Description:** Multiple files call `phpinfo()` and are web-accessible. `phpinfo()` outputs the complete server configuration including PHP version, loaded extensions, environment variables, loaded config file paths, and all `$_SERVER` values \u2014 providing an attacker a detailed fingerprint of the server environment.\n* **Impact:** Accelerates exploitation by revealing PHP version, enabled modules, file paths, and environment variable names (which may expose secret names or values).\n* **Remediation:** Delete all `phpinfo()` files from the codebase. If needed for diagnostics, restrict them by IP or remove entirely from production.\n* **Code Fix Example:**\n```bash\nrm demo.php web_services/info.php common/phpinfo.php\n```\n\n---\n\n## [24] Admin Session ID Echoed in HTML Response\n\n* **Severity:** High\n* **CWE/OWASP Reference:** CWE-200, OWASP A02:2021 \u2013 Cryptographic Failures\n* **Location:** `admin/dashboard.php`\n* **Description:** The admin dashboard echoes the PHP session ID directly into the HTML page body. Session IDs appearing in HTML can be captured from server logs, browser history, referrer headers, or network proxies.\n* **Impact:** Session ID disclosure enables session hijacking \u2014 an attacker who obtains the value can authenticate as the admin without credentials.\n* **Remediation:** Remove all `echo session_id()` or equivalent output from production pages.\n* **Code Fix Example:**\n```php\n// REMOVE this line from dashboard.php:\n// echo session_id();\n```\n\n---\n\n## [25] TLS Verification Disabled in cURL Notification Scripts\n\n* **Severity:** High\n* **CWE/OWASP Reference:** CWE-295, OWASP A02:2021 \u2013 Cryptographic Failures\n* **Location:** `common/notification_abhinew.php`; `common/notification_abhi.php`; `common/notification_abhi3.php`; `common/notification_abhi4.php`; `common/notification_cron_test.php`\n* **Description:** Push notification scripts disable TLS certificate validation:\n```php\ncurl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);\ncurl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);\n```\nAll outbound HTTP requests to Firebase and APNs are made without validating the server's certificate.\n* **Impact:** A network-level attacker (MITM) can intercept push notification payloads, inject malicious notification content, or capture the FCM server key transmitted in request headers.\n* **Remediation:** Enable TLS verification. Ensure the server's CA certificate bundle is up to date.\n* **Code Fix Example:**\n```php\n// Remove or replace:\ncurl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); // verify hostname\ncurl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); // verify certificate\ncurl_setopt($ch, CURLOPT_CAINFO, '/etc/ssl/certs/ca-certificates.crt');\n```\n\n---\n\n## [26] Docker: Database and phpMyAdmin Ports Exposed; EOL PHP Runtime\n\n* **Severity:** High\n* **CWE/OWASP Reference:** CWE-16, OWASP A05:2021 \u2013 Security Misconfiguration\n* **Location:** `docker-compose.yml`; `docker/apache.conf`; `docker/php.ini`\n* **Description:**\n  * MySQL is exposed on host port `3306`, making the database reachable from the network without going through the application.\n  * phpMyAdmin is exposed on host port `8082` with no additional authentication.\n  * Credentials in the compose file and init scripts are hardcoded and committed to the repository.\n  * The admin container uses PHP 5.6 and the API container uses PHP 7.4, both of which are end-of-life and receive no security patches.\n  * `docker/php.ini` enables `display_errors`.\n* **Impact:** Direct database access from the network; trivial phpMyAdmin brute-force; exploitation of unpatched PHP CVEs going back years.\n* **Remediation:** Do not expose database or phpMyAdmin ports to the host by default. Move passwords to `.env` files excluded from Git. Upgrade to a supported PHP version (PHP 8.2+). Disable `display_errors` in production.\n* **Code Fix Example:**\n```yaml\n# docker-compose.yml \u2014 remove host port exposure:\nservices:\n  db:\n    # ports:\n    #  - \"3306:3306\"   # remove this line\n  phpmyadmin:\n    # ports:\n    #  - \"8082:80\"     # remove or restrict to 127.0.0.1\n```\n\n---\n\n## [27] Apache `Options Indexes` Enabled \u2014 Directory Listing Active\n\n* **Severity:** High\n* **CWE/OWASP Reference:** CWE-548, OWASP A05:2021 \u2013 Security Misconfiguration\n* **Location:** `docker/apache.conf`\n* **Description:** The Apache configuration enables `Options Indexes FollowSymLinks`, allowing directory listing across the served document root. The repository root is mounted directly as the Apache document root, meaning any directory without an `index.php` exposes a full file listing \u2014 including `common/`, `uploads/`, `web_services/`, `vendor/`, and any committed config, cert, or dump files.\n* **Impact:** An attacker can enumerate all files, discover config files, certificates, backup files, and SQL dumps, and directly request any of them.\n* **Remediation:** Disable directory listing. Serve only a dedicated `public/` directory. Deny access to all sensitive paths via `.htaccess` or virtual host config.\n* **Code Fix Example:**\n```apache\n\n    Options -Indexes -FollowSymLinks\n    AllowOverride None\n    Require all granted\n\n\n# Deny access to sensitive paths:\n\n    Require all denied\n\n```\n\n---\n\n## [28] Session Fixation \u2014 No `session_regenerate_id()` After Login\n\n* **Severity:** High\n* **CWE/OWASP Reference:** CWE-384, OWASP A07:2021 \u2013 Identification and Authentication Failures\n* **Location:** All admin and author login handlers\n* **Description:** After a successful login, none of the authentication flows call `session_regenerate_id(true)`. The pre-login session ID is reused for the authenticated session. An attacker who can plant a known session ID on a victim's browser (e.g., via XSS or a shared network) can wait for the victim to log in and then use that ID to take over the authenticated session.\n* **Impact:** Session fixation \u2014 attacker-controlled session ID escalates to authenticated admin session.\n* **Remediation:** Call `session_regenerate_id(true)` immediately after setting session variables on successful login.\n* **Code Fix Example:**\n```php\n// After credential verification succeeds:\nsession_regenerate_id(true); // invalidates old session ID\n$_SESSION['admin_id'] = $admin['id'];\n$_SESSION['admin_role'] = $admin['role'];\n```\n\n---\n\n## Medium Findings\n\n---\n\n## [29] No CSRF Protection on State-Changing Admin Forms\n\n* **Severity:** Medium\n* **CWE/OWASP Reference:** CWE-352, OWASP A01:2021 \u2013 Broken Access Control\n* **Location:** `admin/auth_panel/change_password.php`; all admin panel forms throughout `admin/auth_panel/`\n* **Description:** Admin panel forms submit state-changing actions with no CSRF token, allowing a malicious website to silently trigger admin actions on behalf of a logged-in administrator.\n* **Impact:** Admin password reset, content manipulation, and user modification via cross-site request forgery.\n* **Remediation:** Generate a per-session CSRF token stored in `$_SESSION`, embed it in every form, validate it on every POST.\n* **Code Fix Example:**\n```php\n// Generate:\nif (empty($_SESSION['csrf_token'])) {\n    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));\n}\n// Embed in form:\necho '';\n// Validate on POST:\nif (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'] ?? '')) {\n    http_response_code(403); exit('CSRF validation failed');\n}\n```\n\n---\n\n## [30] Database Error Messages Exposed to HTTP Response\n\n* **Severity:** Medium\n* **CWE/OWASP Reference:** CWE-209, OWASP A05:2021 \u2013 Security Misconfiguration\n* **Location:** `connection.php` at line `9`; `web_services/new/src/libs/connection.php` at line `40`\n* **Description:** Connection failures print raw error details to the HTTP response: `die(\"Unable to connect to MySQL: \" . mysqli_error($dbc))` and `die($e)` (full PDO stack trace).\n* **Impact:** Database hostname, error codes, and stack traces exposed in HTTP responses aid attacker reconnaissance.\n* **Remediation:** Log errors server-side; return a generic HTTP 500 to the client.\n* **Code Fix Example:**\n```php\nif (!$dbc) {\n    error_log(\"DB connection failed: \" . mysqli_connect_error());\n    http_response_code(500);\n    exit('Internal server error');\n}\n```\n\n---\n\n## [31] SMTP Debug Mode Enabled in Production\n\n* **Severity:** Medium\n* **CWE/OWASP Reference:** CWE-532, OWASP A09:2021 \u2013 Security Logging and Monitoring Failures\n* **Location:** `function.php` at lines `571`, `601`; `web_services/function.php`; `web_services/new/src/libs/auth.php` at line `121`\n* **Description:** `$mail-&gt;SMTPDebug = 2` in production functions dumps the full SMTP handshake \u2014 including the `AUTH LOGIN` exchange \u2014 into the HTTP response buffer, leaking SMTP credentials to API callers.\n* **Impact:** SMTP username and password visible in API responses.\n* **Remediation:** `$mail-&gt;SMTPDebug = 0` in all production code.\n* **Code Fix Example:**\n```php\n$mail-&gt;SMTPDebug = 0; // never expose SMTP dialogue to clients\n```\n\n---\n\n## [32] Predictable Password Reset Token \u2014 `uniqid()` Is Not Cryptographically Random\n\n* **Severity:** Medium\n* **CWE/OWASP Reference:** CWE-330, OWASP A07:2021 \u2013 Identification and Authentication Failures\n* **Location:** `web_services/register.php` at line `24`; `web_services/forgetpassword.php` at line `12`; `web_services/new/src/routes/api.php` at lines `217`, `479`\n* **Description:** `uniqid()` generates values based on the current timestamp with microsecond precision. An attacker who knows approximately when a reset was triggered can enumerate the small window of possible values. `\"res\" . time()` is equally weak.\n* **Impact:** Account takeover by brute-forcing the password reset token.\n* **Remediation:** Use `bin2hex(random_bytes(32))`. Store a hash of the token in the database. Enforce short expiry and one-time use.\n* **Code Fix Example:**\n```php\n$token        = bin2hex(random_bytes(32)); // send this in the email link\n$token_hash   = hash('sha256', $token);    // store this in the DB\n// On redemption: hash($_GET['rcode']) === DB value, and expiry not exceeded\n```\n\n---\n\n## [33] Stored XSS \u2014 Unescaped User Data in Admin Panel\n\n* **Severity:** Medium\n* **CWE/OWASP Reference:** CWE-79, OWASP A03:2021 \u2013 Injection\n* **Location:** `admin/auth_panel/av_header.php` at line `15`; `admin/auth_panel/av_header_test.php` at line `18`\n* **Description:** The administrator's display name is echoed directly into HTML with no output encoding: `echo $admin_data['name']`. Database values from book titles, descriptions, and filenames are echoed without encoding in `authors_admin/view_books.php` and related views.\n* **Impact:** Stored XSS \u2014 admin session cookie theft, account takeover.\n* **Remediation:** Use `htmlspecialchars($value, ENT_QUOTES, 'UTF-8')` on every value echoed into HTML.\n* **Code Fix Example:**\n```php\n// BEFORE:\necho $admin_data['name'];\n\n// AFTER:\necho htmlspecialchars($admin_data['name'], ENT_QUOTES, 'UTF-8');\n```\n\n---\n\n## [34] Reflected XSS \u2014 Raw `$_GET` / `$_REQUEST` Output in HTML\n\n* **Severity:** Medium\n* **CWE/OWASP Reference:** CWE-79, OWASP A03:2021 \u2013 Injection\n* **Location:**\n  * `authors_admin/view_paymentsTest.php` \u2014 `echo isset($_GET['startDate']) ? $_GET['startDate'] : ''` directly into an HTML attribute value\n  * `admin/auth_panel/boobk_by_aut.php` \u2014 `echo $_REQUEST[\"id\"]` directly into the page\n* **Description:** Request parameters are reflected directly into the HTML response without sanitization. An attacker can craft a URL containing a JavaScript payload that executes in the victim's browser when they click the link.\n* **Impact:** Reflected XSS \u2014 session cookie theft, credential phishing, in-browser actions on behalf of admin or author users.\n* **Remediation:** Escape all `$_GET`, `$_POST`, and `$_REQUEST` values with `htmlspecialchars()` before echoing into HTML. For numeric IDs, cast to `(int)` first.\n* **Code Fix Example:**\n```php\n// BEFORE:\necho $_REQUEST[\"id\"];\nvalue=\"\"\n\n// AFTER:\necho (int) $_REQUEST[\"id\"]; // for numeric IDs\nvalue=\"\"\n```\n\n---\n\n## [35] JWT Middleware `\"secure\" =&gt; false` \u2014 Tokens Transmitted over HTTP\n\n* **Severity:** Medium\n* **CWE/OWASP Reference:** CWE-319, OWASP A02:2021 \u2013 Cryptographic Failures\n* **Location:** `web_services/new/src/routes/api.php` at line `20`\n* **Description:** The JWT middleware is configured with `\"secure\" =&gt; false`, which disables its requirement for HTTPS. Tokens can be transmitted and accepted over plain HTTP, making them interceptable by any on-path observer.\n* **Impact:** JWT token theft via network interception, enabling session hijacking.\n* **Remediation:** Set `\"secure\" =&gt; true` in all non-local environments. Enforce HTTPS at the load balancer or Apache level.\n* **Code Fix Example:**\n```php\n$app-&gt;add(new \\Slim\\Middleware\\JwtAuthentication([\n    \"secret\" =&gt; SECRET,\n    \"secure\" =&gt; (getenv('APP_ENV') !== 'local'), // true in all non-local environments\n    // ...\n]));\n```\n\n---\n\n## [36] Missing HTTP Security Headers\n\n* **Severity:** Medium\n* **CWE/OWASP Reference:** CWE-16, OWASP A05:2021 \u2013 Security Misconfiguration\n* **Location:** Application-wide; no consistent header policy found in Apache config or PHP\n* **Description:** None of the following security headers are set: `Content-Security-Policy`, `X-Frame-Options` / `frame-ancestors`, `X-Content-Type-Options`, `Referrer-Policy`, or `Strict-Transport-Security`. Their absence makes XSS exploitation easier, enables clickjacking, and permits MIME-type sniffing attacks.\n* **Impact:** Exacerbates the impact of all XSS findings. Enables clickjacking against admin and user-facing pages.\n* **Remediation:** Add headers via Apache `Header` directives or a PHP middleware function called on every response.\n* **Code Fix Example:**\n```apache\n# In Apache VirtualHost or .htaccess:\nHeader always set X-Content-Type-Options \"nosniff\"\nHeader always set X-Frame-Options \"DENY\"\nHeader always set Referrer-Policy \"strict-origin-when-cross-origin\"\nHeader always set Strict-Transport-Security \"max-age=63072000; includeSubDomains\"\nHeader always set Content-Security-Policy \"default-src 'self'; script-src 'self'; object-src 'none';\"\n```\n\n---\n\n## [37] Database Dumps and Sensitive Artifacts Committed to Repository\n\n* **Severity:** Medium\n* **CWE/OWASP Reference:** CWE-312, OWASP A05:2021 \u2013 Security Misconfiguration\n* **Location:** `docker/db-init/ebookapp.sql`; `docker/db-init/missing_tables.sql`; `panel/images/5587Emailer.zip`; `uploads/` directory\n* **Description:** SQL dump files containing schema and potentially real user data are committed to the repository. The `uploads/` directory with user-generated content is also tracked. Committed dumps may contain test accounts, personal data, or internal schema details that aid SQL injection exploitation.\n* **Impact:** Personal data exposure, GDPR/compliance liability, schema disclosure that aids further attacks.\n* **Remediation:** Remove dumps and user uploads from the repository. Add `.gitignore` rules for `*.sql`, `uploads/`, `*.log`, `*.pem`, `*.p12`, `error_log`. Use sanitized fixture files for development.\n* **Code Fix Example:**\n```gitignore\n# .gitignore additions:\n*.sql\n*.pem\n*.p12\nuploads/\nerror_log\n**/error_log\n*.log\n.env\ndocker/db-init/*.sql\n```\n\n---\n\n## [38] Outdated Dependencies \u2014 Slim 3, PHPMailer Copies, Frontend Libraries\n\n* **Severity:** Medium\n* **CWE/OWASP Reference:** CWE-1104, OWASP A06:2021 \u2013 Vulnerable and Outdated Components\n* **Location:** `web_services/new/composer.json`; multiple committed `class.phpmailer.php` copies; `assets/js/` and `authors_admin/assets/`\n* **Description:** The Slim API uses Slim 3.x (EOL), `tuupola/slim-jwt-auth` 2.4.x, and the vulnerable `firebase/php-jwt` 5.0.0. Multiple separate copies of an old PHPMailer version are committed to the repository rather than managed via Composer. Vendored frontend libraries include old jQuery, jQuery UI, Bootstrap 3, CKEditor 4, Morris.js, and jQuery File Upload \u2014 all of which have known CVEs.\n* **Impact:** Known public vulnerabilities in PHP and JavaScript libraries leading to XSS, file upload abuse, prototype pollution, and browser-side compromise.\n* **Remediation:** Manage all dependencies through Composer and npm with lockfiles. Remove committed vendor copies. Upgrade to supported major versions. Run `composer audit` and `npm audit` in CI.\n* **Code Fix Example:**\n```bash\n# Replace vendored PHPMailer copies:\ncomposer require phpmailer/phpmailer:^6.0\n\n# Upgrade Slim and JWT:\ncomposer require slim/slim:^4.0\ncomposer require firebase/php-jwt:^6.0\n```\n\n---\n\n## Low Findings\n\n---\n\n## [39] `md5()` Used in Security-Sensitive Context\n\n* **Severity:** Low\n* **CWE/OWASP Reference:** CWE-327, OWASP A02:2021 \u2013 Cryptographic Failures\n* **Location:** `class.phpmailer.php`; any location using `md5(uniqid())` for token generation\n* **Description:** MD5 is cryptographically broken \u2014 vulnerable to collision attacks and trivially reversible for short inputs. Its use for generating unique IDs or security tokens (even non-password contexts) produces values that are predictable and collision-prone.\n* **Impact:** If MD5 is used to generate any token, nonce, or identifier that is relied upon for security, those values can be forged or predicted.\n* **Remediation:** Replace all security-sensitive uses of `md5()` with `bin2hex(random_bytes(16))` for random IDs and `hash('sha256', $value)` when a deterministic digest is needed.\n* **Code Fix Example:**\n```php\n// BEFORE:\n$unique_id = md5(uniqid());\n\n// AFTER:\n$unique_id = bin2hex(random_bytes(16));\n```\n\n---\n\n## Immediate Containment Plan\n\nExecute these steps before any further production deployment:\n\n1. **Rotate all credentials** \u2014 database password, SMTP passwords, AWS SES key, SendGrid API key, Firebase server key, JWT secret. Revoke and regenerate Apple push certificates.\n2. **Block the `deleteUser` endpoint** \u2014 remove it from the JWT passthrough list or take the API offline until patched.\n3. **Remove or firewall `phpinfo()` pages** \u2014 `demo.php`, `web_services/info.php`, `common/phpinfo.php`.\n4. **Disable `display_errors`** \u2014 in all PHP configs and Docker images.\n5. **Disable directory listing** \u2014 set `Options -Indexes` in Apache config.\n6. **Patch `firebase/php-jwt`** \u2014 upgrade from 5.0.0 to 6.x immediately.\n7. **Take down Docker-exposed ports** \u2014 remove host-side `3306` and `8082` bindings.\n8. **Delete or restrict upload execution** \u2014 add an `.htaccess` `php_flag engine off` in all upload directories.\n\n---\n\n## Recommended Remediation Phases\n\n### Phase 1 \u2014 Stop Active Exploitation (Week 1)\n- Rotate and purge all committed secrets from history.\n- Patch or disable the unauthenticated deleteUser endpoint.\n- Remove phpinfo files and disable debug display.\n- Lock down Apache config: remove `Indexes`, restrict document root to `public/`.\n- Deny PHP execution in upload directories.\n- Upgrade `firebase/php-jwt` to 6.x.\n\n### Phase 2 \u2014 Fix Authentication and Identity (Week 2\u20133)\n- Migrate all passwords to `password_hash()`. Force reset for all users.\n- Fix `reset.php` to hash the new password before storage.\n- Add `session_regenerate_id(true)` after every successful login.\n- Secure session cookies: `HttpOnly`, `Secure`, `SameSite=Lax`.\n- Remove `extract()` on superglobals everywhere.\n- Validate social login tokens server-side against provider APIs.\n\n### Phase 3 \u2014 Eliminate Injection and Upload Risks (Week 3\u20135)\n- Replace all `mysql_*` calls with PDO prepared statements across the entire codebase.\n- Rewrite `insert_entry()` and `update_entry()` helpers to use bound parameters.\n- Add server-side MIME validation, random filename generation, and out-of-webroot storage for all file uploads.\n- Replace `unlink($_POST['...'])` with DB-driven file path resolution.\n\n### Phase 4 \u2014 Harden Infrastructure and Dependencies (Week 5\u20138)\n- Upgrade PHP runtime to 8.2+ in all containers.\n- Move all dependencies to Composer/npm with lockfiles. Remove committed vendor copies.\n- Add `composer audit` and `npm audit` to CI pipeline.\n- Add HTTP security headers: CSP, HSTS, X-Frame-Options, X-Content-Type-Options.\n- Add secret scanning (gitleaks or trufflehog) to the pre-commit hook and CI.\n\n---\n\n*End of Report*\n", "creation_timestamp": "2026-05-12T12:19:28.000000Z"}