Externe Kontrolle von als unveränderlich angenommenen Web-Parametern

Beschreibung

Externe Kontrolle von als unveränderlich angenommenen Web-Parametern tritt auf, wenn eine Webanwendung Eingaben aus HTTP-Parametern verwendet, von denen der Entwickler annimmt, dass sie von Endbenutzern nicht modifiziert werden können, die aber tatsächlich manipuliert werden können. Dies umfasst versteckte Formularfelder, Cookies, HTTP-Header, URL-Parameter und alle clientseitigen Daten, denen der Server ohne Validierung vertraut. Angreifer können diese "versteckten" Werte modifizieren, um Sicherheitskontrollen zu umgehen, Preise zu ändern, Benutzerrollen zu verändern oder die Anwendungslogik zu manipulieren.

Risiko

Entwickler nehmen oft an, dass versteckte Felder, Cookies oder client-generierte Werte sicher sind, weil Benutzer sie "nicht sehen" oder "nicht ändern" können. Allerdings sind alle clientseitigen Daten trivial modifizierbar mit Browser-Entwicklertools, Proxy-Tools wie Burp Suite oder einfachen curl-Befehlen. Preismanipulation, Privilegieneskalation und Business-Logic-Umgehungen sind häufige Ergebnisse. E-Commerce-Anwendungen haben durch Manipulation versteckter Preisfelder erhebliche finanzielle Verluste erlitten.

Lösung

Vertrauen Sie niemals clientseitigen Daten für Sicherheitsentscheidungen. Validieren Sie alle Eingaben auf der Serverseite, unabhängig von ihrer Quelle. Verwenden Sie serverseitige Session-Speicherung für sensible Werte anstelle von versteckten Feldern oder Cookies. Implementieren Sie Integritätsprüfungen (HMAC-Signaturen) für client-gespeicherten Zustand, der hin und her übertragen werden muss. Verwenden Sie serverseitige Preisabfragen anstelle von client-übermittelten Preisen. Implementieren Sie ordnungsgemäße Autorisierungsprüfungen, die sich nicht auf client-bereitgestellte Rolleninformationen verlassen. Betrachten Sie alle HTTP-Parameter als potenziell bösartig.

Häufige Auswirkungen

AuswirkungDetails
IntegritätUmfang: Datenmanipulation

Angreifer können Preise, Mengen und andere Transaktionsdaten modifizieren.
ZugriffskontrolleUmfang: Privilegieneskalation

Versteckte Rollenfelder ermöglichen es Benutzern, sich selbst erhöhte Privilegien zu gewähren.
FinanziellUmfang: Betrug

Preismanipulation führt zu direkten finanziellen Verlusten.

Beispielcode

Anfälliger Code

<!-- ANFÄLLIG: Verstecktes Feld für Preis -->
<form action="/checkout" method="POST">
    <input type="hidden" name="product_id" value="12345">
    <input type="hidden" name="price" value="99.99">  <!-- Angreifer ändert auf 0.01 -->
    <input type="hidden" name="user_role" value="customer">  <!-- Angreifer ändert auf admin -->
    <input type="number" name="quantity" value="1">
    <button type="submit">Jetzt kaufen</button>
</form>

<!-- ANFÄLLIG: Verstecktes Feld für Rabatt -->
<form action="/apply-discount" method="POST">
    <input type="hidden" name="discount_percent" value="0">  <!-- Angreifer ändert auf 100 -->
    <button type="submit">Rabatt anwenden</button>
</form>
# ANFÄLLIG: Vertrauen auf verstecktes Formularfeld für Preis
from flask import Flask, request

@app.route('/checkout', methods=['POST'])
def checkout_vulnerable():
    product_id = request.form['product_id']
    price = float(request.form['price'])  # Vertrauen auf client-übermittelten Preis!
    quantity = int(request.form['quantity'])

    total = price * quantity

    # Zahlung mit manipuliertem Preis verarbeiten
    charge_customer(total)

    return f"Belastet ${total}"

# ANFÄLLIG: Vertrauen auf Cookie für Benutzerrolle
@app.route('/admin/users')
def admin_users_vulnerable():
    # Rolle aus Cookie - leicht manipulierbar!
    role = request.cookies.get('user_role')

    if role == 'admin':
        return get_all_users()

    return 'Zugriff verweigert', 403

# ANFÄLLIG: Vertrauen auf verstecktes Feld für Benutzer-ID
@app.route('/update-profile', methods=['POST'])
def update_profile_vulnerable():
    # Benutzer-ID aus verstecktem Feld - kann auf jeden Benutzer geändert werden!
    user_id = request.form['user_id']
    new_email = request.form['email']

    # Aktualisiert E-Mail JEDES Benutzers!
    User.query.get(user_id).email = new_email
    db.session.commit()

    return 'Profil aktualisiert'
// ANFÄLLIG: Java-Servlet vertraut versteckten Feldern
@WebServlet("/checkout")
public class VulnerableCheckout extends HttpServlet {

    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        String productId = request.getParameter("product_id");
        double price = Double.parseDouble(request.getParameter("price"));  // Client-Preis!
        int quantity = Integer.parseInt(request.getParameter("quantity"));

        double total = price * quantity;

        // Mit manipuliertem Preis verarbeiten
        paymentService.charge(total);

        response.getWriter().println("Belastet: $" + total);
    }
}

// ANFÄLLIG: Vertrauen auf Cookie für Autorisierung
@WebServlet("/admin")
public class VulnerableAdmin extends HttpServlet {

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        // Rolle aus Cookie!
        Cookie[] cookies = request.getCookies();
        String role = null;

        for (Cookie cookie : cookies) {
            if ("user_role".equals(cookie.getName())) {
                role = cookie.getValue();
                break;
            }
        }

        if ("admin".equals(role)) {
            // Admin-Zugriff basierend auf Cookie gewähren!
            showAdminPanel(response);
        } else {
            response.sendError(403);
        }
    }
}
// ANFÄLLIG: Express vertraut Client-Daten
app.post('/checkout', (req, res) => {
    const { product_id, price, quantity } = req.body;

    // Vertrauen auf client-übermittelten Preis!
    const total = parseFloat(price) * parseInt(quantity);

    chargeCustomer(total);
    res.json({ total });
});

// ANFÄLLIG: Clientseitige Rollenprüfung auf Server repliziert
app.get('/api/admin/data', (req, res) => {
    // Vertrauen auf Header der von clientseitigem JavaScript gesetzt wurde!
    const isAdmin = req.headers['x-user-admin'] === 'true';

    if (isAdmin) {
        res.json(getAdminData());
    } else {
        res.status(403).json({ error: 'Zugriff verweigert' });
    }
});

// ANFÄLLIG: Frontend-Code der "sichere" Werte setzt
// frontend.js
function checkout(productId, quantity) {
    fetch('/checkout', {
        method: 'POST',
        body: JSON.stringify({
            product_id: productId,
            price: document.getElementById('price').value,  // Aus verstecktem Feld!
            quantity: quantity
        })
    });
}

Korrigierter Code

# SICHER: Serverseitige Preisabfrage
from flask import Flask, request, session
from functools import wraps

@app.route('/checkout', methods=['POST'])
def checkout_safe():
    product_id = request.form['product_id']
    quantity = int(request.form['quantity'])

    # Preis aus Datenbank abrufen, nicht vom Client!
    product = Product.query.get(product_id)
    if not product:
        return 'Produkt nicht gefunden', 404

    price = product.price  # Serverseitiger Preis!
    total = price * quantity

    charge_customer(total)

    return f"Belastet ${total}"

# SICHER: Serverseitige Session für Rolle
def require_role(role):
    def decorator(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            # Rolle aus serverseitiger Session abrufen, nicht aus Cookie
            if session.get('user_role') != role:
                return 'Zugriff verweigert', 403
            return f(*args, **kwargs)
        return decorated_function
    return decorator

@app.route('/admin/users')
@require_role('admin')
def admin_users_safe():
    return get_all_users()

# SICHER: Verwendung der authentifizierten Benutzer-ID aus Session
@app.route('/update-profile', methods=['POST'])
def update_profile_safe():
    # Benutzer-ID aus serverseitiger Session!
    user_id = session.get('user_id')
    if not user_id:
        return 'Nicht authentifiziert', 401

    new_email = request.form['email']

    # Nur Profil des aktuellen Benutzers aktualisieren
    User.query.get(user_id).email = new_email
    db.session.commit()

    return 'Profil aktualisiert'

# SICHER: HMAC-signierte versteckte Felder für Integrität
import hmac
import hashlib
import json
import base64

SECRET_KEY = os.environ['FORM_SIGNING_KEY']

def sign_form_data(data):
    """Formulardaten mit HMAC signieren."""
    json_data = json.dumps(data, sort_keys=True)
    signature = hmac.new(
        SECRET_KEY.encode(),
        json_data.encode(),
        hashlib.sha256
    ).hexdigest()
    return base64.b64encode(json_data.encode()).decode(), signature

def verify_form_data(encoded_data, signature):
    """Signierte Formulardaten verifizieren."""
    json_data = base64.b64decode(encoded_data).decode()
    expected_sig = hmac.new(
        SECRET_KEY.encode(),
        json_data.encode(),
        hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(expected_sig, signature):
        return None

    return json.loads(json_data)

@app.route('/product/<product_id>')
def show_product(product_id):
    product = Product.query.get(product_id)

    # Formulardaten signieren
    form_data = {'product_id': product_id, 'price': str(product.price)}
    encoded, signature = sign_form_data(form_data)

    return render_template('product.html',
                          product=product,
                          form_data=encoded,
                          signature=signature)

@app.route('/checkout', methods=['POST'])
def checkout_signed():
    encoded_data = request.form['form_data']
    signature = request.form['signature']

    # Integrität verifizieren
    data = verify_form_data(encoded_data, signature)
    if not data:
        return 'Ungültige Formulardaten', 400

    # Verifizierte Daten verwenden
    product_id = data['product_id']
    price = float(data['price'])
    quantity = int(request.form['quantity'])

    # Zusätzliche serverseitige Verifizierung
    product = Product.query.get(product_id)
    if str(product.price) != data['price']:
        return 'Preisabweichung', 400

    total = price * quantity
    charge_customer(total)

    return f"Belastet ${total}"
// SICHER: Serverseitige Preisabfrage in Java
@WebServlet("/checkout")
public class SecureCheckout extends HttpServlet {

    @Inject
    private ProductRepository productRepository;

    @Inject
    private PaymentService paymentService;

    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        String productId = request.getParameter("product_id");
        int quantity = Integer.parseInt(request.getParameter("quantity"));

        // Preis aus Datenbank abrufen!
        Product product = productRepository.findById(productId)
            .orElseThrow(() -> new NotFoundException("Produkt nicht gefunden"));

        BigDecimal price = product.getPrice();  // Serverseitig!
        BigDecimal total = price.multiply(BigDecimal.valueOf(quantity));

        paymentService.charge(total);

        response.getWriter().println("Belastet: $" + total);
    }
}

// SICHER: Session-basierte Autorisierung
@WebServlet("/admin")
public class SecureAdmin extends HttpServlet {

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        HttpSession session = request.getSession(false);

        if (session == null) {
            response.sendError(401);
            return;
        }

        // Rolle aus serverseitiger Session!
        String role = (String) session.getAttribute("user_role");

        if (!"admin".equals(role)) {
            response.sendError(403);
            return;
        }

        showAdminPanel(response);
    }
}

// SICHER: JWT für zuständslose Verifizierung
@WebFilter("/*")
public class JwtAuthFilter implements Filter {

    private final String jwtSecret = System.getenv("JWT_SECRET");

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) req;
        String token = request.getHeader("Authorization");

        if (token != null && token.startsWith("Bearer ")) {
            try {
                // JWT verifizieren - kann nicht manipuliert werden
                Claims claims = Jwts.parserBuilder()
                    .setSigningKey(jwtSecret.getBytes())
                    .build()
                    .parseClaimsJws(token.substring(7))
                    .getBody();

                request.setAttribute("userId", claims.getSubject());
                request.setAttribute("role", claims.get("role"));

            } catch (JwtException e) {
                ((HttpServletResponse) res).sendError(401);
                return;
            }
        }

        chain.doFilter(req, res);
    }
}
// SICHER: Serverseitige Preisabfrage in Node.js
app.post('/checkout', async (req, res) => {
    const { product_id, quantity } = req.body;

    // Preis aus Datenbank abrufen!
    const product = await Product.findById(product_id);
    if (!product) {
        return res.status(404).json({ error: 'Produkt nicht gefunden' });
    }

    const price = product.price;  // Serverseitig!
    const total = price * parseInt(quantity);

    await chargeCustomer(total);
    res.json({ total });
});

// SICHER: Session-basierte Rollenverifizierung
app.get('/api/admin/data', requireAuth, (req, res) => {
    // Rolle aus serverseitiger Session
    if (req.session.role !== 'admin') {
        return res.status(403).json({ error: 'Zugriff verweigert' });
    }

    res.json(getAdminData());
});

// SICHER: Signierte Formulardaten
const crypto = require('crypto');

function signFormData(data) {
    const json = JSON.stringify(data);
    const signature = crypto
        .createHmac('sha256', process.env.FORM_SECRET)
        .update(json)
        .digest('hex');

    return {
        data: Buffer.from(json).toString('base64'),
        signature
    };
}

function verifyFormData(encodedData, signature) {
    const json = Buffer.from(encodedData, 'base64').toString();
    const expectedSig = crypto
        .createHmac('sha256', process.env.FORM_SECRET)
        .update(json)
        .digest('hex');

    if (!crypto.timingSafeEqual(
        Buffer.from(expectedSig),
        Buffer.from(signature)
    )) {
        return null;
    }

    return JSON.parse(json);
}

// Template-Rendering mit signierten Daten
app.get('/product/:id', async (req, res) => {
    const product = await Product.findById(req.params.id);

    const signed = signFormData({
        product_id: product.id,
        price: product.price.toString()
    });

    res.render('product', { product, ...signed });
});

// Bei Übermittlung verifizieren
app.post('/checkout', async (req, res) => {
    const { data, signature, quantity } = req.body;

    const verified = verifyFormData(data, signature);
    if (!verified) {
        return res.status(400).json({ error: 'Ungültige Formulardaten' });
    }

    // Zusätzliche Datenbank-Verifizierung
    const product = await Product.findById(verified.product_id);
    if (product.price.toString() !== verified.price) {
        return res.status(400).json({ error: 'Preis geändert' });
    }

    const total = parseFloat(verified.price) * parseInt(quantity);
    await chargeCustomer(total);

    res.json({ total });
});

Ausgenutzt in der Praxis

E-Commerce Preismanipulation

Mehrere Online-Shops haben finanzielle Verluste erlitten, als Angreifer versteckte Preisfelder modifizierten, um Artikel für minimale Beträge zu kaufen.

Privilegieneskalation über Cookies

Anwendungen, die Rolleninformationen in Cookies speichern, wurden ausgenutzt, um administrativen Zugriff zu erlangen.

Business-Logic-Umgehungen

Angreifer haben versteckte Felder manipuliert, die Rabatte, Versandkosten und Transaktionslimits steuern.


Werkzeuge zum Testen/Ausnutzen

  • Burp Suite — HTTP-Parameter abfangen und modifizieren.

  • Browser-Entwicklertools — versteckte Felder direkt bearbeiten.

  • curl — Anfragen mit beliebigen Parametern senden.

  • OWASP ZAP — automatisierte Parametermanipulation.


CVE-Beispiele

  • CVE-2021-22893 — Pulse Secure Parametermanipulation.

  • CVE-2019-16113 — Bludit CMS Hidden-Field-Manipulation.

  • Zahlreiche anwendungsspezifische Schwachstellen.


Referenzen

  1. MITRE. "CWE-472: External Control of Assumed-Immutable Web Parameter." https://cwe.mitre.org/data/definitions/472.html

  2. OWASP. "Testing for HTTP Parameter Pollution." https://owasp.org/www-project-web-security-testing-guide/