Skip to content

Commit 4f5ca45

Browse files
CWE-472: External Control of Assumed-Immutable Web Parameter (#985)
* CWE-472 as part of #531 --------- Signed-off-by: Bartlomiej Karas <[email protected]> Signed-off-by: Helge Wehder <[email protected]> Co-authored-by: Helge Wehder <[email protected]>
1 parent d84091d commit 4f5ca45

File tree

2 files changed

+148
-0
lines changed

2 files changed

+148
-0
lines changed
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
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)|
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# SPDX-FileCopyrightText: OpenSSF project contributors
2+
# SPDX-License-Identifier: MIT
3+
""" Example Code Example """
4+
from typing import Dict, List
5+
6+
7+
class WebserverConnection:
8+
"""_Simulates grossly simplified webserver_"""
9+
users = ["admin", "Alice", "Bob"]
10+
roles: Dict[str, List[str]] = {
11+
"read": ["admin", "Alice", "Bob"],
12+
"write": ["admin"],
13+
}
14+
15+
def __init__(self, username: str):
16+
# TODO: implement authentication logic
17+
pass
18+
19+
def post(
20+
self, form_data: Dict[str, str] = {"session_user": "", "action": ""}
21+
) -> tuple[Dict[str, str], str]:
22+
"""
23+
form_data: Represents request data send in by the client
24+
"""
25+
action = form_data.get("action", "")
26+
session_user = form_data.get("session_user", "")
27+
if session_user not in self.users:
28+
return form_data, "401: Please login"
29+
action_data = self._submit(session_user, action)
30+
return form_data, action_data
31+
32+
def _submit(self, username: str, action: str) -> str:
33+
if action in self.roles and username in self.roles[action]:
34+
return f"200: user '{username}' has '{action}' access"
35+
return f"401: user '{username}' does not have '{action}' access"
36+
37+
38+
#####################
39+
# Attempting to exploit above code example
40+
#####################
41+
42+
# Alice is logged in server-side (session_user is 'Alice')
43+
# User should be authenticated server-side via a session token, cookie or JWT.
44+
USER = "Alice"
45+
web = WebserverConnection(USER)
46+
47+
# expected 'normal' operation with no data:
48+
html_form, body = web.post()
49+
print(f"html_form={html_form}, body={body}")
50+
51+
html_form = {"session_user": USER, "action": "write"}
52+
html_form, body = web.post(html_form)
53+
print(f"html_form={html_form}, body={body}")
54+
55+
# Attacker tries to tamper by adding 'session_user': 'admin' to the html_form
56+
html_form = {"action": "write", "session_user": "admin"}
57+
html_form, body = web.post(html_form)
58+
print(f"html_form={html_form}, body={body}")

0 commit comments

Comments
 (0)