Skip to content

Commit 6b6e783

Browse files
committed
CSRF usage added
1 parent 3ad1040 commit 6b6e783

File tree

1 file changed

+139
-13
lines changed

1 file changed

+139
-13
lines changed

docs/classic-ui/csrf.md

Lines changed: 139 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
---
22
myst:
33
html_meta:
4-
"description": ""
5-
"property=og:description": ""
6-
"property=og:title": ""
7-
"keywords": ""
4+
"description": "How to protect Plone against CSRF attacks."
5+
"property=og:description": "How to protect Plone against CSRF attacks."
6+
"property=og:title": "Cross-Site Request Forgery protection in Plone"
7+
"keywords": "CSRF, security, token, protection"
88
---
99

1010
(classic-ui-csrf-label)=
1111

1212
# Cross-Site Request Forgery (CSRF)
1313

1414
Cross-Site Request Forgery (CSRF or XSRF) is a type of web attack that allows an attacker to send malicious requests to a web application on behalf of a legitimate user.
15-
The attack works by tricking the user's web browser into sending a request to the web application that the user did not intentionally make. This can allow an attacker to perform actions on the web application without the user's knowledge or consent.
15+
The attack works by tricking the user's web browser into sending a request to the web application that the user did not intentionally make.
16+
This can allow an attacker to perform actions on the web application without the user's knowledge or consent.
1617

1718
For example, consider a web application that allows users to transfer money between accounts.
1819
An attacker could craft a malicious link or form that, when clicked or submitted by a victim, would transfer money from the victim's account to the attacker's account.
@@ -38,16 +39,141 @@ This includes, but is not limited to the following:
3839

3940
## Manual protection
4041

41-
TODO
42-
- protecting views
43-
- POST Only
44-
- adding a token to an URL for a link (in view code, in template code)
42+
To ensure that code that is not part of a database transaction, such as code that writes to an external API or a service that is not automatically included in the transaction mechanism, is protected, you will need to manually implement protection for that code.
4543

46-
## Allowing writes in absence of a protecting token
44+
`plone.protect` offers the `@protect` decorator.
45+
The decorator expects a callable to perform the check.
46+
There are two checks implemented in `plone.protect`:
4747

48-
TODO
49-
- marking the request to allow all writes
50-
- marking single modified objects explicit to allow them to persist
48+
### CSRF token check with `CheckAuthenticator`
49+
50+
Checks whether a valid CSRF token is present in the request and raises `Unauthorized` if not.
51+
52+
Usage example:
53+
54+
```python
55+
from plone.protect import CheckAuthenticator
56+
from plone.protect import protec
57+
58+
@protect(CheckAuthenticator)
59+
def write_to_api_or_service(self):
60+
# code here
61+
...
62+
```
63+
64+
### HTTP POST check with `PostOnly`
65+
66+
Checks whether the request is an HTTP POST and raise `Unauthorized` if not.
67+
This helps to mitigate clicks on malicious links.
68+
69+
Usage example:
70+
71+
```python
72+
from plone.protect import PostOnly
73+
from plone.protect import protect
74+
75+
@protect(PostOnly)
76+
def write_to_api_or_service(self):
77+
# code here
78+
...
79+
```
80+
81+
## How to add a CSRF-Token to a Link or Form
82+
83+
To pass a token you need either to:
84+
85+
- pass an HTTP GET parameter name `_authenticator` with the token as the value,
86+
- include a form field named `_authenticator` with the token as the value and submit it with the form, or
87+
- add an HTTP header named `X-CSRF-TOKEN` with the token as the value.
88+
89+
To add a token as an HTTP GET parameter to a link in a template, you can utilize the authenticator view:
90+
91+
```html
92+
<tal:authenticator tal:define="authenticator context/@@authenticator">
93+
<a href="${python:context.absolute_url()}/myprotected_view?_authenticator=${token}" />
94+
</tal:authenticator>
95+
```
96+
97+
To add a hidden field with a token to a form in a template, the above view can be used too like this:
98+
99+
```html
100+
<span tal:replace="structure context/@@authenticator/authenticator"/>
101+
```
102+
103+
In Python code, a helper function can be used:
104+
105+
```python
106+
from plone.protect.authenticator import createToken
107+
108+
token = createToken()
109+
```
110+
111+
To add an authenticator token to an existing URL with query parameters:
112+
113+
```python
114+
from plone.protect.authenticator import createToken
115+
from urllib.parse import urlencode
116+
from urllib.parse import urlparse
117+
from urllib.parse import urlunparse
118+
119+
# The existing URL that you want to add a query parameter to
120+
url = f"https://www.example.com?param1=value1"
121+
122+
# Parse the URL into its component parts
123+
parsed_url = urlparse(url)
124+
125+
# Add the new query parameters to the 'query' component of the URL
126+
token_query = urlencode({'_authenticator': createToken()})
127+
new_query = f'{parsed_url.query}&{token_query}'
128+
129+
# Reassemble the URL with the updated query string
130+
final_url = urlunparse(
131+
(
132+
parsed_url.scheme,
133+
parsed_url.netloc,
134+
parsed_url.path,
135+
parsed_url.params,
136+
new_query,
137+
parsed_url.fragment)
138+
)
139+
```
140+
141+
142+
## How to allow writes in absence of a protecting token
143+
144+
To allow certain objects to be modified and written to the database without protection, follow these steps:
145+
146+
1. Identify the modified object as a single object in the database.
147+
2. If an attribute of the object is a "persistent" attribute (e.g., a PersistentDict or PersistentList instance, a BTree, or an annotation), use this instead.
148+
3. Use the `safeWrite` function to mark the object as safe for writing.
149+
150+
Note: This is the preferred method for allowing modification and writing of specific objects to the database.
151+
152+
```python
153+
from plone.protect.utils import safeWrite
154+
155+
def some_fucntion(obj, request):
156+
safeWrite(obj, request)
157+
obj.foo = "bar" # modify obj
158+
```
159+
160+
If there are lots of modifications or it is not possible to identify the boundaries of the writes, the protection can be disabled for the whole current request.
161+
Then the request can be marked with the `IDisableCSRFProtection` marker interface.
162+
163+
```python
164+
from plone.protect.interfaces import IDisableCSRFProtection
165+
from zope.interface import alsoProvides
166+
167+
def some_function(request):
168+
alsoProvides(request, IDisableCSRFProtection)
169+
# modify the database here
170+
```
171+
172+
Disabling all CSRF protection for the whole Plone instance is possible by starting Plone with the environment variable `PLONE_CSRF_DISABLED=true` set.
173+
This is not recommended but can be handy temporarily in special situations.
174+
175+
176+
## Further reading
51177

52178
```{seealso}
53179
The [README file of `plone.protect`](https://github.com/plone/plone.protect/blob/master/README.rst) explains the usage and also validation in detail.

0 commit comments

Comments
 (0)