source: trunk/server/common/patches/httpd-suexec-scripts.patch @ 2774

Last change on this file since 2774 was 2774, checked in by andersk, 8 years ago
Apply the 2015 suexec patch for CVE-2016-5387 “httpoxy”. Also remove our inexplicable whitelist entry for HTTPS_* environment variables.
File size: 9.5 KB
RevLine 
[2591]1From 427d432a56df94d69a11cc438b08adb070615005 Mon Sep 17 00:00:00 2001
2From: Alexander Chernyakhovsky <achernya@mit.edu>
3Date: Fri, 3 May 2013 21:38:58 -0400
4Subject: [PATCH] Add scripts-specific support to suexec
[103]5
[2591]6This patch make suexec aware of static-cat, Scripts' tool to serve
7static content out of AFS.  Specifically, this introduces a whitelist
8of extensions for which suexec is supposed to invoke static-cat as a
9content-handler.
[103]10
[2591]11Additionally, this patch also sets JAVA_TOOL_OPTIONS, to allow the JVM
12to start up in Scripts' limited memory environment.
13
14Furthermore, this patch deals with some of suexec's paranoia being
15incorrect in an AFS world, by ignoring some of the irrelevant stat
16results.
17
18Finally, add support for invoking php-cgi for php files, in a safe
19manner that will strip arguments passed by Apache to php-cgi.
20---
21 configure.in     |   4 ++
22 support/suexec.c | 172 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
23 2 files changed, 173 insertions(+), 3 deletions(-)
24
25diff --git a/configure.in b/configure.in
26index 811aace..a95349f 100644
27--- a/configure.in
28+++ b/configure.in
29@@ -721,6 +721,10 @@ AC_ARG_WITH(suexec-userdir,
[823]30 APACHE_HELP_STRING(--with-suexec-userdir,User subdirectory),[
31   AC_DEFINE_UNQUOTED(AP_USERDIR_SUFFIX, "$withval", [User subdirectory] ) ] )
32 
33+AC_ARG_WITH(suexec-trusteddir,
34+APACHE_HELP_STRING(--with-suexec-trusteddir,Trusted SuExec directory),[
35+  AC_DEFINE_UNQUOTED(AP_TRUSTED_DIRECTORY, "$withval", [Trusted SuExec directory] ) ] )
36+
37 AC_ARG_WITH(suexec-docroot,
38 APACHE_HELP_STRING(--with-suexec-docroot,SuExec root directory),[
39   AC_DEFINE_UNQUOTED(AP_DOC_ROOT, "$withval", [SuExec root directory] ) ] )
[2591]40diff --git a/support/suexec.c b/support/suexec.c
41index 32e7320..3a4d802 100644
42--- a/support/suexec.c
43+++ b/support/suexec.c
[842]44@@ -30,6 +30,9 @@
[298]45  *
46  */
47 
[1590]48+#define STATIC_CAT_PATH "/usr/bin/static-cat"
[842]49+#define PHP_PATH "/usr/bin/php-cgi"
[298]50+
51 #include "apr.h"
52 #include "ap_config.h"
53 #include "suexec.h"
[2591]54@@ -268,9 +272,108 @@ static void clean_env(void)
[298]55     environ = cleanenv;
56 }
57 
58+static const char *static_extensions[] = {
59+    "html",
60+    "css",
61+    "gif",
62+    "jpg",
63+    "png",
64+    "htm",
65+    "jpeg",
66+    "js",
67+    "ico",
68+    "xml",
69+    "xsl",
70+    "tiff",
71+    "tif",
72+    "tgz",
73+    "tar",
74+    "jar",
75+    "zip",
76+    "pdf",
77+    "ps",
78+    "doc",
79+    "xls",
80+    "ppt",
[1877]81+    "dot",
82+    "docx",
83+    "dotx",
84+    "docm",
85+    "dotm",
86+    "xlt",
87+    "xla",
88+    "xlsx",
89+    "xltx",
90+    "xlsm",
91+    "xltm",
92+    "xlam",
93+    "xlsb",
94+    "pot",
95+    "pps",
96+    "ppa",
97+    "pptx",
98+    "potx",
99+    "ppsx",
100+    "ppam",
101+    "pptm",
102+    "potm",
103+    "ppsm",
[298]104+    "swf",
105+    "mp3",
106+    "mov",
107+    "wmv",
108+    "mpg",
109+    "mpeg",
110+    "avi",
111+    "il",
[315]112+    "xhtml",
[618]113+    "svg",
[944]114+    "xaml",
115+    "xap",
[1464]116+    "wav",
117+    "mid",
118+    "midi",
[1785]119+    "ttf",
120+    "otf",
[1877]121+    "odc",
122+    "odb",
123+    "odf",
124+    "odg",
125+    "otg",
126+    "odi",
127+    "odp",
128+    "otp",
129+    "ods",
130+    "ots",
131+    "odt",
132+    "odm",
133+    "ott",
134+    "oth",
[298]135+    NULL
136+};
137+
138+static int is_static_extension(const char *file)
139+{
140+    const char *extension = strrchr(file, '.');
141+    const char **p;
142+    if (extension == NULL) return 0;
143+    for (p = static_extensions; *p; ++p) {
[1464]144+        if (strcasecmp(extension + 1, *p) == 0) return 1;
[298]145+    }
146+    return 0;
147+}
148+
[842]149+static int is_php_extension(const char *file)
150+{
151+    const char *extension = strrchr(file, '.');
152+    if (extension == NULL) return 0;
153+    return strcmp(extension + 1, "php") == 0;
154+}
155+
[298]156 int main(int argc, char *argv[])
157 {
158     int userdir = 0;        /* ~userdir flag             */
[823]159+    int trusteddir = 0;     /* TRUSTED_DIRECTORY flag    */
160     uid_t uid;              /* user information          */
161     gid_t gid;              /* target group placeholder  */
162     char *target_uname;     /* target user name          */
[2591]163@@ -290,6 +393,7 @@ int main(int argc, char *argv[])
[1169]164      * Start with a "clean" environment
165      */
166     clean_env();
167+    setenv("JAVA_TOOL_OPTIONS", "-Xmx128M", 1); /* scripts.mit.edu local hack */
[1259]168 
[1169]169     /*
[2591]170      * Check existence/validity of the UID of the user
171@@ -373,6 +477,20 @@ int main(int argc, char *argv[])
[823]172 #endif /*_OSD_POSIX*/
173 
174     /*
175+     * First check if this is an absolute path to the directory
176+     * of trusted executables. These are supposed to be security
177+     * audited to check parameters and validity on their own...
178+     */
179+    if (strstr(cmd, AP_TRUSTED_DIRECTORY) == cmd) {
180+        if (strstr(cmd, "/../") != NULL) {
181+            log_err("invalid command (%s)\n", cmd);
182+            exit(104);
183+        }
184+        trusteddir = 1;
185+        goto TRUSTED_DIRECTORY;
186+    }
187+
188+    /*
189      * Check for a leading '/' (absolute path) in the command to be executed,
190      * or attempts to back up out of the current directory,
191      * to protect against attacks.  If any are
[2591]192@@ -394,6 +512,7 @@ int main(int argc, char *argv[])
[823]193         userdir = 1;
194     }
195 
196+TRUSTED_DIRECTORY:
197     /*
198      * Error out if the target username is invalid.
199      */
[2591]200@@ -482,7 +601,7 @@ int main(int argc, char *argv[])
[103]201      * Error out if attempt is made to execute as root or as
202      * a UID less than AP_UID_MIN.  Tsk tsk.
203      */
204-    if ((uid == 0) || (uid < AP_UID_MIN)) {
[1474]205+    if ((uid == 0) || (uid < AP_UID_MIN && uid != 102)) { /* uid 102 = signup  */
[2591]206         log_err("cannot run as forbidden uid (%lu/%s)\n", (unsigned long)uid, cmd);
[103]207         exit(107);
208     }
[2591]209@@ -514,6 +633,7 @@ int main(int argc, char *argv[])
210         log_err("failed to setuid (%lu: %s)\n", (unsigned long)uid, cmd);
[103]211         exit(110);
212     }
[908]213+    setenv("HOME", target_homedir, 1);
[103]214 
215     /*
216      * Get the current working directory, as well as the proper
[2591]217@@ -536,6 +656,21 @@ int main(int argc, char *argv[])
[823]218             log_err("cannot get docroot information (%s)\n", target_homedir);
219             exit(112);
[1]220         }
[823]221+        size_t expected_len = strlen(target_homedir)+1+strlen(AP_USERDIR_SUFFIX)+1;
222+        char *expected = malloc(expected_len);
223+        snprintf(expected, expected_len, "%s/%s", target_homedir, AP_USERDIR_SUFFIX);
224+        if (strncmp(cwd, expected, expected_len-1) != 0) {
225+            log_err("error: file's directory not a subdirectory of user's home directory (%s, %s)\n", cwd, expected);
226+            exit(114);
227+        }
228+    }
229+    else if (trusteddir) {
230+        if (((chdir(AP_TRUSTED_DIRECTORY)) != 0) ||
231+            ((getcwd(dwd, AP_MAXPATH)) == NULL) |
232+            ((chdir(cwd)) != 0)) {
233+            log_err("cannot get docroot information (%s)\n", AP_TRUSTED_DIRECTORY);
234+            exit(112);
235+        }
[1]236     }
[823]237     else {
238         if (((chdir(AP_DOC_ROOT)) != 0) ||
[2591]239@@ -562,15 +697,17 @@ int main(int argc, char *argv[])
[1]240     /*
241      * Error out if cwd is writable by others.
242      */
243+#if 0
244     if ((dir_info.st_mode & S_IWOTH) || (dir_info.st_mode & S_IWGRP)) {
245         log_err("directory is writable by others: (%s)\n", cwd);
246         exit(116);
247     }
248+#endif
249 
250     /*
251      * Error out if we cannot stat the program.
252      */
253-    if (((lstat(cmd, &prg_info)) != 0) || (S_ISLNK(prg_info.st_mode))) {
254+    if (((lstat(cmd, &prg_info)) != 0) /*|| (S_ISLNK(prg_info.st_mode))*/) {
255         log_err("cannot stat program: (%s)\n", cmd);
256         exit(117);
257     }
[2591]258@@ -578,10 +715,12 @@ int main(int argc, char *argv[])
[1]259     /*
260      * Error out if the program is writable by others.
261      */
262+#if 0
263     if ((prg_info.st_mode & S_IWOTH) || (prg_info.st_mode & S_IWGRP)) {
264         log_err("file is writable by others: (%s/%s)\n", cwd, cmd);
265         exit(118);
266     }
267+#endif
268 
269     /*
270      * Error out if the file is setuid or setgid.
[2591]271@@ -595,6 +734,7 @@ int main(int argc, char *argv[])
[1]272      * Error out if the target name/group is different from
273      * the name/group of the cwd or the program.
274      */
275+#if 0
276     if ((uid != dir_info.st_uid) ||
277         (gid != dir_info.st_gid) ||
278         (uid != prg_info.st_uid) ||
[2591]279@@ -606,12 +746,14 @@ int main(int argc, char *argv[])
280                 (unsigned long)prg_info.st_uid, (unsigned long)prg_info.st_gid);
[1]281         exit(120);
282     }
283+#endif
284     /*
285      * Error out if the program is not executable for the user.
286      * Otherwise, she won't find any error in the logs except for
[842]287      * "[error] Premature end of script headers: ..."
288      */
289-    if (!(prg_info.st_mode & S_IXUSR)) {
290+    if (!is_static_extension(cmd) && !is_php_extension(cmd) &&
[873]291+        !(prg_info.st_mode & S_IXUSR)) {
[842]292         log_err("file has no execute permission: (%s/%s)\n", cwd, cmd);
[873]293         exit(121);
[842]294     }
[2591]295@@ -660,6 +802,30 @@ int main(int argc, char *argv[])
[1355]296     /*
297      * Execute the command, replacing our image with its own.
298      */
[298]299+    if (is_static_extension(cmd)) {
[1590]300+        if (setenv("PATH_TRANSLATED", cmd, 1) != 0) {
301+            log_err("setenv failed\n");
302+            exit(255);
303+        }
304+        execl(STATIC_CAT_PATH, STATIC_CAT_PATH, (const char *)NULL);
305+        log_err("(%d)%s: static-cat exec failed (%s)\n", errno, strerror(errno), STATIC_CAT_PATH);
[1259]306+        exit(255);
[298]307+    }
[842]308+    if (is_php_extension(cmd)) {
309+        setenv("PHPRC", ".", 1);
310+        argv[1] = PHP_PATH;
311+        argv[2] = "-f";
[2186]312+        /*
313+         * argv[3] is the command to run. argv[4] is either an argument or
314+         * already null. We don't want to pass any arguments through from
315+         * Apache (since they're untrusted), so we chop off the remainder
316+         * of argv here.
317+         */
318+        argv[4] = 0;
[842]319+        execv(PHP_PATH, &argv[1]);
[1354]320+        log_err("(%d)%s: php exec failed (%s)\n", errno, strerror(errno), argv[1]);
[1259]321+        exit(255);
[842]322+    }
[1355]323 #ifdef NEED_HASHBANG_EMUL
324     /* We need the #! emulation when we want to execute scripts */
325     {
[2591]326--
3271.8.1.2
328
Note: See TracBrowser for help on using the repository browser.