Secret was an easy machine. The source code of the api was provided to us. With this we could use git log and see the token_secret which we could use to access and admin feature which allowed use to provide a filename which we then used to gain a reverse shell.Root had a binary with setuid bit set, but also had coredumps enabled which we used to dump the memory state of a program while the content of the file we wanted to read was there, this is how we got the id_rsa of root and then ssh as root.
nmap found three open ports: 22, 80 and 3000.
Visiting the web server we are presented with a website which has some documentation for an api. But also there is a Download source code which gives us the source code of the website. Looking into the documentation we have the following endpoints:
Registering a user:
POST http://localhost:3000/api/user/register
Login User:
POST http://localhost:3000/api/user/login
Private Route:
GET http://localhost:3000/api/priv
Trying to create a user
curl -X POST http://10.10.11.120:3000/api/user/register -d '{"name": "fuxsocy", "email": "fuxsocy@dasith.works", "password": "Kekc8swFgD6zU"}' -H "Content-Type: application/json"
and I get back response :
{"user":"fuxsocy"}
Logging in gives back a jwt token
curl -X POST http://10.10.11.120:3000/api/user/login -d '{"email": "fuxsocy@dasith.works", "password": "Kekc8swFgD6zU"}' -H "Content-Type: application/json"
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MTg2OWEwN2RiYTQ1MTA0NzMwNDUzNmUiLCJuYW1lIjoiZnV4c29jeSIsImVtYWlsIjoiZnV4c29jeUBkYXNpdGgud29ya3MiLCJpYXQiOjE2MzYyMTEyNjN9.lE-CF269UpQEgj47erFlKcYjJrlQDhBOCklYOPVSnKA
Let's use on /priv endpoint:
curl http://10.10.11.120:3000/api/priv -H "auth-token:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MTg2OWEwN2RiYTQ1MTA0NzMwNDUzNmUiLCJuYW1lIjoiZnV4c29jeSIsImVtYWlsIjoiZnV4c29jeUBkYXNpdGgud29ya3MiLCJpYXQiOjE2MzYyMTEyNjN9.lE-CF269UpQEgj47erFlKcYjJrlQDhBOCklYOPVSnKA"
The Response is :
{"role":{"role":"you are normal user","desc":"fuxsocy"}}
Since we have the source code, let's analyze it and then look at the api again if needed.
Downloading the source code we have a zip folder, I use unzip
to get the contents. I use vscode and look at the source code. Inside routes there is a private.js file which has and endpoint /logs and if our username is theadmin the following code runs:
const getLogs = `git log --oneline ${file}`;
We can control the ${file} parameter since this is from const file = req.query.file;
as per the code. But trying to create a user using the api with name as theadmin the api returns that it already exists.
Doing a ls -la
.git folder is there.
Let's look at commits history using git log
we can get the previous commits.
There was a commit which removed .env for security reasons. Let's move our head before this commit using git checkout de0a46b5107a2f4d26e348303e76d85ae4870934
and then get the contents of .env.
This token_secret hopefully is being used to generate jwt token, so technicaly we can generate one using it.
Using https://jwt.io/ I give the TOKEN_SECRET on VERIFY SIGNATURE section. On the payload section I add name theadmin since this can give us admin access on /priv endpoint.
Now trying again using curl we can see that we are admin.
Since I am admin now. I use /logs endpoint but this time give it a file parameter. Whatever I provide as a file parameter is executed as a parameter on git log git log --oneline ${file}
I can inject our own command by using a semiclon as a command separator. I can use this to read system files or even get a reverse shell. First I will try to confirm that this technique works by getting /etc/passwd.
I will use base64 to encode my command and then decode it and give it to bash in the remote machine to gain a reverse shell, while also listening on another terminal using nc -nvlp 4242
.
After looking into the basics of privilege escalation, I found a code.c
file along with it's compiled binary count on the /opt folder. This binary has setuid bit set.
This binary runs as root user thus setuid bit set. The binary takes a directory or a filename and then saves the results of files inside of it(if directory) or lines that these files have on a directory we specify. Using something like /root/ I can also see the contents of the directory but nothing besides that.
Inside the main function there are some comments:
The first one is that it drops the privileges to the running user to prevent file write, and the second one is to enable coredump generation.
In computing, a core dump, a memory dump, crash dump, system dump, or ABEND dump consists of the recorded state of the working memory of a computer program at a specific time, generally when the program has crashed or otherwise terminated abnormally Source: Wikipedia
The code prctl(PR_SET_DUMPABLE, 1);
is enabling this. I can crash a running program using kill -SIGSEGV PID
and find the coredump inside /var/crash/
. The idea here is to run the program on one reverse shell and set /root/.ssh/id_rsa as the file I want the binary to read and before doing the next step find the proccess PID using ps aux | grep count
and then crash it. This will generate a file inside /var/crash which then I can extract it's content to a folder I previously created. Using apport-unpack _opt_count.1000.crash /tmp/results/
The folder /tmp/results/ will contain lots of files
Doing a strings against CoreDump will give me the content of id_rsa which I can use it to gain ssh as root user.