diff options
-rw-r--r-- | flags/Makefile | 21 | ||||
-rw-r--r-- | flags/README.md | 182 | ||||
-rw-r--r-- | flags/exec.c | 41 | ||||
-rw-r--r-- | flags/format.c | 36 | ||||
-rwxr-xr-x | flags/make-keys.rsa | 11 | ||||
-rw-r--r-- | flags/rsa-runner.c | 24 | ||||
-rwxr-xr-x | flags/rsa.py | 81 | ||||
-rw-r--r-- | flags/simple.c | 23 | ||||
-rw-r--r-- | flags/simple.not.the.flag | 1 |
9 files changed, 420 insertions, 0 deletions
diff --git a/flags/Makefile b/flags/Makefile new file mode 100644 index 0000000..17fd13d --- /dev/null +++ b/flags/Makefile @@ -0,0 +1,21 @@ +all: exec format rsa-runner simple + +exec: exec.c + gcc -o exec exec.c + chown root:exec exec + chmod 2755 exec + +format: format.c + gcc -o format format.c + chown root:format format + chmod 2755 format + +rsa-runner: rsa-runner.c + gcc -o rsa-runner rsa-runner.c + chown root:rsa rsa-runner + chmod 2755 rsa-runner + +simple: simple.c + gcc -o simple simple.c + chown root:simple simple + chmod 2755 simple diff --git a/flags/README.md b/flags/README.md new file mode 100644 index 0000000..04040a4 --- /dev/null +++ b/flags/README.md @@ -0,0 +1,182 @@ +Simple +====== + +Files +----- + + -rwxr-sr-x 1 root simple 5266 Apr 6 02:08 simple + -rw-r--r-- 1 root root 363 Apr 6 02:08 simple.c + -rw-r----- 1 root simple 33 Apr 6 02:00 simple.flag + -rw-r--r-- 1 root root 33 Apr 6 01:59 simple.not.the.flag + +Synopsis +-------- + +This program opens a file named "simple.not.the.flag" and prints its contents. +The file containing the flag is named "/flags/simple.flag". + +Vulnerability +------------- + +The open call looks in the current directory for a file named +"simple.not.the.flag" and will follow symlinks, rather than opening the +intended file named "/flags/simple.not.the.flag" which the unprivileged user +has no control over. + +Exploit +------- + +Change the current directory to one in which we have write permissions. + + cd ~ + +Create a symlink named "simple.not.the.flag" that points to the flag file we +want. + + ln -s /flags/simple.flag simple.not.the.flag + +Run the program from this directory. + + /flags/simple + +Exec +==== + +Files +----- + + -rwxr-sr-x 1 root exec 6477 Apr 5 22:50 exec + -rw-r--r-- 1 root root 883 Apr 5 22:50 exec.c + -rw-r----- 1 root exec 33 Apr 5 00:32 exec.flag + +Synopsis +-------- + +This program: + +1. Opens the flag file "/flags/exec.flag". +2. Reads it into memory. +3. Forks a child process. +4. Drops privileges in the child process before executing a user-specified +program. +5. Waits for the child to exit. + +Vulnerability +------------- + +The program forgets to close the open file descriptor to the flag file before +execing the user-specified program in step 4. Step 2 is simply misdirection +supported by comments in the code. + +Exploit +------- + +Write a program which performs the following operations on file descriptor +number 3: + +1. Seeks to the beginning of the file. +2. Reads the contents of the file and prints it out. + +Run the exec program given the name of the our exploit program to execute. + +RSA +=== + +Files +----- + + -rwxr-xr-x 1 root root 217 Apr 6 00:41 make-keys.rsa + -rw-r----- 1 root rsa 33 Apr 5 23:19 rsa.flag + -rw-r----- 1 root rsa 1600 Apr 6 00:48 rsa.keys + -rwxr-xr-x 1 root root 1966 Apr 6 23:12 rsa.py + -rwxr-sr-x 1 root rsa 5794 Apr 6 20:19 rsa-runner + -rw-r--r-- 1 root root 468 Apr 6 20:19 rsa-runner.c + +Synopsis +-------- + +The make-keys.rsa program is a python script which generates the rsa.keys +keystore containing a private and public key for "Alice" and a public key for +"Bob". +The rsa-runner program is a setgid wrapper for rsa.py, since interpreted +scripts cannot be run setgid. +The rsa.py program reads the flag file and the keystore into memory and then +starts a TCPServer to handle requests. When a request is received it is +unpickled into a python object and the "request" property is examined. +If the request property is the string "start" a new python object with the +following properties is pickled and sent to the remote peer: + + name = "Alice" + request = "get_flag" + keyid = MD5(DER encoding of Alice's public key) + signature = Sig(keyid + ":" + request, Alice's private key) + +If the request property is the string "get_flag" the message must pass these +tests before the flag is sent to the remote peer: + +1. The the name property must exist in the keystore. +2. The keyid property must match the MD5 hash of a key in the keystore. +3. The name must not be the string "Alice". +4. The signature is validated against the key in the keystore which matches the +keyid property. + +Vulnerability +------------- + +The name property is not covered by the signature, nor is it checked against +keyid, nor is it used to select the signature verification key, so it can be +modified without detection leaving a "valid" get_flag request. + +Exploit +------- + +Using the python REPL, connect to the rsa.py server, send the "start" request, +and unpickle the response. + + >>> import socket, pickle + >>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + >>> sock.connect(('localhost', 6666)) + >>> class Request: + ... pass + ... + >>> msg = Request() + >>> msg.request='start' + >>> sock.sendall(pickle.dumps(msg)) + >>> buf = sock.recv(1024) + >>> msg2 = pickle.loads(buf) + +Reconnect to the server (this has something to do with the way I coded rsa.py), +modify the name property of the reponse to "Bob" and send it back. + + >>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + >>> sock.connect(('localhost', 6666)) + >>> msg2.name='Bob' + >>> sock.sendall(pickle.dumps(msg2)) + +Consume the response and unpickle it. + + >>> buf = sock.recv(1024) + >>> pickle.loads(buf) + +Lottery +======= + +Files +----- + + -rw-r----- 1 root 1666 33 Apr 5 22:33 lottery.flag + +Synopsis +-------- + +This is simply a flag file that can only be read by group 1666. + +Vulnerability +------------- + +The CTF website allows competitors to create a user and group for themselves. + +Exploit +------- + +Write a script to create users and groups until group number 1666 is assigned. diff --git a/flags/exec.c b/flags/exec.c new file mode 100644 index 0000000..24daf7d --- /dev/null +++ b/flags/exec.c @@ -0,0 +1,41 @@ +#include <sys/types.h> +#include <sys/wait.h> +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> + +char flag[32]; + +int main(int argc, char **argv) { + int fd; + pid_t pid; + int status; + + fprintf(stderr, "real %d:%d effective %d:%d\n", + getuid(), getgid(), geteuid(), getegid()); + + fd = open("exec.flag", O_RDONLY); + if(-1 != fd) { + // load flag into memory where nobody will ever find it + read(fd, flag, sizeof(flag)); + } + // we'll even run something for you! + if(argc != 2) { + fprintf(stderr, "give me something to run\n"); + return -1; + } + pid = fork(); + if(pid < 0) { + perror("fork failed"); + } + if(0 == pid) { + // drop privs + seteuid(getuid()); + setegid(getgid()); + // exec tears down the address space, goodbye flag! + execlp(argv[1], argv[1], NULL); + perror("exec failed"); + } + waitpid(pid, &status, 0); + return 0; +} diff --git a/flags/format.c b/flags/format.c new file mode 100644 index 0000000..4fc30fd --- /dev/null +++ b/flags/format.c @@ -0,0 +1,36 @@ +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +void hax(char *shellcode) { + int stack; + printf(shellcode, &stack); +} + +int main(int argc, char **argv) { + char *buf, *p; + int i; + + if(argc != 2) { + printf("What? Are you chicken?\n"); + return -1; + } + // remove %n from format string, we're not *that* stupid + buf = strdup(argv[1]); + for(p = argv[1], i = 0; p[0]; ++p) { + if(p[0] == '%' && p[1] == 'n') { + ++p; + if(p[0]) { + continue; + } else { + break; + } + } + buf[i++] = p[0]; + } + buf[i] = '\0'; + hax(buf); + free(buf); + putchar('\n'); + return 0; +} diff --git a/flags/make-keys.rsa b/flags/make-keys.rsa new file mode 100755 index 0000000..e6fba64 --- /dev/null +++ b/flags/make-keys.rsa @@ -0,0 +1,11 @@ +#!/usr/bin/env python + +from Crypto.PublicKey import RSA +import pickle + +keys = {} +keys['Alice'] = RSA.generate(1024) +keys['Bob'] = RSA.generate(1024).publickey() + +with open('rsa.keys', 'w') as f: + pickle.dump(keys, f) diff --git a/flags/rsa-runner.c b/flags/rsa-runner.c new file mode 100644 index 0000000..2b8c9d2 --- /dev/null +++ b/flags/rsa-runner.c @@ -0,0 +1,24 @@ +#include <sys/types.h> +#include <sys/wait.h> +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> + +int main(int argc, char **argv) { + pid_t pid; + int status; + + fprintf(stderr, "real %d:%d effective %d:%d\n", + getuid(), getgid(), geteuid(), getegid()); + + pid = fork(); + if(pid < 0) { + perror("fork failed"); + } + if(0 == pid) { + execlp("/flags/rsa.py", "rsa.py", NULL); + perror("exec failed"); + } + waitpid(pid, &status, 0); + return 0; +} diff --git a/flags/rsa.py b/flags/rsa.py new file mode 100755 index 0000000..96fc947 --- /dev/null +++ b/flags/rsa.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python + +# Load up the goods. +with open('rsa.flag', 'r') as f: + flag = f.read(32) + +from Crypto.PublicKey import RSA +from Crypto.Hash import MD5 +from Crypto import Random +from SocketServer import TCPServer, StreamRequestHandler +import pickle + +# Load up our keystore. +# This should be a dict of names (strings) to RSA keys. +with open('/flags/rsa.keys', 'r') as f: + keys = pickle.load(f) + +def keyid(key): + return MD5.new(key.publickey().exportKey('DER')).digest() + +def getkey(by_id): + for key in keys.values(): + if keyid(key) == by_id: + return key + +def digest(msg): + buf = MD5.new(msg.keyid + ':' + msg.request).digest() + print "Message digest is {0}".format(buf.encode('hex')) + return buf + +def sign(msg): + rng = Random.new().read + return getkey(msg.keyid).sign(digest(msg), rng) + +def verify(msg): + return getkey(msg.keyid).verify(digest(msg), msg.signature) + +class Request: + pass + +class Handler(StreamRequestHandler): + def start(self): + # Ask Bob for his flag, this should be fun. + msg = Request() + msg.name = 'Alice' + msg.request = 'get_flag' + msg.keyid = keyid(keys[msg.name]) + msg.signature = sign(msg) + pickle.dump(msg, self.wfile) + + def get_flag(self, request): + # Make sure it's from someone we trust + if(request.name not in keys.keys() + or request.keyid not in + [keyid(key) for key in keys.values()] + or request.name == 'Alice'): + print "We don't trust this person" + return + # Verify the signature + if(not verify(request)): + print "This message was altered" + return + # Send the goods. + pickle.dump(flag, self.wfile) + + def handle(self): + request = pickle.load(self.rfile) + print "we got: {0}".format(request) + if(request.request == 'start'): + self.start() + if(request.request == 'get_flag'): + self.get_flag(request) + +for port in range(6666, 6999): + try: + server = TCPServer(('localhost', port), Handler) + print 'server running at port {0}'.format(port) + break + except: + continue +server.serve_forever() diff --git a/flags/simple.c b/flags/simple.c new file mode 100644 index 0000000..e69e8e5 --- /dev/null +++ b/flags/simple.c @@ -0,0 +1,23 @@ +#include <fcntl.h> +#include <stdio.h> + +// this one is really simple +int main() { + int fd; + char buff[32]; + int len; + + fd = open("simple.not.the.flag", O_RDONLY); + if(-1 == fd) { + perror("open failed"); + return -1; + } + len = read(fd, buff, sizeof(buff)); + if(len < 0) { + perror("read failed"); + return -1; + } + write(1, buff, len); + putchar('\n'); + return 0; +} diff --git a/flags/simple.not.the.flag b/flags/simple.not.the.flag new file mode 100644 index 0000000..73fe8ce --- /dev/null +++ b/flags/simple.not.the.flag @@ -0,0 +1 @@ +7db61f4aafe27bd210359d445241587a |