T05 · Unauthorized Access and Privilege Escalation
Error
- Location
- scripts/server.py:690
- Finding
- Unauthenticated Arbitrary Local File Disclosure Through the Widget Route<![CDATA[ ## Vulnerability Details **File Location**: `scripts/server.py:690-701` and duplicated at `clawhub-raon-os/scripts/server.py:690-701` **Vulnerability Type**: Path traversal and absolute-path injection **Risk Level**: Critical ### Vulnerable Code ```python elif path.startswith("/widget/"): widget_dir = Path(__file__).resolve().parent.parent / "widget" file_path = widget_dir / path[8:] if file_path.exists() and file_path.is_file(): body = file_path.read_bytes() ct = "application/javascript" if str(file_path).endswith(".js") else "text/plain" self.send_response(200) self.send_header("Content-Type", ct) self.send_header("Content-Length", str(len(body))) self.end_headers() self.wfile.write(body) else: self._send_error(404, "not_found") ``` ### Technical Analysis The route takes the portion of the request path after `/widget/` and appends it directly to the intended widget directory. It does not reject absolute paths, `..` path components, symbolic-link escapes, or resolved paths outside the widget directory. With `pathlib`, joining a base path with an absolute second path discards the base path. For example, if `path[8:]` is `/etc/passwd`, `file_path` becomes `/etc/passwd`, not a file below the widget directory. The route is also outside the `/v1/` authentication middleware. Consequently, any client that can connect to the HTTP server can attempt to read files using the server process's operating-system privileges. ### Attack Path 1. A user starts the HTTP server using its default configuration. 2. The server listens on all network interfaces. 3. An attacker sends a path-preserving request such as: ```text GET /widget//etc/passwd HTTP/1.1 Host: target.example ``` Alternatively, the attacker can use traversal components, for example `/widget/../SKILL.md`, where intermediary infrastructure does not normalize the path. 4. `path[8:]` is interpreted as an absol ...[truncated 829 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions Resolve and validate the requested path before reading it: ```python widget_dir = (Path(__file__).resolve().parent.parent / "widget").resolve() relative_name = path.removeprefix("/widget/") if not relative_name or Path(relative_name).is_absolute(): self._send_error(400, "invalid_path") return candidate = (widget_dir / relative_name).resolve() try: candidate.relative_to(widget_dir) except ValueError: self._send_error(403, "forbidden") return if not candidate.is_file(): self._send_error(404, "not_found") return ``` Additional hardening should include: - Serve only an explicit allowlist such as `raon-chat.js` and `raon-chat.min.js`. - Reject paths containing `..`, backslashes, null bytes, or encoded separators. - Avoid following symbolic links, or verify the resolved target after link resolution. - Apply authentication if arbitrary widget assets are not intended to be public. - Add regression tests for absolute paths, traversal paths, encoded traversal, and symbolic-link escapes. - Apply the same fix to the duplicated `clawhub-raon-os/scripts/server.py`. ]]>
