Skip to content

Loading…

Remove ability to parse symbols and yaml #34

Merged
merged 6 commits into from

5 participants

@nate

These changes are pretty much the exact ones that were done in Rails with the addition of a fix to the contribution guide and the Rakefile around generating documentation. Fixes issue #33.

@nate nate commented on the diff
@@ -10,7 +10,7 @@ task :default => :spec
namespace :doc do
require 'yard'
YARD::Rake::YardocTask.new do |task|
- task.files = ['LICENSE.md', 'lib/**/*.rb']
+ task.files = ['lib/**/*.rb', '-', 'LICENSE.md']
@nate
nate added a note

I was getting warnings:

[warn]: Syntax error in `license.md`:(1,18): syntax error, unexpected tinteger, expecting $end
[warn]: ParserSyntaxError: syntax error in `LICENSE.md`:(1,18): syntax error, unexpected tINTEGER, expecting $end
[warn]: Stack trace:
    /Users/nate/.rvm/gems/ruby-1.9.3-p327/gems/yard-0.8.3/lib/yard/parser/ruby/ruby_parser.rb:537:in `on_parse_error'
    /Users/nate/.rvm/gems/ruby-1.9.3-p327/gems/yard-0.8.3/lib/yard/parser/ruby/ruby_parser.rb:50:in `parse'
    /Users/nate/.rvm/gems/ruby-1.9.3-p327/gems/yard-0.8.3/lib/yard/parser/ruby/ruby_parser.rb:50:in `parse'
    /Users/nate/.rvm/gems/ruby-1.9.3-p327/gems/yard-0.8.3/lib/yard/parser/ruby/ruby_parser.rb:15:in `parse'
    /Users/nate/.rvm/gems/ruby-1.9.3-p327/gems/yard-0.8.3/lib/yard/parser/source_parser.rb:439:in `parse'
    /Users/nate/.rvm/gems/ruby-1.9.3-p327/gems/yard-0.8.3/lib/yard/parser/source_parser.rb:44:in `block in parse'

The code says it should be formatted like this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@nate

Bumping this pull request. There are 91 gems that depend on multi_xml and I'm sure countless apps, it would be great if we could get this out there.

@acook

This is kind of a big deal.

@dblock

I'm prepping an advisor for Grape that depends on multi_xml. We need a release with this, please, I already have a working exploit with no workaround for existing apps.

@sferik
Owner

Overall, this looks good. What do you think about also removing lines 22 and 30 from https://github.com/sferik/multi_xml/blob/master/lib/multi_xml.rb? It seems like those are inherently unsafe.

@nate

I feel like they should be left in since they're only unsafe if you're parsing untrusted input. This pull requests makes parsing default to not trusting the xml and requires users to deliberately mark each and every instance of xml parsing as trusted if they want YAML parsing. This makes the library not only secure by default, but flexible if you really need it. Also, this mimics ActiveSupport's xml parsing and I think it would be nice to keep feature parity, if possible. What do you think?

It may be worth adding more docs around the fact that using allowing all types while parsing untrusted input is dangerous, and maybe even link to the CVE, or this pull request.

@sferik
Owner

Sounds good.

@sferik sferik merged commit c94b136 into sferik:master

1 check passed

Details default The Travis build passed
@nate nate deleted the nate:hotfix/remove-ability-to-parse-symbols-and-yaml branch
@nate

For those subscribed to this thread, a new version of multi_xml has been pushed with these fixes. Get it while it's hot!

https://rubygems.org/gems/multi_xml/versions/0.5.2

Thanks @sferik!

@sferik
Owner

@nate: Thanks so much for submitting this patch.

@nate

Glad to help. :)

@reedloden

This particular issue has been assigned CVE-2013-0175.

@dblock

Thanks @sferik and @nate, committed into Grape, ruby-grape/grape@e15b7c3

@titanous titanous referenced this pull request in jnunemaker/httparty
Merged

Bump multi_xml dependency to 0.5.2 for CVE-2013-0175 fix #181

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Showing with 68 additions and 17 deletions.
  1. +1 −1 CONTRIBUTING.md
  2. +8 −4 README.md
  3. +1 −1 Rakefile
  4. +23 −6 lib/multi_xml.rb
  5. +35 −5 spec/parser_shared_example.rb
View
2 CONTRIBUTING.md
@@ -39,7 +39,7 @@ Ideally, a bug report should include a pull request with failing specs.
7. Run `open coverage/index.html`. If your changes are not completely covered
by your tests, return to step 3.
8. Add documentation for your feature or bug fix.
-9. Run `bundle exec rake yard`. If your changes are not 100% documented, go
+9. Run `bundle exec rake doc:yard`. If your changes are not 100% documented, go
back to step 8.
10. Add, commit, and push your changes.
11. [Submit a pull request.][pr]
View
12 README.md
@@ -20,16 +20,20 @@ use it like so:
```ruby
require 'multi_xml'
-MultiXml.parser = :ox MultiXml.parser = MultiXml::Parsers::Ox # Same as above
+MultiXml.parser = :ox
+MultiXml.parser = MultiXml::Parsers::Ox # Same as above
MultiXml.parse('<tag>This is the contents</tag>') # Parsed using Ox
-MultiXml.parser = :libxml MultiXml.parser = MultiXml::Parsers::Libxml # Same as above
+MultiXml.parser = :libxml
+MultiXml.parser = MultiXml::Parsers::Libxml # Same as above
MultiXml.parse('<tag>This is the contents</tag>') # Parsed using LibXML
-MultiXml.parser = :nokogiri MultiXml.parser = MultiXml::Parsers::Nokogiri # Same as above
+MultiXml.parser = :nokogiri
+MultiXml.parser = MultiXml::Parsers::Nokogiri # Same as above
MultiXml.parse('<tag>This is the contents</tag>') # Parsed using Nokogiri
-MultiXml.parser = :rexml MultiXml.parser = MultiXml::Parsers::Rexml # Same as above
+MultiXml.parser = :rexml
+MultiXml.parser = MultiXml::Parsers::Rexml # Same as above
MultiXml.parse('<tag>This is the contents</tag>') # Parsed using REXML
```
The `parser` setter takes either a symbol or a class (to allow for custom XML
View
2 Rakefile
@@ -10,7 +10,7 @@ task :default => :spec
namespace :doc do
require 'yard'
YARD::Rake::YardocTask.new do |task|
- task.files = ['LICENSE.md', 'lib/**/*.rb']
+ task.files = ['lib/**/*.rb', '-', 'LICENSE.md']
@nate
nate added a note

I was getting warnings:

[warn]: Syntax error in `license.md`:(1,18): syntax error, unexpected tinteger, expecting $end
[warn]: ParserSyntaxError: syntax error in `LICENSE.md`:(1,18): syntax error, unexpected tINTEGER, expecting $end
[warn]: Stack trace:
    /Users/nate/.rvm/gems/ruby-1.9.3-p327/gems/yard-0.8.3/lib/yard/parser/ruby/ruby_parser.rb:537:in `on_parse_error'
    /Users/nate/.rvm/gems/ruby-1.9.3-p327/gems/yard-0.8.3/lib/yard/parser/ruby/ruby_parser.rb:50:in `parse'
    /Users/nate/.rvm/gems/ruby-1.9.3-p327/gems/yard-0.8.3/lib/yard/parser/ruby/ruby_parser.rb:50:in `parse'
    /Users/nate/.rvm/gems/ruby-1.9.3-p327/gems/yard-0.8.3/lib/yard/parser/ruby/ruby_parser.rb:15:in `parse'
    /Users/nate/.rvm/gems/ruby-1.9.3-p327/gems/yard-0.8.3/lib/yard/parser/source_parser.rb:439:in `parse'
    /Users/nate/.rvm/gems/ruby-1.9.3-p327/gems/yard-0.8.3/lib/yard/parser/source_parser.rb:44:in `block in parse'

The code says it should be formatted like this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
task.options = [
'--no-private',
'--protected',
View
29 lib/multi_xml.rb
@@ -7,6 +7,11 @@
module MultiXml
class ParseError < StandardError; end
+ class DisallowedTypeError < StandardError
+ def initialize(type)
+ super "Disallowed type attribute: #{type.inspect}"
+ end
+ end
REQUIREMENT_MAP = [
['ox', :ox],
@@ -54,6 +59,8 @@ class ParseError < StandardError; end
'Hash' => 'hash'
} unless defined?(TYPE_NAMES)
+ DISALLOWED_XML_TYPES = %w(symbol yaml)
+
class << self
# Get the current parser class.
def parser
@@ -105,6 +112,8 @@ def parser=(new_parser)
# <b>Options</b>
#
# <tt>:symbolize_keys</tt> :: If true, will use symbols instead of strings for the keys.
+ #
+ # <tt>:disallowed_types</tt> :: Types to disallow from being typecasted. Defaults to `['yaml', 'symbol']`. Use `[]` to allow all types.
def parse(xml, options={})
xml ||= ''
@@ -116,7 +125,9 @@ def parse(xml, options={})
return {} if char.nil?
xml.ungetc(char)
- hash = typecast_xml_value(undasherize_keys(parser.parse(xml))) || {}
+ hash = typecast_xml_value(undasherize_keys(parser.parse(xml)), options[:disallowed_types]) || {}
+ rescue DisallowedTypeError
+ raise
rescue parser.parse_error => error
raise ParseError, error.to_s, error.backtrace
end
@@ -191,9 +202,15 @@ def undasherize_keys(params)
end
end
- def typecast_xml_value(value)
+ def typecast_xml_value(value, disallowed_types=nil)
+ disallowed_types ||= DISALLOWED_XML_TYPES
+
case value
when Hash
+ if value.include?('type') && !value['type'].is_a?(Hash) && disallowed_types.include?(value['type'])
+ raise DisallowedTypeError, value['type']
+ end
+
if value['type'] == 'array'
# this commented-out suggestion helps to avoid the multiple attribute
@@ -216,9 +233,9 @@ def typecast_xml_value(value)
else
case entries
when Array
- entries.map {|entry| typecast_xml_value(entry)}
+ entries.map {|entry| typecast_xml_value(entry, disallowed_types)}
when Hash
- [typecast_xml_value(entries)]
+ [typecast_xml_value(entries, disallowed_types)]
else
raise "can't typecast #{entries.class.name}: #{entries.inspect}"
end
@@ -252,7 +269,7 @@ def typecast_xml_value(value)
nil
else
xml_value = value.inject({}) do |hash, (k, v)|
- hash[k] = typecast_xml_value(v)
+ hash[k] = typecast_xml_value(v, disallowed_types)
hash
end
@@ -261,7 +278,7 @@ def typecast_xml_value(value)
xml_value['file'].is_a?(StringIO) ? xml_value['file'] : xml_value
end
when Array
- value.map!{|i| typecast_xml_value(i)}
+ value.map!{|i| typecast_xml_value(i, disallowed_types)}
value.length > 1 ? value : value.first
when String
value
View
40 spec/parser_shared_example.rb
@@ -323,12 +323,26 @@
@xml = "<tag type=\"yaml\">--- \n1: returns an integer\n:message: Have a nice day\narray: \n- has-dashes: true\n has_underscores: true\n</tag>"
end
- it "returns a Hash" do
- expect(MultiXml.parse(@xml)['tag']).to be_a(Hash)
+ it "raises MultiXML::DisallowedTypeError by default" do
+ expect{ MultiXml.parse(@xml)['tag'] }.to raise_error(MultiXml::DisallowedTypeError)
end
- it "returns the correctly parsed YAML" do
- expect(MultiXml.parse(@xml)['tag']).to eq({:message => "Have a nice day", 1 => "returns an integer", "array" => [{"has-dashes" => true, "has_underscores" => true}]})
+ it "returns the correctly parsed YAML when the type is allowed" do
+ expect(MultiXml.parse(@xml, :disallowed_types => [])['tag']).to eq({:message => "Have a nice day", 1 => "returns an integer", "array" => [{"has-dashes" => true, "has_underscores" => true}]})
+ end
+ end
+
+ context "with an attribute type=\"symbol\"" do
+ before do
+ @xml = "<tag type=\"symbol\">my_symbol</tag>"
+ end
+
+ it "raises MultiXML::DisallowedTypeError" do
+ expect{ MultiXml.parse(@xml)['tag'] }.to raise_error(MultiXml::DisallowedTypeError)
+ end
+
+ it "returns the correctly parsed Symbol when the type is allowed" do
+ expect(MultiXml.parse(@xml, :disallowed_types => [])['tag']).to eq(:my_symbol)
end
end
@@ -418,7 +432,7 @@
end
end
- %w(integer boolean date datetime yaml file).each do |type|
+ %w(integer boolean date datetime file).each do |type|
context "with an empty attribute type=\"#{type}\"" do
before do
@xml = "<tag type=\"#{type}\"/>"
@@ -430,6 +444,22 @@
end
end
+ %w{yaml symbol}.each do |type|
+ context "with an empty attribute type=\"#{type}\"" do
+ before do
+ @xml = "<tag type=\"#{type}\"/>"
+ end
+
+ it "raises MultiXml::DisallowedTypeError by default" do
+ expect{ MultiXml.parse(@xml)['tag']}.to raise_error(MultiXml::DisallowedTypeError)
+ end
+
+ it "returns nil when the type is allowed" do
+ expect(MultiXml.parse(@xml, :disallowed_types => [])['tag']).to be_nil
+ end
+ end
+ end
+
context "with an empty attribute type=\"array\"" do
before do
@xml = '<tag type="array"/>'
Something went wrong with that request. Please try again.