Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
944a690d96 | ||
|
|
e7facc3bba | ||
|
|
aa83b62064 | ||
|
|
f9ee39dfd0 | ||
|
|
087b95388f |
No files matched your search
@@ -1,5 +1,13 @@
|
|||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
|
## 1.2.15 (2026-03-26)
|
||||||
|
|
||||||
|
- Fix critical RCE vulnerability in /api/ping endpoint (follow-up to
|
||||||
|
CVE-2021-46704). Credit: JuHwiSang.
|
||||||
|
|
||||||
|
- Fix device IDs containing special characters (e.g., %) not being handled
|
||||||
|
correctly in the UI due to a regression in Mithril 2.3.8.
|
||||||
|
|
||||||
## 1.2.14 (2026-03-12)
|
## 1.2.14 (2026-03-12)
|
||||||
|
|
||||||
- Prevent UI crash when a malformed URL is sent to the server.
|
- Prevent UI crash when a malformed URL is sent to the server.
|
||||||
|
|||||||
+16
-7
@@ -1,6 +1,6 @@
|
|||||||
import { platform } from "node:os";
|
import { platform } from "node:os";
|
||||||
import { exec } from "node:child_process";
|
import { execFile } from "node:child_process";
|
||||||
import { domainToASCII } from "node:url";
|
import { domainToASCII, domainToUnicode } from "node:url";
|
||||||
|
|
||||||
export interface PingResult {
|
export interface PingResult {
|
||||||
packetsTransmitted: number;
|
packetsTransmitted: number;
|
||||||
@@ -16,9 +16,16 @@ function isValidHost(host: string): boolean {
|
|||||||
// Valid chars in IPv4, IPv6, domain names
|
// Valid chars in IPv4, IPv6, domain names
|
||||||
if (/^[a-zA-Z0-9\-.:[\]-]+$/.test(host)) return true;
|
if (/^[a-zA-Z0-9\-.:[\]-]+$/.test(host)) return true;
|
||||||
|
|
||||||
|
const lowercase = host.toLowerCase();
|
||||||
|
const ascii = domainToASCII(lowercase);
|
||||||
|
|
||||||
// Check if input is an IDN convert to Punycode
|
// Check if input is an IDN convert to Punycode
|
||||||
// Can't merge with above because domainToASCII doesn't accept IP addresses
|
// Can't merge with above because domainToASCII doesn't accept IP addresses
|
||||||
return /^[a-zA-Z0-9\-.:[\]-]+$/.test(domainToASCII(host));
|
if (!/^[a-zA-Z0-9\-.:[\]-]+$/.test(ascii)) return false;
|
||||||
|
|
||||||
|
// Round-trip to ensure the original host matches its normalized form.
|
||||||
|
// This prevents bypasses where domainToASCII strips paths (e.g. "example.com/;cmd").
|
||||||
|
return lowercase === domainToUnicode(ascii);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parsePing(osPlatform: string, stdout: string): PingResult {
|
export function parsePing(osPlatform: string, stdout: string): PingResult {
|
||||||
@@ -73,24 +80,26 @@ export function ping(
|
|||||||
): void {
|
): void {
|
||||||
// Validate input to prevent possible remote code execution
|
// Validate input to prevent possible remote code execution
|
||||||
// Credit to Alex Hordijk for reporting this vulnerability
|
// Credit to Alex Hordijk for reporting this vulnerability
|
||||||
|
// Credit to JuHwiSang for reporting a follow-up vulnerability
|
||||||
if (!isValidHost(host)) return callback(new Error("Invalid host"));
|
if (!isValidHost(host)) return callback(new Error("Invalid host"));
|
||||||
host = host.replace("[", "").replace("]", "");
|
host = host.replace("[", "").replace("]", "");
|
||||||
let cmd: string;
|
let args: string[];
|
||||||
|
|
||||||
switch (platform()) {
|
switch (platform()) {
|
||||||
case "linux":
|
case "linux":
|
||||||
cmd = `ping -w 1 -i 0.2 -c 3 ${host}`;
|
args = ["-w", "1", "-i", "0.2", "-c", "3", "--", host];
|
||||||
break;
|
break;
|
||||||
case "freebsd":
|
case "freebsd":
|
||||||
// Send a single packet because on FreeBSD only superuser can send
|
// Send a single packet because on FreeBSD only superuser can send
|
||||||
// packets that are only 200 ms apart.
|
// packets that are only 200 ms apart.
|
||||||
cmd = `ping -t 1 -c 3 ${host}`;
|
args = ["-t", "1", "-c", "3", "--", host];
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return callback(new Error("Platform not supported"));
|
return callback(new Error("Platform not supported"));
|
||||||
}
|
}
|
||||||
|
|
||||||
exec(cmd, (err, stdout) => {
|
// Use execFile instead of exec to avoid shell interpretation of the host
|
||||||
|
execFile("ping", args, (err, stdout) => {
|
||||||
if (err) return callback(err);
|
if (err) return callback(err);
|
||||||
const parsed: PingResult = parsePing(platform(), stdout);
|
const parsed: PingResult = parsePing(platform(), stdout);
|
||||||
return callback(err, parsed, stdout);
|
return callback(err, parsed, stdout);
|
||||||
|
|||||||
Generated
+27
-24
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "genieacs",
|
"name": "genieacs",
|
||||||
"version": "1.2.14",
|
"version": "1.2.15",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "genieacs",
|
"name": "genieacs",
|
||||||
"version": "1.2.14",
|
"version": "1.2.15",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@breejs/later": "^4.2.0",
|
"@breejs/later": "^4.2.0",
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
"ipaddr.js": "^2.3.0",
|
"ipaddr.js": "^2.3.0",
|
||||||
"jsonwebtoken": "^9.0.3",
|
"jsonwebtoken": "^9.0.3",
|
||||||
"koa": "^2.16.4",
|
"koa": "^2.16.4",
|
||||||
"koa-compress": "^5.2.0",
|
"koa-compress": "^5.2.1",
|
||||||
"koa-jwt": "^4.0.3",
|
"koa-jwt": "^4.0.3",
|
||||||
"koa-send": "^5.0.1",
|
"koa-send": "^5.0.1",
|
||||||
"mongodb": "^4.16.0",
|
"mongodb": "^4.16.0",
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
"@types/jsonwebtoken": "^9.0.10",
|
"@types/jsonwebtoken": "^9.0.10",
|
||||||
"@types/koa": "^2.15.0",
|
"@types/koa": "^2.15.0",
|
||||||
"@types/koa-compress": "^4.0.7",
|
"@types/koa-compress": "^4.0.7",
|
||||||
"@types/mithril": "^2.2.7",
|
"@types/mithril": "^2.2.8",
|
||||||
"@types/node": "^20.19.37",
|
"@types/node": "^20.19.37",
|
||||||
"@types/parsimmon": "^1.10.9",
|
"@types/parsimmon": "^1.10.9",
|
||||||
"@types/seedrandom": "^3.0.8",
|
"@types/seedrandom": "^3.0.8",
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
"sql.js": "^1.14.1",
|
"sql.js": "^1.14.1",
|
||||||
"svgo": "^3.3.3",
|
"svgo": "^3.3.3",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"yaml": "^1.10.2"
|
"yaml": "^1.10.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.13.0"
|
"node": ">=12.13.0"
|
||||||
@@ -2080,10 +2080,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/mithril": {
|
"node_modules/@types/mithril": {
|
||||||
"version": "2.2.7",
|
"version": "2.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/mithril/-/mithril-2.2.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/mithril/-/mithril-2.2.8.tgz",
|
||||||
"integrity": "sha512-uetxoYizBMHPELl6DSZUfO6Q/aOm+h0NUCv9bVAX2iAxfrdBSOvU9KKFl+McTtxR13F+BReYLY814pJsZvnSxg==",
|
"integrity": "sha512-FN9Tv1+Nlr0LNPGnIL/xOxLJfu5WW2n8HAFeo4yxF+/O0per/8g080xlXoo+xj8baowAcfsNI3k80DxyLY34gQ==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/ms": {
|
"node_modules/@types/ms": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
@@ -4016,9 +4017,10 @@
|
|||||||
"integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw=="
|
"integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw=="
|
||||||
},
|
},
|
||||||
"node_modules/koa-compress": {
|
"node_modules/koa-compress": {
|
||||||
"version": "5.2.0",
|
"version": "5.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/koa-compress/-/koa-compress-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/koa-compress/-/koa-compress-5.2.1.tgz",
|
||||||
"integrity": "sha512-RsRnI+v+/rs1lYpcAUcxowUzHYssf71qbMr0Mpdq1wktbtXDZmxBIgxJHtaEsBjSe4jiWYELpGFbASa2AemmOg==",
|
"integrity": "sha512-k8VJMZI7vvSq1/G/t6Jggeg4h7fec1NJxcYaDWC4/1PK6mJFPs0OYqj44vx/soUcDVEJR7ysR5cc6pszKpnzXA==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bytes": "^3.1.2",
|
"bytes": "^3.1.2",
|
||||||
"compressible": "^2.0.18",
|
"compressible": "^2.0.18",
|
||||||
@@ -5282,10 +5284,11 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/yaml": {
|
"node_modules/yaml": {
|
||||||
"version": "1.10.2",
|
"version": "1.10.3",
|
||||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz",
|
||||||
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
|
"integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
@@ -6824,9 +6827,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/mithril": {
|
"@types/mithril": {
|
||||||
"version": "2.2.7",
|
"version": "2.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/mithril/-/mithril-2.2.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/mithril/-/mithril-2.2.8.tgz",
|
||||||
"integrity": "sha512-uetxoYizBMHPELl6DSZUfO6Q/aOm+h0NUCv9bVAX2iAxfrdBSOvU9KKFl+McTtxR13F+BReYLY814pJsZvnSxg==",
|
"integrity": "sha512-FN9Tv1+Nlr0LNPGnIL/xOxLJfu5WW2n8HAFeo4yxF+/O0per/8g080xlXoo+xj8baowAcfsNI3k80DxyLY34gQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/ms": {
|
"@types/ms": {
|
||||||
@@ -8246,9 +8249,9 @@
|
|||||||
"integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw=="
|
"integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw=="
|
||||||
},
|
},
|
||||||
"koa-compress": {
|
"koa-compress": {
|
||||||
"version": "5.2.0",
|
"version": "5.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/koa-compress/-/koa-compress-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/koa-compress/-/koa-compress-5.2.1.tgz",
|
||||||
"integrity": "sha512-RsRnI+v+/rs1lYpcAUcxowUzHYssf71qbMr0Mpdq1wktbtXDZmxBIgxJHtaEsBjSe4jiWYELpGFbASa2AemmOg==",
|
"integrity": "sha512-k8VJMZI7vvSq1/G/t6Jggeg4h7fec1NJxcYaDWC4/1PK6mJFPs0OYqj44vx/soUcDVEJR7ysR5cc6pszKpnzXA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"bytes": "^3.1.2",
|
"bytes": "^3.1.2",
|
||||||
"compressible": "^2.0.18",
|
"compressible": "^2.0.18",
|
||||||
@@ -9111,9 +9114,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"yaml": {
|
"yaml": {
|
||||||
"version": "1.10.2",
|
"version": "1.10.3",
|
||||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz",
|
||||||
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
|
"integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"ylru": {
|
"ylru": {
|
||||||
|
|||||||
+4
-4
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "genieacs",
|
"name": "genieacs",
|
||||||
"version": "1.2.14",
|
"version": "1.2.15",
|
||||||
"description": "A TR-069 Auto Configuration Server (ACS)",
|
"description": "A TR-069 Auto Configuration Server (ACS)",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
"ipaddr.js": "^2.3.0",
|
"ipaddr.js": "^2.3.0",
|
||||||
"jsonwebtoken": "^9.0.3",
|
"jsonwebtoken": "^9.0.3",
|
||||||
"koa": "^2.16.4",
|
"koa": "^2.16.4",
|
||||||
"koa-compress": "^5.2.0",
|
"koa-compress": "^5.2.1",
|
||||||
"koa-jwt": "^4.0.3",
|
"koa-jwt": "^4.0.3",
|
||||||
"koa-send": "^5.0.1",
|
"koa-send": "^5.0.1",
|
||||||
"mongodb": "^4.16.0",
|
"mongodb": "^4.16.0",
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
"@types/jsonwebtoken": "^9.0.10",
|
"@types/jsonwebtoken": "^9.0.10",
|
||||||
"@types/koa": "^2.15.0",
|
"@types/koa": "^2.15.0",
|
||||||
"@types/koa-compress": "^4.0.7",
|
"@types/koa-compress": "^4.0.7",
|
||||||
"@types/mithril": "^2.2.7",
|
"@types/mithril": "^2.2.8",
|
||||||
"@types/node": "^20.19.37",
|
"@types/node": "^20.19.37",
|
||||||
"@types/parsimmon": "^1.10.9",
|
"@types/parsimmon": "^1.10.9",
|
||||||
"@types/seedrandom": "^3.0.8",
|
"@types/seedrandom": "^3.0.8",
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
"sql.js": "^1.14.1",
|
"sql.js": "^1.14.1",
|
||||||
"svgo": "^3.3.3",
|
"svgo": "^3.3.3",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"yaml": "^1.10.2"
|
"yaml": "^1.10.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.13.0"
|
"node": ">=12.13.0"
|
||||||
|
|||||||
+29
-2
@@ -3,6 +3,29 @@ import { m } from "./components.ts";
|
|||||||
import config from "./config.ts";
|
import config from "./config.ts";
|
||||||
import * as store from "./store.ts";
|
import * as store from "./store.ts";
|
||||||
|
|
||||||
|
// Workaround for Mithril 2.3.8 regression
|
||||||
|
// https://github.com/MithrilJS/mithril.js/issues/3064
|
||||||
|
function getPathParamFromHash(hash: string, prefix: string): string | null {
|
||||||
|
if (!hash.startsWith(prefix)) return null;
|
||||||
|
|
||||||
|
const pathWithQuery = hash.slice(prefix.length);
|
||||||
|
|
||||||
|
// Stop at query string or hash fragment (mirrors Mithril's parsePathname)
|
||||||
|
const queryIdx = pathWithQuery.indexOf("?");
|
||||||
|
const hashIdx = pathWithQuery.indexOf("#");
|
||||||
|
let pathEnd = pathWithQuery.length;
|
||||||
|
if (queryIdx >= 0) pathEnd = Math.min(pathEnd, queryIdx);
|
||||||
|
if (hashIdx >= 0) pathEnd = Math.min(pathEnd, hashIdx);
|
||||||
|
|
||||||
|
const rawPath = pathWithQuery.slice(0, pathEnd);
|
||||||
|
|
||||||
|
// Normalize: collapse slashes, remove trailing slash (matches Mithril)
|
||||||
|
const normalizedPath = rawPath.replace(/\/{2,}/g, "/").replace(/\/$/, "");
|
||||||
|
|
||||||
|
// Decode once (Mithril already decoded with decodeURIComponentSafe)
|
||||||
|
return decodeURIComponent(normalizedPath);
|
||||||
|
}
|
||||||
|
|
||||||
export function init(
|
export function init(
|
||||||
args: Record<string, unknown>,
|
args: Record<string, unknown>,
|
||||||
): Promise<Record<string, unknown>> {
|
): Promise<Record<string, unknown>> {
|
||||||
@@ -12,9 +35,13 @@ export function init(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deviceId =
|
||||||
|
getPathParamFromHash(window.location.hash, "#!/devices/") ??
|
||||||
|
(args.id as string);
|
||||||
|
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
deviceId: args.id,
|
deviceId: deviceId,
|
||||||
deviceFilter: ["=", ["PARAM", "DeviceID.ID"], args.id],
|
deviceFilter: ["=", ["PARAM", "DeviceID.ID"], deviceId],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user