Skip to content

Commit

Permalink
Merge pull request #85 from AikidoSec/drop-zend-execute
Browse files Browse the repository at this point in the history
Drop zend_execute and move the IP allowlist blocking to GetBlockingStatus
  • Loading branch information
willem-delbare authored Nov 13, 2024
2 parents 57d352c + 487b0b0 commit 35297dc
Show file tree
Hide file tree
Showing 10 changed files with 62 additions and 82 deletions.
16 changes: 13 additions & 3 deletions docs/should_block_request.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,14 @@ if (extension_loaded('aikido')) {

if ($decision->block) {
if ($decision->type == "blocked") {
// If the user is blocked, return a 403 status code
// If the user/ip is blocked, return a 403 status code
http_response_code(403);
echo "Your user is blocked!";
if ($decision->trigger == "user") {
echo "Your user is blocked!";
}
else if ($decision->trigger == "ip") {
echo "Your IP address is not allowed to access this endpoint! (Your IP: {$decision->ip})";
}
}
else if ($decision->type == "ratelimited") {
// If the rate limit is exceeded, return a 429 status code
Expand Down Expand Up @@ -86,7 +91,12 @@ class ZenBlockDecision

if ($decision->block) {
if ($decision->type == "blocked") {
return response('Your user is blocked!', 403);
if ($decision->trigger == "user") {
return response('Your user is blocked!', 403);
}
else if ($decision->trigger == "ip") {
return response("Your IP address is not allowed to access this endpoint! (Your IP: {$decision->ip})", 403);
}
}
else if ($decision->type == "ratelimited") {
if ($decision->trigger == "user") {
Expand Down
1 change: 0 additions & 1 deletion lib/php-extension/Aikido.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ PHP_MINIT_FUNCTION(aikido) {

HookFunctions();
HookMethods();
HookExecute();

/* If SAPI name is "cli" run in "simple" mode */
if (AIKIDO_GLOBAL(sapi_name) == "cli") {
Expand Down
30 changes: 0 additions & 30 deletions lib/php-extension/Hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,33 +126,3 @@ void UnhookMethods() {
it.second.original_handler = nullptr;
}
}

static void (*original_zend_execute_ex)(zend_execute_data *execute_data) = nullptr;

void aikido_zend_execute_ex(zend_execute_data *execute_data) {
if (action.Exit()) {
AIKIDO_LOG_INFO("Current request is marked for exit. Bailing out...\n");
zend_bailout();
}
if (original_zend_execute_ex) {
original_zend_execute_ex(execute_data);
}
}

void HookExecute() {
if (original_zend_execute_ex) {
AIKIDO_LOG_WARN("Function zend_execute_ex already hooked (original handler %p)!\n", original_zend_execute_ex);
return;
}
original_zend_execute_ex = zend_execute_ex;
zend_execute_ex = aikido_zend_execute_ex;
}

void UnhookExecute() {
if (!original_zend_execute_ex) {
AIKIDO_LOG_WARN("Cannot unhook zend_execute_ex without an original handler (was not hooked before)!\n");
return;
}
zend_execute_ex = original_zend_execute_ex;
original_zend_execute_ex = nullptr;
}
1 change: 0 additions & 1 deletion lib/php-extension/PhpLifecycle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ void PhpLifecycle::ModuleShutdown() {
AIKIDO_LOG_INFO("Unhooking functions...\n");
UnhookFunctions();
UnhookMethods();
UnhookExecute();
AIKIDO_LOG_INFO("Uninitializing Aikido Agent...\n");
AIKIDO_GLOBAL(agent).Uninit();
} else {
Expand Down
4 changes: 0 additions & 4 deletions lib/php-extension/include/Hooks.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,3 @@ void UnhookFunctions();
void HookMethods();

void UnhookMethods();

void HookExecute();

void UnhookExecute();
16 changes: 15 additions & 1 deletion lib/request-processor/attack/attack.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package attack

import (
"encoding/json"
"fmt"
"main/context"
"main/grpc"
Expand Down Expand Up @@ -64,8 +65,21 @@ func BuildAttackDetectedMessage(result utils.InterceptorResult) string {
utils.EscapeHTML(result.PathToPayload))
}

func GetThrowAction(message string, code int) string {
actionMap := map[string]interface{}{
"action": "throw",
"message": message,
"code": code,
}
actionJson, err := json.Marshal(actionMap)
if err != nil {
return ""
}
return string(actionJson)
}

func GetAttackDetectedAction(result utils.InterceptorResult) string {
return fmt.Sprintf(`{"action": "throw", "message": "%s", "code": -1}`, BuildAttackDetectedMessage(result))
return GetThrowAction(BuildAttackDetectedMessage(result), -1)
}

func ReportAttackDetected(res *utils.InterceptorResult) string {
Expand Down
34 changes: 21 additions & 13 deletions lib/request-processor/handle_blocking_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,26 @@ import (
"time"
)

func GetStoreAction(actionType, trigger, ip string) string {
actionMap := map[string]interface{}{
"action": "store",
"type": actionType,
"trigger": trigger,
}
if trigger == "ip" {
actionMap["ip"] = ip
}
actionJson, err := json.Marshal(actionMap)
if err != nil {
return ""
}
return string(actionJson)
}

func OnGetBlockingStatus() string {
userId := context.GetUserId()
if utils.IsUserBlocked(userId) {
return `{"action": "store", "type": "blocked", "trigger": "user"}`
return GetStoreAction("blocked", "user", "")
}

method := context.GetMethod()
Expand All @@ -35,23 +51,15 @@ func OnGetBlockingStatus() string {
// do a sync call via gRPC to see if the request should be blocked or not
rateLimitingStatus := grpc.GetRateLimitingStatus(method, route, userId, ip, 10*time.Millisecond)
if rateLimitingStatus != nil && rateLimitingStatus.Block {
action := map[string]interface{}{
"action": "store",
"type": "ratelimited",
"trigger": rateLimitingStatus.Trigger,
}
if rateLimitingStatus.Trigger == "ip" {
action["ip"] = ip
}
actionJson, err := json.Marshal(action)
if err == nil {
return string(actionJson)
}
return GetStoreAction("ratelimited", rateLimitingStatus.Trigger, ip)
}
} else {
log.Infof("IP \"%s\" is bypassed for rate limiting!", ip)
}
}

if !utils.IsIpAllowed(endpointData.AllowedIPAddresses, ip) {
return GetStoreAction("blocked", "ip", ip)
}
return ""
}
26 changes: 0 additions & 26 deletions lib/request-processor/handle_request_metadata.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package main

import (
"fmt"
"main/api_discovery"
"main/context"
"main/grpc"
Expand All @@ -12,31 +11,6 @@ import (

func OnPreRequest() string {
context.Clear()

method := context.GetMethod()
route := context.GetParsedRoute()
if method == "" || route == "" {
return ""
}

log.Infof("Got request metadata: %s %s", method, route)

endpointData, err := utils.GetEndpointConfig(method, route)
if err != nil {
log.Debugf("Method+route in not configured in endpoints! Skipping checks...")
return ""
}

ip := context.GetIp()

if !utils.IsIpAllowed(endpointData.AllowedIPAddresses, ip) {
message := "Your IP address is not allowed to access this resource!"
if ip != "" {
message += fmt.Sprintf(" (Your IP: %s)", ip)
}
return fmt.Sprintf(`{"action": "exit", "message": "%s", "response_code": 403}`, message)
}

return ""
}

Expand Down
12 changes: 11 additions & 1 deletion tests/server/test_allowed_ip/index.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
<?php

echo "Something\n";
if (extension_loaded('aikido')) {
$decision = \aikido\should_block_request();

if ($decision->block && $decision->type == "blocked" && $decision->trigger == "ip") {
http_response_code(403);
echo "Your IP address is not allowed to access this endpoint! (Your IP: {$decision->ip})";
exit();
}
}

echo "Something";

?>
4 changes: 2 additions & 2 deletions tests/server/test_allowed_ip/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def run_test():
response = php_server_get("/somethingVerySpecific")
assert_response_code_is(response, 403)
assert_response_header_contains(response, "Content-Type", "text")
assert_response_body_contains(response, "Your IP address is not allowed to access this resource! (Your IP: ")
assert_response_body_contains(response, "Your IP address is not allowed to access this endpoint! (Your IP: ")

apply_config("change_config_remove_allowed_ip.json")

Expand All @@ -27,7 +27,7 @@ def run_test():
response = php_server_get("/somethingVerySpecific")
assert_response_code_is(response, 403)
assert_response_header_contains(response, "Content-Type", "text")
assert_response_body_contains(response, "Your IP address is not allowed to access this resource! (Your IP: ")
assert_response_body_contains(response, "Your IP address is not allowed to access this endpoint! (Your IP: ")


if __name__ == "__main__":
Expand Down

0 comments on commit 35297dc

Please sign in to comment.