Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions lib/php-extension/HookAst.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,89 @@ void ensure_ast_hashtable_initialized() {
}
}

/*
Transform ZEND_AST_SHELL_EXEC (backtick operator) into a shell_exec() function call.
This ensures backtick expressions go through the same security hooks as shell_exec().
*/
zend_ast* transform_shell_exec_to_call(zend_ast *shell_exec_ast) {
ensure_ast_hashtable_initialized();
auto& global_ast_to_clean = AIKIDO_GLOBAL(globalAstToClean);

AIKIDO_LOG_DEBUG("Transforming backtick shell execution to shell_exec() call at line %d\n", shell_exec_ast->lineno);

// Create function name node for "shell_exec"
zend_ast_zval *name_var = (zend_ast_zval*)emalloc(sizeof(zend_ast_zval));
name_var->kind = ZEND_AST_ZVAL;
ZVAL_STRING(&name_var->val, "shell_exec");
name_var->val.u2.lineno = shell_exec_ast->lineno;
zend_hash_next_index_insert_ptr(global_ast_to_clean, name_var);

// Create argument list with the shell command as the first argument
zend_ast_list *arg_list = (zend_ast_list*)emalloc(sizeof(zend_ast_list) + sizeof(zend_ast*));
arg_list->kind = ZEND_AST_ARG_LIST;
arg_list->lineno = shell_exec_ast->lineno;
arg_list->children = 1;
arg_list->child[0] = shell_exec_ast->child[0]; // The command expression from backticks
zend_hash_next_index_insert_ptr(global_ast_to_clean, arg_list);

// Create function call node
zend_ast *call = (zend_ast*)emalloc(sizeof(zend_ast) + sizeof(zend_ast*));
call->kind = ZEND_AST_CALL;
call->lineno = shell_exec_ast->lineno;
call->child[0] = (zend_ast*)name_var;
call->child[1] = (zend_ast*)arg_list;
zend_hash_next_index_insert_ptr(global_ast_to_clean, call);

return call;
}

/*
Recursively traverse the AST and transform all ZEND_AST_SHELL_EXEC nodes
into shell_exec() function calls.
*/
void transform_backticks_in_ast(zend_ast *ast) {
if (!ast) {
return;
}

// Handle list nodes (e.g., statement lists, argument lists)
if (zend_ast_is_list(ast)) {
zend_ast_list *list = zend_ast_get_list(ast);
for (uint32_t i = 0; i < list->children; i++) {
if (list->child[i]) {
// Check if this child is a shell exec node
if (list->child[i]->kind == ZEND_AST_SHELL_EXEC) {
list->child[i] = transform_shell_exec_to_call(list->child[i]);
} else {
// Recursively process this child
transform_backticks_in_ast(list->child[i]);
}
}
}
return;
}

// For non-list nodes, check if it's a shell exec
if (ast->kind == ZEND_AST_SHELL_EXEC) {
// This case shouldn't happen in normal traversal since we handle it in the parent,
// but we keep it for completeness
return;
}

// Recursively process children of non-list nodes
uint32_t children = zend_ast_get_num_children(ast);
for (uint32_t i = 0; i < children; i++) {
zend_ast *child = ast->child[i];
if (child) {
if (child->kind == ZEND_AST_SHELL_EXEC) {
ast->child[i] = transform_shell_exec_to_call(child);
} else {
transform_backticks_in_ast(child);
}
}
}
}

zend_ast *create_ast_call(const char *name) {
ensure_ast_hashtable_initialized();

Expand Down Expand Up @@ -123,6 +206,10 @@ void aikido_ast_process(zend_ast *ast) {
return;
}

// Transform backtick shell execution into shell_exec() calls
// This must be done before insert_call_to_ast to ensure backticks are intercepted
transform_backticks_in_ast(ast);

insert_call_to_ast(ast);

if(original_ast_process){
Expand Down
Loading