Reliance on a Single Factor in a Security Decision

Description

Reliance on a Single Factor in a Security Decision occurs when an application uses only one piece of information to make security-critical decisions. This includes using only a password without MFA, trusting only IP address for access control, relying solely on client-provided data, or using a single check for authorization. If that single factor is compromised, the entire security control fails.

Risk

Credential theft leads to complete account compromise. IP spoofing bypasses network-based access controls. Single points of failure in security architecture. Social engineering attacks succeed with one piece of information. Replay attacks succeed when tokens aren't combined with other factors. Automated attacks are easier without multiple challenges.

Solution

Implement multi-factor authentication. Use defense in depth with multiple security layers. Combine something you know, have, and are. Verify multiple independent attributes. Implement risk-based authentication. Use behavioral analysis alongside credentials. Don't rely solely on any single identifier.

Common Consequences

ImpactDetails
AuthenticationScope: Account Takeover

Single factor compromise grants full access.
AuthorizationScope: Access Control Bypass

Single check easily circumvented.
Non-RepudiationScope: Impersonation

Cannot prove user identity with single factor.

Example Code + Solution Code

Vulnerable Code

// VULNERABLE: Password-only authentication
public class VulnerableSingleFactorAuth {

    public AuthResult authenticate(String username, String password) {
        User user = userRepository.findByUsername(username);

        // VULNERABLE: Only password check
        if (user != null && passwordEncoder.matches(password, user.getPasswordHash())) {
            return AuthResult.success(user);
        }

        return AuthResult.failure();
    }
}

// VULNERABLE: IP-only access control
public class VulnerableIPBasedAuth {

    private Set<String> allowedIPs = Set.of("10.0.0.1", "10.0.0.2");

    public boolean isAuthorized(HttpServletRequest request) {
        // VULNERABLE: IP address can be spoofed
        String clientIP = request.getRemoteAddr();
        return allowedIPs.contains(clientIP);
    }
}

// VULNERABLE: Single token for all access
public class VulnerableSingleTokenAuth {

    public boolean validateAccess(String token) {
        // VULNERABLE: Token alone grants all access
        // If token is stolen, attacker has full access
        return tokenService.isValid(token);
    }

    public void performSensitiveAction(String token) {
        // VULNERABLE: No additional verification for sensitive actions
        if (validateAccess(token)) {
            executeSensitiveOperation();
        }
    }
}

// VULNERABLE: Referer-only CSRF protection
public class VulnerableRefererCheck {

    public boolean isValidRequest(HttpServletRequest request) {
        // VULNERABLE: Referer can be manipulated/removed
        String referer = request.getHeader("Referer");
        return referer != null && referer.startsWith("https://myapp.com");
    }
}
# VULNERABLE: Single factor authentication
class VulnerableAuth:
    def authenticate(self, username, password):
        user = get_user(username)

        # VULNERABLE: Password only
        if user and check_password(password, user.password_hash):
            return create_session(user)

        return None

    # VULNERABLE: API key only
    def authenticate_api(self, api_key):
        # Single key grants full API access
        if api_key in valid_api_keys:
            return True
        return False

# VULNERABLE: Cookie-only session validation
class VulnerableSessionAuth:
    def validate_session(self, session_id):
        # VULNERABLE: Only checks session cookie
        # If cookie is stolen (XSS), attacker has full access
        session = get_session(session_id)
        return session is not None

    def transfer_funds(self, session_id, amount, to_account):
        # VULNERABLE: No re-authentication for sensitive action
        if self.validate_session(session_id):
            execute_transfer(amount, to_account)

# VULNERABLE: Secret question only for password reset
class VulnerablePasswordReset:
    def verify_identity(self, username, answer):
        user = get_user(username)
        # VULNERABLE: Single question, often guessable
        if user.security_answer.lower() == answer.lower():
            return True
        return False

    def reset_password(self, username, answer, new_password):
        # VULNERABLE: Single factor for critical action
        if self.verify_identity(username, answer):
            set_password(username, new_password)
// VULNERABLE: Single factor auth in Node.js
class VulnerableAuth {
    async authenticate(username, password) {
        const user = await db.findUser(username);

        // VULNERABLE: Password only
        if (user && await bcrypt.compare(password, user.passwordHash)) {
            return this.createToken(user);
        }

        return null;
    }

    // VULNERABLE: Bearer token only
    validateRequest(req) {
        const token = req.headers.authorization?.split(' ')[1];
        // VULNERABLE: Token alone grants access
        // No device fingerprinting, no location check
        return this.verifyToken(token);
    }
}

// VULNERABLE: Origin-only CORS protection
app.use((req, res, next) => {
    const origin = req.headers.origin;
    // VULNERABLE: Origin can be spoofed in some scenarios
    if (allowedOrigins.includes(origin)) {
        res.setHeader('Access-Control-Allow-Origin', origin);
    }
    next();
});

// VULNERABLE: User-Agent for bot detection
function isBot(req) {
    // VULNERABLE: User-Agent easily spoofed
    const ua = req.headers['user-agent'];
    return botPatterns.some(pattern => pattern.test(ua));
}

// VULNERABLE: Email verification only for account recovery
async function recoverAccount(email) {
    const user = await db.findByEmail(email);
    if (user) {
        // VULNERABLE: Email access is single factor
        // If email is compromised, account is compromised
        const token = generateResetToken();
        await sendResetEmail(email, token);
    }
}
<?php
// VULNERABLE: Password-only authentication
class VulnerableAuth {
    public function login($username, $password) {
        $user = $this->getUser($username);

        // VULNERABLE: Single factor
        if ($user && password_verify($password, $user['password_hash'])) {
            $_SESSION['user_id'] = $user['id'];
            return true;
        }
        return false;
    }

    // VULNERABLE: Session-only for admin actions
    public function deleteUser($targetUserId) {
        // VULNERABLE: No re-authentication for destructive action
        if (isset($_SESSION['user_id'])) {
            $this->db->delete('users', $targetUserId);
            return true;
        }
        return false;
    }
}

// VULNERABLE: API key only
function authenticateAPI() {
    $apiKey = $_SERVER['HTTP_X_API_KEY'] ?? '';
    // VULNERABLE: Key alone grants full access
    return in_array($apiKey, getValidApiKeys());
}

// VULNERABLE: CAPTCHA only for registration
function registerUser($data, $captchaResponse) {
    // VULNERABLE: CAPTCHA is only bot prevention
    // No email verification, no phone verification
    if (verifyCaptcha($captchaResponse)) {
        createUser($data);
        return true;
    }
    return false;
}
?>

Fixed Code

// SAFE: Multi-factor authentication
public class MultiFactorAuth {

    private final PasswordEncoder passwordEncoder;
    private final TotpService totpService;
    private final RiskAnalysisService riskService;

    public AuthResult authenticate(AuthRequest request) {
        User user = userRepository.findByUsername(request.getUsername());
        if (user == null) {
            return AuthResult.failure("Invalid credentials");
        }

        // Factor 1: Password (something you know)
        if (!passwordEncoder.matches(request.getPassword(), user.getPasswordHash())) {
            return AuthResult.failure("Invalid credentials");
        }

        // Factor 2: TOTP (something you have)
        if (user.hasMfaEnabled()) {
            if (!totpService.verify(user.getTotpSecret(), request.getTotpCode())) {
                return AuthResult.failure("Invalid MFA code");
            }
        }

        // Factor 3: Risk-based checks (behavioral)
        RiskAssessment risk = riskService.assess(request, user);
        if (risk.isHigh()) {
            // Require additional verification
            return AuthResult.requireAdditionalVerification(risk.getReason());
        }

        return AuthResult.success(user);
    }

    // SAFE: Re-authentication for sensitive actions
    public void performSensitiveAction(User user, String password, String totpCode) {
        // Re-verify credentials before sensitive action
        if (!passwordEncoder.matches(password, user.getPasswordHash())) {
            throw new AuthenticationException("Invalid password");
        }

        if (user.hasMfaEnabled()) {
            if (!totpService.verify(user.getTotpSecret(), totpCode)) {
                throw new AuthenticationException("Invalid MFA code");
            }
        }

        executeSensitiveOperation();
    }
}

// SAFE: Multiple factors for access control
public class MultiFactorAccessControl {

    public boolean isAuthorized(HttpServletRequest request, User user) {
        // Factor 1: Valid session
        if (!sessionService.isValidSession(request)) {
            return false;
        }

        // Factor 2: IP in allowed range (for admin)
        if (user.isAdmin()) {
            String ip = getClientIP(request);
            if (!allowedAdminIPs.contains(ip)) {
                return false;
            }
        }

        // Factor 3: Device fingerprint matches known device
        String fingerprint = request.getHeader("X-Device-Fingerprint");
        if (!user.getKnownDevices().contains(fingerprint)) {
            // Flag for additional verification
            notifySecurityTeam(user, "Unknown device access");
        }

        // Factor 4: No anomalous behavior
        if (riskService.detectAnomaly(user, request)) {
            return false;
        }

        return true;
    }
}
# SAFE: Multi-factor authentication in Python
import pyotp
from flask import request

class MultiFactorAuth:
    def __init__(self, risk_service, device_service):
        self.risk_service = risk_service
        self.device_service = device_service

    def authenticate(self, username, password, totp_code=None, device_id=None):
        user = get_user(username)
        if not user:
            return None

        # Factor 1: Password
        if not check_password(password, user.password_hash):
            self.log_failed_attempt(username, 'password')
            return None

        # Factor 2: TOTP (if enabled)
        if user.mfa_enabled:
            if not totp_code:
                return {'status': 'mfa_required'}

            totp = pyotp.TOTP(user.totp_secret)
            if not totp.verify(totp_code):
                self.log_failed_attempt(username, 'totp')
                return None

        # Factor 3: Device verification
        if device_id:
            if not self.device_service.is_known_device(user.id, device_id):
                # New device - send verification email
                self.send_device_verification(user, device_id)
                return {'status': 'device_verification_required'}

        # Factor 4: Risk assessment
        risk = self.risk_service.assess(user, request)
        if risk.score > 0.8:
            return {'status': 'additional_verification_required', 'reason': risk.factors}

        return {'status': 'success', 'user': user}

    def transfer_funds(self, session, amount, to_account, password, totp_code):
        user = session.user

        # Re-authenticate for sensitive action
        if not check_password(password, user.password_hash):
            raise AuthenticationError("Invalid password")

        if user.mfa_enabled:
            totp = pyotp.TOTP(user.totp_secret)
            if not totp.verify(totp_code):
                raise AuthenticationError("Invalid MFA code")

        # Additional check: transaction limits
        if amount > user.transaction_limit:
            raise SecurityError("Exceeds transaction limit")

        execute_transfer(user, amount, to_account)

# SAFE: Multi-layer API authentication
class SecureAPIAuth:
    def authenticate_request(self, request):
        # Factor 1: API key
        api_key = request.headers.get('X-API-Key')
        if not api_key or api_key not in valid_api_keys:
            return None

        # Factor 2: Request signature (HMAC)
        signature = request.headers.get('X-Signature')
        expected_sig = self.compute_signature(request, api_key)
        if not hmac.compare_digest(signature, expected_sig):
            return None

        # Factor 3: Timestamp (prevent replay)
        timestamp = request.headers.get('X-Timestamp')
        if not self.is_recent_timestamp(timestamp):
            return None

        # Factor 4: IP whitelist (if configured)
        api_config = get_api_config(api_key)
        if api_config.ip_whitelist:
            if request.remote_addr not in api_config.ip_whitelist:
                return None

        return api_config.client_id
// SAFE: Multi-factor authentication in Node.js
const speakeasy = require('speakeasy');

class MultiFactorAuth {
    async authenticate(credentials) {
        const { username, password, totpCode, deviceId } = credentials;

        const user = await db.findUser(username);
        if (!user) {
            return { success: false };
        }

        // Factor 1: Password
        if (!await bcrypt.compare(password, user.passwordHash)) {
            await this.logFailedAttempt(username, 'password');
            return { success: false };
        }

        // Factor 2: TOTP
        if (user.mfaEnabled) {
            if (!totpCode) {
                return { success: false, mfaRequired: true };
            }

            const verified = speakeasy.totp.verify({
                secret: user.totpSecret,
                encoding: 'base32',
                token: totpCode
            });

            if (!verified) {
                await this.logFailedAttempt(username, 'totp');
                return { success: false };
            }
        }

        // Factor 3: Device verification
        if (deviceId) {
            const knownDevice = await db.isKnownDevice(user.id, deviceId);
            if (!knownDevice) {
                await this.sendDeviceVerificationEmail(user, deviceId);
                return { success: false, deviceVerificationRequired: true };
            }
        }

        // Factor 4: Risk-based analysis
        const risk = await this.riskService.analyze(user, credentials);
        if (risk.score > 0.7) {
            return {
                success: false,
                additionalVerificationRequired: true,
                riskFactors: risk.factors
            };
        }

        return { success: true, user };
    }

    // SAFE: Sensitive action requires re-authentication
    async transferFunds(session, transferData) {
        const { amount, toAccount, password, totpCode } = transferData;
        const user = session.user;

        // Re-verify password
        if (!await bcrypt.compare(password, user.passwordHash)) {
            throw new AuthError('Invalid password');
        }

        // Re-verify MFA
        if (user.mfaEnabled) {
            const verified = speakeasy.totp.verify({
                secret: user.totpSecret,
                encoding: 'base32',
                token: totpCode
            });

            if (!verified) {
                throw new AuthError('Invalid MFA code');
            }
        }

        // Proceed with transfer
        await this.executeTransfer(user, amount, toAccount);
    }
}

Exploited in the Wild

Credential Stuffing

Password-only auth vulnerable to stuffed credentials.

SIM Swapping

SMS-only 2FA bypassed via carrier social engineering.

Session Hijacking

Token-only auth compromised via XSS.


Tools to test/exploit

  • Credential testing tools (Hydra, Medusa).

  • Session hijacking tools.

  • MFA bypass techniques.


CVE Examples

  • Account takeovers through single-factor systems.

  • API breaches via exposed API keys.


References

  1. MITRE. "CWE-654: Reliance on a Single Factor in a Security Decision." https://cwe.mitre.org/data/definitions/654.html

  2. NIST SP 800-63B. "Digital Identity Guidelines."