commit bcb8884e6fb74b6d3e3c234caa8ffec7be005ecf
parent 16b3dff93e5d1096174749e1b809728f585d95fb
Author: Louis Burda <quent.burda@gmail.com>
Date: Wed, 19 May 2021 20:39:47 +0200
added permium users, second vuln and minor fixes all around
Diffstat:
9 files changed, 363 insertions(+), 156 deletions(-)
diff --git a/documentation/README.md b/documentation/README.md
@@ -14,7 +14,7 @@ and generate reports that include information on the files..
Uploaded models and generated reports are stored in a directory structure.
Unregistered users have their files saved in a collective directory, which
-allows users to query for public models via model name. Registered users have
+allows users to search for public models via model name. Registered users have
their uploads saved to a private directory. This (theoretically) prevents other
users from accessing their files.
@@ -31,13 +31,14 @@ which can be used to cause havoc on vulnboxes and make services go mumble.
1. Enable additional security features via flags during compilation:
- `CFLAGS = "-fPIE -fstack-protector-strong -D_FORTIFY_SOURCE=2"`
+ `CFLAGS = -fPIE -fstack-protector-strong -D_FORTIFY_SOURCE=2 -O2`
- `-fPIE`: enable position independent executable section
- `-fstack-protector-strong`: enable stack canaries in functions with local variables that are prone to overflow
- `-D_FORTIFY_SOURCE=2`: gcc buffer overflow detection
+ - `-O2`: enable level 2 of compiler optimizations (required for `_FORITFY_SOURCE`)
- `LDFLAGS = "-Wl,-z,now -Wl,-z,relro"`
+ `LDFLAGS = -Wl,-z,now -Wl,-z,relro`
- `-Wl,-z,now`: tell dynamic linker to resolve symbols ASAP instead of lazy loading
- `-Wl,-z,relro`: tell dynamic linker to make `got` section read-only after resolving symbols
@@ -53,29 +54,29 @@ Checker
The checker checks the following behavior:
-- File upload and query (for both bin and ascii):
+- File upload and search (for both bin and ascii):
- Open a session
- Upload a file of random, valid contents with random model name
- - Ensure file is listed in query
+ - Ensure file is listed in search
- Open a new session
- - Ensure file is listed in query
- - Ensure file is not listed in query for different model name
+ - Ensure file is listed in search
+ - Ensure file is not listed in search for different model name
- Registering and private files (for both bin and ascii):
- Open a session
- Register with a random password
- Upload a file of random, valid contents with random model name
- Open a new session
- - Ensure file is not listed in query
+ - Ensure file is not listed in search
- Register with previous password
- - Ensure file is listed in query
+ - Ensure file is listed in search
- Check upload ordering and accessing indeces != 0:
- Open a session
- Upload a file of random, valid contents with random model and solid name
- Upload a different file of random, valid contents with same model name but different solid name
- Open a new session
- - Query for same model name and pick 1st entry
+ - search for same model name and pick 1st entry
- Compare returned solid name with expected (1st upload)
- - Query for same model name and pick 2nd entry
+ - search for same model name and pick 2nd entry
- Compare returned solid name with expected (2nd upload)
@@ -87,17 +88,17 @@ The checker tenets:
- A checker SHOULD use unusual, incorrect or pseudomalicious input to detect network filters
satisfied, send various garbage bytes for model name and file contents (TODO)
-The checker does the following to submit the first flagstore's flag:
+The checker does the following to upload the first flagstore's flag:
- Open a session
-- Use `submit` to upload a file of random, valid contents with a
+- Use `upload` to upload a file of random, valid contents with a
the flag as the model name
-The checker does the following to submit the second flagstore's flag:
+The checker does the following to upload the second flagstore's flag:
- Open a session
- Register as a premium user
-- Use `submit` to upload a binary STL with the flag as its solidname
+- Use `upload` to upload a binary STL with the flag as its solidname
and a random model name chosen from a wordlist with numbers for
collision resistance
@@ -116,7 +117,7 @@ Notes on evading detection of the checker through traffic analysis:
since the binary file format is used and viewing the file contents does not
help identify the 3d model encoded
- The first flagstore's flag IS easily identifiable, since it is sent in cleartext
- to the service, however, the checker makes sure to test upload and querying
+ to the service, however, the checker makes sure to test upload and searching
functionality of files with other model names in another session as well
@@ -128,7 +129,7 @@ Discovery
This vulnerability can be found by reading the source code.
-The internal utility function `void freadstr(FILE *f, char **dst)` reads a
+The utility function `void freadstr(FILE *f, char **dst)` reads a
null-terminated string from the given file, allocates space for it and saves the
pointer to its contents in `dst`.
@@ -162,7 +163,7 @@ This allows an attacker to cleverly truncate a string before it has ended to
manipulate the content of strings which follow it. In this case, the model name
is saved before the model hash in the information file. By adding a `0xff` to
the end of our uploaded model's name, the model hash is read as an empty string
-following a `query` of the file's contents. Since any following `query` will use
+following a `search` of the file's contents. Since any following `search` will use
the previously loaded models hash to find the file via prefix match, any files
uploaded by unregistered users may be accessed by a user.
@@ -173,18 +174,18 @@ Exploiting
----------
1. Open a session
-2. Use `submit` to upload an STL file and specify a model name ending in `0xff`
+2. Use `upload` to upload an STL file and specify a model name ending in `0xff`
3. Open a new session
-4. Use `query` with the same model name from **step 1** to retrieve to load the
+4. Use `search` with the same model name from **step 1** to retrieve to load the
parsed information of the file you just uploaded
-5. Use `query` again.. this will now use the cached hash which should be empty,
+5. Use `search` again.. this will now use the cached hash which should be empty,
allowing you to accesss any of the files uploaded by unregistered users
Patching
--------
-For an example fix, see the unified patch `patches/flagstore1.diff`.
+For an example fix, see the unified format patch `services/src/patches/flagstore1.diff`.
Vuln 2: Invalid Format String
@@ -195,13 +196,69 @@ Discovery
This vulnerability can be found by reading the source code.
-TODO..
+The utility function `const char* mhash(const char *str, int len)` is used
+to generate a fixed-width hash from a string input in a static buffer
+and return a pointer to that buffer.
+
+```C
+const char*
+mhash(const char *str, int len)
+{
+ static char buf[MHASHLEN + 1];
+ int i, k, v;
+ char c, *bp;
+
+ if (len == -1) len = strlen(str) + 1;
+
+ for (v = 0, i = 0; i < len; i++) v += str[i];
+ srand(v);
+
+ for (bp = buf, i = 0; i < MHASHLEN / 2; i++)
+ bp += sprintf(bp, "%02x", str[i % len] ^ (rand() % 256));
+
+ return buf;
+}
+```
+
+Although the format specifier suggests otherwise, negative values
+passed to printf will result in more than 2 characters. This can
+be used to overflow the static buffer.
+
+The buffer is zero-initialized and as such is stored in the `.bss`
+section. If we inspect this section of the compiled binary we can
+see that it is adjacent to where the `loggedin` variable is stored:
-The flag is saved in a binary STL as a 3d model. Each character is constructed
-in the XY-Plane via a minimal number of triangles. The positions and triangles
-per character do not change, which should help automation. Additionaly, drawing
-the triangles into a 2d buffer and running a text-detection over it should be
-possible.
+```
+0x000083c0 0000 0000 0000 0000 0000 0000 0000 0000 ................ ; obj.buf.1 (our static buffer)
+0x000083d0 0000 0000 0000 0000 0000 0000 0000 0000 ................
+0x000083e0 0000 0000 0000 0000 0000 0000 0000 0000 ................
+0x000083f0 0000 0000 0000 0000 0000 0000 0000 0000 ................
+0x00008400 0000 0000 0000 0000 0000 0000 0000 0000 ................ ; obj.loggedin ; obj.echo ; obj.resultdir
+```
+
+The arrangement of static variables in `.bss` (atleast using gcc)
+from different compilation units depends on their linking order..
+observe the command used to create the binary and the order of
+static variables from the respective files.
+
+```
+build/stldoctor: build/stlfile.o build/util.o main.c | build
+ $(CC) -o $@ $^ $(CFLAGS) $(LDLIBS)
+```
+
+The fact that many compiler flags are set to protect against
+buffer overflows is misleading to the players in this case,
+because most of them only detect overflows on the *stack*.
+
+Since overwriting the global `loggedin` variable gives you
+permission to use the `list` command and the `resultdir` has
+not changes (as is usually the case using `auth`), the attacker
+can now list the hashes of all registered users.
+
+The next step is to reverse the mhash function using the respective
+hashes to log in as them and query information about the files.
+
+TODO..
Exploiting
@@ -213,6 +270,6 @@ TODO..
Patching
--------
-For an example fix, see the unified patch `patches/flagstore2.diff`.
+For an example fix, see the unified format patch `services/src/patches/flagstore2.diff`.
diff --git a/service/do.sh b/service/do.sh
@@ -24,23 +24,60 @@ if [ "$1" == "compose" ]; then
docker-compose ${@:2}
popd
elif [ "$1" == "cleansrc" ]; then
+ if [ $# -lt 3 ]; then
+ echo "USAGE: do.sh cleansrc <SRC> <DST>"
+ exit 0
+ fi
+
# copy files
src="$2"
dst="$3"
[ -e "$dst" ] && rm -rf "$dst"
- cp -r "$src" "$dst"
+ mkdir -p "$dst"
+ cp -r "$src"/{*.c,*.h,Makefile,msgs} "$dst"
# strip comments
find "$dst" | while read path; do
if [ -f "$path" ]; then
- sed -i -e 's/^\s*\/\*.*\*\/\s*$//g' "$path" # remove /* */ style comments
- sed -i -e 's/\s*\/\*.*\*\/\s*/ /g' "$path" # remove /* */ style comments
- sed -i -e 's/\/\/.*//g' "$path" # remove // style comments
- sed -i -e ':a;N;$!ba;s/\n{2,}/\n/g' "$path" # collapse multiple newlines
+ if [ ! -z $(echo "$path" | grep '.[hc]$') ]; then
+ sed -i -e 's/^\s*\/\*.*\*\/\s*$//g' "$path" # remove /* */ style comments
+ sed -i -e 's/\s*\/\*.*\*\/\s*/ /g' "$path" # remove /* */ style comments
+ sed -i -e 's/\/\/.*//g' "$path" # remove // style comments
+ sed -i -e ':a;N;$!ba;s/\n\{3,\}/\n\n/g' "$path" # collapse multiple newlines
+ sed -i -e 's/fprintf(\s*stderr\s*,\s*/printf(/g' "$path" # replace fprintf stderr
+ elif [ "$(basename "$path")" == "Makefile" ]; then
+ sed -i -e 's/\s*#.*//g' "$path" # remove # style comments
+ sed -i -e ':a;N;$!ba;s/\n\{3,\}/\n\n/g' "$path" # collapse multiple newlines
+ fi
fi
done
+elif [ "$1" == "test" ]; then
+ SRCDIR="$PWD/src" DATADIR="$PWD/data" bash "tests/test.sh" ${@:2}
+elif [ "$1" == "make" ]; then
+ cd src
+
+ make clean
+ make
+elif [ "$1" == "make-safe" ]; then
+ cd "src"
+
+ make clean
+
+ for f in $(ls | grep '\.[ch]$'); do
+ cp "$f" "safe_$f"
+ done
+
+ git apply patches/flagstore1.diff
+ git apply patches/flagstore2.diff
+
+ PREFIX="safe_" make
+
+ rm safe_*
else
echo "USAGE: do.sh (compose) [args..]"
echo "EXAMPLES:"
- echo " do.sh compose up --build # starts the docker container"
+ echo " do.sh compose up --build # starts the docker container"
+ echo " do.sh cleansrc <src> <dst> # post-process source files for release"
+ echo " do.sh make-safe # create patched version of binary"
+ echo " do.sh test <cmd> # run a test on the binary"
fi
diff --git a/service/patches/flagstore1.diff b/service/patches/flagstore1.diff
@@ -1,15 +0,0 @@
-diff --git a/service/src/util.c b/service/src/util.c
---- a/service/src/util.c
-+++ b/service/src/util.c
-@@ -75,10 +75,9 @@ void
- freadstr(FILE *f, char **dst)
- {
- size_t start, len;
-- char c;
-
- start = ftell(f);
-- for (len = 0; (c = fgetc(f)) != EOF && c; len++);
-+ for (len = 0; fgetc(f) > 0; len++);
- fseek(f, start, SEEK_SET);
-
- *dst = checkp(calloc(1, len + 1));
diff --git a/service/src/.gitignore b/service/src/.gitignore
@@ -2,3 +2,4 @@ stldoctor
scans
*.o
vgcore.*
+safe_*
diff --git a/service/src/Makefile b/service/src/Makefile
@@ -1,14 +1,21 @@
CFLAGS = -g -I .
+# fortify source code
+CFLAGS += -fPIE -fstack-protector-strong -D_FORTIFY_SOURCE=2 -O2
+LDFLAGS = -Wl,-z,now -Wl,-z,relro
+
.PHONY: all clean
-all: stldoctor
+all: build/stldoctor
clean:
- rm -f stldoctor *.o
+ rm -rf build
+
+build:
+ mkdir build
-%.o: %.c %.h
+build/%.o: %.c %.h | build
$(CC) -c -o $@ $< $(CFLAGS) $(LDLIBS)
-stldoctor: main.c stlfile.o util.o
+build/stldoctor: build/$(PREFIX)stlfile.o build/$(PREFIX)util.o $(PREFIX)main.c | build
$(CC) -o $@ $^ $(CFLAGS) $(LDLIBS)
diff --git a/service/src/main.c b/service/src/main.c
@@ -6,6 +6,7 @@
#include <fcntl.h>
#include <sys/stat.h>
#include <time.h>
+#include <errno.h>
#include "stlfile.h"
#include "util.h"
@@ -14,30 +15,37 @@
struct command {
const char *name;
- void (*func)(char *);
+ void (*func)(const char *);
+ const char *desc;
};
int save_submission(struct parseinfo *info, char *data, int len);
-void cat_cmd(char *arg);
-void list_cmd(char *arg);
-void exit_cmd(char *arg);
-void echo_cmd(char *arg);
-void submit_cmd(char *arg);
-void query_cmd(char *arg);
+void cat_cmd(const char *arg);
+void help_cmd(const char *arg);
+void exit_cmd(const char *arg);
+void echo_cmd(const char *arg);
+void upload_cmd(const char *arg);
+void search_cmd(const char *arg);
+void list_cmd(const char *arg);
+void auth_cmd(const char *arg);
+
+void cleanexit();
struct command commands[] = {
- { "cat", cat_cmd },
- { "help", list_cmd },
- { "exit", exit_cmd },
- { "echo", echo_cmd },
- { "submit", submit_cmd },
- { "query", query_cmd },
+ { "cat", cat_cmd, "Cat cmd go prrrrr." },
+ { "help", help_cmd, "You already know what this does." },
+ { "exit", exit_cmd, "Closes the session." },
+ { "echo", echo_cmd, "Repeat after me!" },
+ { "upload", upload_cmd, "Upload an STL file to analyze." },
+ { "search", search_cmd, "Search for an STL file by model name." },
+ { "list", list_cmd, "List your uploaded files." },
+ { "auth", auth_cmd, "Login to upload files to a private dir." }
};
struct parseinfo cached;
-const char *resultdir;
-int echo = 0;
+char *resultdir;
+int echo = 0, loggedin = 0;
int
save_submission(struct parseinfo *info, char *stldata, int stlsize)
@@ -46,7 +54,10 @@ save_submission(struct parseinfo *info, char *stldata, int stlsize)
FILE *f = NULL;
char *dirpath = NULL, *infopath = NULL, *modelpath = NULL;
- dirpath = aprintf("%s/%s-%i", resultdir, info->hash, time(NULL));
+ if (loggedin)
+ dirpath = aprintf("%s/.%s-%i", resultdir, info->hash, time(NULL));
+ else
+ dirpath = aprintf("%s/%s-%i", resultdir, info->hash, time(NULL));
if (mkdir(dirpath, S_IRWXU | S_IRWXG | S_IRWXO)) goto fail;
modelpath = aprintf("%s/%s", dirpath, "model");
@@ -82,7 +93,7 @@ fail:
}
void
-cat_cmd(char *arg)
+cat_cmd(const char *arg)
{
if (arg && !strncmp(arg, "flag", 4))
dump("msgs/cat_flag");
@@ -91,10 +102,19 @@ cat_cmd(char *arg)
}
void
-list_cmd(char *arg)
+help_cmd(const char *arg)
{
int i;
+ if (arg) {
+ for (i = 0; i < ARRSIZE(commands); i++) {
+ if (!strcmp(commands[i].name, arg)) {
+ printf("%s\n", commands[i].desc);
+ return;
+ }
+ }
+ }
+
printf("Available commands:\n");
for (i = 0; i < ARRSIZE(commands); i++)
printf("%s%s", i ? " " : "", commands[i].name);
@@ -102,49 +122,53 @@ list_cmd(char *arg)
}
void
-exit_cmd(char *arg)
+exit_cmd(const char *arg)
{
exit(0);
}
void
-echo_cmd(char *arg)
+echo_cmd(const char *arg)
{
echo ^= 1;
printf("Echo is %s\n", echo ? "enabled" : "disabled");
}
void
-submit_cmd(char *arg)
+upload_cmd(const char *arg)
{
const char *bufp;
char *end, *contents;
size_t len;
- bufp = ask("> How large is your file? ");
+ bufp = ask("How large is your file? ");
len = strtoul(bufp, &end, 10);
if (len <= 0 || len >= MAXFILESIZE || *end) {
fprintf(stderr, "Invalid file length!\n");
return;
}
- printf("> Ok! Im listening..\n");
+ printf("Ok! Im listening..\n");
contents = checkp(malloc(len + 1));
- fread(contents, 1, len, stdin);
+ if (fread(contents, 1, len, stdin) != len) {
+ fprintf(stderr, "Hm, I'm missing some bytes.. try again!\n");
+ goto cleanup;
+ }
contents[len] = '\0';
if ((cached.valid = parse_file(&cached, contents, len))) {
if (save_submission(&cached, contents, len) != OK)
fprintf(stderr, "Failed to save your submission!\n");
else
- printf("> Your file was saved with ID %s!\n", cached.hash);
+ printf("Your file was saved with ID %s!\n", cached.hash);
}
+cleanup:
free(contents);
}
void
-query_cmd(char *arg)
+search_cmd(const char *arg)
{
char *end, *scandir = NULL, *infopath = NULL, *modelpath = NULL;
const char *hash;
@@ -161,7 +185,7 @@ query_cmd(char *arg)
}
hash = cached.hash;
} else {
- hash = mhash(ask("> Model name: "), -1);
+ hash = mhash(arg ? arg : ask("Model name: "), -1);
}
if (!(d = opendir(resultdir))) return;
@@ -178,7 +202,7 @@ query_cmd(char *arg)
fprintf(stderr, "Sorry, couldnt find a matching scan result!\n");
goto cleanup;
} else {
- which = strtoul(ask("> Which of these results? "), &end, 10);
+ which = strtoul(ask("Which of these results? "), &end, 10);
if (which >= i || which < 0 || *end) {
fprintf(stderr, "Invalid index!\n");
goto cleanup;
@@ -196,8 +220,9 @@ query_cmd(char *arg)
}
}
+ /* file got cleaned up during race condition by background task */
if (!scandir) {
- fprintf(stderr, "Unexpected error!\n");
+ fprintf(stderr, "Selected result spontaneously combusted!\n");
goto cleanup;
}
@@ -210,14 +235,14 @@ query_cmd(char *arg)
print_info(&cached);
- if (strchr(ask("> Download the model? "), 'y')) {
+ if (strchr(ask("Download the model? "), 'y')) {
modelpath = aprintf("%s/%s", scandir, "model");
if (!(f = fopen(modelpath, "r"))) goto cleanup;
fseek(f, 0, SEEK_END);
size = ftell(f);
fseek(f, 0, SEEK_SET);
if (size > MAXFILESIZE) goto cleanup;
- printf("> Here you go.. (%liB)\n", size);
+ printf("Here you go.. (%liB)\n", size);
while ((i = getc(f)) != EOF)
putc(i, stdout);
fclose(f);
@@ -233,6 +258,52 @@ cleanup:
}
void
+list_cmd(const char *arg)
+{
+ DIR *d;
+ struct dirent *de;
+
+ if (!loggedin) {
+ fprintf(stderr, "Not logged in!\n");
+ return;
+ }
+
+ if (!(d = opendir(resultdir))) return;
+
+ while ((de = readdir(d))) {
+ if (*de->d_name == '.' && !strchr(".", de->d_name[1])) {
+ printf("%s\n", de->d_name);
+ }
+ }
+}
+
+void
+auth_cmd(const char *arg)
+{
+ const char *hash;
+ char *ndir;
+
+ if (loggedin) {
+ fprintf(stderr, "Already logged in!\n");
+ return;
+ }
+
+ hash = mhash(arg ? arg : ask("Enter a password: "), -1);
+ ndir = aprintf("%s/.%s", resultdir, hash);
+ if (mkdir(ndir, S_IRWXU | S_IRWXG | S_IRWXO) && errno != EEXIST) {
+ fprintf(stderr, "Auth failed!\n");
+ return;
+ }
+
+ printf("Success!\n");
+
+ free(resultdir);
+ resultdir = ndir;
+ loggedin = 1;
+ cached.valid = 0;
+}
+
+void
cleanexit()
{
printf("see you later!\n");
@@ -242,11 +313,14 @@ cleanexit()
int
main()
{
- char linebuf[256], *cp, *arg;
- int exit, i;
+ const char *cmd;
+ char *cp, *arg;
+ int exit, i, cmdlen;
- if (!(resultdir = getenv("RESULTDIR")))
- resultdir = "scans";
+ if (!(resultdir = checkp(strdup(getenv("RESULTDIR"))))) {
+ fprintf(stderr, "RESULTDIR not defined\n");
+ return 1;
+ }
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
@@ -258,30 +332,24 @@ main()
exit = 0;
while (!exit) {
- memset(linebuf, '\0', sizeof(linebuf));
-
- printf("$ ");
- exit = !fgets(linebuf, sizeof(linebuf), stdin);
- if (exit || !*linebuf) break;
-
- if (*linebuf == '\n') continue;
- if (linebuf[strlen(linebuf) - 1] == '\n')
- linebuf[strlen(linebuf) - 1] = '\0';
-
- if (echo) printf("%s\n", linebuf);
+ errno = 0;
+ cmd = ask("$ ");
+ if (!*cmd && errno == EBADMSG) break;
+ if (!*cmd) continue;
- cp = strchr(linebuf, ' ');
+ cp = strchr(cmd, ' ');
arg = cp ? cp + 1 : NULL;
- if (cp) *cp = 0;
+ cmdlen = cp ? cp - cmd : strlen(cmd);
for (i = 0; i < ARRSIZE(commands); i++) {
- if (!strcmp(commands[i].name, linebuf)) {
+ if (!strncmp(commands[i].name, cmd, cmdlen)
+ && cmdlen == strlen(commands[i].name)) {
commands[i].func(arg);
break;
}
}
- if (i == ARRSIZE(commands) && strlen(linebuf) != 0)
+ if (i == ARRSIZE(commands) && strlen(cmd) != 0)
fprintf(stderr, "No such command!\n");
}
}
diff --git a/service/src/util.c b/service/src/util.c
@@ -43,45 +43,52 @@ aprintf(const char *fmtstr, ...)
}
const char*
-mhash(const char *filename, int len)
+mhash(const char *str, int len)
{
- static const char *hexalph = "0123456789ABCDEF";
- static char buf[2 * MHASHLEN + 1];
- int i, k;
-
- if (len == -1) len = strlen(filename);
-
- for (i = 0; i < MIN(MHASHLEN, len); i++) {
- unsigned char v = 0;
- for (k = i; k < len; k += MHASHLEN)
- v ^= filename[k];
- buf[i*2+0] = hexalph[(v >> 4) & 0x0f];
- buf[i*2+1] = hexalph[(v >> 0) & 0x0f];
- }
+ static char buf[MHASHLEN + 1];
+ int i, k, v;
+ char c, *bp;
- if (i == 0) {
- memset(buf, '0', MHASHLEN);
- } else if (i < MHASHLEN) {
- for (k = 0; k < MHASHLEN; k++)
- buf[k] = buf[k % i];
- }
+ /* VULN #2: BUFFER OVERFLOW */
+ /* see documentation/README.md for more details */
+
+ if (len == -1) len = strlen(str) + 1;
- buf[MHASHLEN] = '\0';
+ for (v = 0, i = 0; i < len; i++) v += str[i];
+ srand(v);
+
+ for (bp = buf, i = 0; i < MHASHLEN / 2; i++)
+ bp += sprintf(bp, "%02x", str[i % len] ^ (rand() % 256));
return buf;
}
+int
+checkalph(const char *str, const char *alph)
+{
+ int i;
+
+ for (i = 0; i < strlen(str); i++)
+ if (!strchr(alph, str[i])) return 0;
+
+ return 1;
+}
+
void
freadstr(FILE *f, char **dst)
{
- size_t start, len;
+ size_t start, len, tmp;
+ char c;
+
+ /* VULN #1: BAD CAST */
+ /* see documentation/README.md for more details */
start = ftell(f);
- for (len = 0; fgetc(f) > 0; len++);
+ for (len = 0; (c = fgetc(f)) != EOF && c; len++);
fseek(f, start, SEEK_SET);
*dst = checkp(calloc(1, len + 1));
- fread(*dst, len, 1, f);
+ tmp = fread(*dst, len, 1, f);
fgetc(f);
}
@@ -111,6 +118,8 @@ ask(const char *fmtstr, ...)
if (echo) printf("%s\n", linebuf);
}
+ if (fail) errno = EBADMSG;
+
return fail ? "" : linebuf;
}
@@ -123,7 +132,7 @@ dump(const char *filename)
if (!(f = fopen(filename, "r"))) return;
- while ((nb = fread(buf, 1, sizeof(buf), f)))
+ while ((nb = fread(buf, 1, sizeof(buf) - 1, f)))
printf("%.*s\n", nb, buf);
fclose(f);
diff --git a/service/src/util.h b/service/src/util.h
@@ -5,6 +5,7 @@
#include <stdarg.h>
#include <string.h>
#include <stdio.h>
+#include <errno.h>
#define ARRSIZE(x) (sizeof(x)/sizeof((x)[0]))
#define MIN(x,y) ((x) > (y) ? (y) : (x))
@@ -12,7 +13,7 @@
#define NULLFREE(p) do { free(p); p = NULL; } while (0)
-#define MHASHLEN 32
+#define MHASHLEN 40
enum { FAIL = 0, OK = 1 };
@@ -21,6 +22,7 @@ void* die(const char *fmtstr, ...);
char* aprintf(const char *fmtstr, ...);
const char* mhash(const char *filename, int len);
+int checkalph(const char *str, const char *alph);
void freadstr(FILE *f, char **dst);
void fputstr(FILE *f, char *s);
diff --git a/service/tests/test.sh b/service/tests/test.sh
@@ -2,13 +2,18 @@
set -e
-# RUNTYPE=1
+if [ -z "$SRCDIR" -o -z "$DATADIR" ]; then
+ echo "Missing either SRCDIR or DATADIR env vars"
+ exit 1
+fi
+
+export RESULTDIR="$DATADIR/uploads"
+export ECHO_INPUT=1
SCRIPTPATH="$(dirname $(readlink -f "$0"))"
-cd "$SCRIPTPATH"
+TESTDATA="$SCRIPTPATH/data"
-export RESULTDIR="../data/scans"
-export ECHO_INPUT=1
+cd "$SRCDIR"
announce() {
count=$(echo "$1" | wc -c)
@@ -25,7 +30,7 @@ print()
}
checkleaks() {
- valgrind --leak-check=full ./stldoctor 2>&1 | tee /tmp/testlog
+ valgrind --leak-check=full ./build/stldoctor 2>&1 | tee /tmp/testlog
if [ -z "$(grep "no leaks are possible" /tmp/testlog)" ]; then
echo "Valgrind exited with errors!"
exit 1
@@ -38,42 +43,43 @@ connect() {
elif [ "$RUNTYPE" == "debug" ]; then
checkleaks
else
- ./stldoctor
+ ./build/stldoctor
fi
}
+[ ! -z "$RESULTDIR" ] && rm -rf "$RESULTDIR"
+mkdir -p "$RESULTDIR"
+
if [ "$1" == "stl" ]; then
announce "Testing ASCII STL Parsing"
(
echo "echo"
- echo "submit"
- cat tests/sample-ascii.stl | wc -c
- cat tests/sample-ascii.stl
+ echo "upload"
+ cat "$TESTDATA/sample-ascii.stl" | wc -c
+ cat "$TESTDATA/sample-ascii.stl"
echo "ASCII-testname"
) | checkleaks
announce "Testing BIN STL Parsing"
(
echo "echo"
- echo "submit"
- cat tests/sample-binary.stl | wc -c
- cat tests/sample-binary.stl
+ echo "upload"
+ cat "$TESTDATA/sample-binary.stl" | wc -c
+ cat "$TESTDATA/sample-binary.stl"
echo "BIN-testname"
) | checkleaks
-elif [ "$1" == "poc" ]; then
-
- announce "Testing Proof-Of-Concept"
+elif [ "$1" == "vuln1" ]; then
- [ ! -z "$RESULTDIR" ] && rm -rf "$RESULTDIR"/*
+ announce "Testing Flagstore 1"
echo -e "\n--- Uploading target STL ---\n" 1>&2
(
echo "echo"
- echo "submit"
- cat tests/flag1.stl | wc -c
- cat tests/flag1.stl
+ echo "upload"
+ cat "$TESTDATA/flag1.stl" | wc -c
+ cat "$TESTDATA/flag1.stl"
echo "N0TaFL4G"
echo "exit"
) | connect
@@ -81,9 +87,9 @@ elif [ "$1" == "poc" ]; then
echo -e "\n--- Uploading evil STL ---\n" 1>&2
(
echo "echo"
- echo "submit"
- cat tests/evil1.stl | wc -c
- cat tests/evil1.stl
+ echo "upload"
+ cat "$TESTDATA/evil1.stl" | wc -c
+ cat "$TESTDATA/evil1.stl"
echo "EV1L"
echo "exit"
) | connect
@@ -93,27 +99,62 @@ elif [ "$1" == "poc" ]; then
echo "echo"
# try index 0
- echo "query"
+ echo "search"
echo "EV1L"
echo "0"
echo "n"
- echo "query last"
+ echo "search last"
echo "0"
echo "n"
# try index 1
- echo "query"
+ echo "search"
echo -e "EV1L"
echo "0"
echo "n"
- echo "query last"
+ echo "search last"
echo "1"
echo "n"
echo "exit"
) | connect
+elif [ "$1" == "vuln2" ]; then
+
+ announce "Testing Flagstore 2"
+
+ echo -e "\n--- Uploading target STL ---\n" 1>&2
+ (
+ echo "echo"
+ echo "auth test"
+ echo "upload"
+ cat "$TESTDATA/flag1.stl" | wc -c
+ cat "$TESTDATA/flag1.stl"
+ echo "N0TaFL4G"
+ echo "exit"
+ ) | connect
+
+ echo -e "\n--- Testing Exploit ---\n" 1>&2
+ (
+ echo "echo"
+ echo -e "search \xff\xff\xff\xff\xff0000000000000000"
+ echo "auth"
+ echo "list"
+ echo "exit"
+ ) | connect
+
+elif [ "$1" == "authupload" ]; then
+ (
+ echo "echo"
+
+ echo "auth test"
+ echo "upload"
+ cat "$TESTDATA/sample-ascii.stl" | wc -c
+ cat "$TESTDATA/sample-ascii.stl"
+ echo "testname"
+ echo "list"
+ ) | connect
else
connect
fi