ruby-saml 1.12.2 → 1.12.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 25c4115dff650511c702291e7e6e3277a2c50c43b603c4cf68ae1473b3c061b5
4
- data.tar.gz: 375b631e4059b50e112f4fc5b890e48c000ddae894fdef7cc665b9a58bad5b7a
3
+ metadata.gz: 6c7575733bfd28deef0060550daede564f894f423c6749d71007b475694e5409
4
+ data.tar.gz: 4f925319b91da371cecdcb0946f617b056b9f6fbe1665dd5e5ccf2afff9f24a8
5
5
  SHA512:
6
- metadata.gz: 1207da19dae7cb853704a0dbbd1d55791156d6703a5d3162adaa4d47ea1e645e4806687392db53c8c3e9c0a51b2fbb45772b8202975565f9157d32b707fd56a1
7
- data.tar.gz: 9a4a9ba94e5ffd0eb24ef08e4a45435dec63333b2cbf1a0f0ecc164ce0569bb8720941c88874d64aef8524bebb5209bd70299e0e5bbdc953b7546aa055da58be
6
+ metadata.gz: b378e2c5e13810c280783154c7fe25a0c287e89a12698aa65a9d33873b44987b5f2cd0f0676b797fdefad86f993975b10d29a9a5665513ff68ed064efd7c41c1
7
+ data.tar.gz: 1b370d8753b3104ae3b8bc9e1f1c2ddd067abee96a5ea9b896cf2e0571e6999564a92df78aa5d55266821185a77770cf2f2166a2b944fde5ed6216e2b3c4b7f1
@@ -0,0 +1,65 @@
1
+ name: ruby-saml CI
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ test:
7
+ name: Unit test
8
+ strategy:
9
+ fail-fast: false
10
+ matrix:
11
+ os:
12
+ - ubuntu-20.04
13
+ - macos-latest
14
+ - windows-latest
15
+ ruby-version:
16
+ - 2.1
17
+ - 2.2
18
+ - 2.3
19
+ - 2.4
20
+ - 2.5
21
+ - 2.6
22
+ - 2.7
23
+ - 3.0
24
+ - jruby-9.1
25
+ - jruby-9.2
26
+ exclude:
27
+ - os: macos-latest
28
+ ruby-version: 2.1
29
+ - os: macos-latest
30
+ ruby-version: 2.2
31
+ - os: macos-latest
32
+ ruby-version: 2.3
33
+ - os: macos-latest
34
+ ruby-version: 2.4
35
+ - os: macos-latest
36
+ ruby-version: 2.5
37
+ - os: macos-latest
38
+ ruby-version: jruby-9.1
39
+ - os: macos-latest
40
+ ruby-version: jruby-9.2
41
+ - os: windows-latest
42
+ ruby-version: 2.1
43
+ - os: windows-latest
44
+ ruby-version: jruby-9.1
45
+ - os: windows-latest
46
+ ruby-version: jruby-9.2
47
+ - os: windows-latest
48
+ ruby-version: jruby-9.3
49
+ - os: windows-latest
50
+ ruby-version: jruby-9.4
51
+ - os: windows-latest
52
+ ruby-version: truffleruby
53
+ runs-on: ${{ matrix.os }}
54
+ steps:
55
+ - uses: actions/checkout@v4
56
+ - name: Set up Ruby ${{ matrix.ruby-version }}
57
+ uses: ruby/setup-ruby@v1
58
+ with:
59
+ ruby-version: ${{ matrix.ruby-version }}
60
+
61
+ - name: Install dependencies
62
+ run: bundle install
63
+
64
+ - name: Run tests
65
+ run: bundle exec rake
data/changelog.md CHANGED
@@ -1,4 +1,10 @@
1
1
  # RubySaml Changelog
2
+ ### 1.12.4 (Mar 12, 2025)
3
+ * Fix vulnerabilities: CVE-2025-25291, CVE-2025-25292: SAML authentication bypass via Signature Wrapping attack allowed due parser differential.
4
+ * Fix vulnerability: CVE-2025-25293: Potential DOS abusing of compressed messages.
5
+
6
+ ### 1.12.3 (Sep 10, 2024)
7
+ * Fix for critical vulnerability CVE-2024-45409: SAML authentication bypass via Incorrect XPath selector
2
8
 
3
9
  ### 1.12.2 (Apr 08, 2022)
4
10
  * [575](https://github.com/onelogin/ruby-saml/pull/575) Fix SloLogoutresponse bug on LogoutRequest
@@ -150,7 +150,8 @@ module OneLogin
150
150
  # @raise [ValidationError] if soft == false and validation fails
151
151
  #
152
152
  def validate_structure
153
- unless valid_saml?(document, soft)
153
+ check_malformed_doc = check_malformed_doc?(settings)
154
+ unless valid_saml?(document, soft, check_malformed_doc)
154
155
  return append_error("Invalid SAML Logout Response. Not match the saml-schema-protocol-2.0.xsd")
155
156
  end
156
157
 
@@ -425,12 +425,13 @@ module OneLogin
425
425
  #
426
426
  def validate_structure
427
427
  structure_error_msg = "Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd"
428
- unless valid_saml?(document, soft)
428
+ check_malformed_doc = check_malformed_doc_enabled?
429
+ unless valid_saml?(document, soft, check_malformed_doc)
429
430
  return append_error(structure_error_msg)
430
431
  end
431
432
 
432
433
  unless decrypted_document.nil?
433
- unless valid_saml?(decrypted_document, soft)
434
+ unless valid_saml?(decrypted_document, soft, check_malformed_doc)
434
435
  return append_error(structure_error_msg)
435
436
  end
436
437
  end
@@ -865,6 +866,8 @@ module OneLogin
865
866
  fingerprint = settings.get_fingerprint
866
867
  opts[:cert] = idp_cert
867
868
 
869
+ check_malformed_doc = check_malformed_doc_enabled?
870
+ opts[:check_malformed_doc] = check_malformed_doc
868
871
  if fingerprint && doc.validate_document(fingerprint, @soft, opts)
869
872
  if settings.security[:check_idp_cert_expiration]
870
873
  if OneLogin::RubySaml::Utils.is_cert_expired(idp_cert)
@@ -879,7 +882,7 @@ module OneLogin
879
882
  valid = false
880
883
  expired = false
881
884
  idp_certs[:signing].each do |idp_cert|
882
- valid = doc.validate_document_with_cert(idp_cert, true)
885
+ valid = doc.validate_document_with_cert(idp_cert, true, check_malformed_doc)
883
886
  if valid
884
887
  if settings.security[:check_idp_cert_expiration]
885
888
  if OneLogin::RubySaml::Utils.is_cert_expired(idp_cert)
@@ -1063,6 +1066,10 @@ module OneLogin
1063
1066
  Time.parse(node.attributes[attribute])
1064
1067
  end
1065
1068
  end
1069
+
1070
+ def check_malformed_doc_enabled?
1071
+ check_malformed_doc?(settings)
1072
+ end
1066
1073
  end
1067
1074
  end
1068
1075
  end
@@ -63,14 +63,13 @@ module OneLogin
63
63
  # Validates the SAML Message against the specified schema.
64
64
  # @param document [REXML::Document] The message that will be validated
65
65
  # @param soft [Boolean] soft Enable or Disable the soft mode (In order to raise exceptions when the message is invalid or not)
66
+ # @param check_malformed_doc [Boolean] check_malformed_doc Enable or Disable the check for malformed XML
66
67
  # @return [Boolean] True if the XML is valid, otherwise False, if soft=True
67
68
  # @raise [ValidationError] if soft == false and validation fails
68
69
  #
69
- def valid_saml?(document, soft = true)
70
+ def valid_saml?(document, soft = true, check_malformed_doc = true)
70
71
  begin
71
- xml = Nokogiri::XML(document.to_s) do |config|
72
- config.options = XMLSecurity::BaseDocument::NOKOGIRI_OPTIONS
73
- end
72
+ xml = XMLSecurity::BaseDocument.safe_load_xml(document, check_malformed_doc)
74
73
  rescue Exception => error
75
74
  return false if soft
76
75
  raise ValidationError.new("XML load failed: #{error.message}")
@@ -97,10 +96,16 @@ module OneLogin
97
96
 
98
97
  decoded = decode(saml)
99
98
  begin
100
- inflate(decoded)
99
+ message = inflate(decoded)
101
100
  rescue
102
- decoded
101
+ message = decoded
102
+ end
103
+
104
+ if message.bytesize > MAX_BYTE_SIZE
105
+ raise ValidationError.new("Encoded SAML Message exceeds " + MAX_BYTE_SIZE.to_s + " bytes, so was rejected")
103
106
  end
107
+
108
+ message
104
109
  end
105
110
 
106
111
  # Deflate, base64 encode and url-encode a SAML Message (To be used in the HTTP-redirect binding)
@@ -157,6 +162,12 @@ module OneLogin
157
162
  def deflate(inflated)
158
163
  Zlib::Deflate.deflate(inflated, 9)[2..-5]
159
164
  end
165
+
166
+ def check_malformed_doc?(settings)
167
+ default_value = OneLogin::RubySaml::Settings::DEFAULTS[:check_malformed_doc]
168
+
169
+ settings.nil? ? default_value : settings.check_malformed_doc
170
+ end
160
171
  end
161
172
  end
162
173
  end
@@ -53,6 +53,7 @@ module OneLogin
53
53
  attr_accessor :compress_request
54
54
  attr_accessor :compress_response
55
55
  attr_accessor :double_quote_xml_attribute_values
56
+ attr_accessor :check_malformed_doc
56
57
  attr_accessor :passive
57
58
  attr_accessor :protocol_binding
58
59
  attr_accessor :attributes_index
@@ -259,6 +260,7 @@ module OneLogin
259
260
  :compress_request => true,
260
261
  :compress_response => true,
261
262
  :soft => true,
263
+ :check_malformed_doc => true,
262
264
  :double_quote_xml_attribute_values => false,
263
265
  :security => {
264
266
  :authn_requests_signed => false,
@@ -199,7 +199,8 @@ module OneLogin
199
199
  # @raise [ValidationError] if soft == false and validation fails
200
200
  #
201
201
  def validate_structure
202
- unless valid_saml?(document, soft)
202
+ check_malformed_doc = check_malformed_doc?(settings)
203
+ unless valid_saml?(document, soft, check_malformed_doc)
203
204
  return append_error("Invalid SAML Logout Request. Not match the saml-schema-protocol-2.0.xsd")
204
205
  end
205
206
 
@@ -1,5 +1,5 @@
1
1
  module OneLogin
2
2
  module RubySaml
3
- VERSION = '1.12.2'
3
+ VERSION = '1.12.4'
4
4
  end
5
5
  end
data/lib/xml_security.rb CHANGED
@@ -42,6 +42,36 @@ module XMLSecurity
42
42
  NOKOGIRI_OPTIONS = Nokogiri::XML::ParseOptions::STRICT |
43
43
  Nokogiri::XML::ParseOptions::NONET
44
44
 
45
+ # Safety load the SAML Message XML
46
+ # @param document [REXML::Document] The message to be loaded
47
+ # @param check_malformed_doc [Boolean] check_malformed_doc Enable or Disable the check for malformed XML
48
+ # @return [Nokogiri::XML] The nokogiri document
49
+ # @raise [ValidationError] If there was a problem loading the SAML Message XML
50
+ def self.safe_load_xml(document, check_malformed_doc = true)
51
+ doc_str = document.to_s
52
+ if doc_str.include?("<!DOCTYPE")
53
+ raise StandardError.new("Dangerous XML detected. No Doctype nodes allowed")
54
+ end
55
+
56
+ begin
57
+ xml = Nokogiri::XML(doc_str) do |config|
58
+ config.options = self::NOKOGIRI_OPTIONS
59
+ end
60
+ rescue StandardError => error
61
+ raise StandardError.new(error.message)
62
+ end
63
+
64
+ if xml.internal_subset
65
+ raise StandardError.new("Dangerous XML detected. No Doctype nodes allowed")
66
+ end
67
+
68
+ unless xml.errors.empty?
69
+ raise StandardError.new("There were XML errors when parsing: #{xml.errors}") if check_malformed_doc
70
+ end
71
+
72
+ xml
73
+ end
74
+
45
75
  def canon_algorithm(element)
46
76
  algorithm = element
47
77
  if algorithm.is_a?(REXML::Element)
@@ -114,10 +144,8 @@ module XMLSecurity
114
144
  #<KeyInfo />
115
145
  #<Object />
116
146
  #</Signature>
117
- def sign_document(private_key, certificate, signature_method = RSA_SHA1, digest_method = SHA1)
118
- noko = Nokogiri::XML(self.to_s) do |config|
119
- config.options = XMLSecurity::BaseDocument::NOKOGIRI_OPTIONS
120
- end
147
+ def sign_document(private_key, certificate, signature_method = RSA_SHA1, digest_method = SHA1, check_malformed_doc = true)
148
+ noko = XMLSecurity::BaseDocument.safe_load_xml(self.to_s, check_malformed_doc)
121
149
 
122
150
  signature_element = REXML::Element.new("ds:Signature").add_namespace('ds', DSIG)
123
151
  signed_info_element = signature_element.add_element("ds:SignedInfo")
@@ -139,9 +167,7 @@ module XMLSecurity
139
167
  reference_element.add_element("ds:DigestValue").text = compute_digest(canon_doc, algorithm(digest_method_element))
140
168
 
141
169
  # add SignatureValue
142
- noko_sig_element = Nokogiri::XML(signature_element.to_s) do |config|
143
- config.options = XMLSecurity::BaseDocument::NOKOGIRI_OPTIONS
144
- end
170
+ noko_sig_element = XMLSecurity::BaseDocument.safe_load_xml(signature_element.to_s, check_malformed_doc)
145
171
 
146
172
  noko_signed_info_element = noko_sig_element.at_xpath('//ds:Signature/ds:SignedInfo', 'ds' => DSIG)
147
173
  canon_string = noko_signed_info_element.canonicalize(canon_algorithm(C14N))
@@ -237,10 +263,12 @@ module XMLSecurity
237
263
  end
238
264
  end
239
265
  end
240
- validate_signature(base64_cert, soft)
266
+ check_malformed_doc = true
267
+ check_malformed_doc = options[:check_malformed_doc] if options.key?(:check_malformed_doc)
268
+ validate_signature(base64_cert, soft, check_malformed_doc)
241
269
  end
242
270
 
243
- def validate_document_with_cert(idp_cert, soft = true)
271
+ def validate_document_with_cert(idp_cert, soft = true, check_malformed_doc = true)
244
272
  # get cert from response
245
273
  cert_element = REXML::XPath.first(
246
274
  self,
@@ -264,13 +292,17 @@ module XMLSecurity
264
292
  else
265
293
  base64_cert = Base64.encode64(idp_cert.to_pem)
266
294
  end
267
- validate_signature(base64_cert, true)
295
+ validate_signature(base64_cert, true, check_malformed_doc)
268
296
  end
269
297
 
270
- def validate_signature(base64_cert, soft = true)
298
+ def validate_signature(base64_cert, soft = true, check_malformed_doc = true)
271
299
 
272
- document = Nokogiri::XML(self.to_s) do |config|
273
- config.options = XMLSecurity::BaseDocument::NOKOGIRI_OPTIONS
300
+ begin
301
+ document = XMLSecurity::BaseDocument.safe_load_xml(self, check_malformed_doc)
302
+ rescue StandardError => error
303
+ @errors << error.message
304
+ return false if soft
305
+ raise ValidationError.new("XML load failed: #{error.message}")
274
306
  end
275
307
 
276
308
  # create a rexml document
@@ -312,17 +344,30 @@ module XMLSecurity
312
344
  canon_string = noko_signed_info_element.canonicalize(canon_algorithm)
313
345
  noko_sig_element.remove
314
346
 
347
+ # get signed info
348
+ signed_info_element = REXML::XPath.first(
349
+ sig_element,
350
+ "./ds:SignedInfo",
351
+ { "ds" => DSIG }
352
+ )
353
+
315
354
  # get inclusive namespaces
316
355
  inclusive_namespaces = extract_inclusive_namespaces
317
356
 
318
357
  # check digests
319
- ref = REXML::XPath.first(sig_element, "//ds:Reference", {"ds"=>DSIG})
358
+ ref = REXML::XPath.first(signed_info_element, "./ds:Reference", {"ds"=>DSIG})
359
+
360
+ reference_nodes = document.xpath("//*[@ID=$id]", nil, { 'id' => extract_signed_element_id })
320
361
 
321
- hashed_element = document.at_xpath("//*[@ID=$id]", nil, { 'id' => extract_signed_element_id })
362
+ if reference_nodes.length > 1 # ensures no elements with same ID to prevent signature wrapping attack.
363
+ return append_error("Duplicated IDs found", soft)
364
+ end
365
+
366
+ hashed_element = reference_nodes[0]
322
367
 
323
368
  canon_algorithm = canon_algorithm REXML::XPath.first(
324
- ref,
325
- '//ds:CanonicalizationMethod',
369
+ signed_info_element,
370
+ './ds:CanonicalizationMethod',
326
371
  { "ds" => DSIG }
327
372
  )
328
373
 
@@ -332,13 +377,13 @@ module XMLSecurity
332
377
 
333
378
  digest_algorithm = algorithm(REXML::XPath.first(
334
379
  ref,
335
- "//ds:DigestMethod",
380
+ "./ds:DigestMethod",
336
381
  { "ds" => DSIG }
337
382
  ))
338
383
  hash = digest_algorithm.digest(canon_hashed_element)
339
384
  encoded_digest_value = REXML::XPath.first(
340
385
  ref,
341
- "//ds:DigestValue",
386
+ "./ds:DigestValue",
342
387
  { "ds" => DSIG }
343
388
  )
344
389
  digest_value = Base64.decode64(OneLogin::RubySaml::Utils.element_text(encoded_digest_value))
@@ -364,7 +409,7 @@ module XMLSecurity
364
409
  def process_transforms(ref, canon_algorithm)
365
410
  transforms = REXML::XPath.match(
366
411
  ref,
367
- "//ds:Transforms/ds:Transform",
412
+ "./ds:Transforms/ds:Transform",
368
413
  { "ds" => DSIG }
369
414
  )
370
415
 
data/ruby-saml.gemspec CHANGED
@@ -47,12 +47,20 @@ Gem::Specification.new do |s|
47
47
  s.add_runtime_dependency('rexml')
48
48
  end
49
49
 
50
+ s.add_development_dependency('simplecov', '<0.22.0')
51
+ if RUBY_VERSION < '2.4.1'
52
+ s.add_development_dependency('simplecov-lcov', '<0.8.0')
53
+ s.add_development_dependency('term-ansicolor', '1.2.2')
54
+ s.add_development_dependency('mime-types', '<3.6.0')
55
+ else
56
+ s.add_development_dependency('simplecov-lcov', '>0.7.0')
57
+ end
58
+
50
59
  s.add_development_dependency('coveralls')
51
60
  s.add_development_dependency('minitest', '~> 5.5')
52
61
  s.add_development_dependency('mocha', '~> 0.14')
53
62
  s.add_development_dependency('rake', '~> 10')
54
63
  s.add_development_dependency('shoulda', '~> 2.11')
55
- s.add_development_dependency('simplecov')
56
64
  s.add_development_dependency('systemu', '~> 2')
57
65
  s.add_development_dependency('timecop', '<= 0.6.0')
58
66
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-saml
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.12.2
4
+ version: 1.12.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - OneLogin LLC
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-04-08 00:00:00.000000000 Z
11
+ date: 2025-03-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -38,6 +38,34 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: simplecov
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "<"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.22.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "<"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.22.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: simplecov-lcov
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.7.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.7.0
41
69
  - !ruby/object:Gem::Dependency
42
70
  name: coveralls
43
71
  requirement: !ruby/object:Gem::Requirement
@@ -108,20 +136,6 @@ dependencies:
108
136
  - - "~>"
109
137
  - !ruby/object:Gem::Version
110
138
  version: '2.11'
111
- - !ruby/object:Gem::Dependency
112
- name: simplecov
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - ">="
116
- - !ruby/object:Gem::Version
117
- version: '0'
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - ">="
123
- - !ruby/object:Gem::Version
124
- version: '0'
125
139
  - !ruby/object:Gem::Dependency
126
140
  name: systemu
127
141
  requirement: !ruby/object:Gem::Requirement
@@ -173,6 +187,7 @@ extra_rdoc_files:
173
187
  - README.md
174
188
  files:
175
189
  - ".document"
190
+ - ".github/workflows/test.yml"
176
191
  - ".gitignore"
177
192
  - ".travis.yml"
178
193
  - Gemfile
@@ -220,7 +235,7 @@ homepage: https://github.com/onelogin/ruby-saml
220
235
  licenses:
221
236
  - MIT
222
237
  metadata: {}
223
- post_install_message:
238
+ post_install_message:
224
239
  rdoc_options:
225
240
  - "--charset=UTF-8"
226
241
  require_paths:
@@ -236,8 +251,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
236
251
  - !ruby/object:Gem::Version
237
252
  version: '0'
238
253
  requirements: []
239
- rubygems_version: 3.0.8
240
- signing_key:
254
+ rubygems_version: 3.5.18
255
+ signing_key:
241
256
  specification_version: 4
242
257
  summary: SAML Ruby Tookit
243
258
  test_files: []