From: Jo-Philipp Wich Date: Fri, 30 Aug 2019 05:50:43 +0000 (+0200) Subject: cgi-io: require whitelisting upload locations X-Git-Url: http://git.openwrt.org/feed/routing.git;lede-17.01?a=commitdiff_plain;h=e8e481e6183a4922b2db0880a47df906fabd892f;p=project%2Fcgi-io.git cgi-io: require whitelisting upload locations Introduce further ACL checks to verify that the request-supplied upload location may be written to. This prevents overwriting things like /bin/busybox and allows to confine uploads to specific directories. To setup the required ACLs, the following ubus command may be used on the command line: ubus call session grant '{ "ubus_rpc_session": "d41d8cd98f00b204e9800998ecf8427e", "scope": "cgi-io", "objects": [ [ "/etc/certificates/*", "write" ], [ "/var/uploads/*", "write" ] ] }' Signed-off-by: Jo-Philipp Wich --- diff --git a/Makefile b/Makefile index 2a734b5..5fff39f 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=cgi-io -PKG_RELEASE:=6 +PKG_RELEASE:=7 PKG_LICENSE:=GPL-2.0+ diff --git a/src/main.c b/src/main.c index 2bfec62..a6ded06 100644 --- a/src/main.c +++ b/src/main.c @@ -263,6 +263,64 @@ postdecode(char **fields, int n_fields) return (found >= n_fields); } +static char * +canonicalize_path(const char *path, size_t len) +{ + char *canonpath, *cp; + const char *p, *e; + + if (path == NULL || *path == '\0') + return NULL; + + canonpath = datadup(path, len); + + if (canonpath == NULL) + return NULL; + + /* normalize */ + for (cp = canonpath, p = path, e = path + len; p < e; ) { + if (*p != '/') + goto next; + + /* skip repeating / */ + if ((p + 1 < e) && (p[1] == '/')) { + p++; + continue; + } + + /* /./ or /../ */ + if ((p + 1 < e) && (p[1] == '.')) { + /* skip /./ */ + if ((p + 2 >= e) || (p[2] == '/')) { + p += 2; + continue; + } + + /* collapse /x/../ */ + if ((p + 2 < e) && (p[2] == '.') && ((p + 3 >= e) || (p[3] == '/'))) { + while ((cp > canonpath) && (*--cp != '/')) + ; + + p += 3; + continue; + } + } + +next: + *cp++ = *p++; + } + + /* remove trailing slash if not root / */ + if ((cp > canonpath + 1) && (cp[-1] == '/')) + cp--; + else if (cp == canonpath) + *cp++ = '/'; + + *cp = '\0'; + + return canonpath; +} + static int response(bool success, const char *message) { @@ -417,6 +475,9 @@ data_begin_cb(multipart_parser *p) if (!st.filename) return response(false, "File data without name"); + if (!session_access(st.sessionid, st.filename, "write")) + return response(false, "Access to path denied by ACL"); + st.tempfd = mkstemp(tmpname); if (st.tempfd < 0) @@ -438,7 +499,7 @@ data_cb(multipart_parser *p, const char *data, size_t len) break; case PART_FILENAME: - st.filename = datadup(data, len); + st.filename = canonicalize_path(data, len); break; case PART_FILEMODE: