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 20:08:04.000000000 +0200
  12. +++ b/ext/standard/exec.c 2024-09-27 14:24:32.175343302 +0200
  13. @@ -116,21 +116,30 @@
  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_esc = php_escape_shell_arg(cmd);
  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. + cmd_p = d;
  30. + } else {
  31. + cmd_p = cmd;
  32. + }
  33. +
  34. #if PHP_SIGCHILD
  35. sig_handler = signal (SIGCHLD, SIG_DFL);
  36. #endif
  37.  
  38. #ifdef PHP_WIN32
  39. - fp = VCWD_POPEN(cmd, "rb");
  40. + fp = VCWD_POPEN(cmd_p, "rb");
  41. #else
  42. - fp = VCWD_POPEN(cmd, "r");
  43. + fp = VCWD_POPEN(cmd_p, "r");
  44. #endif
  45. if (!fp) {
  46. php_error_docref(NULL, E_WARNING, "Unable to fork [%s]", cmd);
  47. @@ -513,7 +522,7 @@
  48. PHP_FUNCTION(shell_exec)
  49. {
  50. FILE *in;
  51. - char *command;
  52. + char *command, *command_p;
  53. size_t command_len;
  54. zend_string *ret;
  55. php_stream *stream;
  56. @@ -531,14 +540,24 @@
  57. RETURN_THROWS();
  58. }
  59.  
  60. + if (PG(exec_dir) && strlen(PG(exec_dir))) {
  61. + zend_string *cmd_esc = php_escape_shell_arg(command);
  62. + spprintf(&command_p, 0, "PATH=%s /bin/bash --php -rc %s", PG(exec_dir), ZSTR_VAL(cmd_esc));
  63. + zend_string_release(cmd_esc);
  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 2024-09-24 20:08:04.000000000 +0200
  85. +++ b/ext/standard/file.c 2024-09-27 14:24:32.175343302 +0200
  86. @@ -825,7 +825,16 @@
  87. RETURN_THROWS();
  88. }
  89.  
  90. - fp = VCWD_POPEN(command, posix_mode);
  91. + if (PG(exec_dir) && strlen(PG(exec_dir))) {
  92. + zend_string *cmd_esc = php_escape_shell_arg(command);
  93. + char *tmp;
  94. + spprintf(&tmp, 0, "PATH=%s /bin/bash --php -rc %s", PG(exec_dir), ZSTR_VAL(cmd_esc));
  95. + fp = VCWD_POPEN(tmp, posix_mode);
  96. + efree(tmp);
  97. + zend_string_release(cmd_esc);
  98. + } else {
  99. + fp = VCWD_POPEN(command, posix_mode);
  100. + }
  101. if (!fp) {
  102. php_error_docref2(NULL, command, posix_mode, E_WARNING, "%s", strerror(errno));
  103. efree(posix_mode);
  104. diff -Naur a/ext/standard/filestat.c b/ext/standard/filestat.c
  105. --- a/ext/standard/filestat.c 2024-09-24 20:08:04.000000000 +0200
  106. +++ b/ext/standard/filestat.c 2024-09-27 14:24:32.175343302 +0200
  107. @@ -734,6 +734,7 @@
  108. zend_stat_t *stat_sb = &ssb.sb;
  109. int flags = 0, rmask=S_IROTH, wmask=S_IWOTH, xmask=S_IXOTH; /* access rights defaults to other */
  110. const char *local = NULL;
  111. + char local_safe[MAXPATHLEN];
  112. php_stream_wrapper *wrapper = NULL;
  113.  
  114. if (IS_ACCESS_CHECK(type)) {
  115. @@ -745,7 +746,7 @@
  116. }
  117.  
  118. if ((wrapper = php_stream_locate_url_wrapper(ZSTR_VAL(filename), &local, 0)) == &php_plain_files_wrapper
  119. - && php_check_open_basedir(local)) {
  120. + && type != FS_IS_X && php_check_open_basedir(local)) {
  121. RETURN_FALSE;
  122. }
  123.  
  124. @@ -775,9 +776,25 @@
  125. break;
  126. #endif
  127. #ifdef X_OK
  128. - case FS_IS_X:
  129. - RETURN_BOOL(VCWD_ACCESS(file_path_to_check, X_OK) == 0);
  130. + case FS_IS_X: {
  131. + bool xok = (VCWD_ACCESS(file_path_to_check, X_OK) == 0);
  132. + if (!xok && PG(exec_dir) && strlen(PG(exec_dir))) {
  133. + if (strstr(file_path_to_check, "..")) {
  134. + RETURN_FALSE;
  135. + } else {
  136. + char *b = strrchr(file_path_to_check, PHP_DIR_SEPARATOR);
  137. + if (b) {
  138. + snprintf(local_safe, MAXPATHLEN, "%s%s", PG(exec_dir), b);
  139. + } else {
  140. + snprintf(local_safe, MAXPATHLEN, "%s%c%s", PG(exec_dir), PHP_DIR_SEPARATOR, file_path_to_check);
  141. + }
  142. + file_path_to_check = (char *)&local_safe;
  143. + }
  144. + xok = (VCWD_ACCESS(file_path_to_check, X_OK) == 0);
  145. + }
  146. + RETURN_BOOL(xok);
  147. break;
  148. + }
  149. #endif
  150. }
  151. }
  152. diff -Naur a/ext/standard/proc_open.c b/ext/standard/proc_open.c
  153. --- a/ext/standard/proc_open.c 2024-09-24 20:08:04.000000000 +0200
  154. +++ b/ext/standard/proc_open.c 2024-09-27 14:25:25.785939297 +0200
  155. @@ -731,7 +731,17 @@
  156. zend_string *command = NULL;
  157. int i = 0;
  158.  
  159. - *argv = safe_emalloc(sizeof(char *), num_elems + 1, 0);
  160. + *argv = safe_emalloc(sizeof(char *), num_elems + 1 + 5, 0);
  161. +
  162. + if (PG(exec_dir) && strlen(PG(exec_dir))) {
  163. + command = zend_string_init("/bin/bash", sizeof("/bin/bash") - 1, 0);
  164. + (*argv)[i++] = estrdup("/bin/bash");
  165. + (*argv)[i++] = estrdup("--php");
  166. + (*argv)[i++] = estrdup("-rc");
  167. + (*argv)[i++] = estrdup("exec \"$@\"");
  168. + (*argv)[i++] = estrdup("bash");
  169. + (*argv)[i] = NULL;
  170. + }
  171.  
  172. ZEND_HASH_FOREACH_VAL(array, arg_zv) {
  173. zend_string *arg_str = get_valid_arg_string(arg_zv, i + 1);
  174. @@ -1225,6 +1235,15 @@
  175. }
  176. #endif
  177.  
  178. + zval envpath;
  179. + if (PG(exec_dir) && strlen(PG(exec_dir))) {
  180. + if (!environment || zend_hash_num_elements(Z_ARRVAL_P(environment)) == 0) {
  181. + array_init_size(&envpath, 1);
  182. + environment = &envpath;
  183. + }
  184. + add_assoc_string(environment, "PATH", PG(exec_dir));
  185. + }
  186. +
  187. if (environment) {
  188. env = _php_array_to_envp(environment);
  189. }
  190. @@ -1348,9 +1367,15 @@
  191. if (argv) {
  192. r = posix_spawnp(&child, ZSTR_VAL(command_str), &factions, NULL, argv, (env.envarray ? env.envarray : environ));
  193. } else {
  194. - r = posix_spawn(&child, "/bin/sh" , &factions, NULL,
  195. - (char * const[]) {"sh", "-c", ZSTR_VAL(command_str), NULL},
  196. - env.envarray ? env.envarray : environ);
  197. + if (PG(exec_dir) && strlen(PG(exec_dir))) {
  198. + r = posix_spawn(&child, "/bin/bash" , &factions, NULL,
  199. + (char * const[]) {"bash", "--php", "-rc", ZSTR_VAL(command_str), NULL},
  200. + env.envarray ? env.envarray : environ);
  201. + } else {
  202. + r = posix_spawn(&child, "/bin/sh" , &factions, NULL,
  203. + (char * const[]) {"sh", "-c", ZSTR_VAL(command_str), NULL},
  204. + env.envarray ? env.envarray : environ);
  205. + }
  206. }
  207. posix_spawn_file_actions_destroy(&factions);
  208. if (r != 0) {
  209. @@ -1383,10 +1408,14 @@
  210. }
  211. execvp(ZSTR_VAL(command_str), argv);
  212. } else {
  213. - if (env.envarray) {
  214. - execle("/bin/sh", "sh", "-c", ZSTR_VAL(command_str), NULL, env.envarray);
  215. + if (PG(exec_dir) && strlen(PG(exec_dir))) {
  216. + execle("/bin/bash", "bash", "--php", "-rc", ZSTR_VAL(command_str), NULL, env.envarray);
  217. } else {
  218. - execl("/bin/sh", "sh", "-c", ZSTR_VAL(command_str), NULL);
  219. + if (env.envarray) {
  220. + execle("/bin/sh", "sh", "-c", ZSTR_VAL(command_str), NULL, env.envarray);
  221. + } else {
  222. + execl("/bin/sh", "sh", "-c", ZSTR_VAL(command_str), NULL);
  223. + }
  224. }
  225. }
  226.  
  227. diff -Naur a/main/main.c b/main/main.c
  228. --- a/main/main.c 2024-09-27 14:23:06.002992683 +0200
  229. +++ b/main/main.c 2024-09-27 14:24:32.177343287 +0200
  230. @@ -777,6 +777,7 @@
  231. STD_PHP_INI_ENTRY("syslog.facility", "LOG_USER", PHP_INI_SYSTEM, OnSetFacility, syslog_facility, php_core_globals, core_globals)
  232. STD_PHP_INI_ENTRY("syslog.ident", "php", PHP_INI_SYSTEM, OnUpdateString, syslog_ident, php_core_globals, core_globals)
  233. STD_PHP_INI_ENTRY("syslog.filter", "no-ctrl", PHP_INI_ALL, OnSetLogFilter, syslog_filter, php_core_globals, core_globals)
  234. + STD_PHP_INI_ENTRY("exec_dir", NULL, PHP_INI_SYSTEM, OnUpdateString, exec_dir, php_core_globals, core_globals)
  235. PHP_INI_END()
  236. /* }}} */
  237.  
  238. diff -Naur a/main/php_globals.h b/main/php_globals.h
  239. --- a/main/php_globals.h 2024-09-24 20:08:04.000000000 +0200
  240. +++ b/main/php_globals.h 2024-09-27 14:24:32.177343287 +0200
  241. @@ -172,6 +172,8 @@
  242. char *syslog_ident;
  243. zend_long syslog_filter;
  244. zend_long error_log_mode;
  245. +
  246. + char *exec_dir;
  247. };
  248.  
  249.  
  250. diff -Naur a/php.ini-development b/php.ini-development
  251. --- a/php.ini-development 2024-09-24 20:08:04.000000000 +0200
  252. +++ b/php.ini-development 2024-09-27 14:24:32.177343287 +0200
  253. @@ -327,6 +327,10 @@
  254. ; https://php.net/open-basedir
  255. ;open_basedir =
  256.  
  257. +; Only executables located in the exec_dir will be allowed to be executed
  258. +; via the exec family of functions.
  259. +exec_dir =
  260. +
  261. ; This directive allows you to disable certain functions.
  262. ; It receives a comma-delimited list of function names.
  263. ; https://php.net/disable-functions
  264. diff -Naur a/php.ini-production b/php.ini-production
  265. --- a/php.ini-production 2024-09-24 20:08:04.000000000 +0200
  266. +++ b/php.ini-production 2024-09-27 14:24:32.178343279 +0200
  267. @@ -327,6 +327,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.