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()})
async def ws_payloads_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 "newpayload";') # BEFORE WE START GETTING NEW THINGS, UPDATE WITH ALL OF THE OLD DATA if user['current_operation'] != "": operation = await db_objects.get(Operation, name=user['current_operation']) payloads = await db_objects.execute(Payload.select().where(Payload.operation == operation).order_by(Payload.id)) for p in payloads: await ws.send(js.dumps(p.to_json())) 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) p = await db_objects.get(Payload, id=id) if p.operation == operation: await ws.send(js.dumps(p.to_json())) 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) return finally: pool.close()
async def get_all_payloads_current_operation(request, user): if user['current_operation'] != "": operation = await db_objects.get(Operation, name=user['current_operation']) payloads = await db_objects.execute(Payload.select().where(Payload.operation == operation)) return json([p.to_json() for p in payloads]) else: return json({"status": "error", 'error': 'must be part of a current operation'})
async def analytics_payload_tree_api(request, user): # each payload is the root of a tree, all of the corresponding callbacks that use it are under that tree dbpayloads = await db_objects.execute(Payload.select()) display_config = {} display_config['inactive'] = False # by default, include only active callbacks display_config['strikethrough'] = False if request.method == 'POST': data = request.json if 'inactive' in data: display_config['inactive'] = data['inactive'] if 'strikethrough' in data: display_config['strikethrough'] = data['strikethrough'] tree = [] for p in dbpayloads: display = await analytics_payload_tree_api_function(p, display_config) ptree = Node(str(p.id), display=display) # now get all callbacks that have this payload tied to it if display_config['inactive']: # we want to display the inactive ones as well using_callbacks = await db_objects.execute(Callback.select().where(Callback.registered_payload==p)) else: using_callbacks = await db_objects.execute(Callback.select().where( (Callback.registered_payload==p) & (Callback.active == True))) tree.append(ptree) for c in using_callbacks: # each of these callbacks has ptree as an associated payload callback_display = await analytics_callback_tree_api_function(c.to_json(), display_config) Node(str(c.id), parent=ptree, display=callback_display) output = "" for t in tree: # this is iterating over each payload-based tree output += str(RenderTree(t, style=DoubleStyle).by_attr("display")) + "\n" return text(output)
async def get_all_payloads(request, user): if user['current_operation'] != "": operation = await db_objects.get(Operation, name=user['current_operation']) payloads = await db_objects.execute( Payload.select().where(Payload.operation == operation)) return json([p.to_json() for p in payloads])
async def get_all_payloads(request, user): if user['admin']: payloads = await db_objects.execute(Payload.select()) return json([p.to_json() for p in payloads]) else: return json({ "status": "error", 'error': 'Must be an admin to see all payloads' })
async def get_payloads_by_type(request, ptype, user): payload_type = unquote_plus(ptype) try: payloadtype = await db_objects.get(PayloadType, ptype=payload_type) except Exception as e: return json({'status': 'error', 'error': 'failed to find payload type'}) if user['current_operation'] != "": operation = await db_objects.get(Operation, name=user['current_operation']) else: return json({'status': 'error', 'error': 'must be part of an active operation'}) payloads = await db_objects.execute(Payload.select().where((Payload.operation == operation) & (Payload.payload_type == payloadtype))) payloads_json = [p.to_json() for p in payloads] return json({'status': 'success', "payloads": payloads_json})
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'] }
async def get_all_payloads(request): payloads = await db_objects.execute(Payload.select()) return json([p.to_json() for p in payloads])
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})