├── docs ├── benchmarks │ └── .gitkeep ├── forge-commands.png ├── forge-environment.png ├── forge-launch-terminal.png ├── ssrf.md ├── token-setup-php.md ├── examples │ └── apache-mod-php-wordpress │ │ └── Dockerfile ├── debian10.md ├── laravel-forge.md └── apache-mod-php.md ├── .devcontainer ├── shared │ └── .gitkeep ├── ubuntu │ └── devcontainer.json ├── centos_arm │ └── devcontainer.json └── centos │ └── devcontainer.json ├── tools ├── rpm_uninstall.sh ├── run_tests.sh ├── dpkg_full_build.sh ├── rpm_full_build.sh ├── dpkg_install.sh ├── rpm_install.sh ├── server_tests │ └── php_built_in │ │ └── main.py └── dpkg_build.sh ├── tests ├── server │ ├── test_disable │ │ ├── env.json │ │ ├── start_config.json │ │ └── index.php │ ├── test_include.php │ ├── test_routes │ │ ├── index.php │ │ └── start_config.json │ ├── test_stats │ │ ├── index.php │ │ ├── start_config.json │ │ ├── expect_stats.json │ │ └── test.py │ ├── test_allowed_ip │ │ ├── index.php │ │ ├── env.json │ │ ├── config_allow_private.json │ │ ├── change_config_remove_allowed_ip.json │ │ └── start_config.json │ ├── test_api_spec │ │ ├── index.php │ │ ├── env.json │ │ └── start_config.json │ ├── test_force_protection_off_for_route │ │ ├── env.json │ │ ├── start_config.json │ │ └── change_config_set_force_protection_off.json │ ├── test_geo_blocked_ip │ │ ├── index.php │ │ ├── env.json │ │ └── change_config_remove_geo_blocked_ips.json │ ├── test_routes_limits │ │ ├── index.php │ │ └── start_config.json │ ├── test_tor_blocked_ip │ │ ├── index.php │ │ ├── env.json │ │ └── change_config_remove_tor_blocked_ips.json │ ├── test_tor_monitored_ip │ │ ├── index.php │ │ ├── env.json │ │ └── expect_ip_monitored.json │ ├── test_allowed_ips_lists │ │ ├── index.php │ │ ├── env.json │ │ ├── change_config_remove_allow_list.json │ │ └── start_config.json │ ├── test_blocked_outbound_domains │ │ ├── env.json │ │ ├── blocked_domains_in_heartbeat.json │ │ ├── config_no_blocking.json │ │ └── config_disable_block_new.json │ ├── test_user_agent_blocked │ │ ├── index.php │ │ ├── env.json │ │ └── change_config_remove_tor_blocked_ua.json │ ├── test_user_agent_monitored │ │ ├── index.php │ │ ├── env.json │ │ └── expect_user_agent_monitored.json │ ├── test_force_protection_off_for_wildcard_route │ │ ├── env.json │ │ └── start_config.json │ ├── test_force_protection_off_most_restrictive │ │ ├── env.json │ │ └── start_config.json │ ├── test_received_any_stats_false │ │ ├── index.php │ │ ├── start_config.json │ │ └── test.py │ ├── test_received_any_stats_true │ │ ├── index.php │ │ ├── start_config.json │ │ └── test.py │ ├── test_blocked_ip_from_dummy_config │ │ ├── index.php │ │ └── env.json │ ├── test_bypassed_ip_for_geo_blocking │ │ ├── index.php │ │ └── env.json │ ├── test_ip_rate_limiting_10_minutes │ │ ├── start_env.json │ │ ├── env.json │ │ ├── index.php │ │ └── start_config.json │ ├── test_non_allowed_ip_is_not_blocked_if_bypassed │ │ ├── index.php │ │ ├── env.json │ │ ├── test.py │ │ └── start_config.json │ ├── test_user_rate_limiting_10_minutes │ │ ├── start_env.json │ │ ├── env.json │ │ ├── index.php │ │ └── start_config.json │ ├── test_group_rate_limiting_10_minutes │ │ ├── start_env.json │ │ ├── env.json │ │ ├── index.php │ │ └── start_config.json │ ├── test_user_limits │ │ ├── index.php │ │ └── start_config.json │ ├── test_ip_rate_limiting_10_minutes_for_wildcard_route │ │ ├── start_env.json │ │ ├── env.json │ │ └── index.php │ ├── test_attack_wave_detection │ │ ├── index.php │ │ ├── env.json │ │ ├── start_config.json │ │ ├── expect_wave_detection.json │ │ └── expect_wave_detection_2.json │ ├── test_ssrf_curl │ │ ├── env.json │ │ ├── start_config.json │ │ ├── change_config_disable_blocking.json │ │ ├── index.php │ │ ├── expect_detection_blocked_resolved_ip.json │ │ └── expect_detection_not_blocked.json │ ├── test_domains_limits │ │ ├── env.json │ │ ├── start_config.json │ │ └── index.php │ ├── test_path_traversal │ │ ├── env.json │ │ ├── start_config.json │ │ ├── change_config_disable_blocking.json │ │ ├── index.php │ │ ├── expect_detection_not_blocked.json │ │ ├── expect_detection_blocked.json │ │ └── expect_detection_blocked_php_filter.json │ ├── test_sql_injection │ │ ├── env.json │ │ ├── start_config.json │ │ └── change_config_disable_blocking.json │ ├── test_shell_injection │ │ ├── env.json │ │ ├── start_config.json │ │ ├── change_config_disable_blocking.json │ │ ├── expect_detection_not_blocked.json │ │ └── expect_detection_blocked.json │ ├── test_http_method_override │ │ ├── env.json │ │ ├── start_config.json │ │ ├── expect_method_overrides.json │ │ └── index.php │ ├── test_ssrf_file_get_contents │ │ ├── env.json │ │ ├── start_config.json │ │ ├── change_config_disable_blocking.json │ │ ├── index.php │ │ ├── expect_detection_blocked_resolved_ip.json │ │ └── expect_detection_not_blocked.json │ ├── test_allowed_ip_for_wildcard_route │ │ ├── env.json │ │ └── index.php │ ├── test_allowed_ip_most_restrictive │ │ ├── env.json │ │ └── index.php │ ├── test_bypassed_ip_for_rate_limiting │ │ ├── env.json │ │ ├── index.php │ │ ├── change_config_remove_bypassed_ip.json │ │ └── start_config.json │ ├── test_group_rate_limiting_1_minute │ │ ├── env.json │ │ ├── index.php │ │ └── expect_rate_limiting.json │ ├── test_ip_rate_limiting_1_minute │ │ ├── env.json │ │ ├── index.php │ │ └── expect_rate_limiting.json │ ├── test_path_traversal_with_include │ │ ├── env.json │ │ ├── start_config.json │ │ ├── change_config_disable_blocking.json │ │ ├── index.php │ │ ├── expect_detection_not_blocked.json │ │ └── expect_detection_blocked.json │ ├── test_set_token │ │ ├── index.php │ │ ├── env.json │ │ └── start_config.json │ ├── test_sql_injection_pdostatement │ │ ├── env.json │ │ ├── start_config.json │ │ ├── change_config_disable_blocking.json │ │ ├── index.php │ │ └── expect_detection_not_blocked_pdostatement.json │ ├── test_user_rate_limiting_1_minute │ │ ├── env.json │ │ └── index.php │ ├── test_bypassed_ip_for_attacks │ │ ├── env.json │ │ ├── change_config_remove_bypassed_ip.json │ │ └── start_config.json │ ├── test_max_detection_events_per_timeframe │ │ ├── env.json │ │ ├── start_config.json │ │ ├── index.php │ │ └── test.py │ ├── test_sql_injection_mysqli_obj_query │ │ ├── env.json │ │ ├── start_config.json │ │ └── change_config_disable_blocking.json │ ├── test_blocking_imds_ip_only_if_found_in_input │ │ ├── env.json │ │ ├── start_config.json │ │ ├── expect_detection_blocked.json │ │ ├── index.php │ │ └── test.py │ ├── test_rate_limiting_with_user_on_multiple_ips │ │ ├── env.json │ │ └── index.php │ ├── test_sql_injection_mysqli_obj_multi_query │ │ ├── env.json │ │ ├── start_config.json │ │ └── change_config_disable_blocking.json │ ├── test_sql_injection_mysqli_obj_real_query │ │ ├── env.json │ │ ├── start_config.json │ │ └── change_config_disable_blocking.json │ ├── test_sql_injection_mysqli_procedure_query │ │ ├── env.json │ │ ├── start_config.json │ │ └── change_config_disable_blocking.json │ ├── test_ip_rate_limiting_1_minute_for_wildcard_route │ │ ├── env.json │ │ ├── index.php │ │ └── expect_rate_limiting.json │ ├── test_ip_rate_limiting_most_restrictive_wildcard │ │ ├── env.json │ │ └── index.php │ ├── test_sql_injection_mysqli_procedure_multi_query │ │ ├── env.json │ │ ├── start_config.json │ │ └── change_config_disable_blocking.json │ ├── test_sql_injection_mysqli_procedure_real_query │ │ ├── env.json │ │ ├── start_config.json │ │ └── change_config_disable_blocking.json │ ├── test_detection_with_aikido_core_down_while_running │ │ ├── env.json │ │ ├── start_config.json │ │ └── index.php │ ├── test_group_rate_limiting_1_minute_for_wildcard_route │ │ ├── env.json │ │ ├── index.php │ │ └── expect_rate_limiting.json │ ├── test_group_rate_limiting_most_restrictive_wildcard │ │ ├── env.json │ │ └── index.php │ ├── test_rate_limiting_with_multiple_users_on_the_same_ip │ │ ├── env.json │ │ ├── index.php │ │ └── test.py │ ├── test_user_rate_limiting_1_minute_for_wildcard_route │ │ ├── env.json │ │ └── index.php │ ├── test_ip_rate_limiting_with_aikido_core_down_after_start │ │ ├── env.json │ │ └── index.php │ ├── test_user_set │ │ ├── index.php │ │ ├── expect_user.json │ │ ├── start_config.json │ │ └── test.py │ ├── test_detection_with_aikido_core_down_from_start │ │ ├── env.json │ │ ├── start_config.json │ │ ├── index.php │ │ └── test.py │ ├── test_ssrf_request_to_itself │ │ ├── env.json │ │ └── start_config.json │ ├── test_user_set_without_name │ │ ├── index.php │ │ ├── expect_user.json │ │ ├── start_config.json │ │ └── test.py │ ├── test_domains │ │ ├── start_config.json │ │ └── test.py │ ├── test_big_request │ │ ├── start_config.json │ │ ├── test.py │ │ └── index.php │ ├── test_routes_param_matchers │ │ ├── start_config.json │ │ ├── index.php │ │ └── expect_routes.json │ └── test_user_block │ │ ├── change_config_remove_blocked_user.json │ │ ├── start_config.json │ │ └── index.php └── cli │ ├── aikido_ops │ ├── test_set_rate_limit_group.phpt │ ├── test_set_token_works.phpt │ ├── test_register_param_matcher.phpt │ └── test_set_token_with_invalid_params.phpt │ ├── path_traversal │ ├── test_path_traversal_file.phpt │ ├── test_path_traversal_chdir.phpt │ ├── test_path_traversal_fopen.phpt │ ├── test_path_traversal_mkdir.phpt │ ├── test_path_traversal_rmdir.phpt │ ├── test_path_traversal_touch.phpt │ ├── test_path_traversal_chgrp.phpt │ ├── test_path_traversal_chmod.phpt │ ├── test_path_traversal_chown.phpt │ ├── test_path_traversal_lchgrp.phpt │ ├── test_path_traversal_lchown.phpt │ ├── test_path_traversal_opendir.phpt │ ├── test_path_traversal_scandir.phpt │ ├── test_path_traversal_unlink.phpt │ ├── test_path_traversal_readfile.phpt │ ├── test_path_traversal_readlink.phpt │ ├── test_path_traversal_realpath.phpt │ ├── test_path_traversal_spl_file_info.phpt │ ├── test_path_traversal_parse_ini_file.phpt │ ├── test_path_traversal_file_get_contents.phpt │ ├── test_path_traversal_file_put_contents.phpt │ ├── test_path_traversal_highlight_file.phpt │ ├── test_path_traversal_copy_1.phpt │ ├── test_path_traversal_copy_2.phpt │ ├── test_path_traversal_link_1.phpt │ ├── test_path_traversal_link_2.phpt │ ├── test_path_traversal_rename_2.phpt │ ├── test_path_traversal_spl_file_object.phpt │ ├── test_path_traversal_php_strip_whitespaces.phpt │ ├── test_path_traversal_rename_1.phpt │ ├── test_path_traversal_symlink_1.phpt │ ├── test_path_traversal_symlink_2.phpt │ ├── test_path_traversal_highlight_file_2_args.phpt │ ├── test_path_traversal_move_uploaded_file_1.phpt │ ├── test_path_traversal_move_uploaded_file_2.phpt │ ├── test_path_traversal_file_get_contents_php_filter.phpt │ └── test_path_traversal_file_get_contents_multiple_php_filter.phpt │ ├── shell_injection │ ├── test_passthru.phpt │ ├── test_shell_exec.phpt │ ├── test_exec.phpt │ ├── test_system.phpt │ ├── test_exec_trim.phpt │ ├── test_popen.phpt │ ├── test_exec_in_class.phpt │ └── test_proc_open.phpt │ ├── outgoing_request │ └── test_outgoing_request_file_get_contents.phpt │ ├── ssrf │ ├── test_ssrf_file_get_contents.phpt │ ├── test_ssrf_file_get_contents_case_insensitive.phpt │ ├── test_ssrf_curl_exec.phpt │ ├── test_ssrf_file_get_contents_php_filter.phpt │ └── test_ssrf_file_get_contents_multiple_php_filter.phpt │ ├── shell_execution │ ├── test_shell_execution_json_decode.phpt │ └── test_proc_open_with_command_as_array.phpt │ └── sql_injection │ └── sql_injection_pdo_exec.phpt ├── benchmarks ├── server │ ├── benchmark_request_simple │ │ ├── index.php │ │ ├── env.json │ │ └── start_config.json │ ├── benchmark_request_with_api_discovery_enabled │ │ ├── index.php │ │ ├── env.json │ │ └── start_config.json │ ├── benchmark_request_with_rate_limiting_enabled │ │ ├── index.php │ │ ├── env.json │ │ └── start_config.json │ ├── benchmark_request_with_user_setting │ │ ├── index.php │ │ ├── env.json │ │ └── start_config.json │ └── benchmark.py └── cli │ ├── benchmark_outgoing_request │ ├── php_destroy.php │ ├── php_code_to_test.php │ └── php_setup.php │ ├── benchmark_shell_execution │ └── php_code_to_test.php │ ├── benchmark_sql_query │ ├── php_code_to_test.php │ ├── php_destroy.php │ └── php_setup.php │ └── benchmark_path_access │ └── php_code_to_test.php ├── lib ├── agent │ ├── api_discovery │ │ ├── getDataSchema.go │ │ └── isPrimitiveType.go │ ├── aikido_types │ │ ├── handle.go │ │ └── route.go │ ├── config │ │ └── config.go │ ├── grpc │ │ └── domain.go │ ├── go.mod │ ├── cloud │ │ ├── event_started.go │ │ └── cloud.go │ └── rate_limiting │ │ └── rate_limiting.go ├── php-extension │ ├── include │ │ ├── Packages.h │ │ ├── HandleFileCompilation.h │ │ ├── HandleUrls.h │ │ ├── HandleShellExecution.h │ │ ├── Environment.h │ │ ├── GoWrappers.h │ │ ├── HandleSetToken.h │ │ ├── HandleQueries.h │ │ ├── HandleSetRateLimitGroup.h │ │ ├── HookAst.h │ │ ├── HandleRegisterParamMatcher.h │ │ ├── HandleUsers.h │ │ ├── PhpLifecycle.h │ │ ├── HandleBypassedIp.h │ │ ├── Server.h │ │ ├── HandleShouldBlockRequest.h │ │ ├── Agent.h │ │ ├── HandlePathAccess.h │ │ ├── PhpWrappers.h │ │ └── Stats.h │ ├── Cache.cpp │ ├── config.w32 │ ├── HandleSetToken.cpp │ ├── HandleBypassedIp.cpp │ └── HandleRateLimitGroup.cpp └── request-processor │ ├── handle_rate_limit_group_event.go │ ├── aikido_types │ ├── server.go │ ├── route.go │ └── handle.go │ ├── vulnerabilities │ ├── ssrf │ │ └── getMetadataForSSRFAttack.go │ ├── web-scanner │ │ ├── isWebScanMethod.go │ │ ├── isWebScanner.go │ │ └── paths │ │ │ └── directoryNames.go │ ├── path-traversal │ │ └── containsUnsafePathParts.go │ └── shell-injection │ │ └── checkContextForShellInjection.go │ ├── helpers │ ├── escapeStringRegexp.go │ ├── tryParseURL.go │ ├── getPortFromURL.go │ ├── tryParseURL_test.go │ ├── parseJson.go │ ├── trimInvisible_test.go │ ├── getPortFromURL_test.go │ ├── mapIPv4ToIPv6_test.go │ ├── mapIPv4ToIPv6.go │ ├── getHostnameAndPortFromURL_test.go │ ├── tryDecodeAsJWT.go │ └── resolveHostname.go │ ├── context │ └── data_sources.go │ ├── handle_user_event.go │ ├── go.mod │ ├── handle_shell_execution.go │ ├── api_discovery │ └── getApiInfo_test.go │ ├── handle_sql_queries.go │ └── handle_path_traversal.go ├── package └── rpm │ ├── opt │ └── aikido │ │ └── aikido.ini │ └── backup │ ├── aikido.pp │ └── aikido.te ├── .aikido ├── .clang-format ├── sample-apps └── sqlite-php-server │ └── docker-compose.yml ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── SECURITY.md └── workflows │ └── Dockerfile.build-libs └── .vscode └── c_cpp_properties.json /docs/benchmarks/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.devcontainer/shared/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tools/rpm_uninstall.sh: -------------------------------------------------------------------------------- 1 | rpm -e aikido-php-firewall -------------------------------------------------------------------------------- /tools/run_tests.sh: -------------------------------------------------------------------------------- 1 | php lib/php-extension/run-tests.php ./tests/cli -------------------------------------------------------------------------------- /tests/server/test_disable/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_DISABLE": "1" 3 | } -------------------------------------------------------------------------------- /tests/server/test_include.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/server/test_routes/index.php: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /tests/server/test_stats/index.php: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /benchmarks/server/benchmark_request_simple/index.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /tests/server/test_allowed_ip/index.php: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /tests/server/test_api_spec/index.php: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /tests/server/test_force_protection_off_for_route/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "0" 3 | } -------------------------------------------------------------------------------- /tests/server/test_geo_blocked_ip/index.php: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /tests/server/test_routes_limits/index.php: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /tests/server/test_tor_blocked_ip/index.php: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /tests/server/test_tor_monitored_ip/index.php: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /benchmarks/cli/benchmark_outgoing_request/php_destroy.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /benchmarks/cli/benchmark_shell_execution/php_code_to_test.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/agent/api_discovery/getDataSchema.go: -------------------------------------------------------------------------------- 1 | ../../request-processor/api_discovery/getDataSchema.go -------------------------------------------------------------------------------- /tests/server/test_allowed_ips_lists/index.php: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /tests/server/test_blocked_outbound_domains/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1" 3 | } 4 | 5 | -------------------------------------------------------------------------------- /tests/server/test_user_agent_blocked/index.php: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /tests/server/test_user_agent_monitored/index.php: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /benchmarks/cli/benchmark_outgoing_request/php_code_to_test.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/server/test_force_protection_off_for_wildcard_route/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "0" 3 | } -------------------------------------------------------------------------------- /tests/server/test_force_protection_off_most_restrictive/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "0" 3 | } -------------------------------------------------------------------------------- /tests/server/test_received_any_stats_false/index.php: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /tests/server/test_received_any_stats_true/index.php: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /benchmarks/server/benchmark_request_with_api_discovery_enabled/index.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /benchmarks/server/benchmark_request_with_rate_limiting_enabled/index.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /docs/forge-commands.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AikidoSec/firewall-php/HEAD/docs/forge-commands.png -------------------------------------------------------------------------------- /tests/server/test_blocked_ip_from_dummy_config/index.php: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /tests/server/test_bypassed_ip_for_geo_blocking/index.php: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /docs/forge-environment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AikidoSec/firewall-php/HEAD/docs/forge-environment.png -------------------------------------------------------------------------------- /package/rpm/opt/aikido/aikido.ini: -------------------------------------------------------------------------------- 1 | ; configuration for php aikido module 2 | ; priority=20 3 | extension=aikido.so -------------------------------------------------------------------------------- /docs/forge-launch-terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AikidoSec/firewall-php/HEAD/docs/forge-launch-terminal.png -------------------------------------------------------------------------------- /lib/php-extension/include/Packages.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | unordered_map GetPackages(); 4 | -------------------------------------------------------------------------------- /package/rpm/backup/aikido.pp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AikidoSec/firewall-php/HEAD/package/rpm/backup/aikido.pp -------------------------------------------------------------------------------- /tests/server/test_ip_rate_limiting_10_minutes/start_env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": 0 3 | } -------------------------------------------------------------------------------- /tests/server/test_non_allowed_ip_is_not_blocked_if_bypassed/index.php: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /tests/server/test_user_rate_limiting_10_minutes/start_env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": 0 3 | } -------------------------------------------------------------------------------- /tools/dpkg_full_build.sh: -------------------------------------------------------------------------------- 1 | dpkg --purge aikido-php-firewall 2 | ./tools/dpkg_build.sh && ./tools/dpkg_install.sh 3 | -------------------------------------------------------------------------------- /benchmarks/cli/benchmark_sql_query/php_code_to_test.php: -------------------------------------------------------------------------------- 1 | query("SELECT * FROM users WHERE id = 3"); 3 | ?> -------------------------------------------------------------------------------- /tests/server/test_group_rate_limiting_10_minutes/start_env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": 0 3 | } -------------------------------------------------------------------------------- /tests/server/test_user_limits/index.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /.aikido: -------------------------------------------------------------------------------- 1 | exclude: 2 | paths: 3 | - benchmarks/ 4 | - tests/ 5 | - docs/ 6 | - tools/benchmark_report.py 7 | -------------------------------------------------------------------------------- /tools/rpm_full_build.sh: -------------------------------------------------------------------------------- 1 | rpm -e aikido-php-firewall 2 | ./tools/build.sh && ./tools/rpm_build.sh && ./tools/rpm_install.sh 3 | -------------------------------------------------------------------------------- /benchmarks/cli/benchmark_sql_query/php_destroy.php: -------------------------------------------------------------------------------- 1 | $pdo = null; 2 | 3 | if (file_exists($dbFile)) { 4 | unlink($dbFile); 5 | } -------------------------------------------------------------------------------- /tests/server/test_ip_rate_limiting_10_minutes_for_wildcard_route/start_env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": 0 3 | } -------------------------------------------------------------------------------- /tests/server/test_attack_wave_detection/index.php: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /benchmarks/cli/benchmark_path_access/php_code_to_test.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/php-extension/include/HandleFileCompilation.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | zend_op_array* handle_file_compilation(zend_file_handle *file_handle, int type); 4 | -------------------------------------------------------------------------------- /lib/php-extension/include/HandleUrls.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | AIKIDO_HANDLER_FUNCTION(handle_pre_curl_exec); 4 | AIKIDO_HANDLER_FUNCTION(handle_post_curl_exec); 5 | -------------------------------------------------------------------------------- /tests/server/test_api_spec/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_ssrf_curl/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_allowed_ip/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_domains_limits/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_geo_blocked_ip/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_path_traversal/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_sql_injection/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_tor_blocked_ip/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /benchmarks/server/benchmark_request_with_user_setting/index.php: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /tests/server/test_allowed_ips_lists/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_shell_injection/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_tor_monitored_ip/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_user_agent_blocked/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /benchmarks/server/benchmark_request_simple/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_attack_wave_detection/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_http_method_override/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_ssrf_file_get_contents/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_user_agent_monitored/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /lib/php-extension/include/HandleShellExecution.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | AIKIDO_HANDLER_FUNCTION(handle_shell_execution); 4 | 5 | AIKIDO_HANDLER_FUNCTION(handle_shell_execution_with_array); 6 | -------------------------------------------------------------------------------- /tests/server/test_allowed_ip_for_wildcard_route/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_allowed_ip_most_restrictive/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_bypassed_ip_for_geo_blocking/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_bypassed_ip_for_rate_limiting/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_group_rate_limiting_1_minute/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_ip_rate_limiting_10_minutes/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_ip_rate_limiting_1_minute/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_path_traversal_with_include/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_set_token/index.php: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /tests/server/test_sql_injection_pdostatement/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_user_rate_limiting_10_minutes/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_user_rate_limiting_1_minute/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_blocked_ip_from_dummy_config/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } 6 | -------------------------------------------------------------------------------- /tests/server/test_bypassed_ip_for_attacks/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } 6 | 7 | -------------------------------------------------------------------------------- /tests/server/test_group_rate_limiting_10_minutes/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_max_detection_events_per_timeframe/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_sql_injection_mysqli_obj_query/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /benchmarks/server/benchmark_request_with_user_setting/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /lib/php-extension/include/Environment.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void LoadEnvironment(); 4 | 5 | bool LoadLaravelEnvFile(); 6 | 7 | bool GetBoolFromString(const std::string& env, bool default_value); 8 | -------------------------------------------------------------------------------- /tests/server/test_blocking_imds_ip_only_if_found_in_input/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_rate_limiting_with_user_on_multiple_ips/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_set_token/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_TOKEN": "", 3 | "AIKIDO_BLOCK": "1", 4 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 5 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 6 | } -------------------------------------------------------------------------------- /tests/server/test_sql_injection_mysqli_obj_multi_query/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_sql_injection_mysqli_obj_real_query/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_sql_injection_mysqli_procedure_query/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /benchmarks/server/benchmark_request_with_api_discovery_enabled/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /benchmarks/server/benchmark_request_with_rate_limiting_enabled/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /lib/agent/aikido_types/handle.go: -------------------------------------------------------------------------------- 1 | package aikido_types 2 | 3 | type HandlerFunction func(map[string]interface{}) string 4 | 5 | type Method struct { 6 | ClassName string 7 | MethodName string 8 | } 9 | -------------------------------------------------------------------------------- /tests/server/test_ip_rate_limiting_1_minute_for_wildcard_route/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_ip_rate_limiting_most_restrictive_wildcard/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_non_allowed_ip_is_not_blocked_if_bypassed/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_sql_injection_mysqli_procedure_multi_query/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_sql_injection_mysqli_procedure_real_query/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_detection_with_aikido_core_down_while_running/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_group_rate_limiting_1_minute_for_wildcard_route/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_group_rate_limiting_most_restrictive_wildcard/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_ip_rate_limiting_10_minutes_for_wildcard_route/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_rate_limiting_with_multiple_users_on_the_same_ip/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tests/server/test_user_rate_limiting_1_minute_for_wildcard_route/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /tools/dpkg_install.sh: -------------------------------------------------------------------------------- 1 | arch=$(uname -m) 2 | VERSION=$(grep '#define PHP_AIKIDO_VERSION' lib/php-extension/include/php_aikido.h | awk -F'"' '{print $2}') 3 | 4 | dpkg -i ./aikido-php-firewall-$VERSION-1.$arch.deb -------------------------------------------------------------------------------- /tests/server/test_ip_rate_limiting_with_aikido_core_down_after_start/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1" 5 | } -------------------------------------------------------------------------------- /lib/php-extension/include/GoWrappers.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | GoString GoCreateString(const std::string&); 4 | 5 | GoSlice GoCreateSlice(const std::vector& v); 6 | 7 | char* GoContextCallback(int callbackId); 8 | -------------------------------------------------------------------------------- /benchmarks/cli/benchmark_outgoing_request/php_setup.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/server/test_user_set/index.php: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /tests/server/test_detection_with_aikido_core_down_from_start/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_ENDPOINT": "http://some-invalid-endpoint/", 4 | "AIKIDO_REALTIME_ENDPOINT": "http://some-invalid-endpoint/" 5 | } -------------------------------------------------------------------------------- /tests/server/test_ssrf_request_to_itself/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "AIKIDO_BLOCK": "1", 3 | "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT": "0", 4 | "AIKIDO_FEATURE_COLLECT_API_SCHEMA": "1", 5 | "AIKIDO_TRUST_PROXY": "1" 6 | } 7 | 8 | -------------------------------------------------------------------------------- /tests/server/test_user_set_without_name/index.php: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | IndentWidth: 4 3 | ColumnLimit: 0 4 | BraceWrapping: 5 | AfterControlStatement: false 6 | AfterFunction: false 7 | BeforeElse: false 8 | IndentBraces: false 9 | SortIncludes: false 10 | -------------------------------------------------------------------------------- /tests/server/test_user_set/expect_user.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "heartbeat", 3 | "users": [ 4 | { 5 | "id": "12345", 6 | "name": "Tudor" 7 | } 8 | ], 9 | "middlewareInstalled": false 10 | } -------------------------------------------------------------------------------- /tests/server/test_user_set_without_name/expect_user.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "heartbeat", 3 | "users": [ 4 | { 5 | "id": "12345", 6 | "name": "" 7 | } 8 | ], 9 | "middlewareInstalled": false 10 | } -------------------------------------------------------------------------------- /tests/server/test_disable/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": false 9 | } -------------------------------------------------------------------------------- /tests/server/test_domains/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": false 9 | } -------------------------------------------------------------------------------- /tests/server/test_routes/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": false 9 | } -------------------------------------------------------------------------------- /tests/server/test_stats/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": false 9 | } -------------------------------------------------------------------------------- /tests/server/test_tor_monitored_ip/expect_ip_monitored.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "heartbeat", 3 | "users": [ 4 | { 5 | "id": "12345", 6 | "name": "Tudor" 7 | } 8 | ], 9 | "middlewareInstalled": false 10 | } -------------------------------------------------------------------------------- /tests/server/test_api_spec/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": false 9 | } -------------------------------------------------------------------------------- /tests/server/test_set_token/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": false 9 | } -------------------------------------------------------------------------------- /lib/php-extension/include/HandleSetToken.h: -------------------------------------------------------------------------------- 1 | #include "Includes.h" 2 | 3 | ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_aikido_set_token, 0, 1, _IS_BOOL, 0) 4 | ZEND_ARG_TYPE_INFO(0, token, IS_STRING, 0) 5 | ZEND_END_ARG_INFO() 6 | 7 | ZEND_FUNCTION(set_token); 8 | -------------------------------------------------------------------------------- /tests/server/test_routes_limits/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": false 9 | } -------------------------------------------------------------------------------- /lib/php-extension/Cache.cpp: -------------------------------------------------------------------------------- 1 | #include "Includes.h" 2 | 3 | RequestCache requestCache; 4 | EventCache eventCache; 5 | 6 | void RequestCache::Reset() { 7 | *this = RequestCache(); 8 | } 9 | 10 | void EventCache::Reset() { 11 | *this = EventCache(); 12 | } 13 | -------------------------------------------------------------------------------- /benchmarks/server/benchmark_request_simple/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": true 9 | } -------------------------------------------------------------------------------- /lib/php-extension/config.w32: -------------------------------------------------------------------------------- 1 | ARG_ENABLE('aikido', 'aikido support', 'no'); 2 | 3 | if (PHP_AIKIDO != 'no') { 4 | AC_DEFINE('HAVE_AIKIDO', 1, 'aikido support enabled'); 5 | 6 | EXTENSION('aikido', 'aikido.cpp' 'GoWrappers.cpp', null, '/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1'); 7 | } 8 | -------------------------------------------------------------------------------- /lib/php-extension/include/HandleQueries.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | AIKIDO_HANDLER_FUNCTION(handle_pre_pdo_query); 4 | AIKIDO_HANDLER_FUNCTION(handle_pre_pdo_exec); 5 | AIKIDO_HANDLER_FUNCTION(handle_pre_pdostatement_execute); 6 | 7 | AIKIDO_HANDLER_FUNCTION(handle_pre_mysqli_query); 8 | -------------------------------------------------------------------------------- /tests/server/test_force_protection_off_for_route/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": true 9 | } -------------------------------------------------------------------------------- /benchmarks/server/benchmark_request_with_user_setting/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": true 9 | } -------------------------------------------------------------------------------- /tests/server/test_force_protection_off_most_restrictive/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": true 9 | } -------------------------------------------------------------------------------- /lib/php-extension/include/HandleSetRateLimitGroup.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_aikido_set_rate_limit_group, 0, 1, _IS_BOOL, 0) 4 | ZEND_ARG_TYPE_INFO(0, group, IS_STRING, 0) 5 | ZEND_END_ARG_INFO() 6 | 7 | ZEND_FUNCTION(set_rate_limit_group); 8 | -------------------------------------------------------------------------------- /tests/server/test_detection_with_aikido_core_down_from_start/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": false 9 | } -------------------------------------------------------------------------------- /tests/server/test_force_protection_off_for_wildcard_route/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": true 9 | } -------------------------------------------------------------------------------- /tests/server/test_ssrf_curl/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": true, 9 | "block": true 10 | } -------------------------------------------------------------------------------- /tests/server/test_user_set/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 60000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": false, 9 | "block": false 10 | } -------------------------------------------------------------------------------- /benchmarks/server/benchmark_request_with_api_discovery_enabled/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": true 9 | } -------------------------------------------------------------------------------- /tests/server/test_big_request/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": false, 9 | "block": true 10 | } -------------------------------------------------------------------------------- /tests/server/test_detection_with_aikido_core_down_while_running/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": false 9 | } -------------------------------------------------------------------------------- /tests/server/test_domains_limits/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 300000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": true, 9 | "block": false 10 | } -------------------------------------------------------------------------------- /tests/server/test_path_traversal/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": true, 9 | "block": true 10 | } -------------------------------------------------------------------------------- /tests/server/test_shell_injection/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": true, 9 | "block": true 10 | } -------------------------------------------------------------------------------- /tests/server/test_sql_injection/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": true, 9 | "block": true 10 | } -------------------------------------------------------------------------------- /tests/server/test_user_limits/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 60000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": false, 9 | "block": false 10 | } -------------------------------------------------------------------------------- /tests/server/test_routes_param_matchers/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": false 9 | } 10 | 11 | 12 | -------------------------------------------------------------------------------- /tests/server/test_received_any_stats_true/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": true, 9 | "block": false 10 | } -------------------------------------------------------------------------------- /tests/server/test_ssrf_file_get_contents/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": true, 9 | "block": true 10 | } -------------------------------------------------------------------------------- /tests/server/test_user_set_without_name/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 60000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": false, 9 | "block": false 10 | } -------------------------------------------------------------------------------- /tests/server/test_path_traversal_with_include/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": true, 9 | "block": true 10 | } -------------------------------------------------------------------------------- /tests/server/test_received_any_stats_false/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": false, 9 | "block": false 10 | } -------------------------------------------------------------------------------- /tests/server/test_sql_injection_mysqli_obj_query/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": true, 9 | "block": true 10 | } -------------------------------------------------------------------------------- /tests/server/test_sql_injection_pdostatement/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": true, 9 | "block": true 10 | } -------------------------------------------------------------------------------- /tests/server/test_ssrf_curl/change_config_disable_blocking.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": true, 9 | "block": false 10 | } -------------------------------------------------------------------------------- /tests/server/test_max_detection_events_per_timeframe/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": false, 9 | "block": true 10 | } -------------------------------------------------------------------------------- /tests/server/test_path_traversal/change_config_disable_blocking.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": true, 9 | "block": false 10 | } -------------------------------------------------------------------------------- /tests/server/test_shell_injection/change_config_disable_blocking.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": true, 9 | "block": false 10 | } -------------------------------------------------------------------------------- /tests/server/test_sql_injection/change_config_disable_blocking.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": true, 9 | "block": false 10 | } -------------------------------------------------------------------------------- /tests/server/test_sql_injection_mysqli_obj_real_query/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": true, 9 | "block": true 10 | } -------------------------------------------------------------------------------- /tests/server/test_ssrf_request_to_itself/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": true, 9 | "block": true 10 | } 11 | 12 | -------------------------------------------------------------------------------- /tests/server/test_user_block/change_config_remove_blocked_user.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 60000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": false, 9 | "block": false 10 | } -------------------------------------------------------------------------------- /tests/server/test_blocking_imds_ip_only_if_found_in_input/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": true, 9 | "block": true 10 | } -------------------------------------------------------------------------------- /tests/server/test_sql_injection_mysqli_obj_multi_query/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": true, 9 | "block": true 10 | } -------------------------------------------------------------------------------- /tests/server/test_sql_injection_mysqli_procedure_query/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": true, 9 | "block": true 10 | } -------------------------------------------------------------------------------- /tools/rpm_install.sh: -------------------------------------------------------------------------------- 1 | arch=$(uname -m) 2 | VERSION=$(grep '#define PHP_AIKIDO_VERSION' lib/php-extension/include/php_aikido.h | awk -F'"' '{print $2}') 3 | 4 | rpm -Uvh --oldpackage ~/rpmbuild/RPMS/$arch/aikido-php-firewall-$VERSION-1.$arch.rpm 5 | cp ~/rpmbuild/RPMS/$arch/aikido-php-firewall-$VERSION-1.$arch.rpm /shared 6 | -------------------------------------------------------------------------------- /tests/cli/aikido_ops/test_set_rate_limit_group.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test \aikido\set_rate_limit_group 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | 7 | --FILE-- 8 | 13 | 14 | --EXPECT-- 15 | [AIKIDO][INFO] Got rate limit group: my_user_group 16 | -------------------------------------------------------------------------------- /tests/server/test_attack_wave_detection/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": ["5.8.19.24"], 8 | "receivedAnyStats": true, 9 | "blockedIPAddresses": [] 10 | } -------------------------------------------------------------------------------- /tests/server/test_sql_injection_mysqli_procedure_multi_query/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": true, 9 | "block": true 10 | } -------------------------------------------------------------------------------- /tests/server/test_sql_injection_mysqli_procedure_real_query/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": true, 9 | "block": true 10 | } -------------------------------------------------------------------------------- /tests/server/test_sql_injection_pdostatement/change_config_disable_blocking.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": true, 9 | "block": false 10 | } -------------------------------------------------------------------------------- /tests/server/test_ssrf_file_get_contents/change_config_disable_blocking.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": true, 9 | "block": false 10 | } -------------------------------------------------------------------------------- /tests/server/test_user_block/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 60000, 5 | "endpoints": [], 6 | "blockedUserIds": [ 7 | "12345" 8 | ], 9 | "allowedIPAddresses": [], 10 | "receivedAnyStats": false, 11 | "block": false 12 | } -------------------------------------------------------------------------------- /tests/cli/aikido_ops/test_set_token_works.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test \aikido\set_token with a valid token 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | 7 | --FILE-- 8 | 13 | 14 | --EXPECT-- 15 | [AIKIDO][INFO] Token changed to "AIK_RUNTIME_***here" 16 | -------------------------------------------------------------------------------- /tests/server/test_bypassed_ip_for_attacks/change_config_remove_bypassed_ip.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": true, 9 | "block": true 10 | } 11 | -------------------------------------------------------------------------------- /tests/server/test_geo_blocked_ip/change_config_remove_geo_blocked_ips.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "blockedIPAddresses": [], 9 | "receivedAnyStats": true 10 | } -------------------------------------------------------------------------------- /tests/server/test_path_traversal_with_include/change_config_disable_blocking.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": true, 9 | "block": false 10 | } -------------------------------------------------------------------------------- /tests/server/test_sql_injection_mysqli_obj_query/change_config_disable_blocking.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": true, 9 | "block": false 10 | } -------------------------------------------------------------------------------- /tests/server/test_stats/expect_stats.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "heartbeat", 3 | "stats": { 4 | "requests": { 5 | "total": 30, 6 | "aborted": 0, 7 | "attacksDetected": { 8 | "total": 0, 9 | "blocked": 0 10 | } 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /tests/server/test_tor_blocked_ip/change_config_remove_tor_blocked_ips.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "blockedIPAddresses": [], 9 | "receivedAnyStats": true 10 | } -------------------------------------------------------------------------------- /tests/server/test_sql_injection_mysqli_obj_multi_query/change_config_disable_blocking.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": true, 9 | "block": false 10 | } -------------------------------------------------------------------------------- /tests/server/test_sql_injection_mysqli_obj_real_query/change_config_disable_blocking.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": true, 9 | "block": false 10 | } -------------------------------------------------------------------------------- /tests/server/test_sql_injection_mysqli_procedure_query/change_config_disable_blocking.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": true, 9 | "block": false 10 | } -------------------------------------------------------------------------------- /lib/request-processor/handle_rate_limit_group_event.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "main/context" 5 | "main/log" 6 | ) 7 | 8 | func OnRateLimitGroupEvent() string { 9 | context.ContextSetRateLimitGroup() 10 | group := context.GetRateLimitGroup() 11 | log.Infof("Got rate limit group: %s", group) 12 | return "" 13 | } 14 | -------------------------------------------------------------------------------- /tests/server/test_sql_injection_mysqli_procedure_real_query/change_config_disable_blocking.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": true, 9 | "block": false 10 | } -------------------------------------------------------------------------------- /tests/server/test_sql_injection_mysqli_procedure_multi_query/change_config_disable_blocking.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": true, 9 | "block": false 10 | } -------------------------------------------------------------------------------- /lib/php-extension/include/HookAst.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | extern HashTable *global_ast_to_clean; 5 | extern ZEND_API void (*original_ast_process)(zend_ast *ast); 6 | 7 | extern bool checkedAutoBlock; 8 | extern bool checkedShouldBlockRequest; 9 | 10 | void HookAstProcess(); 11 | void UnhookAstProcess(); 12 | void DestroyAstToClean(); 13 | -------------------------------------------------------------------------------- /docs/ssrf.md: -------------------------------------------------------------------------------- 1 | # Server-side request forgery (SSRF) 2 | 3 | Zen for PHP secures your app against server-side request forgery (SSRF) attacks. SSRF vulnerabilities allow attackers to send crafted requests to internal services, bypassing firewalls and security controls. Runtime blocks SSRF attacks by intercepting and validating requests to internal services. 4 | -------------------------------------------------------------------------------- /lib/php-extension/include/HandleRegisterParamMatcher.h: -------------------------------------------------------------------------------- 1 | #include "Includes.h" 2 | 3 | ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_aikido_register_param_matcher, 0, 2, _IS_BOOL, 0) 4 | ZEND_ARG_TYPE_INFO(0, param, IS_STRING, 0) 5 | ZEND_ARG_TYPE_INFO(0, regex, IS_STRING, 0) 6 | ZEND_END_ARG_INFO() 7 | 8 | ZEND_FUNCTION(register_param_matcher); 9 | -------------------------------------------------------------------------------- /lib/request-processor/aikido_types/server.go: -------------------------------------------------------------------------------- 1 | package aikido_types 2 | 3 | import ( 4 | "regexp" 5 | "sync" 6 | ) 7 | 8 | type ServerData struct { 9 | AikidoConfig AikidoConfigData 10 | CloudConfig CloudConfigData 11 | CloudConfigMutex sync.Mutex 12 | MiddlewareInstalled bool 13 | ParamMatchers map[string]*regexp.Regexp 14 | } 15 | -------------------------------------------------------------------------------- /tests/server/test_allowed_ips_lists/change_config_remove_allow_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "lists_allowedIPAddresses": [], 9 | "blockedIPAddresses": [], 10 | "receivedAnyStats": true 11 | } -------------------------------------------------------------------------------- /tests/server/test_user_agent_blocked/change_config_remove_tor_blocked_ua.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "blockedIPAddresses": [], 9 | "blockedUserAgents": "", 10 | "receivedAnyStats": true 11 | } -------------------------------------------------------------------------------- /lib/agent/api_discovery/isPrimitiveType.go: -------------------------------------------------------------------------------- 1 | package api_discovery 2 | 3 | func onlyContainsPrimitiveTypes(types []string) bool { 4 | for _, item := range types { 5 | if !isPrimitiveType(item) { 6 | return false 7 | } 8 | } 9 | return true 10 | } 11 | 12 | func isPrimitiveType(typeStr string) bool { 13 | return typeStr != "object" && typeStr != "array" 14 | } 15 | -------------------------------------------------------------------------------- /lib/php-extension/include/HandleUsers.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | ZEND_FUNCTION(set_user); 4 | 5 | ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_aikido_set_user, 0, 1, _IS_BOOL, 0) 6 | ZEND_ARG_TYPE_INFO(0, id, IS_STRING, 0) 7 | ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 0) 8 | ZEND_END_ARG_INFO() 9 | 10 | bool send_user_event(std::string id, std::string username); 11 | -------------------------------------------------------------------------------- /tests/server/test_domains_limits/index.php: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /.devcontainer/ubuntu/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Ubuntu Dev Container", 3 | "runArgs": [], 4 | "mounts": [ 5 | "source=${localWorkspaceFolder}/.devcontainer/shared,target=/shared,type=bind" 6 | ], 7 | "build": { 8 | "platform": "linux/amd64", 9 | "dockerfile": "Dockerfile", 10 | "args": { 11 | "PHP_VERSION": "8.1" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/server/test_routes_param_matchers/index.php: -------------------------------------------------------------------------------- 1 | 11 | 12 | -------------------------------------------------------------------------------- /lib/request-processor/vulnerabilities/ssrf/getMetadataForSSRFAttack.go: -------------------------------------------------------------------------------- 1 | package ssrf 2 | 3 | import "fmt" 4 | 5 | func getMetadataForSSRFAttack(hostname string, port uint32) map[string]string { 6 | metadata := map[string]string{ 7 | "hostname": hostname, 8 | } 9 | 10 | if port != 0 { 11 | metadata["port"] = fmt.Sprintf("%d", port) 12 | } 13 | 14 | return metadata 15 | } 16 | -------------------------------------------------------------------------------- /tests/server/test_blocked_outbound_domains/blocked_domains_in_heartbeat.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "heartbeat", 3 | "hostnames": [ 4 | { 5 | "hostname": "random2.example.com", 6 | "port": 80, 7 | "hits": 1 8 | }, 9 | { 10 | "hostname": "evil.example.com", 11 | "port": 80 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /lib/request-processor/aikido_types/route.go: -------------------------------------------------------------------------------- 1 | package aikido_types 2 | 3 | import "main/ipc/protos" 4 | 5 | type RouteData struct { 6 | Method string 7 | Path string 8 | APISpec protos.APISpec 9 | } 10 | 11 | const ( 12 | JSON = "json" 13 | FormURLEncoded = "form-urlencoded" 14 | FormData = "form-data" 15 | XML = "xml" 16 | Undefined = "" 17 | ) 18 | -------------------------------------------------------------------------------- /lib/request-processor/vulnerabilities/web-scanner/isWebScanMethod.go: -------------------------------------------------------------------------------- 1 | package webscanner 2 | 3 | import ( 4 | "main/utils" 5 | ) 6 | 7 | var methods = utils.StringSliceToMap([]string{"BADMETHOD", "BADHTTPMETHOD", "BADDATA", "BADMTHD", "BDMTHD"}) 8 | 9 | func isWebScanMethod(method string) bool { 10 | if _, ok := methods[method]; ok { 11 | return true 12 | } 13 | return false 14 | } 15 | -------------------------------------------------------------------------------- /tests/server/test_path_traversal_with_include/index.php: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /lib/request-processor/helpers/escapeStringRegexp.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | ) 7 | 8 | func EscapeStringRegexp(str string) string { 9 | re := regexp.MustCompile(`[|\\{}()[\]^$+*?.]`) 10 | str = re.ReplaceAllStringFunc(str, func(match string) string { 11 | return "\\" + match 12 | }) 13 | str = strings.ReplaceAll(str, "-", "\\x2d") 14 | return str 15 | } 16 | -------------------------------------------------------------------------------- /tests/server/test_user_agent_monitored/expect_user_agent_monitored.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "heartbeat", 3 | "users": [ 4 | { 5 | "id": "12345", 6 | "name": "Tudor" 7 | } 8 | ], 9 | "middlewareInstalled": false, 10 | "stats": { 11 | "userAgents":{ 12 | "breakdown":{ 13 | "Googlebot":1 14 | } 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /lib/request-processor/context/data_sources.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | type Source struct { 4 | Name string 5 | CacheGet func() map[string]string 6 | } 7 | 8 | var SOURCES = []Source{ 9 | {"body", GetBodyParsedFlattened}, 10 | {"query", GetQueryParsedFlattened}, 11 | {"headers", GetHeadersParsedFlattened}, 12 | {"cookies", GetCookiesParsedFlattened}, 13 | {"routeParams", GetRouteParamsParsedFlattened}, 14 | } 15 | -------------------------------------------------------------------------------- /lib/request-processor/vulnerabilities/path-traversal/containsUnsafePathParts.go: -------------------------------------------------------------------------------- 1 | package path_traversal 2 | 3 | import "strings" 4 | 5 | var dangerousPathParts = []string{"../", "..\\"} 6 | 7 | func containsUnsafePathParts(filePath string) bool { 8 | for _, dangerousPart := range dangerousPathParts { 9 | if strings.Contains(filePath, dangerousPart) { 10 | return true 11 | } 12 | } 13 | return false 14 | } 15 | -------------------------------------------------------------------------------- /tests/cli/path_traversal/test_path_traversal_file.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path traversal (file) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=../file 10 | 11 | --FILE-- 12 | 19 | 20 | --EXPECTREGEX-- 21 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a path traversal attack.* 22 | -------------------------------------------------------------------------------- /tests/cli/shell_injection/test_passthru.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test PHP shell injection (passthru) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=www.example`whoami`.com 10 | 11 | --FILE-- 12 | 17 | 18 | --EXPECTREGEX-- 19 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a shell injection.* -------------------------------------------------------------------------------- /tests/cli/outgoing_request/test_outgoing_request_file_get_contents.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test outgoing request (file_get_contents) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --FILE-- 9 | 14 | 15 | --EXPECT-- 16 | [AIKIDO][INFO] [BEFORE] Got domain: www.example.com 17 | [AIKIDO][INFO] [AFTER] Got domain: www.example.com port: 80 -------------------------------------------------------------------------------- /tests/cli/path_traversal/test_path_traversal_chdir.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path traversal (chdir) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --FILE-- 9 | 16 | 17 | --POST-- 18 | test=../file 19 | 20 | --EXPECTREGEX-- 21 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a path traversal attack.* 22 | -------------------------------------------------------------------------------- /tests/cli/path_traversal/test_path_traversal_fopen.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path traversal (fopen) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=../file 10 | 11 | --FILE-- 12 | 19 | 20 | --EXPECTREGEX-- 21 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a path traversal attack.* 22 | -------------------------------------------------------------------------------- /tests/cli/path_traversal/test_path_traversal_mkdir.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path traversal (mkdir) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=../file 10 | 11 | --FILE-- 12 | 19 | 20 | --EXPECTREGEX-- 21 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a path traversal attack.* 22 | -------------------------------------------------------------------------------- /tests/cli/path_traversal/test_path_traversal_rmdir.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path traversal (rmdir) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=../file 10 | 11 | --FILE-- 12 | 19 | 20 | --EXPECTREGEX-- 21 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a path traversal attack.* 22 | -------------------------------------------------------------------------------- /tests/cli/path_traversal/test_path_traversal_touch.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path traversal (touch) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=../file 10 | 11 | --FILE-- 12 | 19 | 20 | --EXPECTREGEX-- 21 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a path traversal attack.* 22 | -------------------------------------------------------------------------------- /tests/cli/path_traversal/test_path_traversal_chgrp.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path traversal (chgrp) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=../file 10 | 11 | --FILE-- 12 | 19 | 20 | --EXPECTREGEX-- 21 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a path traversal attack.* 22 | -------------------------------------------------------------------------------- /tests/cli/path_traversal/test_path_traversal_chmod.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path traversal (chmod) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=../file 10 | 11 | --FILE-- 12 | 19 | 20 | --EXPECTREGEX-- 21 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a path traversal attack.* 22 | -------------------------------------------------------------------------------- /tests/cli/path_traversal/test_path_traversal_chown.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path traversal (chown) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=../file 10 | 11 | --FILE-- 12 | 19 | 20 | --EXPECTREGEX-- 21 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a path traversal attack.* 22 | -------------------------------------------------------------------------------- /tests/cli/path_traversal/test_path_traversal_lchgrp.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path traversal (lchgrp) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=../file 10 | 11 | --FILE-- 12 | 19 | 20 | --EXPECTREGEX-- 21 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a path traversal attack.* 22 | -------------------------------------------------------------------------------- /tests/cli/path_traversal/test_path_traversal_lchown.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path traversal (lchown) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=../file 10 | 11 | --FILE-- 12 | 19 | 20 | --EXPECTREGEX-- 21 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a path traversal attack.* 22 | -------------------------------------------------------------------------------- /tests/cli/path_traversal/test_path_traversal_opendir.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path traversal (opendir) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=../file 10 | 11 | --FILE-- 12 | 19 | 20 | --EXPECTREGEX-- 21 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a path traversal attack.* 22 | -------------------------------------------------------------------------------- /tests/cli/path_traversal/test_path_traversal_scandir.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path traversal (scandir) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=../file 10 | 11 | --FILE-- 12 | 19 | 20 | --EXPECTREGEX-- 21 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a path traversal attack.* 22 | -------------------------------------------------------------------------------- /tests/cli/path_traversal/test_path_traversal_unlink.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path traversal (unlink) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=../file 10 | 11 | --FILE-- 12 | 19 | 20 | --EXPECTREGEX-- 21 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a path traversal attack.* 22 | -------------------------------------------------------------------------------- /tests/cli/path_traversal/test_path_traversal_readfile.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path traversal (readfile) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=../file 10 | 11 | --FILE-- 12 | 19 | 20 | --EXPECTREGEX-- 21 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a path traversal attack.* 22 | -------------------------------------------------------------------------------- /tests/cli/path_traversal/test_path_traversal_readlink.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path traversal (readlink) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=../file 10 | 11 | --FILE-- 12 | 19 | 20 | --EXPECTREGEX-- 21 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a path traversal attack.* 22 | -------------------------------------------------------------------------------- /tests/cli/path_traversal/test_path_traversal_realpath.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path traversal (realpath) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=../file 10 | 11 | --FILE-- 12 | 19 | 20 | --EXPECTREGEX-- 21 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a path traversal attack.* 22 | -------------------------------------------------------------------------------- /docs/token-setup-php.md: -------------------------------------------------------------------------------- 1 | # Token setup from PHP code 2 | 3 | Aikido exposes a new function, called `\aikido\set_token` that can be used to pass in the token to the Aikido Agent. 4 | 5 | ```php 6 | 11 | ``` 12 | 13 | This code needs to run with every request, so you can add it in a middleware, as exemplified [here](./should_block_request.md). 14 | -------------------------------------------------------------------------------- /tests/cli/path_traversal/test_path_traversal_spl_file_info.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path traversal (SplFileInfo) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --FILE-- 9 | 16 | 17 | --POST-- 18 | test=../file 19 | 20 | --EXPECTREGEX-- 21 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a path traversal attack.* 22 | -------------------------------------------------------------------------------- /tests/cli/path_traversal/test_path_traversal_parse_ini_file.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path traversal (parse_ini_file) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=../file 10 | 11 | --FILE-- 12 | 19 | 20 | --EXPECTREGEX-- 21 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a path traversal attack.* 22 | -------------------------------------------------------------------------------- /tests/server/test_attack_wave_detection/expect_wave_detection.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "type": "detected_attack_wave", 4 | "request": { 5 | "ipAddress": "5.8.19.22" 6 | }, 7 | "attack": { 8 | "metadata": {}, 9 | "user": { 10 | "id": "12345", 11 | "name": "Tudor" 12 | } 13 | }, 14 | "agent": { 15 | "dryMode": false, 16 | "library": "firewall-php" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/server/test_attack_wave_detection/expect_wave_detection_2.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "type": "detected_attack_wave", 4 | "request": { 5 | "ipAddress": "5.8.19.23" 6 | }, 7 | "attack": { 8 | "metadata": {}, 9 | "user": { 10 | "id": "12345", 11 | "name": "Tudor" 12 | } 13 | }, 14 | "agent": { 15 | "dryMode": false, 16 | "library": "firewall-php" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/request-processor/vulnerabilities/web-scanner/isWebScanner.go: -------------------------------------------------------------------------------- 1 | package webscanner 2 | 3 | func IsWebScanner(method string, route string, queryParams map[string]interface{}) bool { 4 | if method != "" && isWebScanMethod(method) { 5 | return true 6 | } 7 | 8 | if route != "" && isWebScanPath(route) { 9 | return true 10 | } 11 | 12 | if queryParams != nil && checkQuery(queryParams) { 13 | return true 14 | } 15 | 16 | return false 17 | } 18 | -------------------------------------------------------------------------------- /tests/cli/shell_injection/test_shell_exec.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test PHP shell injection (shell_exec) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=www.example`whoami`.com 10 | 11 | --FILE-- 12 | 18 | 19 | --EXPECTREGEX-- 20 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a shell injection.* -------------------------------------------------------------------------------- /benchmarks/cli/benchmark_sql_query/php_setup.php: -------------------------------------------------------------------------------- 1 | 2 | $dbFile = 'unsafe_database.sqlite'; 3 | 4 | $pdo = new PDO('sqlite:unsafe_database.sqlite'); 5 | $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 6 | $pdo->exec("CREATE TABLE IF NOT EXISTS users ( 7 | id INTEGER PRIMARY KEY, 8 | name TEXT, 9 | email TEXT)"); 10 | 11 | $pdo->exec("INSERT INTO users (name, email) VALUES ('John Doe', 'john@example.com')"); 12 | -------------------------------------------------------------------------------- /tests/cli/path_traversal/test_path_traversal_file_get_contents.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path traversal (file_get_contents) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=../file 10 | 11 | --FILE-- 12 | 19 | 20 | --EXPECTREGEX-- 21 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a path traversal attack.* 22 | -------------------------------------------------------------------------------- /tests/cli/path_traversal/test_path_traversal_file_put_contents.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path traversal (file_put_contents) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=../file 10 | 11 | --FILE-- 12 | 19 | 20 | --EXPECTREGEX-- 21 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a path traversal attack.* 22 | -------------------------------------------------------------------------------- /tests/cli/path_traversal/test_path_traversal_highlight_file.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path traversal (highlight_file) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=../file/test.txt 10 | 11 | --FILE-- 12 | 19 | 20 | --EXPECTREGEX-- 21 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a path traversal attack.* 22 | -------------------------------------------------------------------------------- /tests/cli/ssrf/test_ssrf_file_get_contents.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path ssrf (file_get_contents) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=http://127.0.0.1:8081 10 | 11 | --FILE-- 12 | 19 | 20 | --EXPECTREGEX-- 21 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a server-side request forgery.* 22 | -------------------------------------------------------------------------------- /tests/server/test_allowed_ip_most_restrictive/index.php: -------------------------------------------------------------------------------- 1 | block && $decision->type == "blocked" && $decision->trigger == "ip") { 7 | http_response_code(403); 8 | echo "Your IP ({$decision->ip}) is blocked due to: {$decision->description}!"; 9 | exit(); 10 | } 11 | } 12 | 13 | echo "Something"; 14 | 15 | ?> 16 | -------------------------------------------------------------------------------- /tests/server/test_bypassed_ip_for_attacks/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [ 8 | "93.184.216.34", 9 | "23.45.67.0/24", 10 | "2606:2800:220:1:248:1893:25c8:1946", 11 | "2001:0db9:abcd:1234::/64" 12 | ], 13 | "receivedAnyStats": true, 14 | "block": true 15 | } 16 | -------------------------------------------------------------------------------- /lib/php-extension/include/PhpLifecycle.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | class PhpLifecycle { 4 | private: 5 | pid_t mainPID = 0; 6 | 7 | public: 8 | PhpLifecycle() = default; 9 | ~PhpLifecycle() = default; 10 | 11 | void ModuleInit(); 12 | 13 | void RequestInit(); 14 | 15 | void RequestShutdown(); 16 | 17 | void ModuleShutdown(); 18 | 19 | void HookAll(); 20 | 21 | void UnhookAll(); 22 | }; 23 | 24 | extern PhpLifecycle phpLifecycle; 25 | -------------------------------------------------------------------------------- /tests/cli/path_traversal/test_path_traversal_copy_1.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path traversal (copy) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=../file 10 | 11 | --FILE-- 12 | 21 | 22 | --EXPECTREGEX-- 23 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a path traversal attack.* 24 | -------------------------------------------------------------------------------- /tests/cli/path_traversal/test_path_traversal_copy_2.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path traversal (copy) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=../file 10 | 11 | --FILE-- 12 | 21 | 22 | --EXPECTREGEX-- 23 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a path traversal attack.* 24 | -------------------------------------------------------------------------------- /tests/cli/path_traversal/test_path_traversal_link_1.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path traversal (link) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=../file 10 | 11 | --FILE-- 12 | 21 | 22 | --EXPECTREGEX-- 23 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a path traversal attack.* 24 | -------------------------------------------------------------------------------- /tests/cli/path_traversal/test_path_traversal_link_2.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path traversal (link) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=../file 10 | 11 | --FILE-- 12 | 21 | 22 | --EXPECTREGEX-- 23 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a path traversal attack.* 24 | -------------------------------------------------------------------------------- /tests/server/test_allowed_ip_for_wildcard_route/index.php: -------------------------------------------------------------------------------- 1 | block && $decision->type == "blocked" && $decision->trigger == "ip") { 7 | http_response_code(403); 8 | echo "Your IP ({$decision->ip}) is blocked due to: {$decision->description}!"; 9 | exit(); 10 | } 11 | } 12 | 13 | echo "Something"; 14 | 15 | ?> 16 | -------------------------------------------------------------------------------- /tests/cli/aikido_ops/test_register_param_matcher.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test \aikido\register_param_matcher with valid parameters 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_TOKEN=AIK_DUMMY 7 | 8 | --FILE-- 9 | 14 | 15 | --EXPECT-- 16 | [AIKIDO][INFO] Token changed to "AIK_RUNTIME_***UMMY" 17 | [AIKIDO][INFO] Registered param matcher param_name -> {digits}-{alpha} -------------------------------------------------------------------------------- /tests/cli/aikido_ops/test_set_token_with_invalid_params.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test \aikido\set_token without parameter 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | 7 | --SKIPIF-- 8 | 13 | --FILE-- 14 | 19 | 20 | --EXPECTREGEX-- 21 | .*Fatal error: Uncaught ArgumentCountError: aikido\\set_token\(\) expects exactly 1 argument, 0 given.* -------------------------------------------------------------------------------- /tests/cli/path_traversal/test_path_traversal_rename_2.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path traversal (rename) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=../file 10 | 11 | --FILE-- 12 | 21 | 22 | --EXPECTREGEX-- 23 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a path traversal attack.* 24 | -------------------------------------------------------------------------------- /tests/cli/path_traversal/test_path_traversal_spl_file_object.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path traversal (SplFileObject) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --FILE-- 9 | 16 | 17 | --POST-- 18 | test=../file 19 | 20 | --EXPECTREGEX-- 21 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a path traversal attack.* 22 | -------------------------------------------------------------------------------- /tests/cli/path_traversal/test_path_traversal_php_strip_whitespaces.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path traversal (php_strip_whitespaces) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --FILE-- 9 | 16 | 17 | --POST-- 18 | test=../file/test.txt 19 | 20 | --EXPECTREGEX-- 21 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a path traversal attack.* 22 | -------------------------------------------------------------------------------- /tests/cli/path_traversal/test_path_traversal_rename_1.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path traversal (rename) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=../file 10 | 11 | --FILE-- 12 | 21 | 22 | --EXPECTREGEX-- 23 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a path traversal attack.* 24 | -------------------------------------------------------------------------------- /tests/cli/path_traversal/test_path_traversal_symlink_1.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path traversal (symlink) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=../file 10 | 11 | --FILE-- 12 | 21 | 22 | --EXPECTREGEX-- 23 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a path traversal attack.* 24 | -------------------------------------------------------------------------------- /tests/cli/path_traversal/test_path_traversal_symlink_2.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path traversal (symlink) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=../file 10 | 11 | --FILE-- 12 | 21 | 22 | --EXPECTREGEX-- 23 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a path traversal attack.* 24 | -------------------------------------------------------------------------------- /sample-apps/sqlite-php-server/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | sqlite-php-server: 5 | container_name: php-server 6 | build: ../../ 7 | platform: linux/amd64 8 | ports: 9 | - "1337:1337" 10 | volumes: 11 | - .:/home/sqlite-php-server 12 | working_dir: /home/sqlite-php-server 13 | environment: 14 | - AIKIDO_BLOCKING=1 15 | - AIKIDO_DEBUG=1 16 | - AIKIDO_BLOCK=1 17 | command: php -S 0.0.0.0:1337 18 | -------------------------------------------------------------------------------- /tests/cli/path_traversal/test_path_traversal_highlight_file_2_args.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path traversal (highlight_file with two args) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=../file/test.txt 10 | 11 | --FILE-- 12 | 19 | 20 | --EXPECTREGEX-- 21 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a path traversal attack.* 22 | -------------------------------------------------------------------------------- /tests/server/test_ip_rate_limiting_10_minutes/index.php: -------------------------------------------------------------------------------- 1 | block && $decision->type == "ratelimited" && $decision->trigger == "ip") { 8 | http_response_code(429); 9 | echo "Rate limit exceeded"; 10 | exit(); 11 | } 12 | } 13 | 14 | echo "Request successful"; 15 | 16 | ?> 17 | -------------------------------------------------------------------------------- /lib/agent/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | . "main/aikido_types" 5 | ) 6 | 7 | func GetToken(server *ServerData) string { 8 | server.AikidoConfig.ConfigMutex.Lock() 9 | defer server.AikidoConfig.ConfigMutex.Unlock() 10 | 11 | return server.AikidoConfig.Token 12 | } 13 | 14 | func GetBlocking(server *ServerData) bool { 15 | server.AikidoConfig.ConfigMutex.Lock() 16 | defer server.AikidoConfig.ConfigMutex.Unlock() 17 | 18 | return server.AikidoConfig.Blocking 19 | } 20 | -------------------------------------------------------------------------------- /package/rpm/backup/aikido.te: -------------------------------------------------------------------------------- 1 | 2 | module aikido 1.0; 3 | 4 | require { 5 | type httpd_t; 6 | type var_run_t; 7 | type unconfined_service_t; 8 | class sock_file write; 9 | class unix_stream_socket connectto; 10 | } 11 | 12 | #============= httpd_t ============== 13 | 14 | #!!!! This avc is allowed in the current policy 15 | allow httpd_t unconfined_service_t:unix_stream_socket connectto; 16 | 17 | #!!!! This avc is allowed in the current policy 18 | allow httpd_t var_run_t:sock_file write; 19 | -------------------------------------------------------------------------------- /tests/cli/ssrf/test_ssrf_file_get_contents_case_insensitive.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path ssrf (file_get_contents) - Case Insensitive 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=hTTps://lOcalhosT:8081 10 | 11 | --FILE-- 12 | 19 | 20 | --EXPECTREGEX-- 21 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a server-side request forgery.* 22 | -------------------------------------------------------------------------------- /tests/server/test_ssrf_file_get_contents/index.php: -------------------------------------------------------------------------------- 1 | 19 | -------------------------------------------------------------------------------- /docs/examples/apache-mod-php-wordpress/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:8.3-apache 2 | 3 | COPY 000-default.conf /etc/apache2/sites-enabled/000-default.conf 4 | 5 | RUN apt update && \ 6 | apt install unzip 7 | 8 | RUN curl "https://wordpress.org/latest.zip" -o /root/latest.zip && \ 9 | unzip /root/latest.zip -d /var/www/html/ 10 | 11 | RUN curl -L -O https://github.com/AikidoSec/firewall-php/releases/download/v1.0.109/aikido-php-firewall.aarch64.deb && \ 12 | dpkg -i -E ./aikido-php-firewall.aarch64.deb 13 | -------------------------------------------------------------------------------- /tests/cli/path_traversal/test_path_traversal_move_uploaded_file_1.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path traversal (move_uploaded_file) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=../file 10 | 11 | --FILE-- 12 | 21 | 22 | --EXPECTREGEX-- 23 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a path traversal attack.* 24 | -------------------------------------------------------------------------------- /tests/cli/path_traversal/test_path_traversal_move_uploaded_file_2.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path traversal (move_uploaded_file) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=../file 10 | 11 | --FILE-- 12 | 21 | 22 | --EXPECTREGEX-- 23 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a path traversal attack.* 24 | -------------------------------------------------------------------------------- /tests/cli/shell_injection/test_exec.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test PHP shell injection (exec) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=www.example`whoami`.com 10 | 11 | --FILE-- 12 | 21 | 22 | --EXPECTREGEX-- 23 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a shell injection.* -------------------------------------------------------------------------------- /tests/cli/ssrf/test_ssrf_curl_exec.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path ssrf (curl_exec) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=http://127.0.0.1:8081 10 | 11 | --FILE-- 12 | 20 | 21 | --EXPECTREGEX-- 22 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a server-side request forgery.* 23 | -------------------------------------------------------------------------------- /tests/cli/shell_injection/test_system.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test PHP shell injection (system) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=www.example`whoami`.com 10 | 11 | --FILE-- 12 | 21 | 22 | --EXPECTREGEX-- 23 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a shell injection.* 24 | 25 | -------------------------------------------------------------------------------- /tests/server/test_bypassed_ip_for_rate_limiting/index.php: -------------------------------------------------------------------------------- 1 | block && $decision->type == "ratelimited" && $decision->trigger == "ip") { 8 | http_response_code(429); 9 | echo "Rate limit exceeded"; 10 | exit(); 11 | } 12 | } 13 | 14 | // Continue handling the request 15 | echo "Request successful"; 16 | 17 | ?> -------------------------------------------------------------------------------- /lib/request-processor/helpers/tryParseURL.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "net/url" 5 | 6 | "golang.org/x/net/idna" 7 | ) 8 | 9 | func TryParseURL(input string) *url.URL { 10 | parsedURL, err := url.ParseRequestURI(input) 11 | if err != nil { 12 | return nil 13 | } 14 | 15 | // Convert the host to Unicode if it's an IDN (https://www.rfc-editor.org/rfc/rfc3492) 16 | parsedHost, err := idna.ToUnicode(parsedURL.Host) 17 | if err == nil { 18 | parsedURL.Host = parsedHost 19 | } 20 | return parsedURL 21 | } 22 | -------------------------------------------------------------------------------- /tests/cli/shell_injection/test_exec_trim.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test PHP shell injection (exec) trim 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=www.example`whoami`.com 10 | 11 | --FILE-- 12 | 21 | 22 | --EXPECTREGEX-- 23 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a shell injection.* -------------------------------------------------------------------------------- /tests/cli/path_traversal/test_path_traversal_file_get_contents_php_filter.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path traversal (file_get_contents with php filter) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --FILE-- 9 | 16 | 17 | --POST-- 18 | test=../file 19 | 20 | --EXPECTREGEX-- 21 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a path traversal attack.* 22 | -------------------------------------------------------------------------------- /lib/agent/aikido_types/route.go: -------------------------------------------------------------------------------- 1 | package aikido_types 2 | 3 | import "main/ipc/protos" 4 | 5 | type RouteData struct { 6 | Method string 7 | Path string 8 | APISpec protos.APISpec 9 | } 10 | 11 | // BodyDataType represents the type of the body data. 12 | type BodyDataType string 13 | 14 | const ( 15 | JSON BodyDataType = "json" 16 | FormURLEncoded BodyDataType = "form-urlencoded" 17 | FormData BodyDataType = "form-data" 18 | XML BodyDataType = "xml" 19 | Undefined BodyDataType = "" 20 | ) 21 | -------------------------------------------------------------------------------- /tests/cli/shell_execution/test_shell_execution_json_decode.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test PHP shell execution functions (json decode) 3 | 4 | --POST_RAW-- 5 | { 6 | "age": -123123e10000, 7 | "cmd": "cat /etc/passwd" 8 | } 9 | 10 | --ENV-- 11 | AIKIDO_LOG_LEVEL=INFO 12 | AIKIDO_BLOCK=1 13 | 14 | 15 | --FILE-- 16 | 23 | 24 | --EXPECTREGEX-- 25 | .*Aikido firewall has blocked a shell injection.* -------------------------------------------------------------------------------- /tests/server/test_path_traversal/index.php: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /lib/php-extension/include/HandleBypassedIp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // This variable is used to check if the request is bypassed, 4 | // if true, all blocking checks will be skipped. 5 | extern bool isIpBypassed; 6 | 7 | // Initialize the IP bypass check at request start. 8 | // Resets state and checks if the current IP should be bypassed. 9 | // This should be called during request initialization. 10 | void InitIpBypassCheck(); 11 | 12 | // Check if Aikido is disabled or the current IP is bypassed. 13 | bool IsAikidoDisabledOrBypassed(); 14 | -------------------------------------------------------------------------------- /tests/cli/shell_injection/test_popen.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test PHP shell injection (popen) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=www.example`whoami`.com 10 | 11 | --FILE-- 12 | 22 | 23 | --EXPECTREGEX-- 24 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a shell injection.* -------------------------------------------------------------------------------- /tests/cli/ssrf/test_ssrf_file_get_contents_php_filter.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test ssrf (file_get_contents with php filter) - Case Insensitive 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=hTTps://lOcalhosT:8081 10 | 11 | --FILE-- 12 | 19 | 20 | --EXPECTREGEX-- 21 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a server-side request forgery.* 22 | -------------------------------------------------------------------------------- /tests/server/test_ip_rate_limiting_10_minutes_for_wildcard_route/index.php: -------------------------------------------------------------------------------- 1 | block && $decision->type == "ratelimited" && $decision->trigger == "ip") { 8 | http_response_code(429); 9 | echo "Rate limit exceeded"; 10 | exit(); 11 | } 12 | } 13 | 14 | // Continue handling the request 15 | echo "Request successful"; 16 | 17 | ?> 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | labels: enhancement 5 | --- 6 | 7 | ## Feature request 8 | 9 | **Describe the problem** 10 | What problem or limitation are you facing? 11 | 12 | **Proposed solution** 13 | Describe the feature or improvement you’d like to see. 14 | 15 | **Alternatives considered** 16 | Have you tried any workarounds or other approaches? 17 | 18 | **Additional context** 19 | Add any other information, links, or examples that help explain the request. 20 | -------------------------------------------------------------------------------- /tests/server/test_big_request/test.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import time 3 | import sys 4 | from testlib import * 5 | import json 6 | 7 | ''' 8 | 1. Sets up a simple config. 9 | 2. Sends one request that will trigger multiple curl reuqests from php. 10 | 3. Waits for the heartbeat event and validates it. 11 | ''' 12 | 13 | def run_test(): 14 | response = php_server_post("/testDetection", json.load(open("test.json", 'r'))) 15 | assert_response_code_is(response, 200) 16 | 17 | if __name__ == "__main__": 18 | load_test_args() 19 | run_test() 20 | -------------------------------------------------------------------------------- /tests/server/test_group_rate_limiting_10_minutes/index.php: -------------------------------------------------------------------------------- 1 | block && $decision->type == "ratelimited" && $decision->trigger == "group") { 10 | http_response_code(429); 11 | echo "Rate limit exceeded"; 12 | exit(); 13 | } 14 | } 15 | 16 | echo "Request successful"; 17 | 18 | ?> 19 | -------------------------------------------------------------------------------- /lib/request-processor/handle_user_event.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "main/context" 5 | "main/globals" 6 | "main/grpc" 7 | "main/log" 8 | ) 9 | 10 | func OnUserEvent() string { 11 | id := context.GetUserId() 12 | username := context.GetUserName() 13 | ip := context.GetIp() 14 | 15 | log.Infof("Got user event!") 16 | 17 | if id == "" || ip == "" { 18 | return "" 19 | } 20 | 21 | server := globals.GetCurrentServer() 22 | if server == nil { 23 | return "" 24 | } 25 | go grpc.OnUserEvent(server, id, username, ip) 26 | return "" 27 | } 28 | -------------------------------------------------------------------------------- /tests/server/test_disable/index.php: -------------------------------------------------------------------------------- 1 | 21 | -------------------------------------------------------------------------------- /lib/request-processor/helpers/getPortFromURL.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "net/url" 5 | "strconv" 6 | ) 7 | 8 | func GetPortFromURL(u *url.URL) uint32 { 9 | // If a port is explicitly specified and it's a valid integer 10 | if u.Port() != "" { 11 | port, err := strconv.Atoi(u.Port()) 12 | if err != nil || port < 0 { 13 | return 0 14 | } 15 | return uint32(port) 16 | } 17 | 18 | // Default ports based on protocol 19 | switch u.Scheme { 20 | case "https": 21 | return 443 22 | case "http": 23 | return 80 24 | } 25 | return 0 26 | } 27 | -------------------------------------------------------------------------------- /tests/server/test_user_block/index.php: -------------------------------------------------------------------------------- 1 | block && $decision->type == "blocked" && $decision->trigger == "user") { 10 | http_response_code(403); 11 | echo "You are blocked by Aikido Firewall!"; 12 | exit(); 13 | } 14 | } 15 | 16 | // Continue handling the request 17 | echo "User set successfully"; 18 | ?> 19 | -------------------------------------------------------------------------------- /tests/server/test_user_rate_limiting_10_minutes/index.php: -------------------------------------------------------------------------------- 1 | block && $decision->type == "ratelimited" && $decision->trigger == "user") { 9 | http_response_code(429); 10 | echo "Rate limit exceeded"; 11 | exit(); 12 | } 13 | } 14 | 15 | // Continue handling the request 16 | echo "Request successful"; 17 | 18 | ?> 19 | -------------------------------------------------------------------------------- /tests/server/test_ip_rate_limiting_most_restrictive_wildcard/index.php: -------------------------------------------------------------------------------- 1 | block && $decision->type == "ratelimited" && $decision->trigger == "ip") { 10 | http_response_code(429); 11 | echo "Rate limit exceeded"; 12 | exit(); 13 | } 14 | } 15 | 16 | // Continue handling the request 17 | echo "Request successful!"; 18 | 19 | ?> 20 | -------------------------------------------------------------------------------- /tests/server/test_max_detection_events_per_timeframe/index.php: -------------------------------------------------------------------------------- 1 | 21 | -------------------------------------------------------------------------------- /tests/server/test_detection_with_aikido_core_down_from_start/index.php: -------------------------------------------------------------------------------- 1 | 21 | -------------------------------------------------------------------------------- /tests/server/test_ip_rate_limiting_1_minute_for_wildcard_route/index.php: -------------------------------------------------------------------------------- 1 | block && $decision->type == "ratelimited" && $decision->trigger == "ip") { 10 | http_response_code(429); 11 | echo "Rate limit exceeded"; 12 | exit(); 13 | } 14 | } 15 | 16 | // Continue handling the request 17 | echo "Request successful!"; 18 | 19 | ?> 20 | -------------------------------------------------------------------------------- /tests/server/test_ssrf_curl/index.php: -------------------------------------------------------------------------------- 1 | 22 | -------------------------------------------------------------------------------- /tools/server_tests/php_built_in/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | 4 | def php_built_in_start_server(test_data, test_lib_dir, valgrind): 5 | server_port = test_data["server_port"] 6 | 7 | php_server_process_cmd = ['php', '-S', f'127.0.0.1:{server_port}', '-t', test_data["test_dir"]] 8 | if valgrind: 9 | php_server_process_cmd = ['valgrind', f'--suppressions={test_lib_dir}/valgrind.supp', '--track-origins=yes'] + php_server_process_cmd 10 | 11 | return subprocess.Popen( 12 | php_server_process_cmd, 13 | env=test_data["env"] 14 | ) 15 | -------------------------------------------------------------------------------- /tests/server/test_detection_with_aikido_core_down_while_running/index.php: -------------------------------------------------------------------------------- 1 | 21 | -------------------------------------------------------------------------------- /tests/server/test_ip_rate_limiting_1_minute/index.php: -------------------------------------------------------------------------------- 1 | block && $decision->type == "ratelimited" && $decision->trigger == "ip") { 10 | http_response_code(429); 11 | echo "Rate limit exceeded by IP: " . $decision->ip; 12 | exit(); 13 | } 14 | } 15 | 16 | // Continue handling the request 17 | echo "Request successful!"; 18 | 19 | ?> 20 | -------------------------------------------------------------------------------- /tests/server/test_ip_rate_limiting_with_aikido_core_down_after_start/index.php: -------------------------------------------------------------------------------- 1 | block && $decision->type == "ratelimited" && $decision->trigger == "ip") { 10 | http_response_code(429); 11 | echo "Rate limit exceeded"; 12 | exit(); 13 | } 14 | } 15 | 16 | // Continue handling the request 17 | echo "Request successful!"; 18 | 19 | ?> 20 | -------------------------------------------------------------------------------- /tools/dpkg_build.sh: -------------------------------------------------------------------------------- 1 | arch=$(uname -m) 2 | VERSION=$(grep '#define PHP_AIKIDO_VERSION' lib/php-extension/include/php_aikido.h | awk -F'"' '{print $2}') 3 | 4 | alien --to-deb --scripts --keep-version /shared/aikido-php-firewall-$VERSION-1.$arch.rpm 5 | mv aikido-php-firewall_$VERSION-1_amd64.deb temp-aikido-php-firewall-$VERSION-1.$arch.deb 6 | 7 | mkdir deb-temp 8 | dpkg-deb -R temp-aikido-php-firewall-$VERSION-1.$arch.deb deb-temp/ 9 | dpkg-deb -Zgzip -b deb-temp aikido-php-firewall-$VERSION-1.$arch.deb 10 | rm -rf deb-temp 11 | 12 | cp aikido-php-firewall-$VERSION-1.$arch.deb .devcontainer/shared/ 13 | -------------------------------------------------------------------------------- /tests/server/test_big_request/index.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/agent/grpc/domain.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | . "main/aikido_types" 5 | "main/utils" 6 | ) 7 | 8 | func storeDomain(server *ServerData, domain string, port uint32) { 9 | if port == 0 { 10 | return 11 | } 12 | 13 | server.HostnamesMutex.Lock() 14 | defer server.HostnamesMutex.Unlock() 15 | 16 | if _, ok := server.Hostnames[domain]; !ok { 17 | // First time we see this domain 18 | server.Hostnames[domain] = make(map[uint32]uint64) 19 | utils.RemoveOldestFromMapIfMaxExceeded(&server.Hostnames, &server.HostnamesQueue, domain) 20 | } 21 | 22 | server.Hostnames[domain][port]++ 23 | } 24 | -------------------------------------------------------------------------------- /lib/agent/go.mod: -------------------------------------------------------------------------------- 1 | module main 2 | 3 | go 1.25.4 4 | 5 | require ( 6 | github.com/stretchr/testify v1.9.0 7 | google.golang.org/grpc v1.77.0 8 | google.golang.org/protobuf v1.36.10 9 | ) 10 | 11 | require ( 12 | github.com/davecgh/go-spew v1.1.1 // indirect 13 | github.com/pmezard/go-difflib v1.0.0 // indirect 14 | golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 // indirect 15 | golang.org/x/sys v0.37.0 // indirect 16 | golang.org/x/text v0.30.0 // indirect 17 | google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect 18 | gopkg.in/yaml.v3 v3.0.1 // indirect 19 | ) 20 | -------------------------------------------------------------------------------- /tests/cli/ssrf/test_ssrf_file_get_contents_multiple_php_filter.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test ssrf (file_get_contents with multiple php filter) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=https://localhost:8081 10 | 11 | --FILE-- 12 | 18 | 19 | --EXPECTREGEX-- 20 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a server-side request forgery.* 21 | -------------------------------------------------------------------------------- /lib/agent/cloud/event_started.go: -------------------------------------------------------------------------------- 1 | package cloud 2 | 3 | import ( 4 | . "main/aikido_types" 5 | "main/constants" 6 | "main/utils" 7 | ) 8 | 9 | func SendStartEvent(server *ServerData) { 10 | startedEvent := Started{ 11 | Type: "started", 12 | Agent: GetAgentInfo(server), 13 | Time: utils.GetTime(), 14 | } 15 | 16 | response, err := SendCloudRequest(server, server.AikidoConfig.Endpoint, constants.EventsAPI, constants.EventsAPIMethod, startedEvent) 17 | if err != nil { 18 | LogCloudRequestError(server, "Error in sending start event: ", err) 19 | return 20 | } 21 | StoreCloudConfig(server, response) 22 | } 23 | -------------------------------------------------------------------------------- /lib/request-processor/helpers/tryParseURL_test.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "net/url" 5 | "testing" 6 | ) 7 | 8 | func TestTryParseURL_InvalidURL(t *testing.T) { 9 | input := "invalid" 10 | result := TryParseURL(input) 11 | if result != nil { 12 | t.Errorf("Expected nil, got %v", result) 13 | } 14 | } 15 | 16 | func TestTryParseURL_ValidURL(t *testing.T) { 17 | input := "https://example.com" 18 | expected, _ := url.Parse(input) 19 | result := TryParseURL(input) 20 | 21 | if result == nil || result.String() != expected.String() { 22 | t.Errorf("Expected %v, got %v", expected, result) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/server/test_allowed_ip/config_allow_private.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [ 6 | { 7 | "method": "GET", 8 | "route": "/somethingVerySpecific", 9 | "forceProtectionOff": false, 10 | "graphql": null, 11 | "allowedIPAddresses": ["127.0.0.1/32", "::1/128"], 12 | "rateLimiting": { 13 | "enabled": false, 14 | "maxRequests": 100, 15 | "windowSizeInMS": 60000 16 | } 17 | } 18 | ], 19 | "blockedUserIds": [], 20 | "allowedIPAddresses": [], 21 | "receivedAnyStats": true 22 | } 23 | -------------------------------------------------------------------------------- /tests/server/test_user_rate_limiting_1_minute/index.php: -------------------------------------------------------------------------------- 1 | block && $decision->type == "ratelimited" && $decision->trigger == "user") { 12 | http_response_code(429); 13 | echo "Rate limit exceeded"; 14 | exit(); 15 | } 16 | } 17 | 18 | // Continue handling the request 19 | echo "Request successful!"; 20 | 21 | ?> 22 | -------------------------------------------------------------------------------- /tests/server/test_non_allowed_ip_is_not_blocked_if_bypassed/test.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import time 3 | import sys 4 | from testlib import * 5 | 6 | ''' 7 | 1. Non-allowed IP is not blocked if bypassed. 8 | ''' 9 | 10 | 11 | def run_test(): 12 | response = php_server_get("/somethingVerySpecific", headers={"X-Forwarded-For": "185.245.255.211"}) 13 | assert_response_code_is(response, 200) 14 | assert_response_header_contains(response, "Content-Type", "text") 15 | assert_response_body_contains(response, "Something") 16 | 17 | 18 | if __name__ == "__main__": 19 | load_test_args() 20 | run_test() 21 | -------------------------------------------------------------------------------- /tests/server/test_user_rate_limiting_1_minute_for_wildcard_route/index.php: -------------------------------------------------------------------------------- 1 | block && $decision->type == "ratelimited" && $decision->trigger == "user") { 12 | http_response_code(429); 13 | echo "Rate limit exceeded"; 14 | exit(); 15 | } 16 | } 17 | 18 | // Continue handling the request 19 | echo "Request successful!"; 20 | 21 | ?> 22 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting Security Issues 2 | 3 | The Aikido team and community take security bugs in Zen seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions. 4 | 5 | To report a security issue, register on Intigriti and navigate to https://app.intigriti.com/researcher/programs/aikido/aikidoruntime. 6 | 7 | The Intigriti team will send a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance. -------------------------------------------------------------------------------- /tests/server/test_group_rate_limiting_1_minute_for_wildcard_route/index.php: -------------------------------------------------------------------------------- 1 | block && $decision->type == "ratelimited" && $decision->trigger == "group") { 12 | http_response_code(429); 13 | echo "Rate limit exceeded"; 14 | exit(); 15 | } 16 | } 17 | 18 | // Continue handling the request 19 | echo "Request successful!"; 20 | 21 | ?> 22 | -------------------------------------------------------------------------------- /tests/server/test_group_rate_limiting_most_restrictive_wildcard/index.php: -------------------------------------------------------------------------------- 1 | block && $decision->type == "ratelimited" && $decision->trigger == "group") { 12 | http_response_code(429); 13 | echo "Rate limit exceeded"; 14 | exit(); 15 | } 16 | } 17 | 18 | // Continue handling the request 19 | echo "Request successful!"; 20 | 21 | ?> 22 | -------------------------------------------------------------------------------- /tests/server/test_routes_param_matchers/expect_routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "heartbeat", 3 | "routes": [ 4 | { 5 | "path": "/test", 6 | "method": "GET", 7 | "hits": 5 8 | }, 9 | { 10 | "path": "/posts/:date", 11 | "method": "GET", 12 | "hits": 5 13 | }, 14 | { 15 | "path": "/posts/:tenant", 16 | "method": "GET", 17 | "hits": 5 18 | }, 19 | { 20 | "path": "/:tenant/blog/:slug", 21 | "method": "GET", 22 | "hits": 5 23 | } 24 | ] 25 | } 26 | 27 | 28 | -------------------------------------------------------------------------------- /lib/request-processor/helpers/parseJson.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "io" 8 | ) 9 | 10 | func ParseJSON(data []byte, v any) error { 11 | dec := json.NewDecoder(bytes.NewReader(data)) 12 | dec.UseNumber() // unmarshal a number into an interface{} as a [Number] instead of as a float64 13 | // decode first value 14 | if err := dec.Decode(v); err != nil { 15 | return err 16 | } 17 | 18 | // check for trailing garbage 19 | if err := dec.Decode(&struct{}{}); err != io.EOF { 20 | if err == nil { 21 | return errors.New("unexpected extra JSON values") 22 | } 23 | return err 24 | } 25 | return nil 26 | } 27 | -------------------------------------------------------------------------------- /tests/server/test_http_method_override/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [ 6 | { 7 | "method": "GET", 8 | "route": "\/", 9 | "forceProtectionOff": false, 10 | "graphql": null, 11 | "allowedIPAddresses": [], 12 | "rateLimiting": { 13 | "enabled": true, 14 | "maxRequests": 5, 15 | "windowSizeInMS": 60000 16 | } 17 | } 18 | ], 19 | "blockedUserIds": [], 20 | "allowedIPAddresses": [], 21 | "receivedAnyStats": false 22 | } 23 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "includePath": [ 6 | "${workspaceFolder}/**", 7 | "/usr/include/php", 8 | "/usr/include/php/main", 9 | "/usr/include/php/Zend", 10 | "/usr/include/php/TSRM", 11 | "/usr/include/php/ext" 12 | ], 13 | "defines": [], 14 | "compilerPath": "/usr/bin/gcc", 15 | "cStandard": "c11", 16 | "cppStandard": "c++17", 17 | "intelliSenseMode": "linux-gcc-x64" 18 | } 19 | ], 20 | "version": 4 21 | } -------------------------------------------------------------------------------- /tests/cli/path_traversal/test_path_traversal_file_get_contents_multiple_php_filter.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test path traversal (file_get_contents with multiple php filter) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --FILE-- 9 | 16 | 17 | --POST-- 18 | test=../file 19 | 20 | --EXPECTREGEX-- 21 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a path traversal attack.* 22 | -------------------------------------------------------------------------------- /tests/server/test_ip_rate_limiting_10_minutes/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [ 6 | { 7 | "method": "GET", 8 | "route": "\/test", 9 | "forceProtectionOff": false, 10 | "graphql": null, 11 | "allowedIPAddresses": [], 12 | "rateLimiting": { 13 | "enabled": true, 14 | "maxRequests": 30, 15 | "windowSizeInMS": 600000 16 | } 17 | } 18 | ], 19 | "blockedUserIds": [], 20 | "allowedIPAddresses": [], 21 | "receivedAnyStats": true 22 | } -------------------------------------------------------------------------------- /lib/php-extension/include/Server.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class Server { 4 | private: 5 | zval* GetServerVar(); 6 | 7 | public: 8 | Server() = default; 9 | 10 | std::string GetVar(const char* var); 11 | 12 | std::string GetMethod(); 13 | 14 | std::string GetMethodFromQuery(); 15 | 16 | std::string GetRoute(); 17 | 18 | std::string GetStatusCode(); 19 | 20 | std::string GetUrl(); 21 | 22 | std::string GetBody(); 23 | 24 | std::string GetQuery(); 25 | 26 | std::string GetHeaders(); 27 | 28 | std::string GetPost(); 29 | 30 | bool IsHttps(); 31 | 32 | ~Server() = default; 33 | }; 34 | 35 | extern Server server; 36 | -------------------------------------------------------------------------------- /lib/request-processor/go.mod: -------------------------------------------------------------------------------- 1 | module main 2 | 3 | go 1.25.4 4 | 5 | require ( 6 | github.com/stretchr/testify v1.11.1 7 | go4.org/netipx v0.0.0-20231129151722-fdeea329fbba 8 | google.golang.org/grpc v1.77.0 9 | google.golang.org/protobuf v1.36.10 10 | ) 11 | 12 | require ( 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/pmezard/go-difflib v1.0.0 // indirect 15 | golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 // indirect 16 | golang.org/x/sys v0.37.0 // indirect 17 | golang.org/x/text v0.30.0 // indirect 18 | google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect 19 | gopkg.in/yaml.v3 v3.0.1 // indirect 20 | ) 21 | -------------------------------------------------------------------------------- /tests/server/test_allowed_ip/change_config_remove_allowed_ip.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [ 6 | { 7 | "method": "GET", 8 | "route": "/test", 9 | "forceProtectionOff": false, 10 | "graphql": null, 11 | "allowedIPAddresses": [], 12 | "rateLimiting": { 13 | "enabled": false, 14 | "maxRequests": 100, 15 | "windowSizeInMS": 60000 16 | } 17 | } 18 | ], 19 | "blockedUserIds": [], 20 | "allowedIPAddresses": [], 21 | "receivedAnyStats": true 22 | } -------------------------------------------------------------------------------- /tests/server/test_group_rate_limiting_10_minutes/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [ 6 | { 7 | "method": "GET", 8 | "route": "\/test", 9 | "forceProtectionOff": false, 10 | "graphql": null, 11 | "allowedIPAddresses": [], 12 | "rateLimiting": { 13 | "enabled": true, 14 | "maxRequests": 30, 15 | "windowSizeInMS": 600000 16 | } 17 | } 18 | ], 19 | "blockedUserIds": [], 20 | "allowedIPAddresses": [], 21 | "receivedAnyStats": true 22 | } -------------------------------------------------------------------------------- /tests/server/test_user_rate_limiting_10_minutes/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [ 6 | { 7 | "method": "GET", 8 | "route": "\/test", 9 | "forceProtectionOff": false, 10 | "graphql": null, 11 | "allowedIPAddresses": [], 12 | "rateLimiting": { 13 | "enabled": true, 14 | "maxRequests": 30, 15 | "windowSizeInMS": 600000 16 | } 17 | } 18 | ], 19 | "blockedUserIds": [], 20 | "allowedIPAddresses": [], 21 | "receivedAnyStats": true 22 | } -------------------------------------------------------------------------------- /tests/server/test_rate_limiting_with_multiple_users_on_the_same_ip/index.php: -------------------------------------------------------------------------------- 1 | block && $decision->type == "ratelimited") { 13 | http_response_code(429); 14 | echo "Rate limit exceeded"; 15 | exit(); 16 | } 17 | } 18 | 19 | // Continue handling the request 20 | echo "Request successful!"; 21 | 22 | ?> 23 | -------------------------------------------------------------------------------- /lib/request-processor/helpers/trimInvisible_test.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import "testing" 4 | 5 | func TestTrimInvisible(t *testing.T) { 6 | tests := []struct { 7 | input string 8 | expected string 9 | }{ 10 | {"", ""}, 11 | {" ls -la \t", "ls -la"}, 12 | {"ls -la\u0000", "ls -la"}, 13 | {"\u0000ls -la", "ls -la"}, 14 | {"\u0000ls -la\u0000", "ls -la"}, 15 | {"ls \u0000-la\u0000", "ls \u0000-la"}, 16 | {"\u0020 \u0020", ""}, 17 | } 18 | 19 | for _, test := range tests { 20 | result := TrimInvisible(test.input) 21 | if result != test.expected { 22 | t.Errorf("TrimInvisible(%q) = %q; want %q", test.input, result, test.expected) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/cli/shell_injection/test_exec_in_class.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test PHP shell injection (exec) in class 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=www.example`whoami`.com 10 | 11 | --FILE-- 12 | test(); 25 | echo "\n"; 26 | 27 | ?> 28 | 29 | --EXPECTREGEX-- 30 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a shell injection.* -------------------------------------------------------------------------------- /docs/debian10.md: -------------------------------------------------------------------------------- 1 | # Debian10 2 | 3 | 1. In order to install the Aikido PHP firewall on Debian 10, you would need first to install gcc version 10: 4 | 5 | ``` 6 | wget https://mirrors.edge.kernel.org/ubuntu/pool/main/g/gcc-10/gcc-10-base_10-20200411-0ubuntu1_amd64.deb && dpkg -i gcc-10-base_10-20200411-0ubuntu1_amd64.deb 7 | wget https://mirrors.edge.kernel.org/ubuntu/pool/main/g/gcc-10/libgcc-s1_10-20200411-0ubuntu1_amd64.deb && dpkg -i libgcc-s1_10-20200411-0ubuntu1_amd64.deb 8 | ``` 9 | 10 | Be aware that manually installing packages from a newer release can cause dependency conflicts. Ensure that your system's other packages remain compatible and test in staging environment before releasing. 11 | -------------------------------------------------------------------------------- /tests/server/test_bypassed_ip_for_rate_limiting/change_config_remove_bypassed_ip.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [ 6 | { 7 | "method": "GET", 8 | "route": "/test", 9 | "forceProtectionOff": false, 10 | "graphql": null, 11 | "allowedIPAddresses": [], 12 | "rateLimiting": { 13 | "enabled": true, 14 | "maxRequests": 10, 15 | "windowSizeInMS": 60000 16 | } 17 | } 18 | ], 19 | "blockedUserIds": [], 20 | "allowedIPAddresses": [], 21 | "receivedAnyStats": true 22 | } -------------------------------------------------------------------------------- /tests/server/test_rate_limiting_with_multiple_users_on_the_same_ip/test.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import time 3 | import sys 4 | from testlib import * 5 | 6 | def run_test(): 7 | for userId, userName in [("123", "Tudor"), ("0", "Test"), ("50", "Willem")]: 8 | for _ in range(25): 9 | response = php_server_post("/", { "userId": userId, "userName": userName }) 10 | assert_response_code_is(response, 200) 11 | assert_response_header_contains(response, "Content-Type", "text") 12 | assert_response_body_contains(response, "Request successful") 13 | 14 | 15 | if __name__ == "__main__": 16 | load_test_args() 17 | run_test() 18 | -------------------------------------------------------------------------------- /benchmarks/server/benchmark.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import time 3 | import sys 4 | from testlib import * 5 | import json 6 | 7 | 8 | def run_benchmark(): 9 | for _ in range(10000): 10 | response = php_server_post("/test?userId=12345&status=pending&orderId=80", 11 | { "orderPlaced": True }, 12 | { "Content-Type": "application/json", "X-API-Key": "abcdef12345" }, 13 | benchmark=True) 14 | assert_response_code_is(response, 200) 15 | 16 | if __name__ == "__main__": 17 | load_test_args() 18 | benchmark_warmup() 19 | run_benchmark() 20 | benchmark_store_results() 21 | -------------------------------------------------------------------------------- /benchmarks/server/benchmark_request_with_rate_limiting_enabled/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [ 6 | { 7 | "method": "POST", 8 | "route": "\/test", 9 | "forceProtectionOff": false, 10 | "graphql": null, 11 | "allowedIPAddresses": [], 12 | "rateLimiting": { 13 | "enabled": true, 14 | "maxRequests": 100000, 15 | "windowSizeInMS": 600000 16 | } 17 | } 18 | ], 19 | "blockedUserIds": [], 20 | "allowedIPAddresses": [], 21 | "receivedAnyStats": true 22 | } -------------------------------------------------------------------------------- /tests/server/test_force_protection_off_for_route/change_config_set_force_protection_off.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [ 6 | { 7 | "method": "POST", 8 | "route": "/", 9 | "forceProtectionOff": true, 10 | "graphql": null, 11 | "allowedIPAddresses": [], 12 | "rateLimiting": { 13 | "enabled": false, 14 | "maxRequests": 100, 15 | "windowSizeInMS": 60000 16 | } 17 | } 18 | ], 19 | "blockedUserIds": [], 20 | "allowedIPAddresses": [], 21 | "receivedAnyStats": true 22 | } -------------------------------------------------------------------------------- /tests/server/test_blocked_outbound_domains/config_no_blocking.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": true, 9 | "block": false, 10 | "blockNewOutgoingRequests": true, 11 | "domains": [ 12 | { 13 | "hostname": "safe.example.com", 14 | "mode": "allow" 15 | }, 16 | { 17 | "hostname": "evil.example.com", 18 | "mode": "block" 19 | }, 20 | { 21 | "hostname": "allowed-site.com", 22 | "mode": "allow" 23 | } 24 | ] 25 | } 26 | 27 | -------------------------------------------------------------------------------- /tests/server/test_sql_injection_pdostatement/index.php: -------------------------------------------------------------------------------- 1 | setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 8 | 9 | $pdo->exec("CREATE TABLE IF NOT EXISTS cats ( 10 | id INTEGER PRIMARY KEY AUTOINCREMENT, 11 | name TEXT NOT NULL, 12 | age INTEGER NOT NULL 13 | )"); 14 | 15 | 16 | $stmt = $pdo->prepare("INSERT INTO cats (name, age) VALUES ( :name, '" . $_GET['age'] . "')"); 17 | $stmt->execute([':name' => $_GET['name']]); 18 | 19 | echo "Query executed!"; 20 | 21 | } catch (PDOException $e) { 22 | echo "Error: " . $e->getMessage(); 23 | } 24 | ?> -------------------------------------------------------------------------------- /tests/server/test_blocked_outbound_domains/config_disable_block_new.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "receivedAnyStats": true, 9 | "block": true, 10 | "blockNewOutgoingRequests": false, 11 | "domains": [ 12 | { 13 | "hostname": "safe.example.com", 14 | "mode": "allow" 15 | }, 16 | { 17 | "hostname": "evil.example.com", 18 | "mode": "block" 19 | }, 20 | { 21 | "hostname": "allowed-site.com", 22 | "mode": "allow" 23 | } 24 | ] 25 | } 26 | 27 | -------------------------------------------------------------------------------- /lib/php-extension/HandleSetToken.cpp: -------------------------------------------------------------------------------- 1 | #include "Includes.h" 2 | 3 | ZEND_FUNCTION(set_token) { 4 | ScopedTimer scopedTimer("set_token", "aikido_op"); 5 | 6 | if (IsAikidoDisabledOrBypassed()) { 7 | RETURN_BOOL(false); 8 | } 9 | 10 | char* token = nullptr; 11 | size_t tokenLength = 0; 12 | 13 | ZEND_PARSE_PARAMETERS_START(1, 1) 14 | Z_PARAM_STRING(token, tokenLength) 15 | ZEND_PARSE_PARAMETERS_END(); 16 | 17 | if (!token || tokenLength == 0) { 18 | AIKIDO_LOG_ERROR("set_token: token is null!\n"); 19 | RETURN_BOOL(false); 20 | } 21 | 22 | requestProcessor.LoadConfigWithTokenFromPHPSetToken(std::string(token, tokenLength)); 23 | RETURN_BOOL(true); 24 | } 25 | -------------------------------------------------------------------------------- /lib/request-processor/helpers/getPortFromURL_test.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "net/url" 5 | "testing" 6 | ) 7 | 8 | func TestGetPortFromURL(t *testing.T) { 9 | tests := []struct { 10 | input string 11 | expected uint32 12 | }{ 13 | {"http://localhost:4000", 4000}, 14 | {"http://localhost", 80}, 15 | {"https://localhost", 443}, 16 | {"ftp://localhost", 0}, 17 | {"https://test.com:8080/test?abc=123", 8080}, 18 | } 19 | 20 | for _, test := range tests { 21 | parsedURL, _ := url.Parse(test.input) 22 | result := GetPortFromURL(parsedURL) 23 | 24 | if result != test.expected { 25 | t.Errorf("For URL %s, expected port %d, but got %d", test.input, test.expected, result) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/server/test_allowed_ip/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [ 6 | { 7 | "method": "GET", 8 | "route": "/somethingVerySpecific", 9 | "forceProtectionOff": false, 10 | "graphql": null, 11 | "allowedIPAddresses": [ 12 | "185.245.255.212" 13 | ], 14 | "rateLimiting": { 15 | "enabled": false, 16 | "maxRequests": 100, 17 | "windowSizeInMS": 60000 18 | } 19 | } 20 | ], 21 | "blockedUserIds": [], 22 | "allowedIPAddresses": [], 23 | "receivedAnyStats": true 24 | } -------------------------------------------------------------------------------- /tests/server/test_allowed_ips_lists/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [], 6 | "blockedUserIds": [], 7 | "allowedIPAddresses": [], 8 | "lists_allowedIPAddresses": [ 9 | { 10 | "key": "geoip/Romania;ro", 11 | "source": "geoip", 12 | "description": "geo restrictions", 13 | "ips": [ 14 | "2.17.116.0/22", 15 | "2.57.120.0/24", 16 | "2.57.121.0/26", 17 | "2.57.121.64/28", 18 | "2.57.121.80/29" 19 | ] 20 | } 21 | ], 22 | "receivedAnyStats": true, 23 | "blockedIPAddresses": [] 24 | } -------------------------------------------------------------------------------- /tests/server/test_rate_limiting_with_user_on_multiple_ips/index.php: -------------------------------------------------------------------------------- 1 | block && $decision->type == "ratelimited" && $decision->trigger == "user") { 15 | http_response_code(429); 16 | echo "Rate limit exceeded"; 17 | exit(); 18 | } 19 | } 20 | 21 | // Continue handling the request 22 | echo "Request successful!"; 23 | 24 | ?> 25 | -------------------------------------------------------------------------------- /tests/server/test_received_any_stats_true/test.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import time 3 | import sys 4 | from testlib import * 5 | 6 | ''' 7 | 1. Sets up receiveAnyStats to true. 8 | 2. Checks that the 'started' event is valid. 9 | 3. After 1 minute, verifies that the hearbeat events was not yet sent. 10 | ''' 11 | 12 | def run_test(): 13 | php_server_get("/") 14 | 15 | events = mock_server_get_events() 16 | assert_events_length_is(events, 1) 17 | assert_started_event_is_valid(events[0]) 18 | 19 | mock_server_wait_for_new_events(70) 20 | 21 | events = mock_server_get_events() 22 | assert_events_length_is(events, 1) 23 | 24 | if __name__ == "__main__": 25 | load_test_args() 26 | run_test() 27 | -------------------------------------------------------------------------------- /.devcontainer/centos_arm/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Centos (arm64)", 3 | "runArgs": [], 4 | "mounts": [ 5 | "source=${localWorkspaceFolder}/.devcontainer/shared,target=/shared,type=bind" 6 | ], 7 | "build": { 8 | "platform": "linux/arm64", 9 | "dockerfile": "Dockerfile", 10 | "args": { 11 | "PHP_VERSION": "8.2" 12 | } 13 | }, 14 | "customizations": { 15 | "vscode": { 16 | "extensions": [ 17 | "golang.go", 18 | "github.vscode-github-actions", 19 | "ms-vscode.cpptools-extension-pack", 20 | "ms-vscode.cpptools", 21 | "ms-vscode.cpptools-themes", 22 | "austin.code-gnu-global", 23 | "ms-vscode.makefile-tools" 24 | ] 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /lib/request-processor/handle_shell_execution.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "main/attack" 5 | "main/context" 6 | "main/log" 7 | shell_injection "main/vulnerabilities/shell-injection" 8 | ) 9 | 10 | func OnPreShellExecuted() string { 11 | cmd := context.GetCmd() 12 | operation := context.GetFunctionName() 13 | if cmd == "" { 14 | return "" 15 | } 16 | 17 | log.Info("Got shell command: ", cmd) 18 | 19 | if context.IsEndpointProtectionTurnedOff() { 20 | log.Infof("Protection is turned off -> will not run detection logic!") 21 | return "" 22 | } 23 | 24 | res := shell_injection.CheckContextForShellInjection(cmd, operation) 25 | if res != nil { 26 | return attack.ReportAttackDetected(res) 27 | } 28 | return "" 29 | } 30 | -------------------------------------------------------------------------------- /tests/server/test_group_rate_limiting_1_minute/index.php: -------------------------------------------------------------------------------- 1 | block && $decision->type == "ratelimited" && $decision->trigger == "group") { 18 | http_response_code(429); 19 | echo "Rate limit exceeded"; 20 | exit(); 21 | } 22 | } 23 | 24 | // Continue handling the request 25 | echo "Request successful!"; 26 | 27 | ?> -------------------------------------------------------------------------------- /lib/request-processor/api_discovery/getApiInfo_test.go: -------------------------------------------------------------------------------- 1 | package api_discovery 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestIsObject(t *testing.T) { 10 | t.Run("Test isObject", func(t *testing.T) { 11 | assert.Equal(t, false, isObject("")) 12 | assert.Equal(t, false, isObject([]string{"1"})) 13 | assert.Equal(t, false, isObject(nil)) 14 | 15 | assert.Equal(t, true, isObject(map[string]any{"1": "2"})) 16 | assert.Equal(t, true, isObject(map[string]string{"1": "2"})) 17 | assert.Equal(t, true, isObject(map[string]int{"1": 500})) 18 | assert.Equal(t, true, isObject(map[string][]string{"1": {"2"}})) 19 | assert.Equal(t, true, isObject(map[string][]any{"1": {"2"}})) 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /tests/server/test_domains/test.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import time 3 | import sys 4 | from testlib import * 5 | 6 | ''' 7 | 1. Sets up a simple config. 8 | 2. Sends one request that will trigger multiple curl reuqests from php. 9 | 3. Waits for the heartbeat event and validates it. 10 | ''' 11 | 12 | def run_test(): 13 | response = php_server_get("/") 14 | assert_response_code_is(response, 200) 15 | 16 | mock_server_wait_for_new_events(70) 17 | 18 | events = mock_server_get_events() 19 | assert_events_length_is(events, 2) 20 | assert_started_event_is_valid(events[0]) 21 | assert_event_contains_subset_file(events[1], "expect_domains.json") 22 | 23 | if __name__ == "__main__": 24 | load_test_args() 25 | run_test() 26 | -------------------------------------------------------------------------------- /tests/server/test_path_traversal/expect_detection_not_blocked.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "detected_attack", 3 | "request": { 4 | "headers": { 5 | "content_type": [ 6 | "application/json" 7 | ] 8 | }, 9 | "method": "POST", 10 | "body": "{\"file\": \"/etc/passwd\"}", 11 | "route": "/testDetection" 12 | }, 13 | "attack": { 14 | "kind": "path_traversal", 15 | "operation": "fopen", 16 | "blocked": false, 17 | "source": "body", 18 | "path": ".file", 19 | "payload": "/etc/passwd", 20 | "metadata": { 21 | "filename": "/etc/passwd" 22 | } 23 | }, 24 | "agent": { 25 | "dryMode": true 26 | } 27 | } -------------------------------------------------------------------------------- /lib/request-processor/handle_sql_queries.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "main/attack" 5 | "main/context" 6 | "main/log" 7 | sql_injection "main/vulnerabilities/sql-injection" 8 | ) 9 | 10 | func OnPreSqlQueryExecuted() string { 11 | query := context.GetSqlQuery() 12 | dialect := context.GetSqlDialect() 13 | operation := context.GetFunctionName() 14 | if query == "" || dialect == "" { 15 | return "" 16 | } 17 | 18 | if context.IsEndpointProtectionTurnedOff() { 19 | log.Infof("Protection is turned off -> will not run detection logic!") 20 | return "" 21 | } 22 | 23 | res := sql_injection.CheckContextForSqlInjection(query, operation, dialect) 24 | if res != nil { 25 | return attack.ReportAttackDetected(res) 26 | } 27 | return "" 28 | } 29 | -------------------------------------------------------------------------------- /tests/server/test_http_method_override/expect_method_overrides.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "heartbeat", 3 | "routes": [ 4 | { 5 | "path": "/", 6 | "method": "GET", 7 | "hits": 8, 8 | "rateLimitedCount": 3 9 | }, 10 | { 11 | "path": "/", 12 | "method": "POST", 13 | "hits": 1 14 | }, 15 | { 16 | "path": "/", 17 | "method": "PUT", 18 | "hits": 3 19 | }, 20 | { 21 | "path": "/", 22 | "method": "DELETE", 23 | "hits": 1 24 | }, 25 | { 26 | "path": "/", 27 | "method": "PATCH", 28 | "hits": 1 29 | } 30 | ] 31 | } 32 | 33 | -------------------------------------------------------------------------------- /tests/server/test_non_allowed_ip_is_not_blocked_if_bypassed/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [ 6 | { 7 | "method": "GET", 8 | "route": "/somethingVerySpecific", 9 | "forceProtectionOff": false, 10 | "graphql": null, 11 | "allowedIPAddresses": [ 12 | "185.245.255.212" 13 | ], 14 | "rateLimiting": { 15 | "enabled": false, 16 | "maxRequests": 100, 17 | "windowSizeInMS": 60000 18 | } 19 | } 20 | ], 21 | "blockedUserIds": [], 22 | "allowedIPAddresses": ["185.245.255.211"], 23 | "receivedAnyStats": true 24 | } -------------------------------------------------------------------------------- /lib/request-processor/aikido_types/handle.go: -------------------------------------------------------------------------------- 1 | package aikido_types 2 | 3 | import "main/ipc/protos" 4 | 5 | type HandlerFunction func() string 6 | 7 | type Method struct { 8 | ClassName string 9 | MethodName string 10 | } 11 | 12 | type RequestShutdownParams struct { 13 | Server *ServerData 14 | Method string 15 | Route string 16 | RouteParsed string 17 | StatusCode int 18 | User string 19 | UserAgent string 20 | IP string 21 | Url string 22 | RateLimitGroup string 23 | APISpec *protos.APISpec 24 | RateLimited bool 25 | QueryParsed map[string]interface{} 26 | IsWebScanner bool 27 | ShouldDiscoverRoute bool 28 | } 29 | -------------------------------------------------------------------------------- /tests/cli/shell_execution/test_proc_open_with_command_as_array.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test PHP shell execution functions 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | 7 | --SKIPIF-- 8 | = 7.4."); ?> 9 | 10 | --FILE-- 11 | array("pipe", "r"), // stdin is a pipe that the child will read from 15 | 1 => array("pipe", "w"), // stdout is a pipe that the child will write to 16 | 2 => array("pipe", "w") // stderr is a pipe that the child will write to 17 | ); 18 | 19 | $command = ["echo", "test"]; 20 | $process = proc_open($command, $descriptorspec, $pipes); 21 | 22 | ?> 23 | 24 | --EXPECTF-- 25 | -------------------------------------------------------------------------------- /.devcontainer/centos/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Centos Dev Container", 3 | "runArgs": ["--privileged"], 4 | "mounts": [ 5 | "source=${localWorkspaceFolder}/.devcontainer/shared,target=/shared,type=bind" 6 | ], 7 | "build": { 8 | "platform": "linux/amd64", 9 | "dockerfile": "Dockerfile", 10 | "args": { 11 | "PHP_VERSION": "8.2" 12 | } 13 | }, 14 | "customizations": { 15 | "vscode": { 16 | "extensions": [ 17 | "golang.go", 18 | "github.vscode-github-actions", 19 | "ms-vscode.cpptools-extension-pack", 20 | "ms-vscode.cpptools", 21 | "ms-vscode.cpptools-themes", 22 | "austin.code-gnu-global", 23 | "ms-vscode.makefile-tools", 24 | "ms-python.vscode-pylance" 25 | ] 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /lib/php-extension/include/HandleShouldBlockRequest.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | ZEND_BEGIN_ARG_INFO(arginfo_aikido_should_block_request, 0) 4 | // No arguments 5 | ZEND_END_ARG_INFO() 6 | 7 | ZEND_BEGIN_ARG_INFO(arginfo_aikido_auto_block_request, 0) 8 | // No arguments 9 | ZEND_END_ARG_INFO() 10 | 11 | // Function called by the users of Aikido, in order to check if a request should be blocked 12 | // based on user information provided via set_user or if rate limiting exceeded. 13 | ZEND_FUNCTION(should_block_request); 14 | 15 | // Function call automatically injected by Aikido in the PHP code, 16 | // in order to automatically block requests based on IP and User-Agent. 17 | ZEND_FUNCTION(auto_block_request); 18 | 19 | void RegisterAikidoBlockRequestStatusClass(); 20 | 21 | bool get_blocking_status(); 22 | -------------------------------------------------------------------------------- /tests/server/test_path_traversal_with_include/expect_detection_not_blocked.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "detected_attack", 3 | "request": { 4 | "headers": { 5 | "content_type": [ 6 | "application/json" 7 | ] 8 | }, 9 | "method": "POST", 10 | "body": "{\"file\": \"../test_include.php\"}", 11 | "route": "/testDetection" 12 | }, 13 | "attack": { 14 | "kind": "path_traversal", 15 | "operation": "include", 16 | "blocked": false, 17 | "source": "body", 18 | "path": ".file", 19 | "payload": "../test_include.php", 20 | "metadata": { 21 | "filename": "../test_include.php" 22 | } 23 | }, 24 | "agent": { 25 | "dryMode": true 26 | } 27 | } -------------------------------------------------------------------------------- /tests/server/test_received_any_stats_false/test.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import time 3 | import sys 4 | from testlib import * 5 | 6 | ''' 7 | 1. Sets up receiveAnyStats to false. 8 | 2. Checks that the 'started' event is valid. 9 | 3. After 1 minute, checks that the heartbeat event was sent. 10 | ''' 11 | 12 | def run_test(): 13 | php_server_get("/") 14 | events = mock_server_get_events() 15 | assert_events_length_is(events, 1) 16 | assert_started_event_is_valid(events[0]) 17 | 18 | mock_server_wait_for_new_events(70) 19 | 20 | events = mock_server_get_events() 21 | assert_events_length_is(events, 2) 22 | assert_event_contains_subset("__root", events[1], {"type": "heartbeat" }) 23 | 24 | if __name__ == "__main__": 25 | load_test_args() 26 | run_test() 27 | -------------------------------------------------------------------------------- /tests/server/test_blocking_imds_ip_only_if_found_in_input/expect_detection_blocked.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "detected_attack", 3 | "request": { 4 | "method": "POST", 5 | "body": "{\"url\": \"169.254.169.254\"}", 6 | "source": "php", 7 | "route": "/" 8 | }, 9 | "attack": { 10 | "kind": "ssrf", 11 | "operation": "curl_exec", 12 | "module": "curl", 13 | "blocked": true, 14 | "source": "body", 15 | "path": ".url", 16 | "payload": "169.254.169.254", 17 | "metadata": { 18 | "hostname": "169.254.169.254", 19 | "isPrivateIp": "true", 20 | "port": "80" 21 | } 22 | }, 23 | "agent": { 24 | "dryMode": false, 25 | "library": "firewall-php" 26 | } 27 | } -------------------------------------------------------------------------------- /tests/server/test_ip_rate_limiting_1_minute/expect_rate_limiting.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "heartbeat", 3 | "stats": { 4 | "requests": { 5 | "total": 115, 6 | "aborted": 0, 7 | "rateLimited": 10, 8 | "attacksDetected": { 9 | "total": 0, 10 | "blocked": 0 11 | } 12 | } 13 | }, 14 | "routes": [ 15 | { 16 | "path": "/", 17 | "method": "GET", 18 | "hits": 15, 19 | "rateLimitedCount": 10 20 | }, 21 | { 22 | "path": "/test", 23 | "method": "GET", 24 | "hits": 100, 25 | "rateLimitedCount": 0 26 | } 27 | ], 28 | "agent": { 29 | "library": "firewall-php" 30 | } 31 | } -------------------------------------------------------------------------------- /tests/server/test_group_rate_limiting_1_minute/expect_rate_limiting.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "heartbeat", 3 | "stats": { 4 | "requests": { 5 | "total": 115, 6 | "aborted": 0, 7 | "rateLimited": 10, 8 | "attacksDetected": { 9 | "total": 0, 10 | "blocked": 0 11 | } 12 | } 13 | }, 14 | "routes": [ 15 | { 16 | "path": "/", 17 | "method": "GET", 18 | "hits": 15, 19 | "rateLimitedCount": 10 20 | }, 21 | { 22 | "path": "/test", 23 | "method": "GET", 24 | "hits": 100, 25 | "rateLimitedCount": 0 26 | } 27 | ], 28 | "agent": { 29 | "library": "firewall-php" 30 | } 31 | } -------------------------------------------------------------------------------- /lib/agent/cloud/cloud.go: -------------------------------------------------------------------------------- 1 | package cloud 2 | 3 | import ( 4 | . "main/aikido_types" 5 | "main/utils" 6 | ) 7 | 8 | func Init(server *ServerData) { 9 | server.StatsData.StartedAt = utils.GetTime() 10 | server.StatsData.MonitoredSinkTimings = make(map[string]MonitoredSinkTimings) 11 | 12 | CheckConfigUpdatedAt(server) 13 | 14 | utils.StartPollingRoutine(server.PollingData.HeartbeatRoutineChannel, server.PollingData.HeartbeatTicker, SendHeartbeatEvent, server) 15 | utils.StartPollingRoutine(server.PollingData.ConfigPollingRoutineChannel, server.PollingData.ConfigPollingTicker, CheckConfigUpdatedAt, server) 16 | } 17 | 18 | func Uninit(server *ServerData) { 19 | utils.StopPollingRoutine(server.PollingData.HeartbeatRoutineChannel) 20 | utils.StopPollingRoutine(server.PollingData.ConfigPollingRoutineChannel) 21 | } 22 | -------------------------------------------------------------------------------- /lib/php-extension/include/Agent.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | typedef GoUint8 (*AgentInitFn)(GoString initJson); 4 | typedef void (*AgentUninitFn)(); 5 | 6 | class Agent { 7 | private: 8 | pid_t GetPIDFromFile(const std::string& aikidoAgentPidPath); 9 | vector GetPIDsFromRunningProcesses(const std::string& aikidoAgentPath); 10 | 11 | bool RemoveSocketFile(const std::string& aikidoAgentSocketPath); 12 | void KillProcesses(std::set& pids); 13 | 14 | bool IsRunning(const std::string& aikidoAgentPath, const std::string& aikidoAgentSocketPath); 15 | 16 | bool Start(std::string aikidoAgentPath); 17 | bool SpawnDetached(std::string aikidoAgentPath); 18 | 19 | public: 20 | Agent() = default; 21 | ~Agent() = default; 22 | 23 | bool Init(); 24 | void Uninit(); 25 | }; 26 | -------------------------------------------------------------------------------- /lib/php-extension/include/HandlePathAccess.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void helper_handle_pre_file_path_access(char *filename, EVENT_ID &eventId); 4 | 5 | void helper_handle_post_file_path_access(EVENT_ID &eventId); 6 | 7 | /* Handles PHP functions that have a file path as first parameter (before) */ 8 | AIKIDO_HANDLER_FUNCTION(handle_pre_file_path_access); 9 | 10 | /* Handles PHP functions that have a file path as first parameter (after) */ 11 | AIKIDO_HANDLER_FUNCTION(handle_post_file_path_access); 12 | 13 | /* Handles PHP functions that have a file path as both first and second parameter (before) */ 14 | AIKIDO_HANDLER_FUNCTION(handle_pre_file_path_access_2); 15 | 16 | /* Handles PHP functions that have a file path as both first and second parameter (after) */ 17 | AIKIDO_HANDLER_FUNCTION(handle_post_file_path_access_2); 18 | -------------------------------------------------------------------------------- /lib/php-extension/include/PhpWrappers.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if PHP_VERSION_ID >= 80100 4 | #define PHP_GET_CHAR_PTR(x) (ZSTR_VAL(x)) 5 | #else 6 | #define PHP_GET_CHAR_PTR(x) ((char*)x) 7 | #endif 8 | 9 | void CallPhpExit(); 10 | 11 | bool CallPhpEcho(std::string s); 12 | 13 | bool CallPhpFunction(std::string function_name, unsigned int params_number = 0, zval *params = nullptr, zval *return_value = nullptr, zval *object = nullptr); 14 | 15 | bool CallPhpFunctionWithOneParam(std::string function_name, long first_param, zval *return_value = nullptr, zval *object = nullptr); 16 | 17 | bool CallPhpFunctionWithOneParam(std::string function_name, std::string first_param, zval *return_value = nullptr, zval *object = nullptr); 18 | 19 | std::string CallPhpFunctionCurlGetInfo(zval *curl_handle, int curl_info_option); 20 | -------------------------------------------------------------------------------- /tests/server/test_path_traversal_with_include/expect_detection_blocked.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "detected_attack", 3 | "request": { 4 | "headers": { 5 | "content_type": [ 6 | "application/json" 7 | ] 8 | }, 9 | "method": "POST", 10 | "body": "{\"file\": \"../test_include.php\"}", 11 | "route": "/testDetection" 12 | }, 13 | "attack": { 14 | "kind": "path_traversal", 15 | "operation": "include", 16 | "blocked": true, 17 | "source": "body", 18 | "path": ".file", 19 | "stack": "#0 {main}", 20 | "payload": "../test_include.php", 21 | "metadata": { 22 | "filename": "../test_include.php" 23 | } 24 | }, 25 | "agent": { 26 | "dryMode": false 27 | } 28 | } -------------------------------------------------------------------------------- /lib/request-processor/helpers/mapIPv4ToIPv6_test.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestMapIPv4ToIPv6(t *testing.T) { 8 | tests := []struct { 9 | ip string 10 | expected string 11 | }{ 12 | {"127.0.0.0", "::ffff:127.0.0.0/128"}, 13 | {"127.0.0.0/8", "::ffff:127.0.0.0/104"}, 14 | {"10.0.0.0", "::ffff:10.0.0.0/128"}, 15 | {"10.0.0.0/8", "::ffff:10.0.0.0/104"}, 16 | {"10.0.0.1", "::ffff:10.0.0.1/128"}, 17 | {"10.0.0.1/8", "::ffff:10.0.0.1/104"}, 18 | {"192.168.0.0/16", "::ffff:192.168.0.0/112"}, 19 | {"172.16.0.0/12", "::ffff:172.16.0.0/108"}, 20 | } 21 | 22 | for _, test := range tests { 23 | result := MapIPv4ToIPv6(test.ip) 24 | if result != test.expected { 25 | t.Errorf("For IP '%s', expected %v but got %v", test.ip, test.expected, result) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/server/test_path_traversal/expect_detection_blocked.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "detected_attack", 3 | "request": { 4 | "headers": { 5 | "content_type": [ 6 | "application/json" 7 | ] 8 | }, 9 | "method": "POST", 10 | "body": "{\"file\": \"/etc/passwd\"}", 11 | "route": "/testDetection" 12 | }, 13 | "attack": { 14 | "kind": "path_traversal", 15 | "operation": "fopen", 16 | "blocked": true, 17 | "source": "body", 18 | "path": ".file", 19 | "stack": "tests/server/test_path_traversal/index.php(13): fopen()", 20 | "payload": "/etc/passwd", 21 | "metadata": { 22 | "filename": "/etc/passwd" 23 | } 24 | }, 25 | "agent": { 26 | "dryMode": false 27 | } 28 | } -------------------------------------------------------------------------------- /tests/server/test_bypassed_ip_for_rate_limiting/start_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "serviceId": 1, 4 | "heartbeatIntervalInMS": 600000, 5 | "endpoints": [ 6 | { 7 | "method": "GET", 8 | "route": "/test", 9 | "forceProtectionOff": false, 10 | "graphql": null, 11 | "allowedIPAddresses": [], 12 | "rateLimiting": { 13 | "enabled": true, 14 | "maxRequests": 10, 15 | "windowSizeInMS": 600000 16 | } 17 | } 18 | ], 19 | "blockedUserIds": [], 20 | "allowedIPAddresses": [ 21 | "93.184.216.34", 22 | "23.45.67.0/24", 23 | "2606:2800:220:1:248:1893:25c8:1946", 24 | "2001:0db9:abcd:1234::/64" 25 | ], 26 | "receivedAnyStats": true 27 | } -------------------------------------------------------------------------------- /lib/php-extension/HandleBypassedIp.cpp: -------------------------------------------------------------------------------- 1 | #include "Includes.h" 2 | 3 | // This variable is used to check if the request is bypassed, 4 | // if true, all blocking checks will be skipped. 5 | bool isIpBypassed = false; 6 | 7 | void InitIpBypassCheck() { 8 | // Reset state for new request 9 | isIpBypassed = false; 10 | 11 | ScopedTimer scopedTimer("check_ip_bypass", "aikido_op"); 12 | 13 | try { 14 | std::string output; 15 | requestProcessor.SendEvent(EVENT_GET_IS_IP_BYPASSED, output); 16 | action.Execute(output); 17 | } catch (const std::exception &e) { 18 | AIKIDO_LOG_ERROR("Exception encountered in processing IP bypass check event: %s\n", e.what()); 19 | } 20 | } 21 | 22 | 23 | bool IsAikidoDisabledOrBypassed() { 24 | return AIKIDO_GLOBAL(disable) == true || isIpBypassed; 25 | } 26 | 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | labels: bug 5 | --- 6 | 7 | ## Bug report 8 | 9 | **Describe the bug** 10 | A clear and concise description of what went wrong. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior: 14 | 1. … 15 | 2. … 16 | 3. … 17 | 18 | **Expected behavior** 19 | What you expected to happen. 20 | 21 | **Actual behavior** 22 | What actually happened (include logs or error messages if possible). 23 | 24 | **Environment** 25 | - OS: [e.g. Ubuntu 22.04, macOS 15.0, Windows 11] 26 | - Language version: [e.g. 7.4, 8.2, 8.3] 27 | - Framework: [e.g. Laravel, Symfony] 28 | - Environment: [local, Docker, cloud, etc.] 29 | - Aikido Package version: [e.g. 1.2.3] 30 | 31 | **Additional context** 32 | Add any other details that might help troubleshoot. 33 | -------------------------------------------------------------------------------- /tests/server/test_path_traversal/expect_detection_blocked_php_filter.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "detected_attack", 3 | "request": { 4 | "headers": { 5 | "content_type": [ 6 | "application/json" 7 | ] 8 | }, 9 | "method": "POST", 10 | "body": "{\"file\": \"php://filter/convert.base64-encode/resource=/etc/passwd\"}", 11 | "route": "/testDetection" 12 | }, 13 | "attack": { 14 | "kind": "path_traversal", 15 | "operation": "fopen", 16 | "blocked": true, 17 | "source": "body", 18 | "path": ".file", 19 | "payload": "php://filter/convert.base64-encode/resource=/etc/passwd", 20 | "metadata": { 21 | "filename": "/etc/passwd" 22 | } 23 | }, 24 | "agent": { 25 | "dryMode": false 26 | } 27 | } -------------------------------------------------------------------------------- /.github/workflows/Dockerfile.build-libs: -------------------------------------------------------------------------------- 1 | # Dockerfile 2 | FROM ubuntu:20.04 3 | 4 | ENV DEBIAN_FRONTEND=noninteractive 5 | 6 | # Base tools & dependencies for building Go c-shared libs and running protoc 7 | RUN apt-get update && \ 8 | apt-get install -y software-properties-common && \ 9 | add-apt-repository ppa:longsleep/golang-backports && \ 10 | apt-get update && \ 11 | apt-get install -y golang-go protobuf-compiler protobuf-compiler-grpc && \ 12 | rm -rf /var/lib/apt/lists/* 13 | 14 | # Go env 15 | ENV GOPATH=/go 16 | ENV GOBIN=/go/bin 17 | ENV PATH=$GOBIN:/usr/local/go/bin:/usr/lib/go/bin:$PATH 18 | 19 | # Install protoc Go plugins 20 | RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@latest && \ 21 | go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest 22 | 23 | WORKDIR /workspace 24 | 25 | CMD ["/bin/bash"] 26 | -------------------------------------------------------------------------------- /tests/server/test_detection_with_aikido_core_down_from_start/test.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import time 3 | import sys 4 | from testlib import * 5 | 6 | ''' 7 | 1. Simulate aikido-core is down by setting AIKIDO_ENDPOINT & AIKIDO_REALTIME_ENDPOINT to some invalid values. 8 | 2. Check that path traversal detection is still emitted and blocked. 9 | 3. Check no events are submitted. 10 | ''' 11 | 12 | def run_test(): 13 | assert_response_code_is(php_server_post("/testDetection", {"folder": "../../../.."}), 500) 14 | 15 | events = mock_server_get_events() 16 | assert_events_length_is(events, 0) 17 | 18 | time.sleep(65) 19 | 20 | assert_response_code_is(php_server_post("/testDetection", {"folder": "../../../.."}), 500) 21 | assert_events_length_is(events, 0) 22 | 23 | if __name__ == "__main__": 24 | load_test_args() 25 | run_test() 26 | -------------------------------------------------------------------------------- /tests/server/test_user_set/test.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import time 3 | import sys 4 | from testlib import * 5 | 6 | import requests 7 | import time 8 | import sys 9 | from testlib import * 10 | 11 | ''' 12 | 1. Sets up a simple config with receivedAnyStats = false (so heartbeat will be sent after 1 minute). 13 | 2. Sends a get request (on the PHP side a user will be set). 14 | 3. Waits for 1 minute and checks if the user is present in the hearbeat request. 15 | ''' 16 | 17 | def run_test(): 18 | php_server_get("/") 19 | 20 | mock_server_wait_for_new_events(70) 21 | 22 | events = mock_server_get_events() 23 | assert_events_length_is(events, 2) 24 | assert_started_event_is_valid(events[0]) 25 | assert_event_contains_subset_file(events[1], "expect_user.json") 26 | 27 | if __name__ == "__main__": 28 | load_test_args() 29 | run_test() 30 | -------------------------------------------------------------------------------- /lib/php-extension/HandleRateLimitGroup.cpp: -------------------------------------------------------------------------------- 1 | #include "Includes.h" 2 | 3 | ZEND_FUNCTION(set_rate_limit_group) { 4 | if (IsAikidoDisabledOrBypassed()) { 5 | RETURN_BOOL(false); 6 | } 7 | 8 | char* group = nullptr; 9 | size_t groupLength = 0; 10 | 11 | ZEND_PARSE_PARAMETERS_START(1, 1) 12 | Z_PARAM_STRING(group, groupLength) 13 | ZEND_PARSE_PARAMETERS_END(); 14 | 15 | if (group == nullptr || groupLength == 0) { 16 | AIKIDO_LOG_ERROR("set_rate_limit_group: group is null!\n"); 17 | RETURN_BOOL(false); 18 | } 19 | 20 | requestCache.rateLimitGroup = std::string(group, groupLength); 21 | 22 | std::string outputEvent; 23 | requestProcessor.SendEvent(EVENT_SET_RATE_LIMIT_GROUP, outputEvent); 24 | AIKIDO_LOG_DEBUG("Set rate limit group to %s\n", requestCache.rateLimitGroup.c_str()); 25 | 26 | RETURN_BOOL(true); 27 | } -------------------------------------------------------------------------------- /tests/server/test_user_set_without_name/test.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import time 3 | import sys 4 | from testlib import * 5 | 6 | import requests 7 | import time 8 | import sys 9 | from testlib import * 10 | 11 | ''' 12 | 1. Sets up a simple config with receivedAnyStats = false (so heartbeat will be sent after 1 minute). 13 | 2. Sends a get request (on the PHP side a user will be set). 14 | 3. Waits for 1 minute and checks if the user is present in the hearbeat request. 15 | ''' 16 | 17 | def run_test(): 18 | php_server_get("/") 19 | 20 | mock_server_wait_for_new_events(70) 21 | 22 | events = mock_server_get_events() 23 | assert_events_length_is(events, 2) 24 | assert_started_event_is_valid(events[0]) 25 | assert_event_contains_subset_file(events[1], "expect_user.json") 26 | 27 | if __name__ == "__main__": 28 | load_test_args() 29 | run_test() 30 | -------------------------------------------------------------------------------- /tests/server/test_blocking_imds_ip_only_if_found_in_input/index.php: -------------------------------------------------------------------------------- 1 | 30 | -------------------------------------------------------------------------------- /tests/server/test_http_method_override/index.php: -------------------------------------------------------------------------------- 1 | block && $decision->type == "ratelimited" && $decision->trigger == "ip") { 23 | http_response_code(429); 24 | echo "Rate limit exceeded by IP: " . $decision->ip; 25 | exit(); 26 | } 27 | } 28 | 29 | echo "Method: " . $method; 30 | 31 | -------------------------------------------------------------------------------- /tests/server/test_stats/test.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import time 3 | import sys 4 | from testlib import * 5 | 6 | ''' 7 | 1. Sets up a simple config. 8 | 2. Sends multiple requests to different routes. 9 | 3. Waits for the heartbeat event and validates the stats. 10 | ''' 11 | 12 | routes = { 13 | "/", 14 | "/test", 15 | "/api/123" 16 | } 17 | 18 | def run_test(): 19 | for route in routes: 20 | for nr_requests in range(10): 21 | response = php_server_get(route) 22 | assert_response_code_is(response, 200) 23 | 24 | mock_server_wait_for_new_events(70) 25 | 26 | events = mock_server_get_events() 27 | assert_events_length_is(events, 2) 28 | assert_started_event_is_valid(events[0]) 29 | assert_event_contains_subset_file(events[1], "expect_stats.json") 30 | 31 | if __name__ == "__main__": 32 | load_test_args() 33 | run_test() 34 | -------------------------------------------------------------------------------- /tests/server/test_ssrf_curl/expect_detection_blocked_resolved_ip.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "detected_attack", 3 | "request": { 4 | "headers": { 5 | "content_type": [ 6 | "application/json" 7 | ] 8 | }, 9 | "method": "POST", 10 | "source": "php", 11 | "route": "/testDetection" 12 | }, 13 | "attack": { 14 | "kind": "ssrf", 15 | "operation": "curl_exec", 16 | "module": "curl", 17 | "blocked": true, 18 | "source": "body", 19 | "path": ".url", 20 | "metadata": { 21 | "hostname": "app.example.local", 22 | "resolvedIp": "127.0.0.1" 23 | }, 24 | "user": { 25 | "id": "12345", 26 | "name": "Tudor" 27 | } 28 | }, 29 | "agent": { 30 | "dryMode": false, 31 | "library": "firewall-php" 32 | } 33 | } -------------------------------------------------------------------------------- /tests/cli/shell_injection/test_proc_open.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test PHP shell injection (proc_open) 3 | 4 | --ENV-- 5 | AIKIDO_LOG_LEVEL=INFO 6 | AIKIDO_BLOCK=1 7 | 8 | --POST-- 9 | test=www.example`whoami`.com 10 | 11 | --FILE-- 12 | array("pipe", "r"), // stdin is a pipe that the child will read from 16 | 1 => array("pipe", "w"), // stdout is a pipe that the child will write to 17 | 2 => array("pipe", "w") // stderr is a pipe that the child will write to 18 | ); 19 | 20 | $process = proc_open('binary --domain www.example`whoami`.com', $descriptorspec, $pipes); 21 | 22 | if (is_resource($process)) { 23 | while ($s = fgets($pipes[1])) { 24 | echo $s; 25 | } 26 | fclose($pipes[1]); 27 | proc_close($process); 28 | } 29 | 30 | ?> 31 | 32 | --EXPECTREGEX-- 33 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked a shell injection.* -------------------------------------------------------------------------------- /tests/server/test_shell_injection/expect_detection_not_blocked.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "detected_attack", 3 | "request": { 4 | "headers": { 5 | "content_type": [ 6 | "application/json" 7 | ] 8 | }, 9 | "method": "POST", 10 | "body": "{\"command\": \"`whoami`\"}", 11 | "source": "php", 12 | "route": "/testDetection" 13 | }, 14 | "attack": { 15 | "kind": "shell_injection", 16 | "operation": "passthru", 17 | "blocked": false, 18 | "source": "body", 19 | "path": ".command", 20 | "payload": "`whoami`", 21 | "metadata": { 22 | "command": "binary --domain www.example`whoami`.com" 23 | }, 24 | "user": { 25 | "id": "12345", 26 | "name": "Tudor" 27 | } 28 | }, 29 | "agent": { 30 | "dryMode": true 31 | } 32 | } -------------------------------------------------------------------------------- /lib/request-processor/helpers/mapIPv4ToIPv6.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | // Maps an IPv4 address to an IPv6 address. 10 | // e.g. 127.0.0.0/8 -> ::ffff:127.0.0.0/104 11 | func MapIPv4ToIPv6(ip string) string { 12 | if !strings.Contains(ip, "/") { 13 | // No CIDR suffix, assume /32 14 | return fmt.Sprintf("::ffff:%s/128", ip) 15 | } 16 | 17 | parts := strings.Split(ip, "/") 18 | suffix, err := strconv.Atoi(parts[1]) 19 | if err != nil { 20 | // Throw an error if the suffix is not a number 21 | // Because the input is statically defined in the code, this should never happen to a end user 22 | panic(fmt.Sprintf("Invalid CIDR suffix: %s", parts[1])) 23 | } 24 | 25 | // we add 96 to the suffix, since ::ffff: already is 96 bits, so the 32 remaining bits are decided by the IPv4 address 26 | return fmt.Sprintf("::ffff:%s/%d", parts[0], suffix+96) 27 | } 28 | -------------------------------------------------------------------------------- /tests/server/test_sql_injection_pdostatement/expect_detection_not_blocked_pdostatement.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "detected_attack", 3 | "request": { 4 | "method": "GET", 5 | "source": "php", 6 | "route": "/testDetection" 7 | }, 8 | "attack": { 9 | "kind": "sql_injection", 10 | "operation": "pdostatement->execute", 11 | "module": "PDOStatement", 12 | "blocked": false, 13 | "source": "query", 14 | "path": ".age", 15 | "payload": "3'); DROP TABLE cats; --", 16 | "metadata": { 17 | "dialect": "sqlite", 18 | "sql": "INSERT INTO cats (name, age) VALUES ( :name, '3'); DROP TABLE cats; --')" 19 | }, 20 | "user": { 21 | "id": "12345", 22 | "name": "Tudor" 23 | } 24 | }, 25 | "agent": { 26 | "dryMode": true, 27 | "library": "firewall-php" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/server/test_ssrf_file_get_contents/expect_detection_blocked_resolved_ip.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "detected_attack", 3 | "request": { 4 | "headers": { 5 | "content_type": [ 6 | "application/json" 7 | ] 8 | }, 9 | "method": "POST", 10 | "source": "php", 11 | "route": "/testDetection" 12 | }, 13 | "attack": { 14 | "kind": "ssrf", 15 | "operation": "file_get_contents", 16 | "module": "", 17 | "blocked": true, 18 | "source": "body", 19 | "path": ".url", 20 | "metadata": { 21 | "hostname": "app.example.local", 22 | "resolvedIp": "127.0.0.1" 23 | }, 24 | "user": { 25 | "id": "12345", 26 | "name": "Tudor" 27 | } 28 | }, 29 | "agent": { 30 | "dryMode": false, 31 | "library": "firewall-php" 32 | } 33 | } -------------------------------------------------------------------------------- /lib/request-processor/helpers/getHostnameAndPortFromURL_test.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestGetHostnameAndPortFromURL(t *testing.T) { 8 | tests := []struct { 9 | url string 10 | hostname string 11 | port uint32 12 | }{ 13 | {"httpsss", "", 0}, 14 | {"http://localhost:4000", "localhost", 4000}, 15 | {"http://localhost", "localhost", 80}, 16 | {"https://localhost", "localhost", 443}, 17 | {"ftp://localhost", "localhost", 0}, 18 | {"https://test.com:8080/test?abc=123", "test.com", 8080}, 19 | } 20 | 21 | for _, test := range tests { 22 | 23 | hostname, port := GetHostnameAndPortFromURL(test.url) 24 | 25 | if hostname != test.hostname { 26 | t.Errorf("For URL %s, expected hostname %s, but got %s", test.url, test.hostname, hostname) 27 | } 28 | if port != test.port { 29 | t.Errorf("For URL %s, expected port %d, but got %d", test.url, test.port, port) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/request-processor/vulnerabilities/shell-injection/checkContextForShellInjection.go: -------------------------------------------------------------------------------- 1 | package shell_injection 2 | 3 | import ( 4 | "main/context" 5 | "main/helpers" 6 | "main/utils" 7 | ) 8 | 9 | func CheckContextForShellInjection(command string, operation string) *utils.InterceptorResult { 10 | trimmedCommand := helpers.TrimInvisible(command) 11 | for _, source := range context.SOURCES { 12 | mapss := source.CacheGet() 13 | 14 | for str, path := range mapss { 15 | trimmedInputString := helpers.TrimInvisible(str) 16 | if detectShellInjection(trimmedCommand, trimmedInputString) { 17 | return &utils.InterceptorResult{ 18 | Operation: operation, 19 | Kind: utils.Shell_injection, 20 | Source: source.Name, 21 | PathToPayload: path, 22 | Metadata: map[string]string{ 23 | "command": command, 24 | }, 25 | Payload: str, 26 | } 27 | } 28 | } 29 | } 30 | 31 | return nil 32 | } 33 | -------------------------------------------------------------------------------- /docs/laravel-forge.md: -------------------------------------------------------------------------------- 1 | # Laravel Forge 2 | 3 | In Forge, go to your server -> `Settings` -> `Environment` and add the `AIKIDO_TOKEN` in the .env file. 4 | 5 | ![Forge Environment](./forge-environment.png) 6 | 7 | You can get your token from the [Aikido Security Dashboard](https://help.aikido.dev/doc/creating-an-aikido-zen-firewall-token/doc6vRJNzC4u). 8 | 9 | Go to "Commands" and run the following by replacing the sudo password with the one that Forge displays when the server is created: 10 | ``` 11 | curl -L -O https://github.com/AikidoSec/firewall-php/releases/download/v1.4.11/aikido-php-firewall.x86_64.deb && echo "YOUR_SUDO_PASSWORD_HERE" | sudo -S dpkg -i -E ./aikido-php-firewall.x86_64.deb && echo "YOUR_SUDO_PASSWORD_HERE" | sudo -S service php8.4-fpm restart 12 | ``` 13 | 14 | ![Forge Commands](./forge-commands.png) 15 | 16 | Alternatively, you can execute the same command directly from the Forge terminal. 17 | 18 | ![Forge Terminal](./forge-launch-terminal.png) 19 | -------------------------------------------------------------------------------- /lib/agent/rate_limiting/rate_limiting.go: -------------------------------------------------------------------------------- 1 | package rate_limiting 2 | 3 | import ( 4 | . "main/aikido_types" 5 | "main/utils" 6 | ) 7 | 8 | func AdvanceRateLimitingQueues(server *ServerData) { 9 | server.RateLimitingMutex.Lock() 10 | defer server.RateLimitingMutex.Unlock() 11 | 12 | for _, endpoint := range server.RateLimitingMap { 13 | AdvanceSlidingWindowMap(endpoint.UserCounts, endpoint.Config.WindowSizeInMinutes) 14 | AdvanceSlidingWindowMap(endpoint.IpCounts, endpoint.Config.WindowSizeInMinutes) 15 | AdvanceSlidingWindowMap(endpoint.RateLimitGroupCounts, endpoint.Config.WindowSizeInMinutes) 16 | } 17 | } 18 | 19 | func Init(server *ServerData) { 20 | utils.StartPollingRoutine(server.PollingData.RateLimitingChannel, server.PollingData.RateLimitingTicker, AdvanceRateLimitingQueues, server) 21 | AdvanceRateLimitingQueues(server) 22 | } 23 | 24 | func Uninit(server *ServerData) { 25 | utils.StopPollingRoutine(server.PollingData.RateLimitingChannel) 26 | } 27 | -------------------------------------------------------------------------------- /tests/server/test_blocking_imds_ip_only_if_found_in_input/test.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import time 3 | import sys 4 | from testlib import * 5 | 6 | 7 | def run_test(): 8 | response = php_server_post("/", {}) 9 | assert_response_code_is(response, 200) 10 | assert_response_body_contains(response, "test_instance_id") 11 | 12 | events = mock_server_get_events() 13 | assert_events_length_is(events, 1) 14 | assert_started_event_is_valid(events[0]) 15 | 16 | response = php_server_post("/", {"url": "169.254.169.254"}) 17 | assert_response_code_is(response, 500) 18 | 19 | mock_server_wait_for_new_events(5) 20 | 21 | events = mock_server_get_events() 22 | assert_events_length_is(events, 2) 23 | assert_started_event_is_valid(events[0]) 24 | assert_event_contains_subset_file(events[1], "expect_detection_blocked.json") 25 | 26 | if __name__ == "__main__": 27 | load_test_args() 28 | run_test() 29 | -------------------------------------------------------------------------------- /lib/request-processor/handle_path_traversal.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "main/attack" 5 | "main/context" 6 | "main/log" 7 | path_traversal "main/vulnerabilities/path-traversal" 8 | ) 9 | 10 | func OnPrePathAccessed() string { 11 | filename := context.GetFilename() 12 | filename2 := context.GetFilename2() 13 | operation := context.GetFunctionName() 14 | 15 | if filename == "" || operation == "" { 16 | return "" 17 | } 18 | 19 | if context.IsEndpointProtectionTurnedOff() { 20 | log.Infof("Protection is turned off -> will not run detection logic!") 21 | return "" 22 | } 23 | 24 | res := path_traversal.CheckContextForPathTraversal(filename, operation, true) 25 | if res != nil { 26 | return attack.ReportAttackDetected(res) 27 | } 28 | 29 | if filename2 != "" { 30 | res = path_traversal.CheckContextForPathTraversal(filename2, operation, true) 31 | if res != nil { 32 | return attack.ReportAttackDetected(res) 33 | } 34 | } 35 | return "" 36 | } 37 | -------------------------------------------------------------------------------- /lib/request-processor/vulnerabilities/web-scanner/paths/directoryNames.go: -------------------------------------------------------------------------------- 1 | package paths 2 | 3 | import "main/utils" 4 | 5 | var directoryNamesList = []string{ 6 | ".", 7 | "..", 8 | ".anydesk", 9 | ".aptitude", 10 | ".aws", 11 | ".azure", 12 | ".cache", 13 | ".circleci", 14 | ".config", 15 | ".dbus", 16 | ".docker", 17 | ".drush", 18 | ".gem", 19 | ".git", 20 | ".github", 21 | ".gnupg", 22 | ".gsutil", 23 | ".hg", 24 | ".idea", 25 | ".java", 26 | ".kube", 27 | ".lftp", 28 | ".minikube", 29 | ".npm", 30 | ".nvm", 31 | ".pki", 32 | ".snap", 33 | ".ssh", 34 | ".subversion", 35 | ".svn", 36 | ".tconn", 37 | ".thunderbird", 38 | ".tor", 39 | ".vagrant.d", 40 | ".vidalia", 41 | ".vim", 42 | ".vmware", 43 | ".vscode", 44 | "apache", 45 | "apache2", 46 | "grub", 47 | "System32", 48 | "tmp", 49 | "xampp", 50 | "cgi-bin", 51 | "%systemroot%", 52 | } 53 | 54 | var DirectoryNames = utils.StringSliceToMap(utils.SliceToLowercase(directoryNamesList)) 55 | -------------------------------------------------------------------------------- /tests/server/test_max_detection_events_per_timeframe/test.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import time 3 | import sys 4 | from testlib import * 5 | 6 | ''' 7 | 1. Sets up a simple config and env AIKIDO_BLOCK=1. 8 | 2. Sends 200 attack requests to a route, that will cause sending a detection event. 9 | 3. Checks that there are no more than 100 detection events submited. 10 | ''' 11 | 12 | def run_test(): 13 | for _ in range(110): 14 | response = php_server_post("/testDetection", {"folder": "../../../.."}) 15 | assert_response_code_is(response, 500) 16 | time.sleep(0.2) 17 | 18 | time.sleep(5) 19 | 20 | events = mock_server_get_events() 21 | assert_events_length_is(events, 101) 22 | assert_started_event_is_valid(events[0]) 23 | for e in events[1:101]: 24 | assert_event_contains_subset("__root", e, {"type": "detected_attack"}) 25 | 26 | 27 | if __name__ == "__main__": 28 | load_test_args() 29 | run_test() 30 | -------------------------------------------------------------------------------- /lib/request-processor/helpers/tryDecodeAsJWT.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "encoding/base64" 5 | "strings" 6 | ) 7 | 8 | type JWTDecodeResult struct { 9 | JWT bool 10 | Object interface{} 11 | } 12 | 13 | func removePadding(s string) string { 14 | return strings.TrimRight(strings.TrimLeft(s, "="), "=") 15 | } 16 | 17 | func tryDecodeAsJWT(jwt string) JWTDecodeResult { 18 | if !strings.Contains(jwt, ".") { 19 | return JWTDecodeResult{JWT: false} 20 | } 21 | parts := strings.Split(jwt, ".") 22 | if len(parts) != 3 { 23 | return JWTDecodeResult{JWT: false} 24 | } 25 | //remove padding 26 | 27 | payload, err := base64.RawURLEncoding.DecodeString(removePadding(parts[1])) 28 | if err != nil { 29 | return JWTDecodeResult{JWT: false} 30 | } 31 | 32 | var object interface{} 33 | err = ParseJSON(payload, &object) 34 | 35 | if err != nil { 36 | return JWTDecodeResult{JWT: false} 37 | } 38 | 39 | return JWTDecodeResult{JWT: true, Object: object} 40 | } 41 | -------------------------------------------------------------------------------- /docs/apache-mod-php.md: -------------------------------------------------------------------------------- 1 | # Apache (mod_php) 2 | 3 | 1. Pass the Aikido environment variables to PHP from your Apache virtual host configuration (or .htaccess) 4 | 5 | `/etc/apache2/sites-enabled/000-default.conf` 6 | ``` 7 | 8 | ... 9 | 10 | SetEnv AIKIDO_TOKEN "AIK_RUNTIME_..." 11 | SetEnv AIKIDO_BLOCK "False" 12 | 13 | ... 14 | 15 | 16 | ... 17 | 18 | 19 | ``` 20 | 21 | You can get your token from the [Aikido Security Dashboard](https://help.aikido.dev/doc/creating-an-aikido-zen-firewall-token/doc6vRJNzC4u). 22 | 23 | You can also use PassEnv if the environment is already configured at the system level. 24 | ``` 25 | 26 | ... 27 | PassEnv AIKIDO_TOKEN 28 | PassEnv AIKIDO_BLOCK 29 | ... 30 | 31 | ``` 32 | 33 | 2. Restart apache 34 | 35 | (This command might differ on your operating system) 36 | 37 | `service apache2 restart` 38 | -------------------------------------------------------------------------------- /lib/php-extension/include/Stats.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class ScopedTimer { 4 | private: 5 | std::string key; 6 | std::string kind; 7 | std::chrono::high_resolution_clock::time_point start; 8 | uint64_t duration = 0; 9 | 10 | public: 11 | ScopedTimer(); 12 | ScopedTimer(std::string key, std::string kind); 13 | void SetSink(std::string key, std::string kind); 14 | void Start(); 15 | void Stop(); 16 | ~ScopedTimer(); 17 | }; 18 | 19 | class SinkStats { 20 | public: 21 | std::string kind; 22 | uint64_t attacksDetected = 0; 23 | uint64_t attacksBlocked = 0; 24 | uint64_t interceptorThrewError = 0; 25 | uint64_t withoutContext = 0; 26 | std::vector timings; 27 | 28 | void IncrementAttacksDetected(); 29 | void IncrementAttacksBlocked(); 30 | void IncrementInterceptorThrewError(); 31 | void IncrementWithoutContext(); 32 | }; 33 | 34 | extern std::unordered_map stats; 35 | -------------------------------------------------------------------------------- /tests/server/test_ssrf_file_get_contents/expect_detection_not_blocked.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "detected_attack", 3 | "request": { 4 | "headers": { 5 | "content_type": [ 6 | "application/json" 7 | ] 8 | }, 9 | "method": "POST", 10 | "body": "{\"url\": \"http://127.0.0.1:8081\"}", 11 | "source": "php", 12 | "route": "/testDetection" 13 | }, 14 | "attack": { 15 | "kind": "ssrf", 16 | "operation": "file_get_contents", 17 | "blocked": false, 18 | "source": "body", 19 | "path": ".url", 20 | "payload": "http://127.0.0.1:8081", 21 | "metadata": { 22 | "hostname": "127.0.0.1", 23 | "port": "8081" 24 | }, 25 | "user": { 26 | "id": "12345", 27 | "name": "Tudor" 28 | } 29 | }, 30 | "agent": { 31 | "dryMode": true, 32 | "library": "firewall-php" 33 | } 34 | } -------------------------------------------------------------------------------- /tests/server/test_ip_rate_limiting_1_minute_for_wildcard_route/expect_rate_limiting.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "heartbeat", 3 | "stats": { 4 | "requests": { 5 | "total": 120, 6 | "aborted": 0, 7 | "rateLimited": 12, 8 | "attacksDetected": { 9 | "total": 0, 10 | "blocked": 0 11 | } 12 | } 13 | }, 14 | "routes": [ 15 | { 16 | "path": "/login", 17 | "method": "GET", 18 | "hits": 3, 19 | "rateLimitedCount": 1 20 | }, 21 | { 22 | "path": "/login", 23 | "method": "POST", 24 | "hits": 2, 25 | "rateLimitedCount": 1 26 | }, 27 | { 28 | "path": "/test", 29 | "method": "GET", 30 | "hits": 100, 31 | "rateLimitedCount": 0 32 | } 33 | ], 34 | "agent": { 35 | "library": "firewall-php" 36 | } 37 | } -------------------------------------------------------------------------------- /tests/server/test_group_rate_limiting_1_minute_for_wildcard_route/expect_rate_limiting.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "heartbeat", 3 | "stats": { 4 | "requests": { 5 | "total": 120, 6 | "aborted": 0, 7 | "rateLimited": 12, 8 | "attacksDetected": { 9 | "total": 0, 10 | "blocked": 0 11 | } 12 | } 13 | }, 14 | "routes": [ 15 | { 16 | "path": "/login", 17 | "method": "GET", 18 | "hits": 3, 19 | "rateLimitedCount": 1 20 | }, 21 | { 22 | "path": "/login", 23 | "method": "POST", 24 | "hits": 2, 25 | "rateLimitedCount": 1 26 | }, 27 | { 28 | "path": "/test", 29 | "method": "GET", 30 | "hits": 100, 31 | "rateLimitedCount": 0 32 | } 33 | ], 34 | "agent": { 35 | "library": "firewall-php" 36 | } 37 | } -------------------------------------------------------------------------------- /tests/server/test_shell_injection/expect_detection_blocked.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "detected_attack", 3 | "request": { 4 | "headers": { 5 | "content_type": [ 6 | "application/json" 7 | ] 8 | }, 9 | "method": "POST", 10 | "body": "{\"command\": \"`whoami`\"}", 11 | "source": "php", 12 | "route": "/testDetection" 13 | }, 14 | "attack": { 15 | "kind": "shell_injection", 16 | "operation": "passthru", 17 | "blocked": true, 18 | "source": "body", 19 | "stack": "tests/server/test_shell_injection/index.php(30): passthru()", 20 | "path": ".command", 21 | "payload": "`whoami`", 22 | "metadata": { 23 | "command": "binary --domain www.example`whoami`.com" 24 | }, 25 | "user": { 26 | "id": "12345", 27 | "name": "Tudor" 28 | } 29 | }, 30 | "agent": { 31 | "dryMode": false 32 | } 33 | } -------------------------------------------------------------------------------- /tests/server/test_ssrf_curl/expect_detection_not_blocked.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "detected_attack", 3 | "request": { 4 | "headers": { 5 | "content_type": [ 6 | "application/json" 7 | ] 8 | }, 9 | "method": "POST", 10 | "body": "{\"url\": \"http://127.0.0.1:8081\"}", 11 | "source": "php", 12 | "route": "/testDetection" 13 | }, 14 | "attack": { 15 | "kind": "ssrf", 16 | "operation": "curl_exec", 17 | "module": "curl", 18 | "blocked": false, 19 | "source": "body", 20 | "path": ".url", 21 | "payload": "http://127.0.0.1:8081", 22 | "metadata": { 23 | "hostname": "127.0.0.1", 24 | "port": "8081" 25 | }, 26 | "user": { 27 | "id": "12345", 28 | "name": "Tudor" 29 | } 30 | }, 31 | "agent": { 32 | "dryMode": true, 33 | "library": "firewall-php" 34 | } 35 | } -------------------------------------------------------------------------------- /tests/cli/sql_injection/sql_injection_pdo_exec.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test PDO::exec() method for SQL injection 3 | --ENV-- 4 | AIKIDO_LOG_LEVEL=DEBUG 5 | AIKIDO_BLOCK=1 6 | 7 | --GET-- 8 | name=ls%F0%28%8C%BC&age='||sqlite_version()||' 9 | 10 | --FILE-- 11 | setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 17 | 18 | $pdo->exec("CREATE TABLE IF NOT EXISTS cats ( 19 | id INTEGER PRIMARY KEY AUTOINCREMENT, 20 | name TEXT NOT NULL, 21 | age TEXT NOT NULL 22 | )"); 23 | 24 | 25 | $pdo->exec("INSERT INTO cats (name, age) VALUES ( 'name', '" . $_GET['age'] . "')"); 26 | 27 | echo "Query executed!"; 28 | var_dump($pdo->query("SELECT * FROM cats")->fetchObject()); 29 | 30 | } catch (PDOException $e) { 31 | echo "Error: " . $e->getMessage(); 32 | } 33 | ?> 34 | 35 | --EXPECTREGEX-- 36 | .*Fatal error: Uncaught Exception: Aikido firewall has blocked an SQL injection.* 37 | -------------------------------------------------------------------------------- /lib/request-processor/helpers/resolveHostname.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "context" 5 | "main/log" 6 | "net" 7 | "time" 8 | ) 9 | 10 | /* 11 | This function tries to resolve the hostname to a private IP adress, if possible. 12 | It does this by calling DNS resolution from the OS (getaddrinfo for Linux). 13 | */ 14 | func ResolveHostname(hostname string) []string { 15 | ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) 16 | defer cancel() 17 | 18 | resolvedIps, err := net.DefaultResolver.LookupHost(ctx, hostname) 19 | if err != nil { 20 | log.Errorf("Failed to resolve hostname %s: %v", hostname, err) 21 | // If timeout is reached or the OS lookup fail, return an emtpy list of resolved IPs 22 | return []string{} 23 | } 24 | return resolvedIps 25 | } 26 | 27 | func FindPrivateIp(resolvedIps []string) string { 28 | for _, resolvedIp := range resolvedIps { 29 | if IsPrivateIP(resolvedIp) { 30 | return resolvedIp 31 | } 32 | } 33 | return "" 34 | } 35 | --------------------------------------------------------------------------------