|
| 1 | +# CWE-472: External Control of Assumed-Immutable Web Parameter |
| 2 | + |
| 3 | +Ensuring user roles are determined on the server side prevents attackers from manipulating permissions through client-side data. |
| 4 | + |
| 5 | +## Example Code Example |
| 6 | + |
| 7 | +In `example01.py` the `post` function accepts username and action values directly from client-submitted `form_data` without any form of authentication or server-side verification. Non-admin users should only have read permissions, but because the role is taken directly from the user input without verification, an attacker can modify it using browser tools, intercepting proxies like burp suite, or custom scripts to escalate their privileges. The application assumed that the `roles` parameters are immutable and trustworthy, but it is externally controlled and easily manipulated. |
| 8 | + |
| 9 | +[*example01.py:*](example01.py) |
| 10 | + |
| 11 | +```py |
| 12 | +# SPDX-FileCopyrightText: OpenSSF project contributors |
| 13 | +# SPDX-License-Identifier: MIT |
| 14 | +""" Example Code Example """ |
| 15 | +from typing import Dict, List |
| 16 | + |
| 17 | + |
| 18 | +class WebserverConnection: |
| 19 | + """_Simulates grossly simplified webserver_""" |
| 20 | + users = ["admin", "Alice", "Bob"] |
| 21 | + roles: Dict[str, List[str]] = { |
| 22 | + "read": ["admin", "Alice", "Bob"], |
| 23 | + "write": ["admin"], |
| 24 | + } |
| 25 | + |
| 26 | + def __init__(self, username: str): |
| 27 | + # TODO: implement authentication logic |
| 28 | + pass |
| 29 | + |
| 30 | + def post( |
| 31 | + self, form_data: Dict[str, str] = {"session_user": "", "action": ""} |
| 32 | + ) -> tuple[Dict[str, str], str]: |
| 33 | + """ |
| 34 | + form_data: Represents request data send in by the client |
| 35 | + """ |
| 36 | + action = form_data.get("action", "") |
| 37 | + session_user = form_data.get("session_user", "") |
| 38 | + if session_user not in self.users: |
| 39 | + return form_data, "401: Please login" |
| 40 | + action_data = self._submit(session_user, action) |
| 41 | + return form_data, action_data |
| 42 | + |
| 43 | + def _submit(self, username: str, action: str) -> str: |
| 44 | + if action in self.roles and username in self.roles[action]: |
| 45 | + return f"200: user '{username}' has '{action}' access" |
| 46 | + return f"401: user '{username}' does not have '{action}' access" |
| 47 | + |
| 48 | + |
| 49 | +##################### |
| 50 | +# Attempting to exploit above code example |
| 51 | +##################### |
| 52 | + |
| 53 | +# Alice is logged in server-side (session_user is 'Alice') |
| 54 | +# User should be authenticated server-side via a session token, cookie or JWT. |
| 55 | +USER = "Alice" |
| 56 | +web = WebserverConnection(USER) |
| 57 | + |
| 58 | +# expected 'normal' operation with no data: |
| 59 | +html_form, body = web.post() |
| 60 | +print(f"html_form={html_form}, body={body}") |
| 61 | + |
| 62 | +html_form = {"session_user": USER, "action": "write"} |
| 63 | +html_form, body = web.post(html_form) |
| 64 | +print(f"html_form={html_form}, body={body}") |
| 65 | + |
| 66 | +# Attacker tries to tamper by adding 'session_user': 'admin' to the html_form |
| 67 | +html_form = {"action": "write", "session_user": "admin"} |
| 68 | +html_form, body = web.post(html_form) |
| 69 | +print(f"html_form={html_form}, body={body}") |
| 70 | + |
| 71 | +``` |
| 72 | + |
| 73 | +In the code example, Alice increases her privileges using some browser developer tool, and the service now views her as an admin, with write permissions. |
| 74 | + |
| 75 | +A secure implementation should avoid relying on client-supplied fields like `session_user` for authentication. Instead, session state should be managed securely on the server side using mechanisms like session cookies, JSON Web Tokens with proper validation, or integration with an identity and access management solution (IdAM) such as OpenID Connect (OIDC). |
| 76 | + |
| 77 | +## Automated Detection |
| 78 | + |
| 79 | +|Tool|Version|Checker|Description| |
| 80 | +|:---|:---|:---|:---| |
| 81 | +|Bandit|1.7.4 on Python 3.10.4|Not Available|| |
| 82 | +|Flake8|8-4.0.1 on Python 3.10.4|Not Available|| |
| 83 | + |
| 84 | +## Related Guidelines |
| 85 | + |
| 86 | +||| |
| 87 | +|:---|:---| |
| 88 | +|MITRE CWE Pillar| [CWE-693: Protection Mechanism Failure (4.12) (mitre.org)](https://cwe.mitre.org/data/definitions/693.html)| |
| 89 | +|MITRE CWE Class|[CWE-472: External Control of Assumed-Immutable Web Parameter](http://cwe.mitre.org/data/definitions/472.html)| |
| 90 | +|[SEI CERT Oracle Coding Standard for Java](https://wiki.sei.cmu.edu/confluence/display/java/SEI+CERT+Oracle+Coding+Standard+for+Java)|[IDS14-J. Do not trust the contents of hidden form fields](https://wiki.sei.cmu.edu/confluence/display/java/IDS14-J.+Do+not+trust+the+contents+of+hidden+form+fields)| |
0 commit comments