- Location
- scripts/tracker.js:247
- Finding
- Unrestricted Project URL Fetching Permits Server-Side Request Forgery<![CDATA[
## Vulnerability Details
**File Location**: `scripts/tracker.js:247-292, 940-951, 1018-1024`
**Vulnerability Type**: Server-side request forgery and insecure cleartext transport
**Risk Level**: Medium
### Vulnerable Code
The default government endpoint uses cleartext HTTP:
```js
const BASE_URL = 'http://bjjs.zjw.beijing.gov.cn';
const FEISHU_BASE_URL = 'https://open.feishu.cn';
```
The generic fetch routine accepts arbitrary URLs and follows redirects:
```js
async function fetchText(url, options = {}, retries = 3) {
let lastError;
for (let attempt = 1; attempt <= retries; attempt += 1) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 25000);
try {
const response = await fetch(url, {
redirect: 'follow',
signal: controller.signal,
...options,
headers: {
'user-agent': 'Mozilla/5.0 (OpenClaw Skill Tracker)',
...(options.headers || {})
}
});
const text = await response.text();
if (!response.ok) throw new Error(`HTTP ${response.status}`);
clearTimeout(timeout);
return text;
} catch (error) {
clearTimeout(timeout);
lastError = error;
if (attempt < retries) await new Promise(resolve => setTimeout(resolve, attempt * 1500));
}
}
throw lastError;
}
```
Project URLs are stored without protocol, host, port, or path validation:
```js
if (command === 'add') {
if (!options.name || !options.url) throw new Error('add 命令需要同时提供 --name 和 --url');
const project = upsertProject(config, options.name, options.url);
saveConfig(configPath, config);
console.log(`已保存项目映射: ${project.name} (${project.urls.length} 个链接)`);
console.log(`配置文件: ${configPath}`);
return;
}
```
A temporary URL can also be supplied directly to synchronization:
```js
let projectsToSync = [];
if (options.name && options.url) projectsToSync = [{ name: options.name, url: options.url }];
else if (options
...[truncated 3565 chars]
- Remediation
- <![CDATA[
## Remediation Suggestions
1. Permit only `https:` URLs.
2. Enforce an exact hostname allowlist, preferably only `bjjs.zjw.beijing.gov.cn`, before every request.
3. Validate project URLs when they are added, loaded from configuration, and supplied through temporary command options.
4. Validate every extracted building and room URL independently rather than trusting it because it came from an allowed page.
5. Disable automatic redirects or inspect and validate every redirect target before following it.
6. Reject URLs containing embedded credentials, nonstandard ports, fragments, unexpected paths, or malformed encodings.
7. Resolve hostnames and reject loopback, unspecified, link-local, multicast, carrier-grade NAT, and private IPv4/IPv6 ranges. Repeat this check after redirects to mitigate DNS rebinding.
8. Enforce response-size limits, redirect limits, content-type expectations, and a maximum number of building and room requests.
9. Use outbound firewall or sandbox rules so the Skill process cannot reach local services, private networks, or cloud metadata endpoints.
10. If the government service does not support HTTPS, document that limitation, isolate the crawler, pin the expected destination network where practical, and treat all returned content as untrusted.
]]>