CVE-2022-2821 (PHP, published 2022-08-15T11:21Z)
Description: value: Missing Critical Step in Authentication in GitHub repository namelessmc/nameless prior to v2.0.2.
Severity: nan (CVSS v3 base score: 7.5)
Weakness: CWE-304 \u2014 Missing Critical Step in Authentication
The product implements an authentication technique, but it skips a step that weakens the technique.
Repository: https://github.com/namelessmc/nameless
Files changed:
core/classes/Core/Validate.php
custom/languages/en_UK.json
modules/Core/pages/login.php
Commit message: Add ability to rate limit via Validate class (#2998)



Vulnerable code:
            $message = $this->getMessage($error['field'], $error['rule'], $error['fallback']);
    private function getMessage(string $field, string $rule, string $fallback): string {
                        Validate::IS_ACTIVE => true
                        Validate::IS_ACTIVE => true
                    Validate::IS_ACTIVE => $language->get('user', 'inactive_account')
                    Validate::IS_ACTIVE => $language->get('user', 'inactive_account')

Fixed code:
    /**
     * @var string Set a rate limit
     */
    public const RATE_LIMIT = 'rate_limit';

     * @throws Exception If provided configuration for a rule is invalid - not if a provided value is invalid!

                    case self::RATE_LIMIT:
                        if (is_array($rule_value) && count($rule_value) === 2) {
                            // If array treat as [limit, seconds]
                            [$limit, $seconds] = $rule_value;
                        } else if (is_int($rule_value)) {
                            // If integer default seconds to 60
                            [$limit, $seconds] = [$rule_value, 60];
                        }

                        if (!isset($limit) || !isset($seconds)) {
                            throw new Exception('Invalid rate limit configuration');
                        }

                        $key = "rate_limit_{$item}";
                        $session = $_SESSION[$key];
                        $time = date('U');
                        $limit_end = $time + $seconds;

                        if (isset($session) && is_array($session) && count($session) === 2) {
                            [$count, $expires] = $session;
                            $diff = $expires - $time;

                            if (++$count >= $limit && $diff > 0) {
                                $validator->addError([
                                    'field' => $item,
                                    'rule' => self::RATE_LIMIT,
                                    'fallback' => "$item has reached the rate limit which expires in $diff seconds.",
                                    'meta' => ['expires' => $diff],
                                ]);
                                break;
                            }

                            if ($diff <= 0) {
                                // Reset
                                $_SESSION[$key] = [1, $limit_end];
                                break;
                            }

                            $_SESSION[$key] = [$count, $expires];
                        } else {
                            $_SESSION[$key] = [1, $limit_end];
                        }

                        break;
            $message = $this->getMessage($error['field'], $error['rule'], $error['fallback'], $error['meta']);
     * @param ?array $meta optional meta to provide to message.
    private function getMessage(string $field, string $rule, string $fallback, ?array $meta = []): string {
        // If the message is a callback function, provide it with meta
        if (is_callable($this->_messages[$field][$rule])) {
            return $this->_messages[$field][$rule]($meta);
        }

    "general/rate_limit": "Please try again in {{expires}} seconds",
            $rate_limit = [5, 60]; // 5 attempts in 60 seconds - TODO allow this to be customised?

                        Validate::IS_ACTIVE => true,
                        Validate::RATE_LIMIT => $rate_limit,
                        Validate::IS_ACTIVE => true,
                        Validate::RATE_LIMIT => $rate_limit,
                    Validate::IS_ACTIVE => $language->get('user', 'inactive_account'),
                    Validate::RATE_LIMIT => fn($meta) => $language->get('general', 'rate_limit', $meta),
                    Validate::IS_ACTIVE => $language->get('user', 'inactive_account'),
                    Validate::RATE_LIMIT => fn($meta) => $language->get('general', 'rate_limit', $meta),