Skip to content

Commit dddb738

Browse files
authored
Merge branch 'v2.x' into v2.x_safe_load_refactor
2 parents 5286274 + ec62afb commit dddb738

31 files changed

+2300
-1830
lines changed

README.md

Lines changed: 140 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,17 @@ Please note the following **critical vulnerabilities**:
1818

1919
## Sponsors
2020

21-
Thanks to the following sponsors for securing the open source ecosystem,
21+
Thanks to the following sponsors for securing the open source ecosystem:
22+
23+
#### [<img class="circle" src="https://avatars.githubusercontent.com/u/34724717" width="26" height="26" alt="@serpapi">](https://serpapi.com) [<sup>SerpApi</sup>](https://github.com/serpapi)
24+
<sup>*A real-time API to access Google search results. It handle proxies, solve captchas, and parse all rich structured data for you*</sup>
25+
26+
#### [<img class="circle" src="https://avatars.githubusercontent.com/u/9919" width="26" height="26" alt="@github">](https://github.com/) [<sup>Github</sup>](https://github.com/github)
27+
<sup>*The complete developer platform to build, scale, and deliver secure software.*</sup>
28+
29+
#### [<img alt="84codes" src="https://avatars.githubusercontent.com/u/5353257" width="26" height="26">](https://www.84codes.com) [<sup>84codes</sup>](https://github.com/84codes)
30+
<sup>*Simplifying Message Queuing and Streaming. Leave server management to the experts, so you can focus on building great applications.*</sup>
2231

23-
[<img alt="84codes" src="https://avatars.githubusercontent.com/u/5353257" width="75px">](https://www.84codes.com)
2432

2533
## Overview
2634

@@ -31,7 +39,7 @@ Response assertions from Identity Providers (IdPs).
3139
**Important:** This libary does not support the IdP-side of SAML authentication,
3240
such as creating SAML Response messages to assert a user's identity.
3341

34-
A Rails 4 reference implemenation is avaiable at the
42+
A Rails 4 reference implementation is available at the
3543
[Ruby SAML Demo Project](https://github.com/saml-toolkits/ruby-saml-example).
3644

3745
### Vulnerability Reporting
@@ -46,9 +54,10 @@ it by email to the maintainer: sixto.martin.garcia+security@gmail.com
4654
and from a trusted source. Ruby SAML does not perform any validation that the URL
4755
you entered is correct and/or safe.
4856
- **False-Positive Security Warnings:** Some tools may incorrectly report Ruby SAML as a
49-
potential security vulnerability, due to it's dependency on Nokogiri. Such warnings can
57+
potential security vulnerability, due to its dependency on Nokogiri. Such warnings can
5058
be ignored; Ruby SAML uses Nokogiri in a safe way, by always disabling its DTDLOAD option
5159
and enabling its NONET option.
60+
- **Prevent Replay attacks:** A replay attack is when an attacker intercepts a valid SAML assertion and "replays" it at a later time to gain unauthorized access. The `ruby-saml` library provides the tools to prevent this, but **you, the developer, must implement the core logic**, see an specific section later in the README.
5261

5362
### Supported Ruby Versions
5463

@@ -179,7 +188,7 @@ def saml_settings
179188
end
180189
```
181190

182-
The use of settings.issuer is deprecated in favour of settings.sp_entity_id since version 1.11.0
191+
The use of settings.issuer is deprecated in favor of settings.sp_entity_id since version 1.11.0
183192

184193
Some assertion validations can be skipped by passing parameters to `RubySaml::Response.new()`.
185194
For example, you can skip the `AuthnStatement`, `Conditions`, `Recipient`, or the `SubjectConfirmation`
@@ -255,13 +264,13 @@ Ruby SAML allows different ways to validate the signature of the SAML Response:
255264
`idp_cert_fingerprint` and `idp_cert_fingerprint_algorithm` parameters.
256265

257266
In addition, you may pass the option `:relax_signature_validation` to `SloLogoutrequest` and
258-
`Logoutresponse` if want to skip signature validation on logout.
267+
`Logoutresponse` if you want to skip signature validation on logout.
259268

260269
The `idp_cert_fingerprint` option is deprecated for the following reasons. It will be
261270
removed in Ruby SAML version 2.1.0.
262271
1. It only works with HTTP-POST binding, not HTTP-Redirect, since the full certificate
263272
is not sent in the Redirect URL parameters.
264-
2. It is theoretically be susceptible to collision attacks, by which a malicious
273+
2. It is theoretically susceptible to collision attacks, by which a malicious
265274
actor could impersonate the IdP. (However, as of January 2025, such attacks have not
266275
been publicly demonstrated for SHA-256.)
267276
3. It has been removed already from several other SAML libraries in other languages.
@@ -365,8 +374,7 @@ Those return an Hash instead of a `Settings` object, which may be useful for con
365374

366375
### Validating Signature of Metadata and retrieve settings
367376

368-
Right now there is no method at ruby_saml to validate the signature of the metadata that gonna be parsed,
369-
but it can be done as follows:
377+
Right now there is no method at ruby_saml to validate the signature of the metadata that is going to be parsed, but it can be done as follows:
370378
* Download the XML.
371379
* Validate the Signature, providing the cert.
372380
* Provide the XML to the parse method if the signature was validated
@@ -403,7 +411,7 @@ if valid
403411
entity_id: "<entity_id_of_the_entity_to_be_retrieved>"
404412
)
405413
else
406-
print "Metadata Signarture failed to be verified with the cert provided"
414+
print "Metadata Signature failed to be verified with the cert provided"
407415
end
408416
```
409417

@@ -632,7 +640,7 @@ settings.security[:logout_requests_signed] = true # Enable signature on Logout
632640
settings.security[:logout_responses_signed] = true # Enable signature on Logout Response
633641
```
634642

635-
Signatures will be handled automatically for both `HTTP-Redirect` and `HTTP-Redirect` Binding.
643+
Signatures will be handled automatically for both `HTTP-POST` and `HTTP-Redirect` Binding.
636644
Note that the RelayState parameter is used when creating the Signature on the `HTTP-Redirect` Binding.
637645
Remember to provide it to the Signature builder if you are sending a `GET RelayState` parameter or the
638646
signature validation process will fail at the IdP.
@@ -655,7 +663,7 @@ settings.security[:want_assertions_encrypted] = true # Invalidate SAML messages
655663
### Verifying Signature on IdP Assertions
656664

657665
You may require the IdP to sign its SAML Assertions using the following setting.
658-
With will add `<md:SPSSODescriptor WantAssertionsSigned="true">` to your SP Metadata XML.
666+
This will add `<md:SPSSODescriptor WantAssertionsSigned="true">` to your SP Metadata XML.
659667
The signature will be checked against the `<md:KeyDescriptor use="signing">` element
660668
present in the IdP's metadata.
661669

@@ -687,7 +695,7 @@ advanced usage scenarios:
687695
- Specifying separate SP certificates for signing and encryption.
688696

689697
The `sp_cert_multi` parameter replaces `certificate` and `private_key`
690-
(you may not specify both pparameters at the same time.) `sp_cert_multi` has the following shape:
698+
(you may not specify both parameters at the same time.) `sp_cert_multi` has the following shape:
691699

692700
```ruby
693701
settings.sp_cert_multi = {
@@ -729,7 +737,7 @@ JRuby cannot support ECDSA due to a [known issue](https://github.com/jruby/jruby
729737
### Audience Validation
730738

731739
A service provider should only consider a SAML response valid if the IdP includes an <AudienceRestriction>
732-
element containting an <Audience> element that uniquely identifies the service provider. Unless you specify
740+
element containing an <Audience> element that uniquely identifies the service provider. Unless you specify
733741
the `skip_audience` option, Ruby SAML will validate that each SAML response includes an <Audience> element
734742
whose contents matches `settings.sp_entity_id`.
735743

@@ -762,7 +770,7 @@ def sp_logout_request
762770
settings = saml_settings
763771

764772
if settings.idp_slo_service_url.nil?
765-
logger.info "SLO IdP Endpoint not found in settings, executing then a normal logout'"
773+
logger.info "SLO IdP Endpoint not found in settings, then executing a normal logout'"
766774
delete_session
767775
else
768776

@@ -910,7 +918,7 @@ end
910918

911919
### Attribute Service
912920

913-
To request attributes from the IdP the SP needs to provide an attribute service within it's metadata and reference the index in the assertion.
921+
To request attributes from the IdP the SP needs to provide an attribute service within its metadata and reference the index in the assertion.
914922

915923
```ruby
916924
settings = RubySaml::Settings.new
@@ -936,7 +944,7 @@ or underscore, and can only contain letters, digits, underscores, hyphens, and p
936944

937945
### Custom Metadata Fields
938946

939-
Some IdPs may require to add SPs to add additional fields (Organization, ContactPerson, etc.)
947+
Some IdPs may require SPs to add additional fields (Organization, ContactPerson, etc.)
940948
into the SP metadata. This can be done by extending the `RubySaml::Metadata` class and
941949
overriding the `#add_extras` method where the first arg is a
942950
[Nokogiri::XML::Builder](https://nokogiri.org/rdoc/Nokogiri/XML/Builder.html) object as per
@@ -964,6 +972,111 @@ end
964972
MyMetadata.new.generate(settings)
965973
```
966974

975+
### Preventing Replay Attacks
976+
977+
A replay attack is when an attacker intercepts a valid SAML assertion and "replays" it at a later time to gain unauthorized access.
978+
979+
The library only checks the assertion's validity window (`NotBefore` and `NotOnOrAfter` conditions). An attacker can replay a valid assertion as many times as they want within this window.
980+
981+
A robust defense requires tracking of assertion IDs to ensure any given assertion is only accepted once.
982+
983+
#### 1. Extract the Assertion ID after Validation
984+
985+
After a response has been successfully validated, get the assertion ID. The library makes this available via `response.assertion_id`.
986+
987+
988+
#### 2. Store the ID with an Expiry
989+
990+
You must store this ID in a persistent cache (like Redis or Memcached) that is shared across your servers. Do not store it in the user's session, as that is not a secure cache.
991+
992+
The ID should be stored until the assertion's validity window has passed. You will need to check how long the trusted IdPs consider the assertion valid and then add the allowed_clock_drift.
993+
994+
You can define a global value, or set this value dinamically based on the `not_on_or_after` value of the re + `allowed_clock_drift`.
995+
996+
```ruby
997+
# In your `consume` action, after a successful validation:
998+
if response.is_valid?
999+
# Prevent replay of this specific assertion
1000+
assertion_id = response.assertion_id
1001+
authorize_failure("Assertion ID is mandatory") if assertion_id.nil?
1002+
1003+
assertion_not_on_or_after = response.not_on_or_after
1004+
# We set a default of 5 min expiration in case is not provided
1005+
assertion_expiry = (Time.now.utc + 300) if assertion_not_on_or_after.nil?
1006+
1007+
# `is_new_assertion?` is your application's method to check and set the ID
1008+
# in a shared, persistent cache (e.g., Redis, Memcached).
1009+
if is_new_assertion?(assertion_id, expires_at: assertion_expiry)
1010+
# This is a new assertion, so we can proceed
1011+
session[:userid] = response.nameid
1012+
session[:attributes] = response.attributes
1013+
# ...
1014+
else
1015+
# This assertion ID has been seen before. This is a REPLAY ATTACK.
1016+
# Log the security event and reject the user.
1017+
authorize_failure("Replay attack detected")
1018+
end
1019+
else
1020+
authorize_failure("Invalid response")
1021+
end
1022+
```
1023+
1024+
Your `is_new_assertion?` method would look something like this (example for Redis):
1025+
1026+
```ruby
1027+
1028+
def is_new_assertion?(assertion_id, expires_at)
1029+
ttl = (expires_at - Time.now.utc).to_i
1030+
return false if ttl <= 0 # The assertion has already expired
1031+
1032+
# The 'nx' option tells Redis to only set the key if it does not already exist.
1033+
# The command returns `true` if the key was set, `false` otherwise.
1034+
$redis.set("saml_assertion_ids:#{assertion_id}", "1", ex: ttl, nx: true)
1035+
end
1036+
```
1037+
1038+
### Enforce SP-Initiated Flow with `InResponseTo` validation
1039+
1040+
This is the best way to prevent IdP-initiated logins and ensure that you only accept assertions that you recently requested.
1041+
1042+
#### 1. Store the `AuthnRequest` ID
1043+
1044+
When you create an `AuthnRequest`, the library assigns it a unique ID. You must store this ID, for example in the user's session *before* redirecting them to the IdP.
1045+
1046+
```ruby
1047+
def init
1048+
request = OneLogin::RubySaml::Authrequest.new
1049+
# The unique ID of the request is in request.uuid
1050+
session[:saml_request_id] = request.uuid
1051+
redirect_to(request.create(saml_settings))
1052+
end
1053+
```
1054+
1055+
#### 2. Validate the `InResponseTo` value of the `Response` with the Stored ID
1056+
1057+
When you process the `SAMLResponse`, retrieve the ID from the session and pass it to the `Response` constructor. Use `session.delete` to ensure the ID can only be used once.
1058+
1059+
```ruby
1060+
def consume
1061+
request_id = session.delete(:saml_request_id) # Use delete to prevent re-use
1062+
1063+
# You can reject the response if no previous saml_request_id was stored
1064+
raise "IdP-initiaited detected" if request_id.nil?
1065+
1066+
response = OneLogin::RubySaml::Response.new(
1067+
params[:SAMLResponse],
1068+
settings: saml_settings,
1069+
matches_request_id: request_id
1070+
)
1071+
1072+
if response.is_valid?
1073+
# ... authorize user
1074+
else
1075+
# Response is invalid, errors in response.errors
1076+
end
1077+
end
1078+
```
1079+
9671080
## Contributing
9681081

9691082
### Pay it Forward: Support RubySAML and Strengthen Open-Source Security
@@ -998,9 +1111,17 @@ behind paywalls.
9981111

9991112
### Sponsors
10001113

1001-
Thanks to the following sponsors for securing the open source ecosystem.
1114+
Thanks to the following sponsors for securing the open source ecosystem:
1115+
1116+
#### [<img class="circle" src="https://avatars.githubusercontent.com/u/34724717" width="26" height="26" alt="@serpapi">](https://serpapi.com) [<sup>SerpApi</sup>](https://github.com/serpapi)
1117+
<sup>*A real-time API to access Google search results. It handle proxies, solve captchas, and parse all rich structured data for you*</sup>
1118+
1119+
#### [<img class="circle" src="https://avatars.githubusercontent.com/u/9919" width="26" height="26" alt="@github">](https://github.com/) [<sup>Github</sup>](https://github.com/github)
1120+
<sup>*The complete developer platform to build, scale, and deliver secure software.*</sup>
1121+
1122+
#### [<img alt="84codes" src="https://avatars.githubusercontent.com/u/5353257" width="26" height="26">](https://www.84codes.com) [<sup>84codes</sup>](https://github.com/84codes)
1123+
<sup>*Simplifying Message Queuing and Streaming. Leave server management to the experts, so you can focus on building great applications.*</sup>
10021124

1003-
[<img alt="84codes" src="https://avatars.githubusercontent.com/u/5353257" width="75px">](https://www.84codes.com)
10041125

10051126
### Attribution
10061127

UPGRADING.md

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,23 @@ This issue is likely not critical for most IdPs, but since it is not tested, it
2626

2727
### Root "OneLogin" namespace changed to "RubySaml"
2828

29-
RubySaml version `2.0.0` changes the root namespace from `RubySaml::` to just `RubySaml::`.
30-
Please remove `` and `onelogin/` everywhere in your codebase. Aside from this namespace change,
29+
RubySaml version `2.0.0` changes the root namespace from `OneLogin::RubySaml::` to just `RubySaml::`.
30+
Please remove `onelogin/` everywhere in your codebase. Aside from this namespace change,
3131
the class names themselves have intentionally been kept the same.
3232

3333
Note that the project folder structure has also been updated accordingly. Notably, the directory
3434
`lib/onelogin/schemas` is now `lib/ruby_saml/schemas`.
3535

36-
For backward compatibility, the alias `OneLogin = Object` has been set, so `RubySaml::` will still work
37-
as before. This alias will be removed in RubySaml version `3.0.0`.
36+
For backward compatibility, a module is defined at lib/ruby_saml.rb, so `RubySaml::` will still work
37+
as before. This module will be removed in RubySaml version `3.0.0`.
38+
```
39+
unless defined?(::OneLogin)
40+
module OneLogin
41+
RubySaml = ::RubySaml
42+
end
43+
end
44+
```
45+
3846

3947
### Deprecation and removal of "XMLSecurity" namespace
4048

@@ -75,7 +83,7 @@ settings.security[:signature_method] = RubySaml::XML::RSA_SHA1
7583

7684
RubySaml `1.x` used a combination of REXML and Nokogiri for XML parsing and generation.
7785
In `2.0.0`, REXML has been replaced with Nokogiri. As a result, there are minor differences
78-
in how XML is generated, ncluding SAML requests and SP Metadata:
86+
in how XML is generated, including SAML requests and SP Metadata:
7987

8088
1. All XML namespace declarations will be on the root node of the XML. Previously,
8189
some declarations such as `xmlns:ds` were done on child nodes.
@@ -121,7 +129,7 @@ The reasons for this change are:
121129
### Removal of embed_sign setting
122130

123131
The deprecated `settings.security[:embed_sign]` parameter has been removed. If you were using it, please instead switch
124-
to using both the `settings.idp_sso_service_binding` and `settings.idp_slo_service_binding` parameters as show below.
132+
to using both the `settings.idp_sso_service_binding` and `settings.idp_slo_service_binding` parameters as shown below.
125133
(This new syntax is supported on version 1.13.0 and later.)
126134

127135
```ruby
@@ -230,8 +238,8 @@ how validation happens in the toolkit and also the toolkit by default will check
230238
when parsing a SAML Message (`settings.check_malformed_doc`).
231239

232240
The SignedDocument class defined at xml_security.rb experienced several changes.
233-
We don't expect compatibilty issues if you use the main methods offered by ruby-saml, but if
234-
you use a fork or customized usage, is possible that you need to adapt your code.
241+
We don't expect compatibility issues if you use the main methods offered by ruby-saml, but if
242+
you use a fork or customized usage, it is possible that you will need to adapt your code.
235243

236244
## Upgrading from 1.12.x to 1.13.0
237245

@@ -257,7 +265,7 @@ in favor of `idp_sso_service_url` and `idp_slo_service_url`. The `IdpMetadataPar
257265

258266
## Upgrading from 1.10.x to 1.11.0
259267

260-
Version `1.11.0` deprecates the use of `settings.issuer` in favour of `settings.sp_entity_id`.
268+
Version `1.11.0` deprecates the use of `settings.issuer` in favor of `settings.sp_entity_id`.
261269
There are two new security settings: `settings.security[:check_idp_cert_expiration]` and
262270
`settings.security[:check_sp_cert_expiration]` (both false by default) that check if the
263271
IdP or SP X.509 certificate has expired, respectively.
@@ -268,7 +276,7 @@ Version `1.10.1` improves Ruby 1.8.7 support.
268276

269277
## Upgrading from 1.9.0 to 1.10.0
270278

271-
Version `1.10.0` improves IdpMetadataParser to allow parse multiple IDPSSODescriptor,
279+
Version `1.10.0` improves IdpMetadataParser to allow parsing multiple IDPSSODescriptor,
272280
Add Subject support on AuthNRequest to allow SPs provide info to the IdP about the user
273281
to be authenticated and updates the format_cert method to accept certs with /\x0d/
274282

@@ -352,7 +360,7 @@ It adds security improvements in order to prevent Signature wrapping attacks.
352360

353361
## Upgrading from 1.1.x to 1.2.x
354362

355-
Version `1.2` adds IDP metadata parsing improvements, uuid deprecation in favour of SecureRandom,
363+
Version `1.2` adds IDP metadata parsing improvements, uuid deprecation in favor of SecureRandom,
356364
refactor error handling and some minor improvements.
357365

358366
There is no compatibility issue detected.
@@ -367,7 +375,7 @@ Version `1.1` adds some improvements on signature validation and solves some nam
367375

368376
Version `1.0` is a recommended update for all Ruby SAML users as it includes security fixes.
369377

370-
Version `1.0` adds security improvements like entity expansion limitation, more SAML message validations, and other important improvements like decrypt support.
378+
Version `1.0` adds security improvements like entity expansion limitation, more SAML message validations, and other important improvements like decryption support.
371379

372380
### Important Changes
373381

lib/ruby_saml.rb

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,17 @@
2020
require 'ruby_saml/utils'
2121
require 'ruby_saml/version'
2222

23-
# @deprecated This alias adds compatibility with v1.x and will be removed in v3.0.0
24-
OneLogin = Object
23+
# @deprecated This module adds compatibility with v1.x and will be removed in v3.0.0
24+
unless defined?(OneLogin)
25+
module OneLogin
26+
RubySaml = ::RubySaml
27+
end
28+
end
29+
30+
unless defined?(OneLogin::RubySaml::Logging)
31+
module OneLogin
32+
module RubySaml
33+
Logging = ::RubySaml::Logging
34+
end
35+
end
36+
end

0 commit comments

Comments
 (0)