No More Secrets

Published:

Using secret keys to identify applications communicating across the internet has become popular as people have copied the very successful Flickr authentication API. Unfortunately people trust that they can keep these keys secret from attackers, even as they distribute applications that contain the secret keys to other people. I decided to see how hard it would be to extract the secret key and API key from Flickr's new XULRunner based Uploadr.

While the Uploadr is nominally open source, they don't include the key in the source code. In the README they instruct anyone building it to acquire their own key and modify the source code to include that key:

You'll need your own API key and secret from Flickr to build Uploadr.
These can be obtained at http://flickr.com/services/api/.  The key
and secret must be placed in flKey.cpp as indicated.  This file is at:
  UPLOADR/MacUploadr.app/Contents/Resources/components/flKey.cpp

The API key is stored as a string. The secret is stored as individual characters so it is not easily readable from the binary.

Such noble goals.

Getting the API key was as simple as running tcpdump:

sudo tcpdump -i en1 -vvv -n -s 0 -w /tmp/DumpFile.dmp
running the Uploadr, and then looking through the dump file to find where the API key is passed. Look:
api_key=a5c4c07991a15c6b56b88005a76e7774
In theory getting the secret key is harder, but it turned out to be even easier. It's stored in the source code for the flKey::Sign method:
	// The shared secret
#ifdef XP_MACOSX
	bytes[0] = '-';
	bytes[1] = '-';
	bytes[2] = '-';
	bytes[3] = '-';
	bytes[4] = '-';
	bytes[5] = '-';
	bytes[6] = '-';
	bytes[7] = '-';
	bytes[8] = '-';
	bytes[9] = '-';
	bytes[10] = '-';
	bytes[11] = '-';
	bytes[12] = '-';
	bytes[13] = '-';
	bytes[14] = '-';
	bytes[15] = '-';
#endif
All I need to do is find that code, disassemble it and reassemble the string. I ran the Uploadr under gdb:
gdb '/Applications/Flickr Uploadr.app/Contents/MacOS/xulrunner'
and once it was up and running tried to find something to break on that would expose the secret key. There weren't symbols for the flKey::Sign method that's defined in the source file but I found I could break on MD5Init that's called from that function. So:
break MD5Init
kill
run
And now I stop in the middle of the signature function - and from the stack trace I can see where flKey::Sign begins:
Breakpoint 1, 0x149a086b in MD5Init ()
(gdb) where
#0  0x149a086b in MD5Init ()
#1  0x149a1588 in flKey::Sign ()
#2  0x017bb45d in NS_InvokeByIndex_P ()
...
All I have to do is disassemble flKey::Sign:
(gdb) disassemble 0x149a1588
Dump of assembler code for function _ZN5flKey4SignERK9nsAStringRS0_:
0x149a1498 <_ZN5flKey4SignERK9nsAStringRS0_+0>:	push   %ebp
0x149a1499 <_ZN5flKey4SignERK9nsAStringRS0_+1>:	mov    %esp,%ebp
0x149a149b <_ZN5flKey4SignERK9nsAStringRS0_+3>:	push   %edi
0x149a149c <_ZN5flKey4SignERK9nsAStringRS0_+4>:	push   %esi
...
0x149a153e <_ZN5flKey4SignERK9nsAStringRS0_+166>:	movb   $0xXX,(%eax)
0x149a1541 <_ZN5flKey4SignERK9nsAStringRS0_+169>:	movb   $0xXX,0x1(%eax)
0x149a1545 <_ZN5flKey4SignERK9nsAStringRS0_+173>:	movb   $0xXX,0x2(%eax)
0x149a1549 <_ZN5flKey4SignERK9nsAStringRS0_+177>:	movb   $0xXX,0x3(%eax)
0x149a154d <_ZN5flKey4SignERK9nsAStringRS0_+181>:	movb   $0xXX,0x4(%eax)
0x149a1551 <_ZN5flKey4SignERK9nsAStringRS0_+185>:	movb   $0xXX,0x5(%eax)
0x149a1555 <_ZN5flKey4SignERK9nsAStringRS0_+189>:	movb   $0xXX,0x6(%eax)
0x149a1559 <_ZN5flKey4SignERK9nsAStringRS0_+193>:	movb   $0xXX,0x7(%eax)
0x149a155d <_ZN5flKey4SignERK9nsAStringRS0_+197>:	movb   $0xXX,0x8(%eax)
0x149a1561 <_ZN5flKey4SignERK9nsAStringRS0_+201>:	movb   $0xXX,0x9(%eax)
0x149a1565 <_ZN5flKey4SignERK9nsAStringRS0_+205>:	movb   $0xXX,0xa(%eax)
0x149a1569 <_ZN5flKey4SignERK9nsAStringRS0_+209>:	movb   $0xXX,0xb(%eax)
0x149a156d <_ZN5flKey4SignERK9nsAStringRS0_+213>:	movb   $0xXX,0xc(%eax)
0x149a1571 <_ZN5flKey4SignERK9nsAStringRS0_+217>:	movb   $0xXX,0xd(%eax)
0x149a1575 <_ZN5flKey4SignERK9nsAStringRS0_+221>:	movb   $0xXX,0xe(%eax)
0x149a1579 <_ZN5flKey4SignERK9nsAStringRS0_+225>:	movb   $0xXX,0xf(%eax)
...
0x149a168e <_ZN5flKey4SignERK9nsAStringRS0_+502>:	pop    %esi
0x149a168f <_ZN5flKey4SignERK9nsAStringRS0_+503>:	pop    %edi
0x149a1690 <_ZN5flKey4SignERK9nsAStringRS0_+504>:	pop    %ebp
0x149a1691 <_ZN5flKey4SignERK9nsAStringRS0_+505>:	ret
End of assembler dump.
And sure enough if I reconstruct those bytes, the secret key is: REDACTEDREDACTED

That wasn't too hard was it? The fact that the code around the key was open source made it easier to find but even if it was closed source if I knew what kind of algorithm I was looking for it wouldn't be hard to find and examine. It's pretty easy to trace forward from user input or backward from network IO to find the signature algorithm, and the secret's always the input to that.

Unfortunately OAuth follows the same model as Flickr, placing some value in a consumer's "private" key. They do mention in Appendix B.7 that it's often possible for attackers to access a secret it's still a vital part of the protocol.

Since it's not possible to securely identify clients we should move to a model where users approve actions against their accounts rather than granting carte-blanche actions to clients. For example Flickr could put photos that are uploaded into a staging area and require approval on the web site before including them in a user's photo stream. This would allow users to make a informed decisions about third-party access to their accounts.

Updated: at Flickr's request I've removed the "secret" key.

Updated again: I made another post about this topic, expanding on the final paragraph of this post.