CVE-2022-24803 (Ruby, published 2022-04-01T00:15Z)
Description: value: Asciidoctor-include-ext is Asciidoctor’s standard include processor reimplemented as an extension. Versions prior to 0.4.0, when used to render user-supplied input in AsciiDoc markup, may allow an attacker to execute arbitrary system commands on the host operating system. This attack is possible even when `allow-uri-read` is disabled! The problem has been patched in the referenced commits.
Severity: HIGH (CVSS v3 base score: 9.8)
Weakness: CWE-78 \u2014 Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')
The product constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component.
Repository: https://github.com/jirutka/asciidoctor-include-ext
Files changed:
lib/asciidoctor/include_ext/include_processor.rb
spec/integration_spec.rb
Commit message: Fix command injection vulnerability

Reported by Joern Schneeweisz from GitLab GmbH:

> During an internal assessment on some GitLab code I came across a way
> to execute arbitrary commands in your asciidoctor-include-ext Gem.
>
> The following adoc snippet demonstrates the issue:
>
> ```
> :app-name: |id # + \
> http://test.com
>
> include::{app-name}[]
> ```
>
> It uses a linebreak to bypass the `target_uri` check here
> https://github.com/jirutka/asciidoctor-include-ext/blob/master/lib/asciidoctor/include_ext/include_processor.rb#L97
> and feed a command with the `|` prefix to open/IO.foreach.
>
> You can verify this with the above snippet by rendering it like this
>
> ```
> asciidoctor -r asciidoctor-include-ext  -a allow-uri-read home.asciidoc
> ```

See-Also: https://sakurity.com/blog/2015/02/28/openuri.html


Vulnerable code:
      return false if target_uri?(target) && !doc.attributes.key?('allow-uri-read')
      return target if target_uri? target
    # @param filename [String] path of the file to be read.
    def read_lines(filename, selector)
        IO.foreach(filename).select.with_index(1, &selector)
        URI.open(filename, &:read)
    # @return [Boolean] `true` if the *target* is an URI, `false` otherwise.
    def target_uri?(target)
      ::Asciidoctor::Helpers.uriish?(target)

Fixed code:
require 'uri'
      return false if target_http?(target) && !doc.attributes.key?('allow-uri-read')
      return target if target_http? target
    # @param path [String] URL or path of the file to be read.
    def read_lines(path, selector)
        IO.foreach(path).select.with_index(1, &selector)
        URI.open(path, &:read)
    # @return [Boolean] `true` if the *target* is a valid HTTP(S) URI, `false` otherwise.
    def target_http?(target)
      # First do a fast test, then try to parse it.
      target.downcase.start_with?('http://', 'https://') \
        && URI.parse(target).is_a?(URI::HTTP)
    rescue URI::InvalidURIError
      false

    it 'does not allow execution of system command when allow-uri-read is set' do
      options.merge!(attributes: { 'allow-uri-read' => '' })
      given <<~ADOC
        :app-name: |cat LICENSE # + \\
        http://test.com

        include::{app-name}[]
      ADOC

      should match /unresolved/i
      should_not match /The MIT License/
    end