Skip to content

Commit d088362

Browse files
committed
Warn about Open Redirect and Reply attacks
1 parent 00b1f82 commit d088362

File tree

5 files changed

+45
-0
lines changed

5 files changed

+45
-0
lines changed

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,35 @@ your environment is not secure and will be exposed to attacks.
144144

145145
In production also we highly recommend to register on the settings the IdP certificate instead of using the fingerprint method. The fingerprint, is a hash, so at the end is open to a collision attack that can end on a signature validation bypass. Other SAML toolkits deprecated that mechanism, we maintain it for compatibility and also to be used on test environment.
146146

147+
### Avoiding Open Redirect attacks ###
148+
149+
Some implementations uses the RelayState parameter as a way to control the flow when SSO and SLO succeeded. So basically the
150+
user is redirected to the value of the RelayState.
151+
152+
If you are using Signature Validation on the HTTP-Redirect binding, you will have the RelayState value integrity covered, otherwise, and
153+
on HTTP-POST binding, you can't trust the RelayState so before
154+
executing the validation, you need to verify that its value belong
155+
a trusted and expected URL.
156+
157+
Read more about Open Redirect [CWE-601](https://cwe.mitre.org/data/definitions/601.html).
158+
159+
### Avoiding Reply attacks ###
160+
161+
A reply attack is basically try to reuse an intercepted valid SAML Message in order to impersonate a SAML action (SSO or SLO).
162+
163+
SAML Messages have a limited timelife (NotBefore, NotOnOrAfter) that
164+
make harder this kind of attacks, but they are still possible.
165+
166+
In order to avoid them, the SP can keep a list of SAML Messages or Assertion IDs alredy valdidated and processed. Those values only need
167+
to be stored the amount of time of the SAML Message life time, so
168+
we don't need to store all processed message/assertion Ids, but the most recent ones.
169+
170+
The OneLogin_Saml2_Auth class contains the [get_last_request_id](https://github.com/onelogin/python-saml/blob/00b1f823b6c668b0dfb5e4a40d3709a4ceb2a6ae/src/onelogin/saml2/auth.py#L352), [get_last_message_id](https://github.com/onelogin/python-saml/blob/00b1f823b6c668b0dfb5e4a40d3709a4ceb2a6ae/src/onelogin/saml2/auth.py#L359) and [get_last_assertion_id](https://github.com/onelogin/python-saml/blob/00b1f823b6c668b0dfb5e4a40d3709a4ceb2a6ae/src/onelogin/saml2/auth.py#L366) methods to retrieve the IDs
171+
172+
Checking that the ID of the current Message/Assertion does not exists in the lis of the ones already processed will prevent reply
173+
attacks.
174+
175+
147176
Getting Started
148177
---------------
149178

demo-bottle/index.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ def index():
6161
session['samlSessionIndex'] = auth.get_session_index()
6262
self_url = OneLogin_Saml2_Utils.get_self_url(req)
6363
if 'RelayState' in request.forms and self_url != request.forms['RelayState']:
64+
# To avoid 'Open Redirect' attacks, before execute the redirection confirm
65+
# the value of the request.forms['RelayState'] is a trusted URL.
6466
return redirect(request.forms['RelayState'])
6567

6668
if 'samlUserdata' in session:
@@ -110,6 +112,8 @@ def index():
110112
errors = auth.get_errors()
111113
if len(errors) == 0:
112114
if url is not None:
115+
# To avoid 'Open Redirect' attacks, before execute the redirection confirm
116+
# the value of the url is a trusted URL.
113117
return redirect(url)
114118
else:
115119
success_slo = True

demo-django/demo/views.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ def index(request):
8787
request.session['samlNameIdSPNameQualifier'] = auth.get_nameid_spnq()
8888
request.session['samlSessionIndex'] = auth.get_session_index()
8989
if 'RelayState' in req['post_data'] and OneLogin_Saml2_Utils.get_self_url(req) != req['post_data']['RelayState']:
90+
# To avoid 'Open Redirect' attacks, before execute the redirection confirm
91+
# the value of the req['post_data']['RelayState'] is a trusted URL.
9092
return HttpResponseRedirect(auth.redirect_to(req['post_data']['RelayState']))
9193
elif auth.get_settings().is_debug_active():
9294
error_reason = auth.get_last_error_reason()
@@ -99,6 +101,8 @@ def index(request):
99101
errors = auth.get_errors()
100102
if len(errors) == 0:
101103
if url is not None:
104+
# To avoid 'Open Redirect' attacks, before execute the redirection confirm
105+
# the value of the url is a trusted URL.
102106
return HttpResponseRedirect(url)
103107
else:
104108
success_slo = True

demo-flask/index.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ def index():
9191
session['samlSessionIndex'] = auth.get_session_index()
9292
self_url = OneLogin_Saml2_Utils.get_self_url(req)
9393
if 'RelayState' in request.form and self_url != request.form['RelayState']:
94+
# To avoid 'Open Redirect' attacks, before execute the redirection confirm
95+
# the value of the request.form['RelayState'] is a trusted URL.
9496
return redirect(auth.redirect_to(request.form['RelayState']))
9597
elif auth.get_settings().is_debug_active():
9698
error_reason = auth.get_last_error_reason()
@@ -103,6 +105,8 @@ def index():
103105
errors = auth.get_errors()
104106
if len(errors) == 0:
105107
if url is not None:
108+
# To avoid 'Open Redirect' attacks, before execute the redirection confirm
109+
# the value of the request.form['RelayState'] is a trusted URL.
106110
return redirect(url)
107111
else:
108112
success_slo = True

demo_pyramid/demo_pyramid/views.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ def index(request):
6565
session['samlSessionIndex'] = auth.get_session_index()
6666
self_url = OneLogin_Saml2_Utils.get_self_url(req)
6767
if 'RelayState' in request.POST and self_url != request.POST['RelayState']:
68+
# To avoid 'Open Redirect' attacks, before execute the redirection confirm
69+
# the value of the request.POST['RelayState'] is a trusted URL.
6870
return HTTPFound(auth.redirect_to(request.POST['RelayState']))
6971
else:
7072
error_reason = auth.get_last_error_reason()
@@ -74,6 +76,8 @@ def index(request):
7476
errors = auth.get_errors()
7577
if len(errors) == 0:
7678
if url is not None:
79+
# To avoid 'Open Redirect' attacks, before execute the redirection confirm
80+
# the value of the url is a trusted URL.
7781
return HTTPFound(url)
7882
else:
7983
success_slo = True

0 commit comments

Comments
 (0)