Created
December 28, 2016 11:48
-
-
Save Zenexer/c123604d57914970ac297413751c3f21 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Compile with -std=c11 | |
#include <stdlib.h> | |
#include <stdarg.h> | |
#include <stdio.h> | |
#include <inttypes.h> | |
#include <string.h> | |
#include <limits.h> | |
#define MAX_STR_LEN 4095 | |
#define PHPAPI | |
#define ZSTR_VAL(S) (S->value) | |
#define ZSTR_LEN(S) (S->length) | |
#define E_ERROR 1 | |
size_t cmd_max_len = | |
#if defined(PATH_MAX) | |
PATH_MAX; | |
#elif defined(MAX_PATH) | |
MAX_PATH; | |
#else | |
# error Neither PATH_MAX nor MAX_PATH is defined. | |
#endif | |
typedef struct _zend_string { | |
size_t length; | |
char *value; | |
} zend_string; | |
int main(int argc, char **argv); | |
void test(const char *str); | |
void output(const char *func, const char *os, zend_string *zstr); | |
static void zend_string_release(zend_string *str); | |
static zend_string *zend_string_truncate(zend_string *str, size_t n, size_t pad); | |
static inline size_t php_mblen(const char *str, size_t n); | |
static zend_string *zend_string_safe_alloc(size_t n, size_t m, size_t l, int persistent); | |
static zend_string *ZSTR_EMPTY_ALLOC(); | |
static void php_error_docref(void *unkown, int16_t level, const char *fmt, ...); | |
PHPAPI zend_string *php_escape_shell_cmd_unix(char *str); | |
PHPAPI zend_string *php_escape_shell_arg_unix(char *str); | |
PHPAPI zend_string *php_escape_shell_cmd_wind(char *str); | |
PHPAPI zend_string *php_escape_shell_arg_wind(char *str); | |
int main(int argc, char **argv) | |
{ | |
if (argc > 1) { | |
for (int i = 1; i < argc; i++) { | |
test(argv[i]); | |
} | |
} else { | |
test("'test\\\"test'@test.test"); | |
} | |
return 0; | |
} | |
void test(const char *str) | |
{ | |
size_t len = strlen(str); | |
if (len > MAX_STR_LEN) | |
len = MAX_STR_LEN; | |
char *s = (char *)malloc(len + 1); | |
s = memcpy(s, str, len); | |
s[len] = '\0'; | |
printf("[in] %s\n", s); | |
output("arg", "unix", php_escape_shell_arg_unix(s)); | |
output("arg", "wind", php_escape_shell_arg_wind(s)); | |
output("cmd", "unix", php_escape_shell_cmd_unix(s)); | |
output("cmd", "wind", php_escape_shell_cmd_wind(s)); | |
printf("\n"); | |
fflush(stdout); | |
free(s); | |
} | |
void output(const char *func, const char *os, zend_string *zstr) | |
{ | |
char *str = zstr ? (ZSTR_VAL(zstr) ? ZSTR_VAL(zstr) : "[nullvalue]") : "[nullzstr]"; | |
printf("[%s:%s] %s\n", func, os, str); | |
zend_string_release(zstr); | |
} | |
static void zend_string_release(zend_string *str) | |
{ | |
if (str) { | |
if (str->value) { | |
free(str->value); | |
} | |
free(str); | |
} | |
} | |
static zend_string *zend_string_truncate(zend_string *str, size_t n, size_t pad) | |
{ | |
return (zend_string *)realloc(str->value, n + pad); | |
} | |
static inline size_t php_mblen(const char *str, size_t n) | |
{ | |
return mbtowc(NULL, str, n); | |
} | |
static zend_string *zend_string_safe_alloc(size_t n, size_t m, size_t l, int persistent) | |
{ | |
size_t len = n * m; | |
zend_string *str = (zend_string *)malloc(sizeof(zend_string)); | |
str->length = 0; | |
str->value = (char *)calloc(len + 1, sizeof(char)); | |
str->value[0] = '\0'; | |
if (len) | |
str->value[len] = '\0'; | |
return str; | |
} | |
static zend_string *ZSTR_EMPTY_ALLOC() | |
{ | |
return zend_string_safe_alloc(1, 1, 0, 0); | |
} | |
static void php_error_docref(void *unkown, int16_t level, const char *fmt, ...) | |
{ | |
va_list argp; | |
va_start(argp, fmt); | |
fprintf(stderr, "ERROR: "); | |
vfprintf(stderr, fmt, argp); | |
fprintf(stderr, "\n"); | |
fflush(stderr); | |
} | |
PHPAPI zend_string *php_escape_shell_cmd_unix(char *str) | |
{ | |
register size_t x, y; | |
size_t l = strlen(str); | |
uint64_t estimate = (2 * (uint64_t)l) + 1; | |
zend_string *cmd; | |
#ifndef PHP_WIN32 | |
char *p = NULL; | |
#endif | |
/* max command line length - two single quotes - \0 byte length */ | |
if (l > cmd_max_len - 2 - 1) { | |
php_error_docref(NULL, E_ERROR, "Command exceeds the allowed length of %d bytes", cmd_max_len); | |
return ZSTR_EMPTY_ALLOC(); | |
} | |
cmd = zend_string_safe_alloc(2, l, 0, 0); | |
for (x = 0, y = 0; x < l; x++) { | |
int mb_len = php_mblen(str + x, (l - x)); | |
/* skip non-valid multibyte characters */ | |
if (mb_len < 0) { | |
continue; | |
} else if (mb_len > 1) { | |
memcpy(ZSTR_VAL(cmd) + y, str + x, mb_len); | |
y += mb_len; | |
x += mb_len - 1; | |
continue; | |
} | |
switch (str[x]) { | |
#ifndef PHP_WIN32 | |
case '"': | |
case '\'': | |
if (!p && (p = memchr(str + x + 1, str[x], l - x - 1))) { | |
/* noop */ | |
} else if (p && *p == str[x]) { | |
p = NULL; | |
} else { | |
ZSTR_VAL(cmd)[y++] = '\\'; | |
} | |
ZSTR_VAL(cmd)[y++] = str[x]; | |
break; | |
#else | |
/* % is Windows specific for environmental variables, ^%PATH% will | |
output PATH while ^%PATH^% will not. escapeshellcmd->val will escape all % and !. | |
*/ | |
case '%': | |
case '!': | |
case '"': | |
case '\'': | |
#endif | |
case '#': /* This is character-set independent */ | |
case '&': | |
case ';': | |
case '`': | |
case '|': | |
case '*': | |
case '?': | |
case '~': | |
case '<': | |
case '>': | |
case '^': | |
case '(': | |
case ')': | |
case '[': | |
case ']': | |
case '{': | |
case '}': | |
case '$': | |
case '\\': | |
case '\x0A': /* excluding these two */ | |
case '\xFF': | |
#ifdef PHP_WIN32 | |
ZSTR_VAL(cmd)[y++] = '^'; | |
#else | |
ZSTR_VAL(cmd)[y++] = '\\'; | |
#endif | |
/* fall-through */ | |
default: | |
ZSTR_VAL(cmd)[y++] = str[x]; | |
} | |
} | |
ZSTR_VAL(cmd)[y] = '\0'; | |
if (y > cmd_max_len + 1) { | |
php_error_docref(NULL, E_ERROR, "Escaped command exceeds the allowed length of %d bytes", cmd_max_len); | |
zend_string_release(cmd); | |
return ZSTR_EMPTY_ALLOC(); | |
} | |
if ((estimate - y) > 4096) { | |
/* realloc if the estimate was way overill | |
* Arbitrary cutoff point of 4096 */ | |
cmd = zend_string_truncate(cmd, y, 0); | |
} | |
ZSTR_LEN(cmd) = y; | |
return cmd; | |
} | |
PHPAPI zend_string *php_escape_shell_arg_unix(char *str) | |
{ | |
size_t x, y = 0; | |
size_t l = strlen(str); | |
zend_string *cmd; | |
uint64_t estimate = (4 * (uint64_t)l) + 3; | |
/* max command line length - two single quotes - \0 byte length */ | |
if (l > cmd_max_len - 2 - 1) { | |
php_error_docref(NULL, E_ERROR, "Argument exceeds the allowed length of %d bytes", cmd_max_len); | |
return ZSTR_EMPTY_ALLOC(); | |
} | |
cmd = zend_string_safe_alloc(4, l, 2, 0); /* worst case */ | |
#ifdef PHP_WIN32 | |
ZSTR_VAL(cmd)[y++] = '"'; | |
#else | |
ZSTR_VAL(cmd)[y++] = '\''; | |
#endif | |
for (x = 0; x < l; x++) { | |
int mb_len = php_mblen(str + x, (l - x)); | |
/* skip non-valid multibyte characters */ | |
if (mb_len < 0) { | |
continue; | |
} else if (mb_len > 1) { | |
memcpy(ZSTR_VAL(cmd) + y, str + x, mb_len); | |
y += mb_len; | |
x += mb_len - 1; | |
continue; | |
} | |
switch (str[x]) { | |
#ifdef PHP_WIN32 | |
case '"': | |
case '%': | |
case '!': | |
ZSTR_VAL(cmd)[y++] = ' '; | |
break; | |
#else | |
case '\'': | |
ZSTR_VAL(cmd)[y++] = '\''; | |
ZSTR_VAL(cmd)[y++] = '\\'; | |
ZSTR_VAL(cmd)[y++] = '\''; | |
#endif | |
/* fall-through */ | |
default: | |
ZSTR_VAL(cmd)[y++] = str[x]; | |
} | |
} | |
#ifdef PHP_WIN32 | |
if (y > 0 && '\\' == ZSTR_VAL(cmd)[y - 1]) { | |
int k = 0, n = y - 1; | |
for (; n >= 0 && '\\' == ZSTR_VAL(cmd)[n]; n--, k++); | |
if (k % 2) { | |
ZSTR_VAL(cmd)[y++] = '\\'; | |
} | |
} | |
ZSTR_VAL(cmd)[y++] = '"'; | |
#else | |
ZSTR_VAL(cmd)[y++] = '\''; | |
#endif | |
ZSTR_VAL(cmd)[y] = '\0'; | |
if (y > cmd_max_len + 1) { | |
php_error_docref(NULL, E_ERROR, "Escaped argument exceeds the allowed length of %d bytes", cmd_max_len); | |
zend_string_release(cmd); | |
return ZSTR_EMPTY_ALLOC(); | |
} | |
if ((estimate - y) > 4096) { | |
/* realloc if the estimate was way overill | |
* Arbitrary cutoff point of 4096 */ | |
cmd = zend_string_truncate(cmd, y, 0); | |
} | |
ZSTR_LEN(cmd) = y; | |
return cmd; | |
} | |
////////////////////////////// | |
#define PHP_WIN32 1 | |
////////////////////////////// | |
PHPAPI zend_string *php_escape_shell_cmd_wind(char *str) | |
{ | |
register size_t x, y; | |
size_t l = strlen(str); | |
uint64_t estimate = (2 * (uint64_t)l) + 1; | |
zend_string *cmd; | |
#ifndef PHP_WIN32 | |
char *p = NULL; | |
#endif | |
/* max command line length - two single quotes - \0 byte length */ | |
if (l > cmd_max_len - 2 - 1) { | |
php_error_docref(NULL, E_ERROR, "Command exceeds the allowed length of %d bytes", cmd_max_len); | |
return ZSTR_EMPTY_ALLOC(); | |
} | |
cmd = zend_string_safe_alloc(2, l, 0, 0); | |
for (x = 0, y = 0; x < l; x++) { | |
int mb_len = php_mblen(str + x, (l - x)); | |
/* skip non-valid multibyte characters */ | |
if (mb_len < 0) { | |
continue; | |
} else if (mb_len > 1) { | |
memcpy(ZSTR_VAL(cmd) + y, str + x, mb_len); | |
y += mb_len; | |
x += mb_len - 1; | |
continue; | |
} | |
switch (str[x]) { | |
#ifndef PHP_WIN32 | |
case '"': | |
case '\'': | |
if (!p && (p = memchr(str + x + 1, str[x], l - x - 1))) { | |
/* noop */ | |
} else if (p && *p == str[x]) { | |
p = NULL; | |
} else { | |
ZSTR_VAL(cmd)[y++] = '\\'; | |
} | |
ZSTR_VAL(cmd)[y++] = str[x]; | |
break; | |
#else | |
/* % is Windows specific for environmental variables, ^%PATH% will | |
output PATH while ^%PATH^% will not. escapeshellcmd->val will escape all % and !. | |
*/ | |
case '%': | |
case '!': | |
case '"': | |
case '\'': | |
#endif | |
case '#': /* This is character-set independent */ | |
case '&': | |
case ';': | |
case '`': | |
case '|': | |
case '*': | |
case '?': | |
case '~': | |
case '<': | |
case '>': | |
case '^': | |
case '(': | |
case ')': | |
case '[': | |
case ']': | |
case '{': | |
case '}': | |
case '$': | |
case '\\': | |
case '\x0A': /* excluding these two */ | |
case '\xFF': | |
#ifdef PHP_WIN32 | |
ZSTR_VAL(cmd)[y++] = '^'; | |
#else | |
ZSTR_VAL(cmd)[y++] = '\\'; | |
#endif | |
/* fall-through */ | |
default: | |
ZSTR_VAL(cmd)[y++] = str[x]; | |
} | |
} | |
ZSTR_VAL(cmd)[y] = '\0'; | |
if (y > cmd_max_len + 1) { | |
php_error_docref(NULL, E_ERROR, "Escaped command exceeds the allowed length of %d bytes", cmd_max_len); | |
zend_string_release(cmd); | |
return ZSTR_EMPTY_ALLOC(); | |
} | |
if ((estimate - y) > 4096) { | |
/* realloc if the estimate was way overill | |
* Arbitrary cutoff point of 4096 */ | |
cmd = zend_string_truncate(cmd, y, 0); | |
} | |
ZSTR_LEN(cmd) = y; | |
return cmd; | |
} | |
PHPAPI zend_string *php_escape_shell_arg_wind(char *str) | |
{ | |
size_t x, y = 0; | |
size_t l = strlen(str); | |
zend_string *cmd; | |
uint64_t estimate = (4 * (uint64_t)l) + 3; | |
/* max command line length - two single quotes - \0 byte length */ | |
if (l > cmd_max_len - 2 - 1) { | |
php_error_docref(NULL, E_ERROR, "Argument exceeds the allowed length of %d bytes", cmd_max_len); | |
return ZSTR_EMPTY_ALLOC(); | |
} | |
cmd = zend_string_safe_alloc(4, l, 2, 0); /* worst case */ | |
#ifdef PHP_WIN32 | |
ZSTR_VAL(cmd)[y++] = '"'; | |
#else | |
ZSTR_VAL(cmd)[y++] = '\''; | |
#endif | |
for (x = 0; x < l; x++) { | |
int mb_len = php_mblen(str + x, (l - x)); | |
/* skip non-valid multibyte characters */ | |
if (mb_len < 0) { | |
continue; | |
} else if (mb_len > 1) { | |
memcpy(ZSTR_VAL(cmd) + y, str + x, mb_len); | |
y += mb_len; | |
x += mb_len - 1; | |
continue; | |
} | |
switch (str[x]) { | |
#ifdef PHP_WIN32 | |
case '"': | |
case '%': | |
case '!': | |
ZSTR_VAL(cmd)[y++] = ' '; | |
break; | |
#else | |
case '\'': | |
ZSTR_VAL(cmd)[y++] = '\''; | |
ZSTR_VAL(cmd)[y++] = '\\'; | |
ZSTR_VAL(cmd)[y++] = '\''; | |
#endif | |
/* fall-through */ | |
default: | |
ZSTR_VAL(cmd)[y++] = str[x]; | |
} | |
} | |
#ifdef PHP_WIN32 | |
if (y > 0 && '\\' == ZSTR_VAL(cmd)[y - 1]) { | |
int k = 0, n = y - 1; | |
for (; n >= 0 && '\\' == ZSTR_VAL(cmd)[n]; n--, k++); | |
if (k % 2) { | |
ZSTR_VAL(cmd)[y++] = '\\'; | |
} | |
} | |
ZSTR_VAL(cmd)[y++] = '"'; | |
#else | |
ZSTR_VAL(cmd)[y++] = '\''; | |
#endif | |
ZSTR_VAL(cmd)[y] = '\0'; | |
if (y > cmd_max_len + 1) { | |
php_error_docref(NULL, E_ERROR, "Escaped argument exceeds the allowed length of %d bytes", cmd_max_len); | |
zend_string_release(cmd); | |
return ZSTR_EMPTY_ALLOC(); | |
} | |
if ((estimate - y) > 4096) { | |
/* realloc if the estimate was way overill | |
* Arbitrary cutoff point of 4096 */ | |
cmd = zend_string_truncate(cmd, y, 0); | |
} | |
ZSTR_LEN(cmd) = y; | |
return cmd; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment