# Unsafe ast.literal_eval Replacement via eval() on Untrusted Input

Language: Python
Severity: Critical
CWE: CWE-94

## Source
10

## Flow
10-15

## Sink
15

## Vulnerable Code
```python
from flask import Flask, request, jsonify
import hmac
import hashlib

app = Flask(__name__)

@app.route('/hsm/verify_signature', methods=['POST'])
def hsm_signature_verification():
    payload_data = request.json.get('payload')
    sig_params = request.json.get('signature_params')
    key_id = request.json.get('key_identifier')
    
    hsm_key = retrieve_hsm_key(key_id)
    
    sig_config = eval(sig_params)
    
    algorithm = sig_config.get('algorithm', 'sha256')
    encoding = sig_config.get('encoding', 'utf-8')
    
    computed_sig = hmac.new(
        hsm_key.encode(),
        payload_data.encode(encoding),
        getattr(hashlib, algorithm)
    ).hexdigest()
    
    return jsonify({'signature': computed_sig, 'key_id': key_id})

def retrieve_hsm_key(kid):
    return 'hsm_master_key_' + kid
```

## Explanation

The application accepts untrusted user input via 'signature_params' from a JSON POST request and directly passes it to eval() without any validation or sanitization. This allows an attacker to execute arbitrary Python code on the server, leading to complete system compromise including data exfiltration, privilege escalation, or denial of service.

## Remediation

The fix replaces the dangerous eval() call with json.loads() for safe parsing of the signature parameters string, and also handles the case where the input is already a dictionary. Additionally, whitelist validation is added for both the algorithm and encoding parameters to prevent attackers from accessing arbitrary hashlib attributes or using unexpected encodings.

## Secure Code
```python
from flask import Flask, request, jsonify
import hmac
import hashlib
import json

app = Flask(__name__)

ALLOWED_ALGORITHMS = {'sha256', 'sha384', 'sha512', 'sha224', 'sha1', 'md5'}
ALLOWED_ENCODINGS = {'utf-8', 'ascii', 'latin-1'}

@app.route('/hsm/verify_signature', methods=['POST'])
def hsm_signature_verification():
    payload_data = request.json.get('payload')
    sig_params = request.json.get('signature_params')
    key_id = request.json.get('key_identifier')
    
    hsm_key = retrieve_hsm_key(key_id)
    
    if isinstance(sig_params, str):
        try:
            sig_config = json.loads(sig_params)
        except (json.JSONDecodeError, ValueError):
            return jsonify({'error': 'Invalid signature_params format. Must be a valid JSON string or object.'}), 400
    elif isinstance(sig_params, dict):
        sig_config = sig_params
    else:
        return jsonify({'error': 'Invalid signature_params type. Must be a JSON string or object.'}), 400
    
    if not isinstance(sig_config, dict):
        return jsonify({'error': 'signature_params must resolve to a dictionary.'}), 400
    
    algorithm = sig_config.get('algorithm', 'sha256')
    encoding = sig_config.get('encoding', 'utf-8')
    
    if algorithm not in ALLOWED_ALGORITHMS:
        return jsonify({'error': f'Invalid algorithm. Allowed: {sorted(ALLOWED_ALGORITHMS)}'}), 400
    
    if encoding not in ALLOWED_ENCODINGS:
        return jsonify({'error': f'Invalid encoding. Allowed: {sorted(ALLOWED_ENCODINGS)}'}), 400
    
    computed_sig = hmac.new(
        hsm_key.encode(),
        payload_data.encode(encoding),
        getattr(hashlib, algorithm)
    ).hexdigest()
    
    return jsonify({'signature': computed_sig, 'key_id': key_id})

def retrieve_hsm_key(kid):
    return 'hsm_master_key_' + kid
```
