def slack_oauth_callback(): # security: It's not possible to get here Access token. (This requests comes from users browser after redirect from # Slack. Refresh token should be in cookies, but that might make problems with API calls. It's dificult to say what # should be the correct behaviour. For now I'll lock it down so that the following scenario is not possible. # Scenario: # - Attacker generates URL using endpoint slack_redirect_to_oauth. He sends it to victim. # - Victim fills out the Slack authorization form and submits it. # - Attacker gets the access because he is the one who initiated the request. # Now replace the work Attacker with Employee and it sounds like legit scenario. # Current behaviour: The slack_redirect_to_oauth and slack_oauth_callback need to be initiated by the same user. # The slack_oauth_callback expects refresh token in cookie, can be disabled in config. user_id = None if SlackConfig.check_refresh_cookie_on_callback_endpoint: user_id = authentication_utils.get_user_id_from_jwt_or_exception() auth_code = request.args['code'] db_code = request.args['state'] db_code_valid, res_or_error_msg = randomCodes.validate_code( db_code, randomCodes.ActivityType.SLACK, user_id) if not db_code_valid: return res_or_error_msg, 400 res: db_models.TmpRandomCodes = res_or_error_msg import app.utils.notifications.slack_add_connection as notifications_slack ok = notifications_slack.validate_code_and_save(auth_code, res.user_id) if ok: return 'OK. Window will close in 2 seconds. <script>setTimeout(function(){ close() }, 2000);</script>', 200 return 'fail', 500
def api_target_by_id(target_id: int): user_id = authentication_utils.get_user_id_from_jwt_or_exception() target = actions.get_target_from_id_if_user_can_see(target_id, user_id) if target is None: return "Target either doesn't exist or you're allowed to see it.", 400 if request.method == 'DELETE': scan_order: db_models.ScanOrder = db_utils_advanced.generic_get_create_edit_from_data( db_schemas.ScanOrderSchema, { "target_id": target.id, "user_id": user_id }, get_only=True) scan_order.active = False db_models.db.session.commit() db_utils.actions_on_modification(scan_order) scan_order = db_utils_advanced.generic_get_create_edit_from_data( db_schemas.ScanOrderSchema, { "target_id": target.id, "user_id": user_id }, get_only=True) notifications = get_effective_notification_settings(user_id, target_id) return jsonify( actions.full_target_settings_to_dict(target, scan_order, notifications))
def api_resend_validation_email(): user_id = authentication_utils.get_user_id_from_jwt_or_exception() REQUEST_ERROR_MSG = "Request failed. Possible reasons:\n" \ "- Validation email to this email address was send less then 1 minute ago.\n"\ "- User did not register this email address, so there is nothing to validate.\n" email_to_resend_validation_email_to = json.loads(request.data).get( "email", "").strip() if len(email_to_resend_validation_email_to) == 0: return "No email argument provided. Aborting.", 400 res = db_models.db.session \ .query(db_models.TmpRandomCodes) \ .filter(db_models.TmpRandomCodes.user_id == user_id) \ .filter(db_models.TmpRandomCodes.activity == randomCodes.ActivityType.MAIL_VALIDATION.name) \ .filter(db_models.TmpRandomCodes.timestamp > datetime_to_timestamp(time_source.time() - datetime.timedelta(minutes=1))) \ .all() if res is not None: for x in res: if x.params == email_to_resend_validation_email_to: return REQUEST_ERROR_MSG, 400 res = db_models.db.session \ .query(db_models.MailConnections) \ .filter(db_models.MailConnections.user_id == user_id) \ .filter(db_models.MailConnections.email == email_to_resend_validation_email_to) \ .first() if res is None: return REQUEST_ERROR_MSG, 400 send_mail_validation(user_id, email_to_resend_validation_email_to) return f'ok', 200
def api_get_basic_cert_info_for_target(target_id): user_id = authentication_utils.get_user_id_from_jwt_or_exception() last_scan, scan_result = actions.get_last_scan_and_result( target_id, user_id) last_scan: db_models.LastScan scan_result: db_models.ScanResults if scan_result is None: return "Target either doesn't exist or the current user doesn't have permission to view it.", 401 last_scanned_datetime = timestamp_to_datetime(last_scan.last_scanned) cert_info = scan_result.certificate_information verified_chain = cert_info.verified_certificate_chain_list certificates_in_chain: List[ db_models.Certificate] = db_models.Certificate.select_from_list( verified_chain.chain) list_cert = certificates_in_chain[0] return { 'chain_notBefore': max([x.notBefore for x in certificates_in_chain]), 'chain_notAfter': min([x.notAfter for x in certificates_in_chain]), 'leaf_sni': list_cert.subject_alternative_name_list, 'leaf_subject': list_cert.subject, 'information_fetched_on': last_scanned_datetime }, 200
def api_scan_result_history_without_certs(user_id=None, x_days=30): if user_id is None: user_id = authentication_utils.get_user_id_from_jwt_or_exception() res = actions.get_scan_history(user_id, x_days) if res is None: return "[]", 200 server_info_schema = db_schemas.ServerInfoSchemaWithoutCiphers() res_dict = {} for x in res: try: res_dict[x.ScanResultsHistory.id] = { "timestamp": x.ScanResultsHistory.timestamp, "server_info": server_info_schema.dump(x.ServerInfo), "target_id": x.Target.id, "scan_result_id": x.ScanResultsSimplified.scanresult_id if x.ScanResultsSimplified else None, } except Exception as e: logger.error(f"{x} | {e}") raise return jsonify(res_dict)
def api_get_users_certificate_chains(user_id=None, x_days=30): if user_id is None: user_id = authentication_utils.get_user_id_from_jwt_or_exception() res = actions.get_certificate_chains(user_id, x_days) res_dicts: List[dict] = db_schemas.CertificateChainSchemaWithoutCertificates().dump(res, many=True) res_dict_of_dicts = db_schemas.convert_arr_of_dicts_to_dict_of_dicts(res_dicts) return jsonify(res_dict_of_dicts)
def api_list_domain_monitoring(user_id: Optional[int] = None): if user_id is None: user_id = authentication_utils.get_user_id_from_jwt_or_exception() res = db_models.db.session.query(db_models.SubdomainRescanTarget).\ filter(db_models.SubdomainRescanTarget.subdomain_scan_user_id == user_id).all() res_dict = db_schemas.SubdomainRescanTargetSchema().dump(res, many=True) return jsonify(res_dict)
def api_mail_add_or_delete(): # this can add multiple emails at once user_id = authentication_utils.get_user_id_from_jwt_or_exception() if request.method == "POST": msg, status_code = mail_add(user_id, request.json.get('emails', "")) if request.method == "DELETE": msg, status_code = mail_delete(user_id, request.json.get('emails', "")) return msg, status_code
def api_notification_settings(user_id=None, target_id=None): if user_id is None: user_id = authentication_utils.get_user_id_from_jwt_or_exception() if target_id is not None and not actions.can_user_get_target_definition_by_id( target_id, user_id): return "Target either doesn't exist or user is not allowed to see it.", 401 connection_lists = get_effective_notification_settings(user_id, target_id) return jsonify(connection_lists)
def slack_url_to_oauth(): user_id = authentication_utils.get_user_id_from_jwt_or_exception() db_code = randomCodes.create_and_save_random_code( activity=randomCodes.ActivityType.SLACK, user_id=user_id, expire_in_n_minutes=10) import app.utils.notifications.slack_add_connection url = f'{app.utils.notifications.slack_add_connection.slack_endpoint_url()}&state={db_code}' return url, 200
def api_get_user_targets(): user_id = authentication_utils.get_user_id_from_jwt_or_exception() # todo: the following search only looks at targets, which have scan result. This might be considered a bug. Fix? res = db_models.db.session \ .query(db_models.ScanOrder, db_models.Target, db_models.LastScan, db_models.ScanResults, db_models.ScanResultsSimplified) \ .outerjoin(db_models.ScanResults, db_models.LastScan.result_id == db_models.ScanResults.id) \ .outerjoin(db_models.ScanResultsSimplified, db_models.ScanResultsSimplified.scanresult_id == db_models.ScanResults.id) \ .filter(db_models.LastScan.target_id == db_models.Target.id) \ .filter(db_models.ScanOrder.target_id == db_models.Target.id) \ .filter(db_models.ScanOrder.user_id == user_id) \ .all() # res: List[Tuple[db_models.ScanOrder, db_models.Target, db_models.LastScan, db_models.ScanResults]] schema = db_schemas.TargetSchema(many=True) json_dict = schema.dump([x.Target for x in res]) for obj in json_dict: for single_res in res: if obj["id"] == single_res.Target.id: obj["active"] = 'yes' if single_res.ScanOrder.active else 'no' obj["expires"] = "Not scanned yet" obj["grade"] = "Not scanned yet" if single_res.ScanResults is None: continue if single_res.ScanResultsSimplified: scan_result_simplified = single_res.ScanResultsSimplified else: scan_result_simplified = sslyze_result_simplify.sslyze_result_simplify( single_res.ScanResults) # todo: consider saving the simplified result if scan_result_simplified: if isinstance(single_res.ScanResultsSimplified.notAfter, int): obj["expires"] = str( timestamp_to_datetime( single_res.ScanResultsSimplified.notAfter)) obj["grade"] = single_res.ScanResultsSimplified.grade obj["grade_reasons"] = single_res.ScanResultsSimplified.grade_reasons continue # for x in json_dict: # x["grade"] = random.choice([chr(ord('A')+i) for i in range(5)]) # x["expires"] = datetime.date(2020, 1, 1) + datetime.timedelta(days=random.randint(10, 500)) json_string = json.dumps(json_dict, default=str) # logger.debug(json_string) return json_string, 200
def api_get_user_profile(): user_id = authentication_utils.get_user_id_from_jwt_or_exception() res: db_models.User = db_models.db.session \ .query(db_models.User) \ .get(user_id) return jsons.dumps({ "username": res.username, "main_api_key": res.main_api_key, "email": res.email }), 200
def api_get_users_certificates(user_id=None, x_days=30): if user_id is None: user_id = authentication_utils.get_user_id_from_jwt_or_exception() # logger.debug("Start getting certificate chains") res_chains = actions.get_certificate_chains(user_id, x_days) # logger.debug("Start getting certificates") res_certs = actions.get_certificates(res_chains) # logger.debug("Start serializing certificates") res_dicts: List[dict] = db_schemas.CertificateSchema().dump(res_certs, many=True) res_dict_of_dicts = db_schemas.convert_arr_of_dicts_to_dict_of_dicts(res_dicts) return jsonify(res_dict_of_dicts)
def api_set_notification_settings_raw(user_id: Optional[int] = None, target_id: Optional[int] = None): if user_id is None: user_id = authentication_utils.get_user_id_from_jwt_or_exception() if target_id is not None and not actions.can_user_get_target_definition_by_id( target_id, user_id): return "Target either doesn't exist or user is not allowed to see it.", 401 data = json.loads(request.data) ok = set_notification_settings_raw_single_target(user_id, target_id, data) if ok: return api_notification_settings_raw(user_id, target_id) return "fail", 400
def api_get_users_scan_results_simplified(user_id=None, x_days=30): if user_id is None: user_id = authentication_utils.get_user_id_from_jwt_or_exception() res = actions.get_scan_history(user_id, x_days) if res is None: return "[]", 200 scan_results_simplified = list(map(lambda x: x.ScanResultsSimplified, res)) scan_results_simplified2 = list(filter(lambda x: x, scan_results_simplified)) res2: List[dict] = db_schemas.ScanResultsSimplifiedWithoutCertsSchema().dump(scan_results_simplified2, many=True) res_dict_of_dicts = db_schemas.convert_arr_of_dicts_to_dict_of_dicts(res2) return jsonify(res_dict_of_dicts)
def api_slack_connection_delete(team_id: str = None, channel_id: str = None): user_id = authentication_utils.get_user_id_from_jwt_or_exception() slack_connection: db_models.SlackConnections = db_utils_advanced.generic_get_create_edit_from_data( db_schemas.SlackConnectionsSchema, {"team_id": team_id, "channel_id": channel_id, "user_id": user_id}, get_only=True ) if slack_connection: db_models.db.session.delete(slack_connection) db_models.db.session.commit() return "1 deleted", 200 return "0 deleted", 200
def api_target(): user_id = authentication_utils.get_user_id_from_jwt_or_exception() data = json.loads(request.data) data["target"]["protocol"] = data.get("protocol", "HTTPS").replace( "TlsWrappedProtocolEnum.", "") # todo: remove this hack data["target"].pop("id", None) target_hostnames = data["target"]["hostname"].split(";") target_hostnames = list(map(lambda x: x.strip(), target_hostnames)) target_hostnames = list(filter(lambda x: len(x), target_hostnames)) target_hostnames = list(set(target_hostnames)) target_ids = add_targets(target_hostnames, user_id, data) return f'Inserted {len(target_ids)} targets', 200
def api_enable_target_scan(target_id: int): user_id = authentication_utils.get_user_id_from_jwt_or_exception() target = actions.get_target_from_id_if_user_can_see(target_id, user_id) if target is None: return "Target either doesn't exist or you're allowed to see it.", 400 scan_order: db_models.ScanOrder = db_utils_advanced.generic_get_create_edit_from_data( db_schemas.ScanOrderSchema, { "target_id": target.id, "user_id": user_id }, get_only=True) scan_order.active = True db_models.db.session.commit() db_utils.actions_on_modification(scan_order) return "ok", 200
def api_channel_connection_delete(channel_name: str, channel_id: int): user_id = authentication_utils.get_user_id_from_jwt_or_exception() try: channel_db_model = CONNECTION_DB_MODELS_TYPES[channel_name] except KeyError: return "This channel doesn't exist.", 400 existing_connection = db_models.db.session \ .query(channel_db_model) \ .filter(channel_db_model.user_id == user_id) \ .filter(channel_db_model.id == channel_id) \ .first() if existing_connection: db_models.db.session.delete(existing_connection) db_models.db.session.commit() return 'ok', 200
def api_notification_settings_raw(user_id=None, target_id=None): if user_id is None: user_id = authentication_utils.get_user_id_from_jwt_or_exception() if target_id is not None and not actions.can_user_get_target_definition_by_id( target_id, user_id): return "Target either doesn't exist or user is not allowed to see it.", 401 res = db_models.db.session \ .query(db_models.ConnectionStatusOverrides) \ .filter(db_models.ConnectionStatusOverrides.user_id == user_id) \ .filter(db_models.ConnectionStatusOverrides.target_id == target_id) \ .first() pref = res.preferences if res else "" res2 = load_preferences_from_string(pref) return jsons.dumps(res2), 200
def api_scan_results_history_v2(user_id=None, x_days=30): if user_id is None: user_id = authentication_utils.get_user_id_from_jwt_or_exception() logger.debug("before API requests") a = api_scan_result_history_without_certs(user_id, x_days).json b = api_get_users_scan_results_simplified(user_id, x_days).json c = api_get_users_certificate_chains(user_id, x_days).json d = api_get_users_certificates(user_id, x_days).json e = get_user_targets_only(user_id) logger.debug("after API requests") new_res = convert_scan_results_to_v1(a, b, c, d, e) new_res_2 = sorted(new_res, key=lambda x: x["timestamp"]) logger.debug("after conversion of scan_results for backwards compatibility") # return json.dumps(sorted(new_res, key=lambda x: x["timestamp"]), indent=4, sort_keys=True), 200 return jsonify(new_res_2)
def get_target_id(target_def=None): if target_def: data = target_def else: data = json.loads(request.data) # logger.warning(data) data["protocol"] = data.get("protocol", "HTTPS").replace("TlsWrappedProtocolEnum.", "") # todo: remove this hack target = db_utils_advanced.generic_get_create_edit_from_data( db_schemas.TargetSchema, data, get_only=True) if not target: return "fail", 400 user_id = authentication_utils.get_user_id_from_jwt_or_exception() # validate that the user entered the target definition at least once. Protection against enumaration attack. if not actions.can_user_get_target_definition_by_id(target.id, user_id): return "fail", 400 return jsonify({"id": target.id}), 200
def api_get_result_for_target(target_id): user_id = authentication_utils.get_user_id_from_jwt_or_exception() res_or_none = actions.get_last_scan_and_result(target_id, user_id) if res_or_none is None: return "Target either doesn't exist or the current user doesn't have permission to view it.", 401 last_scan, scan_result = res_or_none last_scan: db_models.LastScan scan_result: db_models.ScanResults last_scanned = last_scan.last_scanned last_scanned_datetime = timestamp_to_datetime(last_scanned) scan_result_str = db_schemas.ScanResultsSchema().dumps(scan_result) return jsonify({ 'result': json.loads(scan_result_str), 'time': last_scanned_datetime }), 200
def mail_validate(db_code): # security: using the same trick as above, i.e. requiring valid refresh cookie. todo: maybe reconsider? user_id = authentication_utils.get_user_id_from_jwt_or_exception() db_code_valid, res_or_error_msg = randomCodes.validate_code(db_code, randomCodes.ActivityType.MAIL_VALIDATION, user_id) if not db_code_valid: return res_or_error_msg, 400 res: db_models.TmpRandomCodes = res_or_error_msg user_id_from_code = res.user_id validated_email = res.params mail_connection: db_models.MailConnections = db_utils_advanced.generic_get_create_edit_from_data( db_schemas.MailConnectionsSchema, {"email": validated_email, "user_id": user_id_from_code}, get_only=True ) if mail_connection is None: return "fail", 500 mail_connection.validated = True db_models.db.session.delete(res) db_models.db.session.commit() return 'ok', 200
def api_scan_result_history_choose_schema(user_id=None, x_days=30, schema_simplified=None): if user_id is None: user_id = authentication_utils.get_user_id_from_jwt_or_exception() res = actions.get_scan_history(user_id, x_days) if res is None: return "[]", 200 logger.debug("START serialization") schema_target = db_schemas.TargetSchema() if schema_simplified is None: schema_simplified = db_schemas.ScanResultsSimplifiedSchema() res_arr = [] for x in res: new_dict = { "timestamp": None, "target": None, "result_simplified": None, } if x.ScanResultsHistory: new_dict["timestamp"] = x.ScanResultsHistory.timestamp new_dict["target"] = schema_target.dump(x.Target) new_dict["result_simplified"] = schema_simplified.dump( x.ScanResultsSimplified) res_arr.append(new_dict) # logger.debug("MID serialization") res_arr_sorted = sorted(res_arr, key=lambda x: x["timestamp"]) # ret = json.dumps(res_arr_sorted), indent=3, sort_keys=True) # logger.debug("END serialization") # return ret, 200 return jsonify(res_arr_sorted)
def api_change_password(): if not request.is_json: return jsonify({"msg": "Missing JSON in request"}), 400 user_id = authentication_utils.get_user_id_from_jwt_or_exception() old_password = request.json.get('old_password', None) new_password = request.json.get('new_password', None) res: db_models.User = db_models.db.session \ .query(db_models.User) \ .get(user_id) login_msg, login_status_code = action_login(res.username, old_password) if login_status_code != 200: return login_msg, login_status_code if new_password is None or len(new_password) == 0: return jsonify({ "msg": "Missing new password parameter." }), 400 # todo: consider concatenating with other error msgs change_ok = authentication_utils.set_user_password(res.id, new_password) return "ok" if change_ok else "fail", 200 if change_ok else 400
def show_notification_connections(target_id=None): user_id = authentication_utils.get_user_id_from_jwt_or_exception() connection_lists = get_effective_notification_settings(user_id, target_id) return jsonify(connection_lists)
def api_remove_subdomain_monitoring(target_id: int): user_id = authentication_utils.get_user_id_from_jwt_or_exception() return remove_subdomain_monitoring(target_id, user_id)
def api_add_subdomains(target_id: int): user_id = authentication_utils.get_user_id_from_jwt_or_exception() data = request.json return add_subdomains(target_id, user_id, data)
def api_slack_connections_get(): user_id = authentication_utils.get_user_id_from_jwt_or_exception() return jsonify(list_connections_of_type(db_models.SlackConnections, user_id))