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 16:53:41.000000000 +0200
  11. +++ b/ext/standard/exec.c 2025-11-14 21:04:45.766282203 +0100
  12. @@ -116,21 +116,30 @@
  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_esc = php_escape_shell_arg(cmd);
  26. + spprintf(&d, 0, "PATH=%s /bin/bash --php -rc %s", PG(exec_dir), ZSTR_VAL(cmd_esc));
  27. + zend_string_release(cmd_esc);
  28. + cmd_p = d;
  29. + } else {
  30. + cmd_p = cmd;
  31. + }
  32. +
  33. #if PHP_SIGCHILD
  34. sig_handler = signal (SIGCHLD, SIG_DFL);
  35. #endif
  36.  
  37. #ifdef PHP_WIN32
  38. - fp = VCWD_POPEN(cmd, "rb");
  39. + fp = VCWD_POPEN(cmd_p, "rb");
  40. #else
  41. - fp = VCWD_POPEN(cmd, "r");
  42. + fp = VCWD_POPEN(cmd_p, "r");
  43. #endif
  44. if (!fp) {
  45. php_error_docref(NULL, E_WARNING, "Unable to fork [%s]", cmd);
  46. @@ -513,7 +522,7 @@
  47. PHP_FUNCTION(shell_exec)
  48. {
  49. FILE *in;
  50. - char *command;
  51. + char *command, *command_p;
  52. size_t command_len;
  53. zend_string *ret;
  54. php_stream *stream;
  55. @@ -531,14 +540,24 @@
  56. RETURN_THROWS();
  57. }
  58.  
  59. + if (PG(exec_dir) && strlen(PG(exec_dir))) {
  60. + zend_string *cmd_esc = php_escape_shell_arg(command);
  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. + } else {
  64. + command_p = estrdup(command);
  65. + }
  66. +
  67. #ifdef PHP_WIN32
  68. - if ((in=VCWD_POPEN(command, "rt"))==NULL) {
  69. + if ((in=VCWD_POPEN(command_p, "rt"))==NULL) {
  70. #else
  71. - if ((in=VCWD_POPEN(command, "r"))==NULL) {
  72. + if ((in=VCWD_POPEN(command_p, "r"))==NULL) {
  73. #endif
  74. php_error_docref(NULL, E_WARNING, "Unable to execute '%s'", command);
  75. + efree(command_p);
  76. RETURN_FALSE;
  77. }
  78. + efree(command_p);
  79.  
  80. stream = php_stream_fopen_from_pipe(in, "rb");
  81. ret = php_stream_copy_to_mem(stream, PHP_STREAM_COPY_ALL, 0);
  82. --- a/ext/standard/file.c 2025-10-21 16:53:41.000000000 +0200
  83. +++ b/ext/standard/file.c 2025-11-14 21:04:45.766282203 +0100
  84. @@ -825,7 +825,16 @@
  85. RETURN_THROWS();
  86. }
  87.  
  88. - fp = VCWD_POPEN(command, posix_mode);
  89. + if (PG(exec_dir) && strlen(PG(exec_dir))) {
  90. + zend_string *cmd_esc = php_escape_shell_arg(command);
  91. + char *tmp;
  92. + spprintf(&tmp, 0, "PATH=%s /bin/bash --php -rc %s", PG(exec_dir), ZSTR_VAL(cmd_esc));
  93. + fp = VCWD_POPEN(tmp, posix_mode);
  94. + efree(tmp);
  95. + zend_string_release(cmd_esc);
  96. + } else {
  97. + fp = VCWD_POPEN(command, posix_mode);
  98. + }
  99. if (!fp) {
  100. php_error_docref2(NULL, command, posix_mode, E_WARNING, "%s", strerror(errno));
  101. efree(posix_mode);
  102. --- a/ext/standard/filestat.c 2025-10-21 16:53:41.000000000 +0200
  103. +++ b/ext/standard/filestat.c 2025-11-14 21:04:45.766282203 +0100
  104. @@ -746,6 +746,7 @@
  105. zend_stat_t *stat_sb = &ssb.sb;
  106. int flags = 0, rmask=S_IROTH, wmask=S_IWOTH, xmask=S_IXOTH; /* access rights defaults to other */
  107. const char *local = NULL;
  108. + char local_safe[MAXPATHLEN];
  109. php_stream_wrapper *wrapper = NULL;
  110.  
  111. if (IS_ACCESS_CHECK(type)) {
  112. @@ -757,7 +758,7 @@
  113. }
  114.  
  115. if ((wrapper = php_stream_locate_url_wrapper(ZSTR_VAL(filename), &local, 0)) == &php_plain_files_wrapper
  116. - && php_check_open_basedir(local)) {
  117. + && type != FS_IS_X && php_check_open_basedir(local)) {
  118. RETURN_FALSE;
  119. }
  120.  
  121. @@ -787,9 +788,25 @@
  122. break;
  123. #endif
  124. #ifdef X_OK
  125. - case FS_IS_X:
  126. - RETURN_BOOL(VCWD_ACCESS(file_path_to_check, X_OK) == 0);
  127. + case FS_IS_X: {
  128. + bool xok = (VCWD_ACCESS(file_path_to_check, X_OK) == 0);
  129. + if (!xok && PG(exec_dir) && strlen(PG(exec_dir))) {
  130. + if (strstr(file_path_to_check, "..")) {
  131. + RETURN_FALSE;
  132. + } else {
  133. + char *b = strrchr(file_path_to_check, PHP_DIR_SEPARATOR);
  134. + if (b) {
  135. + snprintf(local_safe, MAXPATHLEN, "%s%s", PG(exec_dir), b);
  136. + } else {
  137. + snprintf(local_safe, MAXPATHLEN, "%s%c%s", PG(exec_dir), PHP_DIR_SEPARATOR, file_path_to_check);
  138. + }
  139. + file_path_to_check = (char *)&local_safe;
  140. + }
  141. + xok = (VCWD_ACCESS(file_path_to_check, X_OK) == 0);
  142. + }
  143. + RETURN_BOOL(xok);
  144. break;
  145. + }
  146. #endif
  147. }
  148. }
  149. --- a/ext/standard/proc_open.c 2025-10-21 16:53:41.000000000 +0200
  150. +++ b/ext/standard/proc_open.c 2025-11-14 21:05:05.225148841 +0100
  151. @@ -786,7 +786,17 @@
  152. zend_string *command = NULL;
  153. int i = 0;
  154.  
  155. - *argv = safe_emalloc(sizeof(char *), num_elems + 1, 0);
  156. + *argv = safe_emalloc(sizeof(char *), num_elems + 1 + 5, 0);
  157. +
  158. + if (PG(exec_dir) && strlen(PG(exec_dir))) {
  159. + command = zend_string_init("/bin/bash", sizeof("/bin/bash") - 1, 0);
  160. + (*argv)[i++] = estrdup("/bin/bash");
  161. + (*argv)[i++] = estrdup("--php");
  162. + (*argv)[i++] = estrdup("-rc");
  163. + (*argv)[i++] = estrdup("exec \"$@\"");
  164. + (*argv)[i++] = estrdup("bash");
  165. + (*argv)[i] = NULL;
  166. + }
  167.  
  168. ZEND_HASH_FOREACH_VAL(array, arg_zv) {
  169. zend_string *arg_str = get_valid_arg_string(arg_zv, i + 1);
  170. @@ -1280,8 +1290,34 @@
  171. }
  172. #endif
  173.  
  174. + zval envpath;
  175. + ZVAL_UNDEF(&envpath);
  176. + if (PG(exec_dir) && strlen(PG(exec_dir))) {
  177. + zend_string *key;
  178. + zend_ulong idx;
  179. + zval *val;
  180. + uint32_t count = environment ? zend_hash_num_elements(Z_ARRVAL_P(environment)) : 0;
  181. + array_init_size(&envpath, count + 1);
  182. + if (count) {
  183. + ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(environment), idx, key, val) {
  184. + if (key) {
  185. + zend_hash_add_new(Z_ARRVAL(envpath), key, val);
  186. + } else {
  187. + zend_hash_index_add_new(Z_ARRVAL(envpath), idx, val);
  188. + }
  189. + zval_add_ref(val);
  190. + } ZEND_HASH_FOREACH_END();
  191. + }
  192. + add_assoc_string(&envpath, "PATH", PG(exec_dir));
  193. + environment = &envpath;
  194. + }
  195. +
  196. if (environment) {
  197. env = _php_array_to_envp(environment);
  198. + if (!Z_ISUNDEF(envpath)) {
  199. + zval_ptr_dtor(&envpath);
  200. + environment = NULL;
  201. + }
  202. }
  203.  
  204. descriptors = alloc_descriptor_array(descriptorspec);
  205. @@ -1403,9 +1439,15 @@
  206. if (argv) {
  207. r = posix_spawnp(&child, ZSTR_VAL(command_str), &factions, NULL, argv, (env.envarray ? env.envarray : environ));
  208. } else {
  209. - r = posix_spawn(&child, "/bin/sh" , &factions, NULL,
  210. - (char * const[]) {"sh", "-c", ZSTR_VAL(command_str), NULL},
  211. - env.envarray ? env.envarray : environ);
  212. + if (PG(exec_dir) && strlen(PG(exec_dir))) {
  213. + r = posix_spawn(&child, "/bin/bash" , &factions, NULL,
  214. + (char * const[]) {"bash", "--php", "-rc", ZSTR_VAL(command_str), NULL},
  215. + env.envarray ? env.envarray : environ);
  216. + } else {
  217. + r = posix_spawn(&child, "/bin/sh" , &factions, NULL,
  218. + (char * const[]) {"sh", "-c", ZSTR_VAL(command_str), NULL},
  219. + env.envarray ? env.envarray : environ);
  220. + }
  221. }
  222. posix_spawn_file_actions_destroy(&factions);
  223. if (r != 0) {
  224. @@ -1438,10 +1480,14 @@
  225. }
  226. execvp(ZSTR_VAL(command_str), argv);
  227. } else {
  228. - if (env.envarray) {
  229. - execle("/bin/sh", "sh", "-c", ZSTR_VAL(command_str), NULL, env.envarray);
  230. + if (PG(exec_dir) && strlen(PG(exec_dir))) {
  231. + execle("/bin/bash", "bash", "--php", "-rc", ZSTR_VAL(command_str), NULL, env.envarray);
  232. } else {
  233. - execl("/bin/sh", "sh", "-c", ZSTR_VAL(command_str), NULL);
  234. + if (env.envarray) {
  235. + execle("/bin/sh", "sh", "-c", ZSTR_VAL(command_str), NULL, env.envarray);
  236. + } else {
  237. + execl("/bin/sh", "sh", "-c", ZSTR_VAL(command_str), NULL);
  238. + }
  239. }
  240. }
  241.  
  242. --- a/main/main.c 2025-11-14 21:04:24.322429168 +0100
  243. +++ b/main/main.c 2025-11-14 21:04:45.767282196 +0100
  244. @@ -813,6 +813,7 @@
  245. STD_PHP_INI_ENTRY("syslog.facility", "LOG_USER", PHP_INI_SYSTEM, OnSetFacility, syslog_facility, php_core_globals, core_globals)
  246. STD_PHP_INI_ENTRY("syslog.ident", "php", PHP_INI_SYSTEM, OnUpdateString, syslog_ident, php_core_globals, core_globals)
  247. STD_PHP_INI_ENTRY("syslog.filter", "no-ctrl", PHP_INI_ALL, OnSetLogFilter, syslog_filter, php_core_globals, core_globals)
  248. + STD_PHP_INI_ENTRY("exec_dir", NULL, PHP_INI_SYSTEM, OnUpdateString, exec_dir, php_core_globals, core_globals)
  249. PHP_INI_END()
  250. /* }}} */
  251.  
  252. --- a/main/php_globals.h 2025-11-14 21:04:24.204429977 +0100
  253. +++ b/main/php_globals.h 2025-11-14 21:04:45.768282189 +0100
  254. @@ -173,6 +173,8 @@
  255. char *syslog_ident;
  256. zend_long syslog_filter;
  257. zend_long error_log_mode;
  258. +
  259. + char *exec_dir;
  260. };
  261.  
  262.  
  263. --- a/php.ini-development 2025-11-14 21:04:24.204429977 +0100
  264. +++ b/php.ini-development 2025-11-14 21:04:45.768282189 +0100
  265. @@ -327,6 +327,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. --- a/php.ini-production 2025-11-14 21:04:24.205429970 +0100
  277. +++ b/php.ini-production 2025-11-14 21:04:45.769282182 +0100
  278. @@ -327,6 +327,10 @@
  279. ; https://php.net/open-basedir
  280. ;open_basedir =
  281.  
  282. +; Only executables located in the exec_dir will be allowed to be executed
  283. +; via the exec family of functions.
  284. +exec_dir =
  285. +
  286. ; This directive allows you to disable certain functions.
  287. ; It receives a comma-delimited list of function names.
  288. ; https://php.net/disable-functions
  289.