Download | Plain Text | No Line Numbers


  1. This patch is based on http://kyberdigi.cz/projects/execdir/ but instead trying
  2. to sanitize the exec command string inside PHP this job is handled by bash and
  3. a special restricted PHP-mode which strips off all path informations.
  4.  
  5. This patch depends on:
  6. - bash restricted php mode patch
  7. https://manuel.mausz.at/coding/patches/php/bash-4.2-restricted-php.patch
  8.  
  9. No warranty!
  10. diff -Naur a/ext/standard/exec.c b/ext/standard/exec.c
  11. --- a/ext/standard/exec.c 2025-09-09 13:09:59.000000000 +0200
  12. +++ b/ext/standard/exec.c 2025-09-16 21:32:25.596400863 +0200
  13. @@ -114,17 +114,28 @@
  14. FILE *fp;
  15. char *buf;
  16. int pclose_return;
  17. - char *b, *d=NULL;
  18. + char *cmd_p, *b, *d=NULL;
  19. php_stream *stream;
  20. size_t buflen, bufl = 0;
  21. #if PHP_SIGCHILD
  22. void (*sig_handler)(int) = signal(SIGCHLD, SIG_DFL);
  23. #endif
  24.  
  25. + if (PG(exec_dir) && strlen(PG(exec_dir))) {
  26. + zend_string *cmd_str = zend_string_init(cmd, strlen(cmd), 0);
  27. + zend_string *cmd_esc = php_escape_shell_arg(cmd_str);
  28. + spprintf(&d, 0, "PATH=%s /bin/bash --php -rc %s", PG(exec_dir), ZSTR_VAL(cmd_esc));
  29. + zend_string_release(cmd_esc);
  30. + zend_string_release(cmd_str);
  31. + cmd_p = d;
  32. + } else {
  33. + cmd_p = cmd;
  34. + }
  35. +
  36. #ifdef PHP_WIN32
  37. - fp = VCWD_POPEN(cmd, "rb");
  38. + fp = VCWD_POPEN(cmd_p, "rb");
  39. #else
  40. - fp = VCWD_POPEN(cmd, "r");
  41. + fp = VCWD_POPEN(cmd_p, "r");
  42. #endif
  43. if (!fp) {
  44. php_error_docref(NULL, E_WARNING, "Unable to fork [%s]", cmd);
  45. @@ -498,7 +509,7 @@
  46. PHP_FUNCTION(shell_exec)
  47. {
  48. FILE *in;
  49. - char *command;
  50. + char *command, *command_p;
  51. size_t command_len;
  52. zend_string *ret;
  53. php_stream *stream;
  54. @@ -512,14 +523,26 @@
  55. RETURN_THROWS();
  56. }
  57.  
  58. + if (PG(exec_dir) && strlen(PG(exec_dir))) {
  59. + zend_string *cmd_str = zend_string_init(command, command_len, 0);
  60. + zend_string *cmd_esc = php_escape_shell_arg(cmd_str);
  61. + spprintf(&command_p, 0, "PATH=%s /bin/bash --php -rc %s", PG(exec_dir), ZSTR_VAL(cmd_esc));
  62. + zend_string_release(cmd_esc);
  63. + zend_string_release(cmd_str);
  64. + } else {
  65. + command_p = estrdup(command);
  66. + }
  67. +
  68. #ifdef PHP_WIN32
  69. - if ((in=VCWD_POPEN(command, "rt"))==NULL) {
  70. + if ((in=VCWD_POPEN(command_p, "rt"))==NULL) {
  71. #else
  72. - if ((in=VCWD_POPEN(command, "r"))==NULL) {
  73. + if ((in=VCWD_POPEN(command_p, "r"))==NULL) {
  74. #endif
  75. php_error_docref(NULL, E_WARNING, "Unable to execute '%s'", command);
  76. + efree(command_p);
  77. RETURN_FALSE;
  78. }
  79. + efree(command_p);
  80.  
  81. stream = php_stream_fopen_from_pipe(in, "rb");
  82. ret = php_stream_copy_to_mem(stream, PHP_STREAM_COPY_ALL, 0);
  83. diff -Naur a/ext/standard/file.c b/ext/standard/file.c
  84. --- a/ext/standard/file.c 2025-09-09 13:09:59.000000000 +0200
  85. +++ b/ext/standard/file.c 2025-09-16 21:32:25.597400856 +0200
  86. @@ -28,6 +28,7 @@
  87. #include "ext/standard/basic_functions.h"
  88. #include "php_ini.h"
  89. #include "zend_smart_str.h"
  90. +#include "ext/standard/exec.h"
  91.  
  92. #include <stdio.h>
  93. #include <stdlib.h>
  94. @@ -805,7 +806,18 @@
  95. RETURN_THROWS();
  96. }
  97.  
  98. - fp = VCWD_POPEN(command, posix_mode);
  99. + if (PG(exec_dir) && strlen(PG(exec_dir))) {
  100. + zend_string *cmd_str = zend_string_init(command, command_len, 0);
  101. + zend_string *cmd_esc = php_escape_shell_arg(cmd_str);
  102. + char *tmp;
  103. + spprintf(&tmp, 0, "PATH=%s /bin/bash --php -rc %s", PG(exec_dir), ZSTR_VAL(cmd_esc));
  104. + fp = VCWD_POPEN(tmp, posix_mode);
  105. + efree(tmp);
  106. + zend_string_release(cmd_esc);
  107. + zend_string_release(cmd_str);
  108. + } else {
  109. + fp = VCWD_POPEN(command, posix_mode);
  110. + }
  111. if (!fp) {
  112. php_error_docref2(NULL, command, posix_mode, E_WARNING, "%s", strerror(errno));
  113. efree(posix_mode);
  114. diff -Naur a/ext/standard/filestat.c b/ext/standard/filestat.c
  115. --- a/ext/standard/filestat.c 2025-09-09 13:09:59.000000000 +0200
  116. +++ b/ext/standard/filestat.c 2025-09-16 21:32:25.597400856 +0200
  117. @@ -746,6 +746,7 @@
  118. zend_stat_t *stat_sb = &ssb.sb;
  119. int flags = 0, rmask=S_IROTH, wmask=S_IWOTH, xmask=S_IXOTH; /* access rights defaults to other */
  120. const char *local = NULL;
  121. + char local_safe[MAXPATHLEN];
  122. php_stream_wrapper *wrapper = NULL;
  123.  
  124. if (IS_ACCESS_CHECK(type)) {
  125. @@ -757,7 +758,7 @@
  126. }
  127.  
  128. if ((wrapper = php_stream_locate_url_wrapper(ZSTR_VAL(filename), &local, 0)) == &php_plain_files_wrapper
  129. - && php_check_open_basedir(local)) {
  130. + && type != FS_IS_X && php_check_open_basedir(local)) {
  131. RETURN_FALSE;
  132. }
  133.  
  134. @@ -787,9 +788,25 @@
  135. break;
  136. #endif
  137. #ifdef X_OK
  138. - case FS_IS_X:
  139. - RETURN_BOOL(VCWD_ACCESS(file_path_to_check, X_OK) == 0);
  140. + case FS_IS_X: {
  141. + bool xok = (VCWD_ACCESS(file_path_to_check, X_OK) == 0);
  142. + if (!xok && PG(exec_dir) && strlen(PG(exec_dir))) {
  143. + if (strstr(file_path_to_check, "..")) {
  144. + RETURN_FALSE;
  145. + } else {
  146. + char *b = strrchr(file_path_to_check, PHP_DIR_SEPARATOR);
  147. + if (b) {
  148. + snprintf(local_safe, MAXPATHLEN, "%s%s", PG(exec_dir), b);
  149. + } else {
  150. + snprintf(local_safe, MAXPATHLEN, "%s%c%s", PG(exec_dir), PHP_DIR_SEPARATOR, file_path_to_check);
  151. + }
  152. + file_path_to_check = (char *)&local_safe;
  153. + }
  154. + xok = (VCWD_ACCESS(file_path_to_check, X_OK) == 0);
  155. + }
  156. + RETURN_BOOL(xok);
  157. break;
  158. + }
  159. #endif
  160. }
  161. }
  162. diff -Naur a/ext/standard/proc_open.c b/ext/standard/proc_open.c
  163. --- a/ext/standard/proc_open.c 2025-09-09 13:09:59.000000000 +0200
  164. +++ b/ext/standard/proc_open.c 2025-09-16 21:32:25.597400856 +0200
  165. @@ -780,7 +780,17 @@
  166. zend_string *command = NULL;
  167. uint32_t i = 0;
  168.  
  169. - *argv = safe_emalloc(sizeof(char *), num_elems + 1, 0);
  170. + *argv = safe_emalloc(sizeof(char *), num_elems + 1 + 5, 0);
  171. +
  172. + if (PG(exec_dir) && strlen(PG(exec_dir))) {
  173. + command = zend_string_init("/bin/bash", sizeof("/bin/bash") - 1, 0);
  174. + (*argv)[i++] = estrdup("/bin/bash");
  175. + (*argv)[i++] = estrdup("--php");
  176. + (*argv)[i++] = estrdup("-rc");
  177. + (*argv)[i++] = estrdup("exec \"$@\"");
  178. + (*argv)[i++] = estrdup("bash");
  179. + (*argv)[i] = NULL;
  180. + }
  181.  
  182. ZEND_HASH_FOREACH_VAL(array, arg_zv) {
  183. zend_string *arg_str = get_valid_arg_string(arg_zv, i + 1);
  184. @@ -1278,6 +1288,17 @@
  185. }
  186. #endif
  187.  
  188. + zval envpath;
  189. + if (PG(exec_dir) && strlen(PG(exec_dir))) {
  190. + if (!environment) {
  191. + ALLOC_HASHTABLE(environment);
  192. + zend_hash_init(environment, 1, NULL, NULL, 0);
  193. + }
  194. + zval tmp;
  195. + ZVAL_STRING(&tmp, PG(exec_dir));
  196. + zend_hash_str_add(environment, "PATH", strlen("PATH"), &tmp);
  197. + }
  198. +
  199. if (environment) {
  200. env = php_array_to_envp(environment);
  201. }
  202. @@ -1403,9 +1422,15 @@
  203. if (argv) {
  204. r = posix_spawnp(&child, ZSTR_VAL(command_str), &factions, NULL, argv, (env.envarray ? env.envarray : environ));
  205. } else {
  206. - r = posix_spawn(&child, "/bin/sh" , &factions, NULL,
  207. - (char * const[]) {"sh", "-c", ZSTR_VAL(command_str), NULL},
  208. - env.envarray ? env.envarray : environ);
  209. + if (PG(exec_dir) && strlen(PG(exec_dir))) {
  210. + r = posix_spawn(&child, "/bin/bash" , &factions, NULL,
  211. + (char * const[]) {"bash", "--php", "-rc", ZSTR_VAL(command_str), NULL},
  212. + env.envarray ? env.envarray : environ);
  213. + } else {
  214. + r = posix_spawn(&child, "/bin/sh" , &factions, NULL,
  215. + (char * const[]) {"sh", "-c", ZSTR_VAL(command_str), NULL},
  216. + env.envarray ? env.envarray : environ);
  217. + }
  218. }
  219. posix_spawn_file_actions_destroy(&factions);
  220. if (r != 0) {
  221. @@ -1438,10 +1463,14 @@
  222. }
  223. execvp(ZSTR_VAL(command_str), argv);
  224. } else {
  225. - if (env.envarray) {
  226. - execle("/bin/sh", "sh", "-c", ZSTR_VAL(command_str), NULL, env.envarray);
  227. + if (PG(exec_dir) && strlen(PG(exec_dir))) {
  228. + execle("/bin/bash", "bash", "--php", "-rc", ZSTR_VAL(command_str), NULL, env.envarray);
  229. } else {
  230. - execl("/bin/sh", "sh", "-c", ZSTR_VAL(command_str), NULL);
  231. + if (env.envarray) {
  232. + execle("/bin/sh", "sh", "-c", ZSTR_VAL(command_str), NULL, env.envarray);
  233. + } else {
  234. + execl("/bin/sh", "sh", "-c", ZSTR_VAL(command_str), NULL);
  235. + }
  236. }
  237. }
  238.  
  239. diff -Naur a/main/main.c b/main/main.c
  240. --- a/main/main.c 2025-09-09 13:09:59.000000000 +0200
  241. +++ b/main/main.c 2025-09-16 21:32:25.598400849 +0200
  242. @@ -886,6 +886,7 @@
  243. STD_PHP_INI_ENTRY("syslog.facility", "LOG_USER", PHP_INI_SYSTEM, OnSetFacility, syslog_facility, php_core_globals, core_globals)
  244. STD_PHP_INI_ENTRY("syslog.ident", "php", PHP_INI_SYSTEM, OnUpdateString, syslog_ident, php_core_globals, core_globals)
  245. STD_PHP_INI_ENTRY("syslog.filter", "no-ctrl", PHP_INI_ALL, OnSetLogFilter, syslog_filter, php_core_globals, core_globals)
  246. + STD_PHP_INI_ENTRY("exec_dir", NULL, PHP_INI_SYSTEM, OnUpdateString, exec_dir, php_core_globals, core_globals)
  247. PHP_INI_END()
  248. /* }}} */
  249.  
  250. diff -Naur a/main/php_globals.h b/main/php_globals.h
  251. --- a/main/php_globals.h 2025-09-09 13:09:59.000000000 +0200
  252. +++ b/main/php_globals.h 2025-09-16 21:32:25.598400849 +0200
  253. @@ -173,6 +173,8 @@
  254. char *syslog_ident;
  255. zend_long syslog_filter;
  256. zend_long error_log_mode;
  257. +
  258. + char *exec_dir;
  259. };
  260.  
  261.  
  262. diff -Naur a/php.ini-development b/php.ini-development
  263. --- a/php.ini-development 2025-09-09 13:09:59.000000000 +0200
  264. +++ b/php.ini-development 2025-09-16 21:32:25.599400843 +0200
  265. @@ -317,6 +317,10 @@
  266. ; https://php.net/open-basedir
  267. ;open_basedir =
  268.  
  269. +; Only executables located in the exec_dir will be allowed to be executed
  270. +; via the exec family of functions.
  271. +exec_dir =
  272. +
  273. ; This directive allows you to disable certain functions.
  274. ; It receives a comma-delimited list of function names.
  275. ; https://php.net/disable-functions
  276. diff -Naur a/php.ini-production b/php.ini-production
  277. --- a/php.ini-production 2025-09-09 13:09:59.000000000 +0200
  278. +++ b/php.ini-production 2025-09-16 21:32:25.599400843 +0200
  279. @@ -317,6 +317,10 @@
  280. ; https://php.net/open-basedir
  281. ;open_basedir =
  282.  
  283. +; Only executables located in the exec_dir will be allowed to be executed
  284. +; via the exec family of functions.
  285. +exec_dir =
  286. +
  287. ; This directive allows you to disable certain functions.
  288. ; It receives a comma-delimited list of function names.
  289. ; https://php.net/disable-functions
  290.