Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
944a690d96 | ||
|
|
e7facc3bba | ||
|
|
aa83b62064 | ||
|
|
f9ee39dfd0 | ||
|
|
087b95388f |
No files matched your search
@@ -1,5 +1,13 @@
|
||||
# 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)
|
||||
|
||||
- Prevent UI crash when a malformed URL is sent to the server.
|
||||
|
||||
+16
-7
@@ -1,6 +1,6 @@
|
||||
import { platform } from "node:os";
|
||||
import { exec } from "node:child_process";
|
||||
import { domainToASCII } from "node:url";
|
||||
import { execFile } from "node:child_process";
|
||||
import { domainToASCII, domainToUnicode } from "node:url";
|
||||
|
||||
export interface PingResult {
|
||||
packetsTransmitted: number;
|
||||
@@ -16,9 +16,16 @@ function isValidHost(host: string): boolean {
|
||||
// Valid chars in IPv4, IPv6, domain names
|
||||
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
|
||||
// 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 {
|
||||
@@ -73,24 +80,26 @@ export function ping(
|
||||
): void {
|
||||
// Validate input to prevent possible remote code execution
|
||||
// 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"));
|
||||
host = host.replace("[", "").replace("]", "");
|
||||
let cmd: string;
|
||||
let args: string[];
|
||||
|
||||
switch (platform()) {
|
||||
case "linux":
|
||||
cmd = `ping -w 1 -i 0.2 -c 3 ${host}`;
|
||||
args = ["-w", "1", "-i", "0.2", "-c", "3", "--", host];
|
||||
break;
|
||||
case "freebsd":
|
||||
// Send a single packet because on FreeBSD only superuser can send
|
||||
// packets that are only 200 ms apart.
|
||||
cmd = `ping -t 1 -c 3 ${host}`;
|
||||
args = ["-t", "1", "-c", "3", "--", host];
|
||||
break;
|
||||
default:
|
||||
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);
|
||||
const parsed: PingResult = parsePing(platform(), stdout);
|
||||
return callback(err, parsed, stdout);
|
||||
|
||||
Generated
+27
-24
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "genieacs",
|
||||
"version": "1.2.14",
|
||||
"version": "1.2.15",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "genieacs",
|
||||
"version": "1.2.14",
|
||||
"version": "1.2.15",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@breejs/later": "^4.2.0",
|
||||
@@ -18,7 +18,7 @@
|
||||
"ipaddr.js": "^2.3.0",
|
||||
"jsonwebtoken": "^9.0.3",
|
||||
"koa": "^2.16.4",
|
||||
"koa-compress": "^5.2.0",
|
||||
"koa-compress": "^5.2.1",
|
||||
"koa-jwt": "^4.0.3",
|
||||
"koa-send": "^5.0.1",
|
||||
"mongodb": "^4.16.0",
|
||||
@@ -36,7 +36,7 @@
|
||||
"@types/jsonwebtoken": "^9.0.10",
|
||||
"@types/koa": "^2.15.0",
|
||||
"@types/koa-compress": "^4.0.7",
|
||||
"@types/mithril": "^2.2.7",
|
||||
"@types/mithril": "^2.2.8",
|
||||
"@types/node": "^20.19.37",
|
||||
"@types/parsimmon": "^1.10.9",
|
||||
"@types/seedrandom": "^3.0.8",
|
||||
@@ -51,7 +51,7 @@
|
||||
"sql.js": "^1.14.1",
|
||||
"svgo": "^3.3.3",
|
||||
"typescript": "^5.9.3",
|
||||
"yaml": "^1.10.2"
|
||||
"yaml": "^1.10.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.13.0"
|
||||
@@ -2080,10 +2080,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/mithril": {
|
||||
"version": "2.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/mithril/-/mithril-2.2.7.tgz",
|
||||
"integrity": "sha512-uetxoYizBMHPELl6DSZUfO6Q/aOm+h0NUCv9bVAX2iAxfrdBSOvU9KKFl+McTtxR13F+BReYLY814pJsZvnSxg==",
|
||||
"dev": true
|
||||
"version": "2.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/mithril/-/mithril-2.2.8.tgz",
|
||||
"integrity": "sha512-FN9Tv1+Nlr0LNPGnIL/xOxLJfu5WW2n8HAFeo4yxF+/O0per/8g080xlXoo+xj8baowAcfsNI3k80DxyLY34gQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/ms": {
|
||||
"version": "2.1.0",
|
||||
@@ -4016,9 +4017,10 @@
|
||||
"integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw=="
|
||||
},
|
||||
"node_modules/koa-compress": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/koa-compress/-/koa-compress-5.2.0.tgz",
|
||||
"integrity": "sha512-RsRnI+v+/rs1lYpcAUcxowUzHYssf71qbMr0Mpdq1wktbtXDZmxBIgxJHtaEsBjSe4jiWYELpGFbASa2AemmOg==",
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/koa-compress/-/koa-compress-5.2.1.tgz",
|
||||
"integrity": "sha512-k8VJMZI7vvSq1/G/t6Jggeg4h7fec1NJxcYaDWC4/1PK6mJFPs0OYqj44vx/soUcDVEJR7ysR5cc6pszKpnzXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bytes": "^3.1.2",
|
||||
"compressible": "^2.0.18",
|
||||
@@ -5282,10 +5284,11 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "1.10.2",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
|
||||
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
|
||||
"version": "1.10.3",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz",
|
||||
"integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
@@ -6824,9 +6827,9 @@
|
||||
}
|
||||
},
|
||||
"@types/mithril": {
|
||||
"version": "2.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/mithril/-/mithril-2.2.7.tgz",
|
||||
"integrity": "sha512-uetxoYizBMHPELl6DSZUfO6Q/aOm+h0NUCv9bVAX2iAxfrdBSOvU9KKFl+McTtxR13F+BReYLY814pJsZvnSxg==",
|
||||
"version": "2.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/mithril/-/mithril-2.2.8.tgz",
|
||||
"integrity": "sha512-FN9Tv1+Nlr0LNPGnIL/xOxLJfu5WW2n8HAFeo4yxF+/O0per/8g080xlXoo+xj8baowAcfsNI3k80DxyLY34gQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/ms": {
|
||||
@@ -8246,9 +8249,9 @@
|
||||
"integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw=="
|
||||
},
|
||||
"koa-compress": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/koa-compress/-/koa-compress-5.2.0.tgz",
|
||||
"integrity": "sha512-RsRnI+v+/rs1lYpcAUcxowUzHYssf71qbMr0Mpdq1wktbtXDZmxBIgxJHtaEsBjSe4jiWYELpGFbASa2AemmOg==",
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/koa-compress/-/koa-compress-5.2.1.tgz",
|
||||
"integrity": "sha512-k8VJMZI7vvSq1/G/t6Jggeg4h7fec1NJxcYaDWC4/1PK6mJFPs0OYqj44vx/soUcDVEJR7ysR5cc6pszKpnzXA==",
|
||||
"requires": {
|
||||
"bytes": "^3.1.2",
|
||||
"compressible": "^2.0.18",
|
||||
@@ -9111,9 +9114,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"yaml": {
|
||||
"version": "1.10.2",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
|
||||
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
|
||||
"version": "1.10.3",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz",
|
||||
"integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==",
|
||||
"dev": true
|
||||
},
|
||||
"ylru": {
|
||||
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "genieacs",
|
||||
"version": "1.2.14",
|
||||
"version": "1.2.15",
|
||||
"description": "A TR-069 Auto Configuration Server (ACS)",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -34,7 +34,7 @@
|
||||
"ipaddr.js": "^2.3.0",
|
||||
"jsonwebtoken": "^9.0.3",
|
||||
"koa": "^2.16.4",
|
||||
"koa-compress": "^5.2.0",
|
||||
"koa-compress": "^5.2.1",
|
||||
"koa-jwt": "^4.0.3",
|
||||
"koa-send": "^5.0.1",
|
||||
"mongodb": "^4.16.0",
|
||||
@@ -46,7 +46,7 @@
|
||||
"@types/jsonwebtoken": "^9.0.10",
|
||||
"@types/koa": "^2.15.0",
|
||||
"@types/koa-compress": "^4.0.7",
|
||||
"@types/mithril": "^2.2.7",
|
||||
"@types/mithril": "^2.2.8",
|
||||
"@types/node": "^20.19.37",
|
||||
"@types/parsimmon": "^1.10.9",
|
||||
"@types/seedrandom": "^3.0.8",
|
||||
@@ -61,7 +61,7 @@
|
||||
"sql.js": "^1.14.1",
|
||||
"svgo": "^3.3.3",
|
||||
"typescript": "^5.9.3",
|
||||
"yaml": "^1.10.2"
|
||||
"yaml": "^1.10.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.13.0"
|
||||
|
||||
+29
-2
@@ -3,6 +3,29 @@ import { m } from "./components.ts";
|
||||
import config from "./config.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(
|
||||
args: 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({
|
||||
deviceId: args.id,
|
||||
deviceFilter: ["=", ["PARAM", "DeviceID.ID"], args.id],
|
||||
deviceId: deviceId,
|
||||
deviceFilter: ["=", ["PARAM", "DeviceID.ID"], deviceId],
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user