From 44ce233ab82694ae1c85d0969ec3dd89d04c4992 Mon Sep 17 00:00:00 2001 From: Jann Horn Date: Mon, 12 Nov 2012 17:02:52 +0100 Subject: [PATCH] version one --- cgistuff.c | 121 +++++++++++++++++++++++++++++++ checkstatus.c | 13 ++++ compile | 6 ++ cwebfiles.h | 39 ++++++++++ hex.c | 35 +++++++++ listdir.c | 60 ++++++++++++++++ login.c | 68 ++++++++++++++++++ octalmodes | 23 ++++++ octalmodes_to_js | 1 + static/index.html | 179 ++++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 545 insertions(+) create mode 100644 cgistuff.c create mode 100644 checkstatus.c create mode 100755 compile create mode 100644 cwebfiles.h create mode 100644 hex.c create mode 100644 listdir.c create mode 100644 login.c create mode 100644 octalmodes create mode 100644 octalmodes_to_js create mode 100644 static/index.html diff --git a/cgistuff.c b/cgistuff.c new file mode 100644 index 0000000..ac816e2 --- /dev/null +++ b/cgistuff.c @@ -0,0 +1,121 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include "cwebfiles.h" +#include +#include +#include +#include +#include + +extern char **environ; + +// don't use before you've authenticated successfully! +struct cgi_serialized_session_status session; + +// when we call this function, we usually leak memory - not an issue +static char *session_file_path(char *cookie) { + char *file = malloc(strlen(SESSION_FOLDER)+COOKIE_LENGTH+1); + memcpy(file, SESSION_FOLDER, strlen(SESSION_FOLDER)); + memcpy(file+strlen(SESSION_FOLDER), cookie, COOKIE_LENGTH); + file[strlen(SESSION_FOLDER)+COOKIE_LENGTH] = '\0'; + return file; +} + +int authenticate_by_cookie(char *cookie) { + // check cookie string for conformity + if (strlen(cookie) != COOKIE_LENGTH) return 1; + if (!checkhex(cookie, COOKIE_LENGTH)) return 2; + + // read session data + int fd = open(session_file_path(cookie), O_RDONLY); + + // verify that the session exists + if (fd == -1) return 3; + int res = read(fd, &session, sizeof(session)); + close(fd); + + // verify that the session file is valid + if (res != sizeof(session)) return 4; + session.user_name[32] = 0; + + // verify that the session is still active + time_t cur_time = time(NULL); + if (session.start_time + SESSION_LENGTH < cur_time) return 5; + if (session.start_time > cur_time) return 6; + + // verify that the username and the userid still match + struct passwd *user_data = getpwuid(session.uid); + if (user_data == NULL) return 7; + if (strcmp(user_data->pw_name, session.user_name) != 0) return 8; + + // everything seems fine! :) + return 0; +} + +void persist_session(char *cookie) { + char *file = session_file_path(cookie); + int fd = open(file, O_RDWR|O_EXCL|O_CREAT, S_IRUSR|S_IWUSR); + if (fd == -1) senderr("can't create session file", MYFAULT); + + errno = 0; + int res = write(fd, &session, sizeof(session)); + char errstr[128]; + if (res != sizeof(session)) { + snprintf(errstr, 128, "can't write all session data (%i of %i): %s", (int)res, (int)sizeof(session), strerror(errno)); + } + close(fd); + if (res != sizeof(session)) { + unlink(file); + senderr(errstr, MYFAULT); + } +} + +void senderr(char *errmsg, bool myfault) { + if (myfault) { + puts("Status: 500 CGI error"); + } else { + puts("Status: 400 CGI error"); + } + puts("Content-Type: text/plain;charset=utf8"); + puts("X-Frame-Options: DENY"); + puts(""); + puts("An error occured while processing your request:"); + puts(errmsg); + exit(0); +} + +// may send an error response and exit +void grabrand(unsigned char *out, size_t len) { + int randfd = open(RANDDEV, O_RDONLY); + if (randfd == -1) senderr("can't open random device", MYFAULT); + if (read(randfd, out, len) != len) senderr("reading random data failed", MYFAULT); + close(randfd); +} + +void login_and_setup() { + char *cookies = getenv("HTTP_COOKIE"); + environ = NULL; + setuid(0); + + // Note that this doesn't validate whether this is actually a cookie name or + // part of a value - but that shouldn't be a problem. + // Note: Redo if I feel like doing it. + if (cookies == NULL) senderr("no cookies sent", NOTMYFAULT); + char *session_cookie = strstr(cookies, COOKIE_NAME"="); + if (session_cookie == NULL) senderr("session cookie not present", NOTMYFAULT); + session_cookie += strlen(COOKIE_NAME"="); + if (strlen(session_cookie) < COOKIE_LENGTH) senderr("invalid session cookie (error code 1)", NOTMYFAULT); + session_cookie[COOKIE_LENGTH] = '\0'; + int res = authenticate_by_cookie(session_cookie); + if (res != 0) { + char errstr[128]; + snprintf(errstr, 128, "invalid session cookie (error code 2-%i)", res); + senderr(errstr, NOTMYFAULT); + } +} diff --git a/checkstatus.c b/checkstatus.c new file mode 100644 index 0000000..c620e1f --- /dev/null +++ b/checkstatus.c @@ -0,0 +1,13 @@ +#include +#include "cwebfiles.h" + +int main() { + FASTOUTPUT + + login_and_setup(); + puts("Status: 200 you're authenticated"); + puts("X-Frame-Options: DENY"); + puts("Content-Type: text/plain;charset=utf8"); + puts(""); + puts("OK"); +} diff --git a/compile b/compile new file mode 100755 index 0000000..68ca0e5 --- /dev/null +++ b/compile @@ -0,0 +1,6 @@ +#!/bin/bash +for cgi in login checkstatus listdir; do + diet gcc -O3 -std=gnu99 -Wall -Werror -g -o $cgi $cgi.c cgistuff.c hex.c -lcrypt + sudo chown root:root $cgi + sudo chmod u+s $cgi +done diff --git a/cwebfiles.h b/cwebfiles.h new file mode 100644 index 0000000..cada2eb --- /dev/null +++ b/cwebfiles.h @@ -0,0 +1,39 @@ +#include +#include +#include + +#define SESSION_FOLDER "/var/run/cwebfiles/sessions/" +#define SESSION_LENGTH 60 * 60 * 3 /* unit is seconds; we allow three-hour-sessions */ + +#define COOKIE_NAME "WEBFILESESS" +#define COOKIE_LENGTH 64 + +#define RANDDEV "/dev/urandom" + +#define MYFAULT true +#define NOTMYFAULT false + +struct cgi_serialized_session_status { + // "man useradd" says 32 chars is the length limit + char user_name[33]; + uid_t uid; + time_t start_time; +}; + +extern struct cgi_serialized_session_status session; + +void unhex(unsigned char *out, char *in, size_t in_len); +void hex(char *out, unsigned char *in, size_t in_len); +bool checkhex(char *p, size_t l); + +int authenticate_by_cookie(char *cookie); +void persist_session(char *cookie); +void senderr(char *errmsg, bool myfault); +void grabrand(unsigned char *out, size_t len); +void login_and_setup(); + +#define OUTBUFSIZE 64*1024 +#define FASTOUTPUT \ + char outbuf[OUTBUFSIZE]; \ + setvbuf(stdout, outbuf, _IOFBF, OUTBUFSIZE); \ + diff --git a/hex.c b/hex.c new file mode 100644 index 0000000..150d4de --- /dev/null +++ b/hex.c @@ -0,0 +1,35 @@ +#include "cwebfiles.h" + +// these routines don't respect nullbytes! + +// we use fast hex. start from 'a'. no checks performed (apart from length validity). +// out must be half the size of in +void unhex(unsigned char *out, char *in, size_t in_len) { + in_len = in_len & ~1; // input length mod 2 must be 0 - mask out the last bit + char *in_end = in + in_len; + while (in != in_end) { + unsigned char in1 = *(in++), in2 = *(in++); + *(out++) = (in1 - 'a') << 4 | (in2 - 'a'); + } +} + +// out must be twice the size of in +void hex(char *out, unsigned char *in, size_t in_len) { + unsigned char *in_end = in+in_len; + while (in != in_end) { + *(out++) = (*in >> 4) + 'a'; + *(out++) = (*in & 0xf) + 'a'; + in++; + } +} + +bool checkhex(char *p, size_t l) { + if ((l&1) == 1) return false; + char *e = p+l; + while (p= 'a'+16) return false; + p++; + } + return true; +} diff --git a/listdir.c b/listdir.c new file mode 100644 index 0000000..bfbd47c --- /dev/null +++ b/listdir.c @@ -0,0 +1,60 @@ +#include +#include +#include +#include "cwebfiles.h" +#include +#include +#include +#include +#include +#include +#include +#include + +int main() { + FASTOUTPUT + + char *encpath = getenv("QUERY_STRING"); + login_and_setup(); // will clear the environment + if (encpath == NULL) senderr("no query string received", NOTMYFAULT); + + // OK, we're authenticated. Drop our fsuid to the user's. + // Yes, there's no error indication apart from this kludge. Sucks. + setfsuid(session.uid); + if (setfsuid(session.uid) != session.uid) senderr("setfsuid failed", MYFAULT); + + // Do what the user wants us to do. + char path[strlen(encpath)/2+1]; + unhex((unsigned char *)path, encpath, strlen(encpath)); + path[strlen(encpath)/2] = 0; + if (chdir(path)) { + char errstr[128]; + snprintf(errstr, 128, "can't access that path: %s", strerror(errno)); + senderr(errstr, NOTMYFAULT); + } + DIR *dir = opendir("."); + if (dir == NULL) { + char errstr[128]; + snprintf(errstr, 128, "can't open that path for reading: %s", strerror(errno)); + senderr(errstr, NOTMYFAULT); + } + + // The directory was opened successfully - send our success header. + puts("Status: 200 here's your listing"); + puts("X-Frame-Options: DENY"); + puts("Content-Type: text/plain;charset=utf8"); + puts(""); + + // send one line per file + struct dirent *dent; + char encfilename[NAME_MAX*2+1]; + while ((dent = readdir(dir)) != NULL) { + size_t name_len = strlen(dent->d_name); + hex(encfilename, (unsigned char *)dent->d_name, name_len); + encfilename[name_len*2] = 0; + struct stat st; + // if we can't really see the file anyway, skip it + if (lstat(dent->d_name, &st)) continue; + printf("%s %lu %u\n", encfilename, (unsigned long)st.st_size, (unsigned int)st.st_mode); + } +} diff --git a/login.c b/login.c new file mode 100644 index 0000000..96b95bc --- /dev/null +++ b/login.c @@ -0,0 +1,68 @@ +#define _GNU_SOURCE + +#include +#include +#include "cwebfiles.h" +#include +#include +#include +#include +#include +#include +#include +#include + +extern char **environ; + +#define BADAUTH "invalid user/pass" + +int main() { + FASTOUTPUT + environ = NULL; + setuid(0); + + char buf[1024]; + int res = read(0, buf, 1023); + if (res == -1) senderr("couldn't read POST data", NOTMYFAULT); + buf[res] = 0; + char *nl = strchr(buf, '\r'); + if (nl != NULL) *nl = 0; + char *user = buf; + char *pass = strchr(buf, ':'); + if (pass == NULL) senderr("can't find user-pass-seperator", NOTMYFAULT); + *pass = 0; + pass++; + + // verify password + struct spwd *shadow_entry = getspnam(user); + struct passwd *passwd_entry = getpwnam(user); + if (passwd_entry == NULL || shadow_entry == NULL) senderr(BADAUTH, NOTMYFAULT); + char *enc_pass = crypt(pass, shadow_entry->sp_pwdp); + if (enc_pass == NULL) senderr(BADAUTH, NOTMYFAULT); + if (strcmp(enc_pass, shadow_entry->sp_pwdp) != 0) senderr(BADAUTH, NOTMYFAULT); + + // now create a new session + // create a cookie + unsigned char bincookie[COOKIE_LENGTH/2]; + grabrand(bincookie, COOKIE_LENGTH/2); + char cookie[COOKIE_LENGTH+1]; + hex(cookie, bincookie, COOKIE_LENGTH/2); + cookie[COOKIE_LENGTH] = 0; + + // set session data + strncpy(session.user_name, user, 33); + session.start_time = time(NULL); + session.uid = passwd_entry->pw_uid; + + // write out session data to the fs + persist_session(cookie); + + // phew, all done! tell the user everything is fine + puts("Status: 200 login successful"); + puts("X-Frame-Options: DENY"); + puts("Content-Type: text/plain;charset=utf8"); + // TODO XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX SET SECURE FLAG! + printf("Set-Cookie: " COOKIE_NAME "=%s; HttpOnly\n", cookie); + puts(""); + puts("OK"); +} diff --git a/octalmodes b/octalmodes new file mode 100644 index 0000000..e4e5627 --- /dev/null +++ b/octalmodes @@ -0,0 +1,23 @@ + S_IFMT 0170000 bit mask for the file type bit fields + S_IFSOCK 0140000 socket + S_IFLNK 0120000 symbolic link + S_IFREG 0100000 regular file + S_IFBLK 0060000 block device + S_IFDIR 0040000 directory + S_IFCHR 0020000 character device + S_IFIFO 0010000 FIFO + S_ISUID 0004000 set UID bit + S_ISGID 0002000 set-group-ID bit (see below) + S_ISVTX 0001000 sticky bit (see below) + S_IRWXU 00700 mask for file owner permissions + S_IRUSR 00400 owner has read permission + S_IWUSR 00200 owner has write permission + S_IXUSR 00100 owner has execute permission + S_IRWXG 00070 mask for group permissions + S_IRGRP 00040 group has read permission + S_IWGRP 00020 group has write permission + S_IXGRP 00010 group has execute permission + S_IRWXO 00007 mask for permissions for others (not in group) + S_IROTH 00004 others have read permission + S_IWOTH 00002 others have write permission + S_IXOTH 00001 others have execute permission diff --git a/octalmodes_to_js b/octalmodes_to_js new file mode 100644 index 0000000..107470b --- /dev/null +++ b/octalmodes_to_js @@ -0,0 +1 @@ +cat octalmodes | sed 's|^ *|var |g' | sed 's| *| = parseInt("|' | sed 's| *|",8) // |' diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..1e6adcc --- /dev/null +++ b/static/index.html @@ -0,0 +1,179 @@ + + + + + + + + + -- 2.20.1