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 2024-09-24 17:59:52.000000000 +0200
  12. +++ b/ext/standard/exec.c 2024-09-27 14:57:40.554136412 +0200
  13. @@ -114,21 +114,32 @@
  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)() = NULL;
  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. #if PHP_SIGCHILD
  37. sig_handler = signal (SIGCHLD, SIG_DFL);
  38. #endif
  39.  
  40. #ifdef PHP_WIN32
  41. - fp = VCWD_POPEN(cmd, "rb");
  42. + fp = VCWD_POPEN(cmd_p, "rb");
  43. #else
  44. - fp = VCWD_POPEN(cmd, "r");
  45. + fp = VCWD_POPEN(cmd_p, "r");
  46. #endif
  47. if (!fp) {
  48. php_error_docref(NULL, E_WARNING, "Unable to fork [%s]", cmd);
  49. @@ -508,7 +519,7 @@
  50. PHP_FUNCTION(shell_exec)
  51. {
  52. FILE *in;
  53. - char *command;
  54. + char *command, *command_p;
  55. size_t command_len;
  56. zend_string *ret;
  57. php_stream *stream;
  58. @@ -522,14 +533,26 @@
  59. RETURN_THROWS();
  60. }
  61.  
  62. + if (PG(exec_dir) && strlen(PG(exec_dir))) {
  63. + zend_string *cmd_str = zend_string_init(command, command_len, 0);
  64. + zend_string *cmd_esc = php_escape_shell_arg(cmd_str);
  65. + spprintf(&command_p, 0, "PATH=%s /bin/bash --php -rc %s", PG(exec_dir), ZSTR_VAL(cmd_esc));
  66. + zend_string_release(cmd_esc);
  67. + zend_string_release(cmd_str);
  68. + } else {
  69. + command_p = estrdup(command);
  70. + }
  71. +
  72. #ifdef PHP_WIN32
  73. - if ((in=VCWD_POPEN(command, "rt"))==NULL) {
  74. + if ((in=VCWD_POPEN(command_p, "rt"))==NULL) {
  75. #else
  76. - if ((in=VCWD_POPEN(command, "r"))==NULL) {
  77. + if ((in=VCWD_POPEN(command_p, "r"))==NULL) {
  78. #endif
  79. php_error_docref(NULL, E_WARNING, "Unable to execute '%s'", command);
  80. + efree(command_p);
  81. RETURN_FALSE;
  82. }
  83. + efree(command_p);
  84.  
  85. stream = php_stream_fopen_from_pipe(in, "rb");
  86. ret = php_stream_copy_to_mem(stream, PHP_STREAM_COPY_ALL, 0);
  87. diff -Naur a/ext/standard/file.c b/ext/standard/file.c
  88. --- a/ext/standard/file.c 2024-09-24 17:59:52.000000000 +0200
  89. +++ b/ext/standard/file.c 2024-09-27 14:57:40.555136404 +0200
  90. @@ -28,6 +28,7 @@
  91. #include "ext/standard/basic_functions.h"
  92. #include "php_ini.h"
  93. #include "zend_smart_str.h"
  94. +#include "ext/standard/exec.h"
  95.  
  96. #include <stdio.h>
  97. #include <stdlib.h>
  98. @@ -813,7 +814,18 @@
  99. RETURN_THROWS();
  100. }
  101.  
  102. - fp = VCWD_POPEN(command, posix_mode);
  103. + if (PG(exec_dir) && strlen(PG(exec_dir))) {
  104. + zend_string *cmd_str = zend_string_init(command, command_len, 0);
  105. + zend_string *cmd_esc = php_escape_shell_arg(cmd_str);
  106. + char *tmp;
  107. + spprintf(&tmp, 0, "PATH=%s /bin/bash --php -rc %s", PG(exec_dir), ZSTR_VAL(cmd_esc));
  108. + fp = VCWD_POPEN(tmp, posix_mode);
  109. + efree(tmp);
  110. + zend_string_release(cmd_esc);
  111. + zend_string_release(cmd_str);
  112. + } else {
  113. + fp = VCWD_POPEN(command, posix_mode);
  114. + }
  115. if (!fp) {
  116. php_error_docref2(NULL, command, posix_mode, E_WARNING, "%s", strerror(errno));
  117. efree(posix_mode);
  118. diff -Naur a/ext/standard/filestat.c b/ext/standard/filestat.c
  119. --- a/ext/standard/filestat.c 2024-09-24 17:59:52.000000000 +0200
  120. +++ b/ext/standard/filestat.c 2024-09-27 14:57:40.556136395 +0200
  121. @@ -734,6 +734,7 @@
  122. zend_stat_t *stat_sb = &ssb.sb;
  123. int flags = 0, rmask=S_IROTH, wmask=S_IWOTH, xmask=S_IXOTH; /* access rights defaults to other */
  124. const char *local = NULL;
  125. + char local_safe[MAXPATHLEN];
  126. php_stream_wrapper *wrapper = NULL;
  127.  
  128. if (IS_ACCESS_CHECK(type)) {
  129. @@ -745,7 +746,7 @@
  130. }
  131.  
  132. if ((wrapper = php_stream_locate_url_wrapper(ZSTR_VAL(filename), &local, 0)) == &php_plain_files_wrapper
  133. - && php_check_open_basedir(local)) {
  134. + && type != FS_IS_X && php_check_open_basedir(local)) {
  135. RETURN_FALSE;
  136. }
  137.  
  138. @@ -775,9 +776,25 @@
  139. break;
  140. #endif
  141. #ifdef X_OK
  142. - case FS_IS_X:
  143. - RETURN_BOOL(VCWD_ACCESS(file_path_to_check, X_OK) == 0);
  144. + case FS_IS_X: {
  145. + bool xok = (VCWD_ACCESS(file_path_to_check, X_OK) == 0);
  146. + if (!xok && PG(exec_dir) && strlen(PG(exec_dir))) {
  147. + if (strstr(file_path_to_check, "..")) {
  148. + RETURN_FALSE;
  149. + } else {
  150. + char *b = strrchr(file_path_to_check, PHP_DIR_SEPARATOR);
  151. + if (b) {
  152. + snprintf(local_safe, MAXPATHLEN, "%s%s", PG(exec_dir), b);
  153. + } else {
  154. + snprintf(local_safe, MAXPATHLEN, "%s%c%s", PG(exec_dir), PHP_DIR_SEPARATOR, file_path_to_check);
  155. + }
  156. + file_path_to_check = (char *)&local_safe;
  157. + }
  158. + xok = (VCWD_ACCESS(file_path_to_check, X_OK) == 0);
  159. + }
  160. + RETURN_BOOL(xok);
  161. break;
  162. + }
  163. #endif
  164. }
  165. }
  166. diff -Naur a/ext/standard/proc_open.c b/ext/standard/proc_open.c
  167. --- a/ext/standard/proc_open.c 2024-09-24 17:59:52.000000000 +0200
  168. +++ b/ext/standard/proc_open.c 2024-09-27 14:58:20.151810732 +0200
  169. @@ -728,7 +728,17 @@
  170. zend_string *command = NULL;
  171. int i = 0;
  172.  
  173. - *argv = safe_emalloc(sizeof(char *), num_elems + 1, 0);
  174. + *argv = safe_emalloc(sizeof(char *), num_elems + 1 + 5, 0);
  175. +
  176. + if (PG(exec_dir) && strlen(PG(exec_dir))) {
  177. + command = zend_string_init("/bin/bash", sizeof("/bin/bash") - 1, 0);
  178. + (*argv)[i++] = estrdup("/bin/bash");
  179. + (*argv)[i++] = estrdup("--php");
  180. + (*argv)[i++] = estrdup("-rc");
  181. + (*argv)[i++] = estrdup("exec \"$@\"");
  182. + (*argv)[i++] = estrdup("bash");
  183. + (*argv)[i] = NULL;
  184. + }
  185.  
  186. ZEND_HASH_FOREACH_VAL(array, arg_zv) {
  187. zend_string *arg_str = get_valid_arg_string(arg_zv, i + 1);
  188. @@ -1222,6 +1232,15 @@
  189. }
  190. #endif
  191.  
  192. + zval envpath;
  193. + if (PG(exec_dir) && strlen(PG(exec_dir))) {
  194. + if (!environment || zend_hash_num_elements(Z_ARRVAL_P(environment)) == 0) {
  195. + array_init_size(&envpath, 1);
  196. + environment = &envpath;
  197. + }
  198. + add_assoc_string(environment, "PATH", PG(exec_dir));
  199. + }
  200. +
  201. if (environment) {
  202. env = _php_array_to_envp(environment);
  203. }
  204. @@ -1347,9 +1366,15 @@
  205. if (argv) {
  206. r = posix_spawnp(&child, ZSTR_VAL(command_str), &factions, NULL, argv, (env.envarray ? env.envarray : environ));
  207. } else {
  208. - r = posix_spawn(&child, "/bin/sh" , &factions, NULL,
  209. - (char * const[]) {"sh", "-c", ZSTR_VAL(command_str), NULL},
  210. - env.envarray ? env.envarray : environ);
  211. + if (PG(exec_dir) && strlen(PG(exec_dir))) {
  212. + r = posix_spawn(&child, "/bin/bash" , &factions, NULL,
  213. + (char * const[]) {"bash", "--php", "-rc", ZSTR_VAL(command_str), NULL},
  214. + env.envarray ? env.envarray : environ);
  215. + } else {
  216. + r = posix_spawn(&child, "/bin/sh" , &factions, NULL,
  217. + (char * const[]) {"sh", "-c", ZSTR_VAL(command_str), NULL},
  218. + env.envarray ? env.envarray : environ);
  219. + }
  220. }
  221. posix_spawn_file_actions_destroy(&factions);
  222. if (r != 0) {
  223. @@ -1382,10 +1407,14 @@
  224. }
  225. execvp(ZSTR_VAL(command_str), argv);
  226. } else {
  227. - if (env.envarray) {
  228. - execle("/bin/sh", "sh", "-c", ZSTR_VAL(command_str), NULL, env.envarray);
  229. + if (PG(exec_dir) && strlen(PG(exec_dir))) {
  230. + execle("/bin/bash", "bash", "--php", "-rc", ZSTR_VAL(command_str), NULL, env.envarray);
  231. } else {
  232. - execl("/bin/sh", "sh", "-c", ZSTR_VAL(command_str), NULL);
  233. + if (env.envarray) {
  234. + execle("/bin/sh", "sh", "-c", ZSTR_VAL(command_str), NULL, env.envarray);
  235. + } else {
  236. + execl("/bin/sh", "sh", "-c", ZSTR_VAL(command_str), NULL);
  237. + }
  238. }
  239. }
  240.  
  241. diff -Naur a/main/main.c b/main/main.c
  242. --- a/main/main.c 2024-09-27 14:37:57.841234655 +0200
  243. +++ b/main/main.c 2024-09-27 14:57:40.557136387 +0200
  244. @@ -824,6 +824,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. diff -Naur a/main/php_globals.h b/main/php_globals.h
  253. --- a/main/php_globals.h 2024-09-24 17:59:52.000000000 +0200
  254. +++ b/main/php_globals.h 2024-09-27 14:57:40.557136387 +0200
  255. @@ -172,6 +172,8 @@
  256. char *syslog_ident;
  257. zend_long syslog_filter;
  258. zend_long error_log_mode;
  259. +
  260. + char *exec_dir;
  261. };
  262.  
  263.  
  264. diff -Naur a/php.ini-development b/php.ini-development
  265. --- a/php.ini-development 2024-09-24 17:59:52.000000000 +0200
  266. +++ b/php.ini-development 2024-09-27 14:57:40.558136379 +0200
  267. @@ -317,6 +317,10 @@
  268. ; https://php.net/open-basedir
  269. ;open_basedir =
  270.  
  271. +; Only executables located in the exec_dir will be allowed to be executed
  272. +; via the exec family of functions.
  273. +exec_dir =
  274. +
  275. ; This directive allows you to disable certain functions.
  276. ; It receives a comma-delimited list of function names.
  277. ; https://php.net/disable-functions
  278. diff -Naur a/php.ini-production b/php.ini-production
  279. --- a/php.ini-production 2024-09-24 17:59:52.000000000 +0200
  280. +++ b/php.ini-production 2024-09-27 14:57:40.559136371 +0200
  281. @@ -317,6 +317,10 @@
  282. ; https://php.net/open-basedir
  283. ;open_basedir =
  284.  
  285. +; Only executables located in the exec_dir will be allowed to be executed
  286. +; via the exec family of functions.
  287. +exec_dir =
  288. +
  289. ; This directive allows you to disable certain functions.
  290. ; It receives a comma-delimited list of function names.
  291. ; https://php.net/disable-functions
  292.