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. --- a/ext/standard/exec.c 2025-10-21 21:23:55.000000000 +0200
  11. +++ b/ext/standard/exec.c 2025-11-14 21:01:23.279669950 +0100
  12. @@ -114,21 +114,32 @@
  13. FILE *fp;
  14. char *buf;
  15. int pclose_return;
  16. - char *b, *d=NULL;
  17. + char *cmd_p, *b, *d=NULL;
  18. php_stream *stream;
  19. size_t buflen, bufl = 0;
  20. #if PHP_SIGCHILD
  21. void (*sig_handler)() = NULL;
  22. #endif
  23.  
  24. + if (PG(exec_dir) && strlen(PG(exec_dir))) {
  25. + zend_string *cmd_str = zend_string_init(cmd, strlen(cmd), 0);
  26. + zend_string *cmd_esc = php_escape_shell_arg(cmd_str);
  27. + spprintf(&d, 0, "PATH=%s /bin/bash --php -rc %s", PG(exec_dir), ZSTR_VAL(cmd_esc));
  28. + zend_string_release(cmd_esc);
  29. + zend_string_release(cmd_str);
  30. + cmd_p = d;
  31. + } else {
  32. + cmd_p = cmd;
  33. + }
  34. +
  35. #if PHP_SIGCHILD
  36. sig_handler = signal (SIGCHLD, SIG_DFL);
  37. #endif
  38.  
  39. #ifdef PHP_WIN32
  40. - fp = VCWD_POPEN(cmd, "rb");
  41. + fp = VCWD_POPEN(cmd_p, "rb");
  42. #else
  43. - fp = VCWD_POPEN(cmd, "r");
  44. + fp = VCWD_POPEN(cmd_p, "r");
  45. #endif
  46. if (!fp) {
  47. php_error_docref(NULL, E_WARNING, "Unable to fork [%s]", cmd);
  48. @@ -508,7 +519,7 @@
  49. PHP_FUNCTION(shell_exec)
  50. {
  51. FILE *in;
  52. - char *command;
  53. + char *command, *command_p;
  54. size_t command_len;
  55. zend_string *ret;
  56. php_stream *stream;
  57. @@ -522,14 +533,26 @@
  58. RETURN_THROWS();
  59. }
  60.  
  61. + if (PG(exec_dir) && strlen(PG(exec_dir))) {
  62. + zend_string *cmd_str = zend_string_init(command, command_len, 0);
  63. + zend_string *cmd_esc = php_escape_shell_arg(cmd_str);
  64. + spprintf(&command_p, 0, "PATH=%s /bin/bash --php -rc %s", PG(exec_dir), ZSTR_VAL(cmd_esc));
  65. + zend_string_release(cmd_esc);
  66. + zend_string_release(cmd_str);
  67. + } else {
  68. + command_p = estrdup(command);
  69. + }
  70. +
  71. #ifdef PHP_WIN32
  72. - if ((in=VCWD_POPEN(command, "rt"))==NULL) {
  73. + if ((in=VCWD_POPEN(command_p, "rt"))==NULL) {
  74. #else
  75. - if ((in=VCWD_POPEN(command, "r"))==NULL) {
  76. + if ((in=VCWD_POPEN(command_p, "r"))==NULL) {
  77. #endif
  78. php_error_docref(NULL, E_WARNING, "Unable to execute '%s'", command);
  79. + efree(command_p);
  80. RETURN_FALSE;
  81. }
  82. + efree(command_p);
  83.  
  84. stream = php_stream_fopen_from_pipe(in, "rb");
  85. ret = php_stream_copy_to_mem(stream, PHP_STREAM_COPY_ALL, 0);
  86. --- a/ext/standard/file.c 2025-10-21 21:23:55.000000000 +0200
  87. +++ b/ext/standard/file.c 2025-11-14 21:01:23.280669943 +0100
  88. @@ -28,6 +28,7 @@
  89. #include "ext/standard/basic_functions.h"
  90. #include "php_ini.h"
  91. #include "zend_smart_str.h"
  92. +#include "ext/standard/exec.h"
  93.  
  94. #include <stdio.h>
  95. #include <stdlib.h>
  96. @@ -813,7 +814,18 @@
  97. RETURN_THROWS();
  98. }
  99.  
  100. - fp = VCWD_POPEN(command, posix_mode);
  101. + if (PG(exec_dir) && strlen(PG(exec_dir))) {
  102. + zend_string *cmd_str = zend_string_init(command, command_len, 0);
  103. + zend_string *cmd_esc = php_escape_shell_arg(cmd_str);
  104. + char *tmp;
  105. + spprintf(&tmp, 0, "PATH=%s /bin/bash --php -rc %s", PG(exec_dir), ZSTR_VAL(cmd_esc));
  106. + fp = VCWD_POPEN(tmp, posix_mode);
  107. + efree(tmp);
  108. + zend_string_release(cmd_esc);
  109. + zend_string_release(cmd_str);
  110. + } else {
  111. + fp = VCWD_POPEN(command, posix_mode);
  112. + }
  113. if (!fp) {
  114. php_error_docref2(NULL, command, posix_mode, E_WARNING, "%s", strerror(errno));
  115. efree(posix_mode);
  116. --- a/ext/standard/filestat.c 2025-10-21 21:23:55.000000000 +0200
  117. +++ b/ext/standard/filestat.c 2025-11-14 21:01:23.280669943 +0100
  118. @@ -746,6 +746,7 @@
  119. zend_stat_t *stat_sb = &ssb.sb;
  120. int flags = 0, rmask=S_IROTH, wmask=S_IWOTH, xmask=S_IXOTH; /* access rights defaults to other */
  121. const char *local = NULL;
  122. + char local_safe[MAXPATHLEN];
  123. php_stream_wrapper *wrapper = NULL;
  124.  
  125. if (IS_ACCESS_CHECK(type)) {
  126. @@ -757,7 +758,7 @@
  127. }
  128.  
  129. if ((wrapper = php_stream_locate_url_wrapper(ZSTR_VAL(filename), &local, 0)) == &php_plain_files_wrapper
  130. - && php_check_open_basedir(local)) {
  131. + && type != FS_IS_X && php_check_open_basedir(local)) {
  132. RETURN_FALSE;
  133. }
  134.  
  135. @@ -787,9 +788,25 @@
  136. break;
  137. #endif
  138. #ifdef X_OK
  139. - case FS_IS_X:
  140. - RETURN_BOOL(VCWD_ACCESS(file_path_to_check, X_OK) == 0);
  141. + case FS_IS_X: {
  142. + bool xok = (VCWD_ACCESS(file_path_to_check, X_OK) == 0);
  143. + if (!xok && PG(exec_dir) && strlen(PG(exec_dir))) {
  144. + if (strstr(file_path_to_check, "..")) {
  145. + RETURN_FALSE;
  146. + } else {
  147. + char *b = strrchr(file_path_to_check, PHP_DIR_SEPARATOR);
  148. + if (b) {
  149. + snprintf(local_safe, MAXPATHLEN, "%s%s", PG(exec_dir), b);
  150. + } else {
  151. + snprintf(local_safe, MAXPATHLEN, "%s%c%s", PG(exec_dir), PHP_DIR_SEPARATOR, file_path_to_check);
  152. + }
  153. + file_path_to_check = (char *)&local_safe;
  154. + }
  155. + xok = (VCWD_ACCESS(file_path_to_check, X_OK) == 0);
  156. + }
  157. + RETURN_BOOL(xok);
  158. break;
  159. + }
  160. #endif
  161. }
  162. }
  163. --- a/ext/standard/proc_open.c 2025-10-21 21:23:55.000000000 +0200
  164. +++ b/ext/standard/proc_open.c 2025-11-14 21:02:07.764365073 +0100
  165. @@ -783,7 +783,17 @@
  166. zend_string *command = NULL;
  167. int 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. @@ -1277,8 +1287,34 @@
  185. }
  186. #endif
  187.  
  188. + zval envpath;
  189. + ZVAL_UNDEF(&envpath);
  190. + if (PG(exec_dir) && strlen(PG(exec_dir))) {
  191. + zend_string *key;
  192. + zend_ulong idx;
  193. + zval *val;
  194. + uint32_t count = environment ? zend_hash_num_elements(Z_ARRVAL_P(environment)) : 0;
  195. + array_init_size(&envpath, count + 1);
  196. + if (count) {
  197. + ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(environment), idx, key, val) {
  198. + if (key) {
  199. + zend_hash_add_new(Z_ARRVAL(envpath), key, val);
  200. + } else {
  201. + zend_hash_index_add_new(Z_ARRVAL(envpath), idx, val);
  202. + }
  203. + zval_add_ref(val);
  204. + } ZEND_HASH_FOREACH_END();
  205. + }
  206. + add_assoc_string(&envpath, "PATH", PG(exec_dir));
  207. + environment = &envpath;
  208. + }
  209. +
  210. if (environment) {
  211. env = _php_array_to_envp(environment);
  212. + if (!Z_ISUNDEF(envpath)) {
  213. + zval_ptr_dtor(&envpath);
  214. + environment = NULL;
  215. + }
  216. }
  217.  
  218. descriptors = alloc_descriptor_array(descriptorspec);
  219. @@ -1402,9 +1438,15 @@
  220. if (argv) {
  221. r = posix_spawnp(&child, ZSTR_VAL(command_str), &factions, NULL, argv, (env.envarray ? env.envarray : environ));
  222. } else {
  223. - r = posix_spawn(&child, "/bin/sh" , &factions, NULL,
  224. - (char * const[]) {"sh", "-c", ZSTR_VAL(command_str), NULL},
  225. - env.envarray ? env.envarray : environ);
  226. + if (PG(exec_dir) && strlen(PG(exec_dir))) {
  227. + r = posix_spawn(&child, "/bin/bash" , &factions, NULL,
  228. + (char * const[]) {"bash", "--php", "-rc", ZSTR_VAL(command_str), NULL},
  229. + env.envarray ? env.envarray : environ);
  230. + } else {
  231. + r = posix_spawn(&child, "/bin/sh" , &factions, NULL,
  232. + (char * const[]) {"sh", "-c", ZSTR_VAL(command_str), NULL},
  233. + env.envarray ? env.envarray : environ);
  234. + }
  235. }
  236. posix_spawn_file_actions_destroy(&factions);
  237. if (r != 0) {
  238. @@ -1437,10 +1479,14 @@
  239. }
  240. execvp(ZSTR_VAL(command_str), argv);
  241. } else {
  242. - if (env.envarray) {
  243. - execle("/bin/sh", "sh", "-c", ZSTR_VAL(command_str), NULL, env.envarray);
  244. + if (PG(exec_dir) && strlen(PG(exec_dir))) {
  245. + execle("/bin/bash", "bash", "--php", "-rc", ZSTR_VAL(command_str), NULL, env.envarray);
  246. } else {
  247. - execl("/bin/sh", "sh", "-c", ZSTR_VAL(command_str), NULL);
  248. + if (env.envarray) {
  249. + execle("/bin/sh", "sh", "-c", ZSTR_VAL(command_str), NULL, env.envarray);
  250. + } else {
  251. + execl("/bin/sh", "sh", "-c", ZSTR_VAL(command_str), NULL);
  252. + }
  253. }
  254. }
  255.  
  256. --- a/main/main.c 2025-11-14 20:59:47.815324216 +0100
  257. +++ b/main/main.c 2025-11-14 21:01:23.281669936 +0100
  258. @@ -860,6 +860,7 @@
  259. STD_PHP_INI_ENTRY("syslog.facility", "LOG_USER", PHP_INI_SYSTEM, OnSetFacility, syslog_facility, php_core_globals, core_globals)
  260. STD_PHP_INI_ENTRY("syslog.ident", "php", PHP_INI_SYSTEM, OnUpdateString, syslog_ident, php_core_globals, core_globals)
  261. STD_PHP_INI_ENTRY("syslog.filter", "no-ctrl", PHP_INI_ALL, OnSetLogFilter, syslog_filter, php_core_globals, core_globals)
  262. + STD_PHP_INI_ENTRY("exec_dir", NULL, PHP_INI_SYSTEM, OnUpdateString, exec_dir, php_core_globals, core_globals)
  263. PHP_INI_END()
  264. /* }}} */
  265.  
  266. --- a/main/php_globals.h 2025-11-14 20:59:47.700325004 +0100
  267. +++ b/main/php_globals.h 2025-11-14 21:01:23.282669929 +0100
  268. @@ -173,6 +173,8 @@
  269. char *syslog_ident;
  270. zend_long syslog_filter;
  271. zend_long error_log_mode;
  272. +
  273. + char *exec_dir;
  274. };
  275.  
  276.  
  277. --- a/php.ini-development 2025-11-14 20:59:47.701324997 +0100
  278. +++ b/php.ini-development 2025-11-14 21:01:23.282669929 +0100
  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. --- a/php.ini-production 2025-11-14 20:59:47.702324990 +0100
  291. +++ b/php.ini-production 2025-11-14 21:01:23.283669922 +0100
  292. @@ -317,6 +317,10 @@
  293. ; https://php.net/open-basedir
  294. ;open_basedir =
  295.  
  296. +; Only executables located in the exec_dir will be allowed to be executed
  297. +; via the exec family of functions.
  298. +exec_dir =
  299. +
  300. ; This directive allows you to disable certain functions.
  301. ; It receives a comma-delimited list of function names.
  302. ; https://php.net/disable-functions
  303.