Skip to content

Instantly share code, notes, and snippets.

@Zenexer
Created December 28, 2016 11:48
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save Zenexer/c123604d57914970ac297413751c3f21 to your computer and use it in GitHub Desktop.
Save Zenexer/c123604d57914970ac297413751c3f21 to your computer and use it in GitHub Desktop.
// 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