/* +----------------------------------------------------------------------+ | Hardening Patch for PHP | +----------------------------------------------------------------------+ | Copyright (c) 2004-2005 Stefan Esser | +----------------------------------------------------------------------+ | This source file is subject to version 2.02 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available at through the world-wide-web at | | http://www.php.net/license/2_02.txt. | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Stefan Esser | +----------------------------------------------------------------------+ */ /* $Id: hardening_patch.c,v 1.2 2004/11/21 09:38:52 ionic Exp $ */ #include "php.h" #include #include #if HAVE_UNISTD_H #include #endif #include "SAPI.h" #include "php_globals.h" #if HARDENING_PATCH #ifdef HAVE_SYS_SOCKET_H #include #endif #if defined(PHP_WIN32) || defined(__riscos__) || defined(NETWARE) #undef AF_UNIX #endif #if defined(AF_UNIX) #include #endif #define SYSLOG_PATH "/dev/log" #include "snprintf.h" #include "hardening_patch.h" #ifdef ZTS #include "hardened_globals.h" int hardened_globals_id; #else struct _hardened_globals hardened_globals; #endif static void hardened_globals_ctor(hardened_globals_struct *hardened_globals TSRMLS_DC) { memset(hardened_globals, 0, sizeof(*hardened_globals)); } PHPAPI void hardened_startup() { #ifdef ZTS ts_allocate_id(&hardened_globals_id, sizeof(hardened_globals_struct), (ts_allocate_ctor) hardened_globals_ctor, NULL); #else hardened_globals_ctor(&hardened_globals TSRMLS_CC); #endif } char *loglevel2string(int loglevel) { switch (loglevel) { case S_FILES: return "FILES"; case S_INCLUDE: return "INCLUDE"; case S_MEMORY: return "MEMORY"; case S_MISC: return "MISC"; case S_SQL: return "SQL"; case S_EXECUTOR: return "EXECUTOR"; case S_VARS: return "VARS"; default: return "UNKNOWN"; } } PHPAPI void php_security_log(int loglevel, char *fmt, ...) { #if defined(AF_UNIX) int s, r, i=0; struct sockaddr_un saun; char buf[4096+64]; char error[4096+100]; char *ip_address; char *fname; int lineno; va_list ap; TSRMLS_FETCH(); if (EG(hphp_log_use_x_forwarded_for)) { ip_address = sapi_getenv("HTTP_X_FORWARDED_FOR", 20 TSRMLS_CC); if (ip_address == NULL) { ip_address = "X-FORWARDED-FOR not set"; } } else { ip_address = sapi_getenv("REMOTE_ADDR", 11 TSRMLS_CC); if (ip_address == NULL) { ip_address = "REMOTE_ADDR not set"; } } va_start(ap, fmt); ap_php_vsnprintf(error, sizeof(error), fmt, ap); va_end(ap); while (error[i]) { if (error[i] < 32) error[i] = '.'; i++; } if (zend_is_executing(TSRMLS_C)) { lineno = zend_get_executed_lineno(TSRMLS_C); fname = zend_get_executed_filename(TSRMLS_C); ap_php_snprintf(buf, sizeof(buf), "ALERT - %s (attacker '%s', file '%s', line %u)", error, ip_address, fname, lineno); } else { fname = sapi_getenv("SCRIPT_FILENAME", 15 TSRMLS_CC); if (fname==NULL) { fname = "unknown"; } ap_php_snprintf(buf, sizeof(buf), "ALERT - %s (attacker '%s', file '%s')", error, ip_address, fname); } /* Syslog-Logging disabled? */ if ((EG(hphp_log_syslog) & loglevel)==0) { goto log_sapi; } ap_php_snprintf(error, sizeof(error), "<%u>hphp[%u]: %s\n", EG(hphp_log_syslog_facility)|EG(hphp_log_syslog_priority),getpid(),buf); s = socket(AF_UNIX, SOCK_DGRAM, 0); if (s == -1) { goto log_sapi; } memset(&saun, 0, sizeof(saun)); saun.sun_family = AF_UNIX; strcpy(saun.sun_path, SYSLOG_PATH); /*saun.sun_len = sizeof(saun);*/ r = connect(s, (struct sockaddr *)&saun, sizeof(saun)); if (r) { close(s); s = socket(AF_UNIX, SOCK_STREAM, 0); if (s == -1) { goto log_sapi; } memset(&saun, 0, sizeof(saun)); saun.sun_family = AF_UNIX; strcpy(saun.sun_path, SYSLOG_PATH); /*saun.sun_len = sizeof(saun);*/ r = connect(s, (struct sockaddr *)&saun, sizeof(saun)); if (r) { close(s); goto log_sapi; } } send(s, error, strlen(error), 0); close(s); log_sapi: /* SAPI Logging activated? */ if ((EG(hphp_log_syslog) & loglevel)!=0) { sapi_module.log_message(buf); } log_script: /* script logging activaed? */ if (((EG(hphp_log_script) & loglevel)!=0) && EG(hphp_log_scriptname)!=NULL) { char cmd[8192], *cmdpos, *bufpos; FILE *in; int space; ap_php_snprintf(cmd, sizeof(cmd), "%s %s \'", EG(hphp_log_scriptname), loglevel2string(loglevel)); space = sizeof(cmd) - strlen(cmd); cmdpos = cmd + strlen(cmd); bufpos = buf; if (space <= 1) return; while (space > 2 && *bufpos) { if (*bufpos == '\'') { if (space<=5) break; *cmdpos++ = '\''; *cmdpos++ = '\\'; *cmdpos++ = '\''; *cmdpos++ = '\''; bufpos++; space-=4; } else { *cmdpos++ = *bufpos++; space--; } } *cmdpos++ = '\''; *cmdpos = 0; if ((in=VCWD_POPEN(cmd, "r"))==NULL) { return; } /* read and forget the result */ while (1) { int readbytes = fread(cmd, 1, sizeof(cmd), in); if (readbytes<=0) { break; } } pclose(in); } #endif } #endif #if HARDENING_PATCH_MM_PROTECT || HARDENING_PATCH_LL_PROTECT || HARDENING_PATCH_HASH_PROTECT /* will be replaced later with more compatible method */ PHPAPI unsigned int php_canary() { time_t t; unsigned int canary; int fd; fd = open("/dev/urandom", 0); if (fd != -1) { int r = read(fd, &canary, sizeof(canary)); close(fd); if (r == sizeof(canary)) { return (canary); } } /* not good but we never want to do this */ time(&t); canary = *(unsigned int *)&t + getpid() << 16; return (canary); } #endif #if HARDENING_PATCH_INC_PROTECT PHPAPI int php_is_valid_include(zval *z) { char *filename; int len, i; TSRMLS_FETCH(); /* must be of type string */ if (z->type != IS_STRING || z->value.str.val == NULL) { return (0); } /* short cut */ filename = z->value.str.val; len = z->value.str.len; /* 1. must be shorter than MAXPATHLEN */ if (len > MAXPATHLEN) { char *fname = estrndup(filename, len); for (i=0; i < len; i++) if (fname[i] < 32) fname[i]='.'; php_security_log(S_INCLUDE, "Include filename ('%s') longer than MAXPATHLEN chars", fname); efree(fname); return (0); } /* 2. must not be cutted */ if (len != strlen(filename)) { char *fname = estrndup(filename, len); for (i=0; fname[i]; i++) if (fname[i] < 32) fname[i]='.'; php_security_log(S_INCLUDE, "Include filename truncated by a \\0 after '%s'", fname); efree(fname); return (0); } /* 3. must not be a URL */ if (strstr(filename, "://")) { char *fname = estrndup(filename, len); for (i=0; i < len; i++) if (fname[i] < 32) fname[i]='.'; php_security_log(S_INCLUDE, "Include filename ('%s') is an URL", fname); efree(fname); return (0); } /* 4. must not be an uploaded file */ if (SG(rfc1867_uploaded_files)) { if (zend_hash_exists(SG(rfc1867_uploaded_files), (char *) filename, len+1)) { php_security_log(S_INCLUDE, "Include filename is an uploaded file"); return (0); } } /* passed all tests */ return (1); } #endif /* * Local variables: * tab-width: 4 * c-basic-offset: 4 * End: * vim600: sw=4 ts=4 fdm=marker * vim<600: sw=4 ts=4 */