diff --git a/apps/common/utils/tool_code.py b/apps/common/utils/tool_code.py index bbc4495b7..d21d8a066 100644 --- a/apps/common/utils/tool_code.py +++ b/apps/common/utils/tool_code.py @@ -197,7 +197,7 @@ exec({dedent(code)!a}) file.write(_code) os.system(f"chown {self.user}:root {exec_python_file}") kwargs = {'cwd': BASE_DIR} - kwargs['env'] = {} + kwargs['env'] = {'LD_PRELOAD': '/opt/maxkb-app/apps/sanbox_ban_host.so'} subprocess_result = subprocess.run( ['su', '-s', python_directory, '-c', "exec(open('" + exec_python_file + "').read())", self.user], text=True, diff --git a/installer/Dockerfile b/installer/Dockerfile index c5ba2e5e3..8e2d3b094 100644 --- a/installer/Dockerfile +++ b/installer/Dockerfile @@ -9,11 +9,12 @@ RUN cd ui && ls -la && if [ -d "dist" ]; then exit 0; fi && \ FROM ghcr.io/1panel-dev/maxkb-base:python3.11-pg17.6 AS stage-build COPY --chmod=700 . /opt/maxkb-app RUN apt-get update && \ - apt-get install -y --no-install-recommends gettext libexpat1-dev libffi-dev && \ + apt-get install -y --no-install-recommends gcc g++ gettext libexpat1-dev libffi-dev && \ apt-get clean all && \ rm -rf /var/lib/apt/lists/* WORKDIR /opt/maxkb-app -RUN rm -rf /opt/maxkb-app/ui && \ +RUN gcc -shared -fPIC -o /opt/maxkb-app/apps/sanbox_ban_host.so /opt/maxkb-app/installer/sanbox_ban_host.c -ldl && \ + rm -rf /opt/maxkb-app/ui && \ pip install uv --break-system-packages && \ python -m uv pip install -r pyproject.toml && \ find /opt/maxkb-app -depth \( -name ".git*" -o -name ".docker*" -o -name ".idea*" -o -name ".editorconfig*" -o -name ".prettierrc*" -o -name "README.md" -o -name "poetry.lock" -o -name "pyproject.toml" \) -exec rm -rf {} + && \ diff --git a/installer/Dockerfile-base b/installer/Dockerfile-base index 7387d66e7..ef24498cc 100644 --- a/installer/Dockerfile-base +++ b/installer/Dockerfile-base @@ -47,6 +47,7 @@ ENV PATH=/opt/py3/bin:$PATH \ MAXKB_SANDBOX=1 \ MAXKB_SANDBOX_PYTHON_PACKAGE_PATHS="/opt/py3/lib/python3.11/site-packages,/opt/maxkb-app/sandbox/python-packages,/opt/maxkb/python-packages" \ MAXKB_SANDBOX_PYTHON_BANNED_KEYWORDS="subprocess.,system(,exec(,execve(,pty.,eval(,compile(,shutil.,input(,__import__" \ + MAXKB_SANDBOX_PYTHON_BANNED_HOSTS="127.0.0.1,localhost" \ MAXKB_ADMIN_PATH=/admin EXPOSE 6379 \ No newline at end of file diff --git a/sanbox_ban_host.c b/sanbox_ban_host.c new file mode 100644 index 000000000..13bd4632e --- /dev/null +++ b/sanbox_ban_host.c @@ -0,0 +1,98 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +static const char *ENV_NAME = "MAXKB_SANDBOX_PYTHON_BANNED_HOSTS"; + +static int match_env_patterns(const char *target, const char *env_val) { + if (!target || !env_val || !*env_val) return 0; + + char *patterns = strdup(env_val); + char *token = strtok(patterns, ","); + int matched = 0; + + while (token) { + // 去掉前后空格 + while (*token == ' ' || *token == '\t') token++; + char *end = token + strlen(token) - 1; + while (end > token && (*end == ' ' || *end == '\t')) *end-- = '\0'; + + if (*token) { + regex_t regex; + if (regcomp(®ex, token, REG_EXTENDED | REG_NOSUB) == 0) { + if (regexec(®ex, target, 0, NULL, 0) == 0) { + matched = 1; + regfree(®ex); + break; + } + regfree(®ex); + } + } + + token = strtok(NULL, ","); + } + + free(patterns); + return matched; +} + +/** + * 拦截 connect() —— 屏蔽直接 IP 访问 + */ +int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { + static int (*real_connect)(int, const struct sockaddr *, socklen_t) = NULL; + static char *banned_env = NULL; + static int initialized = 0; + + if (!real_connect) + real_connect = dlsym(RTLD_NEXT, "connect"); + + if (!initialized) { + banned_env = getenv(ENV_NAME); + initialized = 1; + if (banned_env) + fprintf(stderr, "[ban] Loaded banned hosts: %s\n", banned_env); + } + + if (!banned_env || !*banned_env) + return real_connect(sockfd, addr, addrlen); + + char ip[INET6_ADDRSTRLEN] = {0}; + if (addr->sa_family == AF_INET) { + inet_ntop(AF_INET, &((struct sockaddr_in *)addr)->sin_addr, ip, sizeof(ip)); + } else if (addr->sa_family == AF_INET6) { + inet_ntop(AF_INET6, &((struct sockaddr_in6 *)addr)->sin6_addr, ip, sizeof(ip)); + } + + if (match_env_patterns(ip, banned_env)) { + fprintf(stderr, "Access to host %s is banned for sandbox\n", ip); + return -1; + } + + return real_connect(sockfd, addr, addrlen); +} + +/** + * 拦截 getaddrinfo() —— 屏蔽域名解析 + */ +int getaddrinfo(const char *node, const char *service, + const struct addrinfo *hints, struct addrinfo **res) { + static int (*real_getaddrinfo)(const char *, const char *, + const struct addrinfo *, struct addrinfo **) = NULL; + if (!real_getaddrinfo) + real_getaddrinfo = dlsym(RTLD_NEXT, "getaddrinfo"); + + const char *banned_env = getenv(ENV_NAME); + if (banned_env && node && match_env_patterns(node, banned_env)) { + fprintf(stderr, "Access to host %s is banned for sandbox\n", node); + return EAI_FAIL; // 模拟 DNS 失败 + } + + return real_getaddrinfo(node, service, hints, res); +} \ No newline at end of file