Exemplo n.º 1
0
async def get_all_files_meta(request, user):
    try:
        files = await db_objects.execute(FileMeta.select())
    except Exception as e:
        return json({'status': 'error', 'error': 'failed to get files'})
    return json(
        [f.to_json() for f in files if f.operation.name in user['operations']])
Exemplo n.º 2
0
async def list_all_screencaptures_per_callback(request, user, id):
    try:
        callback = await db_objects.get(Callback, id=id)
    except Exception as e:
        print(e)
        return json({'status': 'error', 'error': 'failed to find callback'})
    screencapture_paths = []
    if callback.operation.name in user['operations']:
        screencaptures = await db_objects.execute(FileMeta.select().where(
            FileMeta.path.regexp(".*{}/downloads/.*/screenshots/".format(
                callback.operation.name))))
        for s in screencaptures:
            if s.task.callback == callback:
                screencapture_paths.append(s.to_json())
        return json({
            'status': 'success',
            'callback': callback.id,
            'files': screencapture_paths
        })
    else:
        return json({
            'status':
            'error',
            'error':
            'must be part of that callback\'s operation to see its screenshots'
        })
Exemplo n.º 3
0
async def ws_screenshots(request, ws, user):
    try:
        async with aiopg.create_pool(apfell.config['DB_POOL_CONNECT_STRING']) as pool:
            async with pool.acquire() as conn:
                async with conn.cursor() as cur:
                    await cur.execute('LISTEN "newfilemeta";')
                    # BEFORE WE START GETTING NEW THINGS, UPDATE WITH ALL OF THE OLD DATA
                    operation = await db_objects.get(Operation, name=user['current_operation'])
                    files = await db_objects.execute(FileMeta.select().where(FileMeta.operation == operation).order_by(FileMeta.id))
                    for f in files:
                        if "{}/downloads/".format(user['current_operation']) in f.path and "/screenshots/" in f.path:
                            await ws.send(js.dumps({**f.to_json(), 'callback_id': f.task.callback.id, 'operator': f.task.operator.username}))
                    await ws.send("")
                    # now pull off any new payloads we got queued up while processing old data
                    while True:
                        try:
                            msg = conn.notifies.get_nowait()
                            blob = js.loads(msg.payload)
                            if "{}/downloads/".format(user['current_operation']) in blob['path'] and "/screenshots" in blob['path']:
                                f = await db_objects.get(FileMeta, id=blob['id'])
                                callback_id = f.task.callback.id
                                await ws.send(js.dumps({**f.to_json(), 'callback_id': callback_id, 'operator': f.task.operator.username}))
                        except asyncio.QueueEmpty as e:
                            await asyncio.sleep(2)
                            await ws.send("")  # this is our test to see if the client is still there
                            continue
                        except Exception as e:
                            print(e)
                            return
    finally:
        pool.close()
Exemplo n.º 4
0
async def create_filemeta_in_database(request, user, id):
    try:
        operation = await db_objects.get(Operation,
                                         name=user['current_operation'])
        filemeta = await db_objects.get(FileMeta, id=id, operation=operation)
    except Exception as e:
        print(e)
        return json({
            'status':
            'error',
            'error':
            'file does not exist or not part of your current operation'
        })
    status = {'status': 'success'}
    filemeta.deleted = True
    try:
        await db_objects.update(filemeta)
    except Exception as e:
        status = {'status': 'error', 'error': str(e)}
    try:
        # only remove the file if there's nothing else pointing to it
        # this could be a payload and the user is just asking to remove the hosted aspect
        file_count = await db_objects.count(
            FileMeta.select().where((FileMeta.path == filemeta.path)
                                    & (FileMeta.deleted == False)))
        file_count += await db_objects.count(
            Payload.select().where((Payload.location == filemeta.path)
                                   & (Payload.deleted == False)))
        if file_count == 0:
            os.remove(filemeta.path)
    except Exception as e:
        pass
    return json({**status, **filemeta.to_json()})
Exemplo n.º 5
0
async def remove_payload(request, puuid, user, from_disk):
    try:
        payload = await db_objects.get(Payload, uuid=puuid)
    except Exception as e:
        print(e)
        return json({
            'status': 'error',
            'error': 'specified payload does not exist'
        })
    try:
        payload.deleted = True
        await db_objects.update(payload)
        if from_disk == 1 and os.path.exists(payload.location):
            try:
                os.remove(payload.location)
            except Exception as e:
                print(e)
        # if we started hosting this payload as a file in our database, we need to remove that as well
        file_metas = await db_objects.execute(
            FileMeta.select().where(FileMeta.path == payload.location))
        for fm in file_metas:
            await db_objects.delete(fm)
        success = {'status': 'success'}
        return json({**success, **payload.to_json()})
    except Exception as e:
        print(e)
        return json({'status': 'error', 'error': 'failed to delete payload'})
Exemplo n.º 6
0
async def clear_tasks_for_callback_func(data, cid, user):
    try:
        callback = await db_objects.get(Callback, id=cid)
        operation = await db_objects.get(Operation, id=callback.operation)
    except Exception as e:
        print(e)
        return {
            'status': 'error',
            'error': 'failed to get callback or operation'
        }
    tasks_removed = []
    if "all" == data['task']:
        tasks = await db_objects.execute(Task.select().join(Callback).where(
            (Task.callback == callback)
            & (Task.status == "submitted")).order_by(Task.timestamp))
    elif len(data['task']) > 0:
        #  if the user specifies a task, make sure that it's not being processed
        tasks = await db_objects.execute(
            Task.select().where((Task.id == data['task'])
                                & (Task.status == "submitted")))
    else:
        # if you don't actually specify a task, remove the the last task that was entered
        tasks = await db_objects.execute(Task.select().where(
            (Task.status == "submitted")
            & (Task.callback == callback)).order_by(-Task.timestamp).limit(1))
    for t in tasks:
        if operation.name in user['operations']:
            try:
                t_removed = t.to_json()
                # don't actually delete it, just mark it as completed with a response of "CLEARED TASK"
                t.status = "processed"
                await db_objects.update(t)
                # we need to adjust all of the things associated with this task now since it didn't actually happen
                # find/remove ATTACKTask, TaskArtifact, FileMeta
                attack_tasks = await db_objects.execute(
                    ATTACKTask.select().where(ATTACKTask.task == t))
                for at in attack_tasks:
                    await db_objects.delete(at, recursive=True)
                task_artifacts = await db_objects.execute(
                    TaskArtifact.select().where(TaskArtifact.task == t))
                for ta in task_artifacts:
                    await db_objects.delete(ta, recursive=True)
                file_metas = await db_objects.execute(
                    FileMeta.select().where(FileMeta.task == t))
                for fm in file_metas:
                    os.remove(fm.path)
                    await db_objects.delete(fm, recursive=True)
                # now create the response so it's easy to track what happened with it
                response = "CLEARED TASK by " + user['username']
                await db_objects.create(Response, task=t, response=response)
                tasks_removed.append(t_removed)
            except Exception as e:
                print(e)
                return {
                    'status': 'error',
                    'error': 'failed to delete task: ' + t.command.cmd
                }
    return {'status': 'success', 'tasks_removed': tasks_removed}
Exemplo n.º 7
0
async def list_all_screencaptures_per_operation(request, user):
    if user['current_operation'] != "":
        operation = await db_objects.get(Operation, name=user['current_operation'])
        screencaptures = await db_objects.execute(FileMeta.select().where(FileMeta.path.regexp(".*{}/downloads/.*/screenshots/".format(operation.name))))
        screencapture_paths = []
        for s in screencaptures:
            screencapture_paths.append(s.to_json())
        return json({'status': 'success', 'files': screencapture_paths})
    else:
        return json({"status": 'error', 'error': 'must be part of a current operation to see an operation\'s screencaptures'})
Exemplo n.º 8
0
async def get_current_operations_files_meta(request, user):
    if user['current_operation'] != "":
        try:
            operation = await db_objects.get(Operation, name=user['current_operation'])
            files = await db_objects.execute(FileMeta.select().where(FileMeta.operation == operation))
        except Exception as e:
            return json({'status': 'error', 'error': 'failed to get files'})
        return json([f.to_json() for f in files if not "screenshots" in f.path ])
    else:
        return json({"status": 'error', 'error': 'must be part of an active operation'})
Exemplo n.º 9
0
async def ws_files_current_operation(request, ws, user):
    try:
        async with aiopg.create_pool(apfell.config['DB_POOL_CONNECT_STRING']) as pool:
            async with pool.acquire() as conn:
                async with conn.cursor() as cur:
                    await cur.execute('LISTEN "newfilemeta";')
                    # BEFORE WE START GETTING NEW THINGS, UPDATE WITH ALL OF THE OLD DATA
                    operation = await db_objects.get(Operation, name=user['current_operation'])
                    files = await db_objects.execute(FileMeta.select().where(
                        (FileMeta.operation == operation) & (FileMeta.deleted == False)).order_by(FileMeta.id))
                    for f in files:
                        if "/screenshots/" not in f.path:
                            if "/{}/downloads/".format(user['current_operation']) not in f.path:
                                # this means it's an upload, so supply additional information as well
                                # two kinds of uploads: via task or manual
                                if f.task is not None:  # this is an upload via agent tasking
                                    await ws.send(js.dumps(
                                        {**f.to_json(), 'host': f.task.callback.host, "upload": f.task.params}))
                                else:  # this is a manual upload
                                    await ws.send(js.dumps({**f.to_json(), 'host': 'MANUAL FILE UPLOAD',
                                                            "upload": "{\"remote_path\": \"Apfell\", \"file_id\": " + str(f.id) + "}", "task": "null"}))
                            else:
                                await ws.send(js.dumps({**f.to_json(), 'host': f.task.callback.host, 'params': f.task.params}))
                    await ws.send("")
                    # now pull off any new payloads we got queued up while processing old data
                    while True:
                        try:
                            msg = conn.notifies.get_nowait()
                            id = (msg.payload)
                            f = await db_objects.get(FileMeta, id=id, operation=operation, deleted=False)
                            if "/screenshots" not in f.path:
                                try:
                                    if "/{}/downloads/".format(user['current_operation']) not in f.path:
                                        # this means it's an upload, so supply additional information as well
                                        # could be upload via task or manual
                                        if f.task is not None:  # this is an upload via gent tasking
                                            await ws.send(js.dumps(
                                                {**f.to_json(), 'host': f.task.callback.host, "upload": f.task.params}))
                                        else: # this is a manual upload
                                            await ws.send(js.dumps({**f.to_json(), 'host': 'MANUAL FILE UPLOAD',
                                                                    "upload": "{\"remote_path\": \"Apfell\", \"file_id\": " + str(f.id) + "}", "task": "null"}))
                                    else:
                                        await ws.send(js.dumps({**f.to_json(), 'host': f.task.callback.host,
                                                                'params': f.task.params}))
                                except Exception as e:
                                    pass  # we got a file that's just not part of our current operation, so move on
                        except asyncio.QueueEmpty as e:
                            await asyncio.sleep(1)
                            await ws.send("")  # this is our test to see if the client is still there
                            continue
                        except Exception as e:
                            print(e)
                            continue
    finally:
        pool.close()
Exemplo n.º 10
0
async def add_task_to_callback_func(data, cid, user):
    try:
        # first see if the operator and callback exists
        op = await db_objects.get(Operator, username=user['username'])
        cb = await db_objects.get(Callback, id=cid)
        operation = await db_objects.get(Operation,
                                         name=user['current_operation'])
        original_params = None
        task = None
        # now check the task and add it if it's valid and valid for this callback's payload type
        try:
            cmd = await db_objects.get(
                Command,
                cmd=data['command'],
                payload_type=cb.registered_payload.payload_type)
        except Exception as e:
            # it's not registered, so check the default tasks/clear
            if data['command'] == "tasks":
                # this means we're just listing out the not-completed tasks, so nothing actually goes to the agent
                task = await db_objects.create(Task,
                                               callback=cb,
                                               operator=op,
                                               params=data['command'],
                                               status="processed",
                                               original_params=data['command'])
                raw_rsp = await get_all_not_completed_tasks_for_callback_func(
                    cb.id, user)
                if raw_rsp['status'] == 'success':
                    rsp = ""
                    for t in raw_rsp['tasks']:
                        rsp += "\nOperator: " + t['operator'] + "\nTask " + str(t['id']) + ": " + t['command'] + " " + \
                               t['params'] + "\nStatus: " + t['status']
                    await db_objects.create(Response, task=task, response=rsp)
                    return {
                        'status': 'success',
                        **task.to_json(), 'command': 'tasks'
                    }
                else:
                    return {
                        'status': 'error',
                        'error': 'failed to get tasks',
                        'cmd': data['command'],
                        'params': data['params']
                    }
            elif data['command'] == "clear":
                # this means we're going to be clearing out some tasks depending on our access levels
                task = await db_objects.create(
                    Task,
                    callback=cb,
                    operator=op,
                    params="clear " + data['params'],
                    status="processed",
                    original_params="clear " + data['params'])
                raw_rsp = await clear_tasks_for_callback_func(
                    {"task": data['params']}, cb.id, user)
                if raw_rsp['status'] == 'success':
                    rsp = "Removed the following:"
                    for t in raw_rsp['tasks_removed']:
                        rsp += "\nOperator: " + t['operator'] + "\nTask " + str(
                            t['id']) + ": " + t['command'] + " " + t['params']
                    await db_objects.create(Response, task=task, response=rsp)
                    return {'status': 'success', **task.to_json()}
                else:
                    return {
                        'status': 'error',
                        'error': raw_rsp['error'],
                        'cmd': data['command'],
                        'params': data['params']
                    }
            # it's not tasks/clear, so return an error
            return {
                'status': 'error',
                'error': data['command'] + ' is not a registered command',
                'cmd': data['command'],
                'params': data['params']
            }
        file_meta = ""
        # some tasks require a bit more processing, so we'll handle that here so it's easier for the implant
        if cmd.cmd == "upload":
            upload_config = js.loads(data['params'])
            # we need to get the file into the database before we can signal for the callback to pull it down
            try:
                # see if we actually submitted "file_id /remote/path/here"
                if 'file_id' in upload_config and upload_config['file_id'] > 0:
                    f = await db_objects.get(FileMeta,
                                             id=upload_config['file_id'])
                    # we don't want to lose our tracking on this file, so we'll create a new database entry
                    file_meta = await db_objects.create(
                        FileMeta,
                        total_chunks=f.total_chunks,
                        chunks_received=f.chunks_received,
                        complete=f.complete,
                        path=f.path,
                        operation=f.operation,
                        operator=op)
                    data['file_updates_with_task'].append(file_meta)
                elif 'file' in upload_config:
                    # we just made the file for this instance, so just use it as the file_meta
                    # in this case it's already added to data['file_updates_with_task']
                    file_meta = await db_objects.get(FileMeta,
                                                     id=upload_config['file'])
                # now normalize the data for the agent since it doesn't care if it was an old or new file_id to upload
                data['params'] = js.dumps({
                    'remote_path':
                    upload_config['remote_path'],
                    'file_id':
                    file_meta.id
                })
            except Exception as e:
                print(e)
                return {
                    'status': 'error',
                    'error':
                    'failed to get file info from the database: ' + str(e),
                    'cmd': data['command'],
                    'params': data['params']
                }

        elif cmd.cmd == "download":
            if '"' in data['params']:
                data['params'] = data['params'][
                    1:
                    -1]  # remove "" around the string at this point if they are there
        elif cmd.cmd == "screencapture":
            data['params'] = datetime.datetime.utcnow().strftime(
                '%Y-%m-%d-%H:%M:%S') + ".png"
        elif cmd.cmd == "load":
            try:
                status = await perform_load_transforms(data, cb, operation, op)
                if status['status'] == 'error':
                    return {
                        **status, 'cmd': data['command'],
                        'params': data['params']
                    }
                # now create a corresponding file_meta
                file_meta = await db_objects.create(FileMeta,
                                                    total_chunks=1,
                                                    chunks_received=1,
                                                    complete=True,
                                                    path=status['path'],
                                                    operation=cb.operation)
                data['file_updates_with_task'].append(file_meta)
                data['params'] = js.dumps({
                    "cmds": data['params'],
                    "file_id": file_meta.id
                })

            except Exception as e:
                print(e)
                return {
                    'status': 'error',
                    'error': 'failed to open and encode new function',
                    'cmd': data['command'],
                    'params': data['params']
                }
        # now actually run through all of the command transforms
        original_params = data['params']
        step_output = {}  # keep track of output at each stage
        step_output["0 - initial params"] = data['params']
        cmd_transforms = await get_commandtransforms_func(
            cmd.id, operation.name)
        if cmd_transforms['status'] == 'success':
            # reload our transforms right before use if we are actually going to do some
            if len(cmd_transforms['transforms']) > 0:
                try:
                    import app.api.transforms.utils
                    importlib.reload(sys.modules['app.api.transforms.utils'])
                except Exception as e:
                    print(e)
                from app.api.transforms.utils import CommandTransformOperation
                commandTransforms = CommandTransformOperation()
            for t in cmd_transforms['transforms']:
                if data['transform_status'][str(
                        t['order'])]:  # if this is set to active, do it
                    try:
                        data['params'] = await getattr(
                            commandTransforms, t['name'])(data['params'],
                                                          t['parameter'])
                        step_output[str(t['order']) + " - " +
                                    t['name']] = data['params']
                    except Exception as e:
                        print(e)
                        return {
                            'status':
                            'error',
                            'error':
                            'failed to apply transform {}, with message: {}'.
                            format(t['name'], str(e)),
                            'cmd':
                            data['command'],
                            'params':
                            original_params
                        }
        else:
            return {
                'status':
                'error',
                'error':
                'failed to get command transforms with message: {}'.format(
                    str(cmd_transforms['error'])),
                'cmd':
                data['command'],
                'params':
                original_params
            }
        if "test_command" in data and data['test_command']:
            # we just wanted to test out how things would end up looking, but don't actually create a Task for this
            # remove all of the fileMeta objects we created in prep for this since it's not a real issuing
            for update_file in data['file_updates_with_task']:
                await db_objects.delete(update_file)
                # we only want to delete the file from disk if there are no other db objects pointing to it
                # so we need to check other FileMeta.paths and Payload.locations
                file_count = await db_objects.count(
                    FileMeta.select().where((FileMeta.path == update_file.path)
                                            & (FileMeta.deleted == False)))
                file_count += await db_objects.count(Payload.select().where(
                    (Payload.location == update_file.path)
                    & (Payload.deleted == False)))
                try:
                    if file_count == 0:
                        os.remove(update_file.path)
                except Exception as e:
                    pass
            try:
                await db_objects.delete(file_meta)
                file_count = await db_objects.count(
                    FileMeta.select().where((FileMeta.path == file_meta.path)
                                            & (FileMeta.deleted == False)))
                file_count += await db_objects.count(
                    Payload.select().where((Payload.location == file_meta.path)
                                           & (Payload.deleted == False)))
                if file_count == 0:
                    os.remove(file_meta.path)
            except Exception as e:
                pass
            return {
                'status': 'success',
                'cmd': data['command'],
                'params': original_params,
                'test_output': step_output
            }
        if original_params is None:
            original_params = data['params']
        if task is None:
            task = await db_objects.create(Task,
                                           callback=cb,
                                           operator=op,
                                           command=cmd,
                                           params=data['params'],
                                           original_params=original_params)
        await add_command_attack_to_task(task, cmd)
        for update_file in data['file_updates_with_task']:
            # now we can associate the task with the filemeta object
            update_file.task = task
            await db_objects.update(update_file)
        status = {'status': 'success'}
        task_json = task.to_json()
        task_json['task_status'] = task_json[
            'status']  # we don't want the two status keys to conflict
        task_json.pop('status')
        return {**status, **task_json}
    except Exception as e:
        print("failed to get something in add_task_to_callback_func " + str(e))
        return {
            'status': 'error',
            'error': 'Failed to create task: ' + str(e),
            'cmd': data['command'],
            'params': data['params']
        }
Exemplo n.º 11
0
async def database_clears(request, user):
    try:
        operator = await db_objects.get(Operator, username=user['username'])
        operation = await db_objects.get(Operation, name=user['current_operation'])
        if operation.name not in user['admin_operations']:
            return json({'status': 'error', 'error': "you must be the admin of the operation to clear the database"})
    except Exception as e:
        return json({'status': 'error', 'error': "failed to get the operation and operation: " + str(e)})
    data = request.json
    if 'object' not in data:
        return json({'status': 'error', 'error': '"object" is a required parameter'})
    deleted_obj_info = {'dbnumber': 0}
    if data['object'] == "payloads":
        payloads = await db_objects.execute(Payload.select().where(Payload.operation == operation))
        for p in payloads:
            try:
                os.remove(p.location)  # delete it from disk first
            except Exception as e:
                print(e)
            await db_objects.delete(p, recursive=True)  # then delete it and everything it relies on from the db
            deleted_obj_info['dbnumber'] = deleted_obj_info['dbnumber'] + 1

    elif data['object'] == "callbacks":
        callbacks = await db_objects.execute(Callback.select().where(Callback.operation == operation))
        for c in callbacks:
            await db_objects.delete(c, recursive=True)
            deleted_obj_info['dbnumber'] = deleted_obj_info['dbnumber'] + 1
    elif data['object'] == "screencaptures":
        screencaptures = await db_objects.execute(FileMeta.select().where( (FileMeta.operation == operation) & (FileMeta.path.contains("/screenshots/")) ))
        for s in screencaptures:
            try:
                os.remove(s.path)
            except Exception as e:
                print(e)
            await db_objects.delete(s, recursive=True)
            deleted_obj_info['dbnumber'] = deleted_obj_info['dbnumber'] + 1
    elif data['object'] == "downloads":
        downloads = await db_objects.execute(FileMeta.select().where( (FileMeta.operation == operation) & (FileMeta.path.contains("/downloads/")) ))
        for d in downloads:
            try:
                os.remove(d.path)
            except Exception as e:
                print(e)
            await db_objects.delete(d, recursive=True)
            deleted_obj_info['dbnumber'] = deleted_obj_info['dbnumber'] + 1
        shutil.rmtree("./app/files/{}/downloads".format(operation.name))  # remove the downloads folder from disk
    elif data['object'] == "uploads":
        uploads = await db_objects.execute(FileMeta.select().where( (FileMeta.operation == operation) & (FileMeta.path.contains("/{}/".format(operation.name))) & ~(FileMeta.path.contains("/downloads")) ))
        for u in uploads:
            try:
                os.remove(u.path)
            except Exception as e:
                print(e)
            await db_objects.delete(u, recursive=True)
            deleted_obj_info['dbnumber'] = deleted_obj_info['dbnumber'] + 1
    elif data['object'] == "keylogs":
        keylogs = await db_objects.execute(Keylog.select().where(Keylog.operation == operation))
        for k in keylogs:
            await db_objects.delete(k, recursive=True)
            deleted_obj_info['dbnumber'] = deleted_obj_info['dbnumber'] + 1
    elif data['object'] == "credentials":
        credentials = await db_objects.execute(Credential.select().where(Credential.operation == operation))
        for c in credentials:
            await db_objects.delete(c, recursive=True)
            deleted_obj_info['dbnumber'] = deleted_obj_info['dbnumber'] + 1
    elif data['object'] == "tasks":
        callbacks = Callback.select().where(Callback.operation == operation)
        tasks = await db_objects.prefetch(Task.select(), callbacks)
        for t in tasks:
            await db_objects.delete(t, recursive=True)
            deleted_obj_info['dbnumber'] = deleted_obj_info['dbnumber'] + 1
    elif data['object'] == "responses":
        callbacks = Callback.select().where(Callback.operation == operation)
        tasks = Task.select()
        responses = await db_objects.prefetch(Response.select(), tasks, callbacks)
        for r in responses:
            await db_objects.delete(r, recursive=True)
            deleted_obj_info['dbnumber'] = deleted_obj_info['dbnumber'] + 1
    return json({"status": "success", 'stats': deleted_obj_info})