Dynamic groups. Advances. Part III

Initial document from EMC about privilege escalations – very complicated and contradictory 😦

We already know that CS enables dynamic groups per RPC if RPC command contains __REQUESTED_PROTECTED_ROLES attribute(s). What DFC classes know about this attribute?

~]$ zipgrep __REQUESTED_PROTECTED_ROLES dfc.jar
com/documentum/fc/client/impl/connection/docbase/DocbaseConnection.class
~]$

Nice, DocbaseConnection.addProtectedRolesIfNecessary() method reads requested groups from RoleRequestManager.getInstance().getRoleRequests() and adds them to RPC command. What DFC classes know about RoleRequestManager class?

~]$ zipgrep RoleRequestManager dfc.jar
com/documentum/fc/client/impl/bof/security/RoleRequestManager.class
com/documentum/fc/client/impl/connection/docbase/DocbaseConnection.class
com/documentum/fc/client/impl/session/Session.class
com/documentum/fc/client/security/DfPrivilegedActionInRole.class
com/documentum/fc/client/security/DfPrivilegedExceptionActionInRole.class
~]$

Let’s imagine that our goal is to replace insecure D2’s D2GetAdminTicketMethod method, actually it is totally useless (Why do we need superuser ticket if we are able to execute code with superuser privileges?), but this is a good demonstration of dynamic group capabilities. We may write something like:

import static java.lang.System.out;
/**
 * @author Andrey B. Panfilov <andrew@panfilov.tel>
 */
public class Test {

    public static void main(String[] args) throws DfException {
        IDfSession session = new DfClientX().getLocalClient().newSession(
                "repo", new DfLoginInfo("test01", "test01"));
        RoleRequestManager requestManager = RoleRequestManager.getInstance();
        DfRoleSpec roleSpec = new DfRoleSpec("dm_superusers_dynamic",
                session.getDocbaseName());
        try {
            requestManager.push(roleSpec);
            out.println(session.getLoginTicketForUser(session.getDocbaseOwnerName()));
        } finally {
            requestManager.pop(roleSpec);
        }
    }

}

or take advantage of DfPrivilegedActionInRole (extremely inconvenient!) and write something like:

import static java.lang.System.out;

/**
 * @author Andrey B. Panfilov <andrew@panfilov.tel>
 */
public class Test {

    public static void main(String[] args) throws DfException {
        final IDfSession session = new DfClientX().getLocalClient().newSession(
                "repo", new DfLoginInfo("test01", "test01"));
        try {
            out.println(new DfPrivilegedActionInRole<String>(
                    new DfRoleSpec("dm_superusers_dynamic", session
                            .getDocbaseName()), new PrivilegedAction<String>() {
                @Override
                public String run() {
                    try {
                        return session.getLoginTicketForUser(session
                                .getDocbaseOwnerName());
                    } catch (DfException ex) {
                        throw new DfRuntimeException(ex);
                    }
                }
            }));
        } catch (DfRuntimeException ex) {
            if (ex.getCause() instanceof DfException) {
                throw (DfException) ex.getCause();
            }
            throw ex;
        }
    }

}

or in more convenient way:

import static java.lang.System.out;

import java.security.PrivilegedAction;

import com.documentum.com.DfClientX;
import com.documentum.fc.client.IDfSession;
import com.documentum.fc.client.security.DfPrivilegedActionInRole;
import com.documentum.fc.client.security.DfRoleSpec;
import com.documentum.fc.common.DfException;
import com.documentum.fc.common.DfLoginInfo;
import com.documentum.fc.common.DfRuntimeException;

/**
 * @author Andrey B. Panfilov <andrew@panfilov.tel>
 */
public class Test {

    public static void main(String[] args) throws DfException {
        final IDfSession session = new DfClientX().getLocalClient().newSession(
                "repo", new DfLoginInfo("test01", "test01"));
        out.println(execute(new PrivilegedAction<String>() {
            @Override
            public String run() {
                try {
                    return session.getLoginTicketForUser(session
                            .getDocbaseOwnerName());
                } catch (DfException ex) {
                    throw new DfRuntimeException(ex);
                }
            }
        }, false, session.getDocbaseName(), "dm_superusers_dynamic"));

    }

    public static <T> T execute(PrivilegedAction<T> action, boolean propagate,
            String docbase, String... roles) throws DfException {
        if (roles == null || roles.length == 0) {
            return action.run();
        }
        DfRoleSpec[] roleSpecs = new DfRoleSpec[roles.length];
        for (int i = 0, n = roles.length; i < n; i++) {
            if (docbase == null) {
                roleSpecs[i] = new DfRoleSpec(roles[i]);
            } else {
                roleSpecs[i] = new DfRoleSpec(roles[i], docbase);
            }
        }
        try {
            return new DfPrivilegedActionInRole<T>(roleSpecs, propagate, action)
                    .run();
        } catch (DfRuntimeException ex) {
            if (ex.getCause() instanceof DfException) {
                throw (DfException) ex.getCause();
            }
            throw ex;
        }
    }

}

Trying to execute:

 ~]$ java Test
22:55:58,290 ERROR [main] com.documentum.fc.client.security.DfPrivilegedActionInRole - 
[DFC_PRIVILEGE_ASSUME_ERROR] failed escalation as 
 '[DfRoleSpec{roleName=dm_superusers_dynamic, docbaseName=repo}]' with propagation: false
 java.security.AccessControlException: 
  access denied ("com.documentum.fc.client.impl.bof.security.RolePermission" 
    "dm_superusers_dynamic.repo")
 at java.security.AccessControlContext.checkPermission(AccessControlContext.java:372)
   .......
Exception in thread "main" java.security.AccessControlException: 
   access denied ("com.documentum.fc.client.impl.bof.security.RolePermission" 
     "dm_superusers_dynamic.repo")
   at java.security.AccessControlContext.checkPermission(AccessControlContext.java:372)
   ......
   at Test.main(Test.java:21)

Oops, DocbaseConnection.addProtectedRolesIfNecessary() methods performs some useless (I’m going to describe this in next blogpost) security checks 😦 What to do?

Option #1: modify java.policy, some examples:

grant {
  // specific group in repository repo
  permission com.documentum.fc.client.impl.bof.security.RolePermission "group.repo";
  // all groups in all repositories
  permission com.documentum.fc.client.impl.bof.security.RolePermission "*";
  // all groups in all repositories with enabled propagation
  permission com.documentum.fc.client.impl.bof.security.RolePermission "*", "propagate";
};

Result:

 ~]$ java Test
DM_TICKET=T0JKIE5VTEwgMAoxMwp2ZXJza...

Option #2: create docbase module – DFC creates its own classloader per module and sets up privileges automatically, the crucial step here is deploy module implementation class into dmc_jar object with jar_type=2, otherwise DFC puts it into shared classloader. A bit of shell/dql magic:

#
# Interface class
#
 ~]$ cat > IPrivilegedExecutor.java
import java.security.PrivilegedAction;

import com.documentum.fc.client.IDfModule;
import com.documentum.fc.common.DfException;

/**
 * @author Andrey B. Panfilov <andrew@panfilov.tel>
 */
public interface IPrivilegedExecutor extends IDfModule {

    <T> T execute(PrivilegedAction<T> action, boolean propagate,
            String docbase, String... roles) throws DfException;

}
#
# Implementation class
#
 ~]$ cat > PrivilegedExecutor.java
import java.security.AccessController;
import java.security.PrivilegedAction;

import com.documentum.fc.client.security.DfPrivilegedActionInRole;
import com.documentum.fc.client.security.DfRoleSpec;
import com.documentum.fc.common.DfException;
import com.documentum.fc.common.DfRuntimeException;

/**
 * @author Andrey B. Panfilov <andrew@panfilov.tel>
 */
public class PrivilegedExecutor implements IPrivilegedExecutor {

    @Override
    public <T> T execute(PrivilegedAction<T> action, boolean propagate,
            String docbase, String... roles) throws DfException {
        if (roles == null || roles.length == 0) {
            return action.run();
        }
        DfRoleSpec[] roleSpecs = new DfRoleSpec[roles.length];
        for (int i = 0, n = roles.length; i < n; i++) {
            if (docbase == null) {
                roleSpecs[i] = new DfRoleSpec(roles[i]);
            } else {
                roleSpecs[i] = new DfRoleSpec(roles[i], docbase);
            }
        }
        try {
            return AccessController
                    .doPrivileged(new DfPrivilegedActionInRole<T>(roleSpecs,
                            propagate, action));
        } catch (DfRuntimeException ex) {
            if (ex.getCause() instanceof DfException) {
                throw (DfException) ex.getCause();
            }
            throw ex;
        }
    }

}
#
# Manifest
#
 ~]$ mkdir META-INF
 ~]$ cat > META-INF/MANIFEST.MF
Manifest-Version: 1.0
#
# Compilation
#
 ~]$ javac IPrivilegedExecutor.java PrivilegedExecutor.java
#
# Packing interface jar
#
 ~]$ jar cf PrivilegedExecutor-api.jar META-INF/MANIFEST.MF IPrivilegedExecutor.class
#
# Packing implementation jar
#
 ~]$ jar cf PrivilegedExecutor-impl.jar META-INF/MANIFEST.MF PrivilegedExecutor.class
#
# Checking jars
#
 ~]$ zipinfo PrivilegedExecutor-api.jar
Archive:  PrivilegedExecutor-api.jar
Zip file size: 742 bytes, number of entries: 3
-rw----     2.0 fat        0 bX defN 14-Sep-06 22:34 META-INF/
-rw----     2.0 fat       68 bl defN 14-Sep-06 22:34 META-INF/MANIFEST.MF
-rw----     2.0 fat      462 bl defN 14-Sep-06 22:34 IPrivilegedExecutor.class
3 files, 530 bytes uncompressed, 328 bytes compressed:  38.1%
 ~]$ zipinfo PrivilegedExecutor-impl.jar
Archive:  PrivilegedExecutor-impl.jar
Zip file size: 1189 bytes, number of entries: 3
-rw----     2.0 fat        0 bX defN 14-Sep-06 22:35 META-INF/
-rw----     2.0 fat       68 bl defN 14-Sep-06 22:35 META-INF/MANIFEST.MF
-rw----     2.0 fat     1441 bl defN 14-Sep-06 22:34 PrivilegedExecutor.class
3 files, 1509 bytes uncompressed, 777 bytes compressed:  48.5%
#
# Creating docbase module
#
 ~]$ idql repo -Udmadmin -Pdmadmin

...

1> create dmc_module object
2> set object_name='IPrivilegedExecutor',
3> append a_interfaces='IPrivilegedExecutor',
4> append a_interfaces='com.documentum.fc.client.IDfModule',
5> set primary_class='PrivilegedExecutor',
6> set implementation_technology='java',
7> set a_is_privileged=TRUE,
8> append a_privilege_roles='dm_superusers_dynamic',
9> link '/System/Modules'
10> go
object_created
----------------
0b022428800ab380
(1 row affected)

1> create dmc_jar object
2> set object_name='PrivilegedExecutor-impl.jar',
3> set jar_type=2,
4> append r_aspect_name='com.documentum.fc.bof.bootstrap.DfModuleItemChangeMonitor'
5> setfile '/home/dmadmin/PrivilegedExecutor-impl.jar'
6> with content_format='jar',
7> link '/System/Modules/IPrivilegedExecutor'
8> go
object_created
----------------
09022428800ab387
(1 row affected)

1> create dmc_jar object
2> set object_name='PrivilegedExecutor-api.jar',
3> set jar_type=1,
4> append r_aspect_name='com.documentum.fc.bof.bootstrap.DfModuleItemChangeMonitor'
5> setfile '/home/dmadmin/PrivilegedExecutor-api.jar'
6> with content_format='jar',
7> link '/System/Modules/IPrivilegedExecutor'
8> go
object_created
----------------
09022428800ab388
(1 row affected)

Result:

 ~]$ cat > Test.java
import static java.lang.System.out;

import java.lang.reflect.Method;
import java.security.PrivilegedAction;

import com.documentum.com.DfClientX;
import com.documentum.fc.client.IDfSession;
import com.documentum.fc.client.IDfSessionManager;
import com.documentum.fc.client.security.DfPrivilegedActionInRole;
import com.documentum.fc.client.security.DfRoleSpec;
import com.documentum.fc.common.DfException;
import com.documentum.fc.common.DfLoginInfo;
import com.documentum.fc.common.DfRuntimeException;

/**
 * @author Andrey B. Panfilov <andrew@panfilov.tel>
 */
public class Test {

    public static void main(String[] args) throws Exception {
        final IDfSession session = new DfClientX().getLocalClient().newSession(
                "repo", new DfLoginInfo("test01", "test01"));
        Object executor = getModule(IPrivilegedExecutor.class, session);
        Method mtd = executor.getClass().getMethod("execute",
                PrivilegedAction.class, boolean.class, String.class,
                String[].class);
        out.println(mtd.invoke(executor, new PrivilegedAction<String>() {
            @Override
            public String run() {
                try {
                    return session.getLoginTicketForUser(session
                            .getDocbaseOwnerName());
                } catch (DfException ex) {
                    throw new DfRuntimeException(ex.getMessage(), ex);
                }
            }
        }, true, session.getDocbaseName(),
                new String[] {"dm_superusers_dynamic" }));

    }

    public static <T> T execute(PrivilegedAction<T> action, boolean propagate,
            String docbase, String... roles) throws DfException {
        if (roles == null || roles.length == 0) {
            return action.run();
        }
        DfRoleSpec[] roleSpecs = new DfRoleSpec[roles.length];
        for (int i = 0, n = roles.length; i < n; i++) {
            if (docbase == null) {
                roleSpecs[i] = new DfRoleSpec(roles[i]);
            } else {
                roleSpecs[i] = new DfRoleSpec(roles[i], docbase);
            }
        }
        try {
            return new DfPrivilegedActionInRole<T>(roleSpecs, propagate, action)
                    .run();
        } catch (DfRuntimeException ex) {
            if (ex.getCause() instanceof DfException) {
                throw (DfException) ex.getCause();
            }
            throw ex;
        }
    }

    public static <T> T getModule(Class<T> cls, String name, IDfSession session)
        throws DfException {
        return getModule(cls, name, session.getSessionManager(),
                session.getDocbaseName());
    }

    public static <T> T getModule(Class<T> cls, IDfSession session)
        throws DfException {
        return getModule(cls, cls.getName(), session);
    }

    @SuppressWarnings("unchecked")
    public static <T> T getModule(Class<T> cls, String name,
            IDfSessionManager sMgr, String docbase) throws DfException {
        return (T) new DfClientX().getLocalClient().newModule(docbase, name,
                sMgr);
    }

}
 ~]$ javac Test.java
 ~]$ java Test
DM_TICKET=T0JKIE5VTEwgMAoxMwp...
 ~]$

2 thoughts on “Dynamic groups. Advances. Part III

  1. Pingback: What is wrong in Documentum? Part I | Documentum in a (nuts)HELL
  2. Pingback: Dynamic groups. Advances. Part V | Documentum in a (nuts)HELL

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s