Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 117 additions & 1 deletion source/Styles/xb3/jst/actionHandler/ajax_at_saving.jst
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?%
/*
If not stated otherwise in this file or this component's Licenses.txt file the

Check failure on line 3 in source/Styles/xb3/jst/actionHandler/ajax_at_saving.jst

View workflow job for this annotation

GitHub Actions / call-fossid-workflow / Fossid Annotate PR

FossID License Issue Detected

Source code with 'Apache-2.0' license found in local file 'source/Styles/xb3/jst/actionHandler/ajax_at_saving.jst' (Match: rdkb/devices/rdkbemu/rdkbemu_xb3/rdkb/devices/rdkbemu/rdkbemu_xb3/1, 146 lines, url: https://code.rdkcentral.com/r/plugins/gitiles/rdkb/devices/rdkbemu/rdkbemu_xb3/+archive/RDKB-RELEASE-TEST-DUNFELL-1.tar.gz, file: components/ccsp/webui/source/Styles/xb3/code/actionHandler/ajax_at_saving.php)
following copyright and licenses apply:
Copyright 2016 RDK Management
Licensed under the Apache License, Version 2.0 (the "License");
Expand All @@ -21,6 +21,122 @@
echo( '<script type="text/javascript">alert("Please Login First!"); location.href="../index.jst";</script>');
exit(0);
}

function sanitize_html(input) {
// keepAttrs: true -> keep attributes for allowed tags (default)
// stripDangerous: true -> remove on* handlers and javascript: urls
var KEEP_ATTRS = true;
var STRIP_DANGEROUS = true;
Comment on lines +26 to +29
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function uses constants KEEP_ATTRS and STRIP_DANGEROUS that are always set to true but never actually used as configuration. These appear to be residual configuration flags that aren't actually configurable. Either make these proper function parameters if they need to be configurable, or remove them and simplify the logic since they're always true.

Copilot uses AI. Check for mistakes.

var ALLOWED_TAGS = ["H2", "DIV", "TABLE", "TBODY", "TR", "TH", "TD"];

function isAllowed(tagName) {
for (var i = 0; i < ALLOWED_TAGS.length; i++) {
if (tagName === ALLOWED_TAGS[i]) return true;
}
return false;
}

// Optional lightweight attribute filter (only used if STRIP_DANGEROUS = true)
function filterAttributes(attrText) {
// Parse attributes in a conservative way: name[=value]
// Keeps spacing as minimal as possible when reconstructing.
var out = [];
var re = /([^\s=\/"'>]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
var m;
while ((m = re.exec(attrText)) !== null) {
var name = (m[1] || '').toLowerCase();
var val = (m[2] != null) ? m[2] : (m[3] != null ? m[3] : (m[4] != null ? m[4] : ''));

// Drop obvious dangerous attributes
if (name.indexOf('on') === 0) continue; // onclick, onload, ...
if (name === 'style') continue; // inline CSS often abused

// Disallow javascript: and data: in URLish attributes
if (name === 'href' || name === 'src' || name === 'xlink:href') {
Comment on lines +48 to +56
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dangerous attribute filtering only checks if attribute names start with 'on' (line 52) and blocks 'style', but this misses several attack vectors. Missing checks include: 1) formaction attribute that can redirect form submissions, 2) attributes like 'srcdoc' on iframes that can execute scripts, 3) namespace-prefixed event handlers like 'xml:onload', and 4) the check for event handlers could be bypassed with tab or other whitespace characters in the attribute name before 'on'.

Suggested change
var name = (m[1] || '').toLowerCase();
var val = (m[2] != null) ? m[2] : (m[3] != null ? m[3] : (m[4] != null ? m[4] : ''));
// Drop obvious dangerous attributes
if (name.indexOf('on') === 0) continue; // onclick, onload, ...
if (name === 'style') continue; // inline CSS often abused
// Disallow javascript: and data: in URLish attributes
if (name === 'href' || name === 'src' || name === 'xlink:href') {
// Normalize attribute name:
// - strip leading control/whitespace chars
// - lowercase
// - extract local name (after any namespace prefix, e.g. xml:onload -> onload)
var rawName = m[1] || '';
var name = rawName.replace(/^[\s\x00-\x1F]+/, '').toLowerCase();
var nameNoNs = (name.indexOf(':') !== -1) ? name.split(':').pop() : name;
var val = (m[2] != null) ? m[2] : (m[3] != null ? m[3] : (m[4] != null ? m[4] : ''));
// Drop obvious dangerous attributes
if (nameNoNs.indexOf('on') === 0) continue; // onclick, onload, xml:onload, ...
if (nameNoNs === 'style') continue; // inline CSS often abused
if (nameNoNs === 'srcdoc') continue; // iframe-like HTML execution
if (nameNoNs === 'formaction') continue; // redirect/override form target
// Disallow javascript: and data: in URLish attributes
if (name === 'href' || name === 'src' || name === 'xlink:href' || nameNoNs === 'formaction') {

Copilot uses AI. Check for mistakes.
var v = String(val).replace(/^\s+|\s+$/g, '').toLowerCase();
if (!v || v.indexOf('javascript:') === 0 || v.indexOf('data:') === 0) continue;
}
Comment on lines +55 to +59
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The URL validation in the filterAttributes function only checks if URLs start with 'javascript:' or 'data:' after trimming and converting to lowercase. However, this check can be bypassed using: 1) URL encoding (e.g., 'j%61vascript:'), 2) Unicode/HTML entities, 3) NULL bytes, 4) Alternative protocols like 'vbscript:', or 5) tab/newline characters within the protocol name. A more robust URL validation that handles encoded characters and validates against a whitelist of safe protocols (like http/https) would be more secure.

Copilot uses AI. Check for mistakes.

// Reconstruct attribute (quote with double-quotes)
if (val === '') out.push(name);
else out.push(name + '="' + val.replace(/"/g, '&quot;') + '"');
}
return out.length ? ' ' + out.join(' ') : '';
Comment on lines +41 to +65
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The attribute filtering regex pattern has a potential issue. The pattern uses a greedy quantifier in the outer loop but doesn't properly handle edge cases where attribute values contain special characters. Specifically, unquoted attribute values matching ([^\s"'=<>`]+) could include characters that should be escaped or rejected. Additionally, the regex doesn't handle HTML entity-encoded attribute values, which could be used to bypass the javascript: and data: URL checks.

Copilot uses AI. Check for mistakes.
Comment on lines +51 to +65
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sanitizer allows 'class' and 'id' attributes without any validation (since they don't fall into the filtered categories). Attackers could potentially inject malicious class or id values that could be exploited through existing CSS rules or JavaScript selectors in the application. Consider adding validation or sanitization for these attributes, especially if the application has any CSS or JavaScript that targets elements by class or id.

Copilot uses AI. Check for mistakes.
}

var result = "";
var i = 0;
var lowerInput = input.toLowerCase();

while (i < input.length) {
if (input[i] === '<') {
var start = i;
var end = input.indexOf('>', start);
if (end === -1) {
// no closing '>' — append the rest and stop
result += input.slice(i);
break;
}

// Raw tag content between '<' and '>'
var raw = input.substring(start + 1, end);
var tagContent = raw.replace(/^\s+|\s+$/g, '');
var isClosing = tagContent.charAt(0) === '/';

// Separate tag name and attributes (for opening tags)
var namePart = isClosing ? tagContent.slice(1) : tagContent;
var spaceIdx = namePart.indexOf(' ');
var tagName = (spaceIdx === -1 ? namePart : namePart.slice(0, spaceIdx)).toUpperCase();
var attrsPart = (spaceIdx === -1 || isClosing) ? '' : namePart.slice(spaceIdx);

// Detect self-closing "/>" (rare for your table tags but harmless to support)
var selfClosing = /\/\s*$/.test(tagContent) && !isClosing;

if (isAllowed(tagName)) {
var tn = tagName.toLowerCase();
if (isClosing) {
result += "</" + tn + ">";
} else {
var attrsOut = '';
if (KEEP_ATTRS) {
attrsOut = STRIP_DANGEROUS ? filterAttributes(attrsPart) : (attrsPart || '');
}
// Normalize: ensure a leading space before attributes when present and not already spaced
if (attrsOut && !STRIP_DANGEROUS) {
// If original attrsPart doesn't start with space, add one
if (!/^\s/.test(attrsOut)) attrsOut = ' ' + attrsOut;
}
result += "<" + tn + (attrsOut || '') + (selfClosing ? "/>" : ">");
}
i = end + 1;
continue;
}

// Not allowed tag
if (!isClosing) {
// Strip the entire element including its content until the matching closing tag
// (simple depth-1 removal; good enough for this whitelist)
var closing = "</" + tagName.toLowerCase() + ">";
var nextClosing = lowerInput.indexOf(closing, end);
if (nextClosing !== -1) {
Comment on lines +121 to +122
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sanitization creates a lowercase version of the entire input string for tag matching (line 70), but this approach has a critical flaw. When searching for closing tags in the lowerInput string, the indices found will be used to slice the original input string. However, if the input contains multi-byte characters or certain Unicode characters, the byte offsets in the lowercase version may not match those in the original string, potentially causing incorrect slicing and bypasses of the sanitization logic.

Suggested change
var nextClosing = lowerInput.indexOf(closing, end);
if (nextClosing !== -1) {
var searchStart = end;
var lowerSlice = input.slice(searchStart).toLowerCase();
var relativeIndex = lowerSlice.indexOf(closing);
if (relativeIndex !== -1) {
var nextClosing = searchStart + relativeIndex;

Copilot uses AI. Check for mistakes.
i = nextClosing + closing.length;
Comment on lines +118 to +123
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tag stripping logic for disallowed tags has an issue. When a disallowed opening tag is found, the code searches for its closing tag in the lowercase version (lowerInput), but it uses simple string matching which doesn't account for nested tags of the same name. For example, if the input contains nested script tags or comments, the code might match the wrong closing tag, allowing malicious content to pass through. A proper HTML parser or stack-based matching should be used instead.

Suggested change
// Strip the entire element including its content until the matching closing tag
// (simple depth-1 removal; good enough for this whitelist)
var closing = "</" + tagName.toLowerCase() + ">";
var nextClosing = lowerInput.indexOf(closing, end);
if (nextClosing !== -1) {
i = nextClosing + closing.length;
// Strip the entire element including its content until the matching closing tag.
// Use simple depth-based matching to correctly handle nested disallowed tags.
var closing = "</" + tagName.toLowerCase() + ">";
var lower = input.toLowerCase();
var openTagPattern = "<" + tagName.toLowerCase();
var depth = 1;
var searchPos = end + 1;
while (depth > 0) {
var nextOpen = lower.indexOf(openTagPattern, searchPos);
var nextClose = lower.indexOf(closing, searchPos);
if (nextClose === -1) {
// No matching closing tag found; give up on depth-based removal
break;
}
if (nextOpen !== -1 && nextOpen < nextClose) {
// Found a nested opening tag of the same disallowed type
depth++;
searchPos = nextOpen + openTagPattern.length;
} else {
// Found a closing tag
depth--;
searchPos = nextClose + closing.length;
}
}
if (depth === 0) {
i = searchPos;

Copilot uses AI. Check for mistakes.
continue;
}
}

// For disallowed closing tags or unmatched structures, just skip the tag itself
i = end + 1;
} else {
result += input[i++];
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Text content between tags is passed through without any escaping or entity encoding. If user-controlled data exists in the text content and contains characters like <, >, &, or quotes, these should be HTML-entity encoded to prevent interpretation as HTML markup. Currently, only the tags themselves are filtered, not the text content between them.

Suggested change
result += input[i++];
var ch = input[i++];
if (ch === '&') {
result += '&amp;';
} else if (ch === '<') {
// This should not normally occur here because '<' starts the tag branch above,
// but we encode it defensively.
result += '&lt;';
} else if (ch === '>') {
result += '&gt;';
} else if (ch === '"') {
result += '&quot;';
} else if (ch === "'") {
result += '&#39;';
} else {
result += ch;
}

Copilot uses AI. Check for mistakes.
}
}
Comment on lines +72 to +133
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sanitizer doesn't handle HTML comments, CDATA sections, DOCTYPE declarations, or processing instructions. An attacker could potentially inject malicious content wrapped in HTML comments or use other HTML constructs to bypass the tag-based filtering. These constructs should either be explicitly stripped or properly validated.

Copilot uses AI. Check for mistakes.

return result;
}

$configInfo = sanitize_html($_POST['configInfo']);

$myfile = fopen("/var/tmp/Wifi_Spectrum_Analyzer_Table.html", "w");
fwrite($myfile, "<style>table th tr {}</style>");
fwrite($myfile, "<style>");
Expand All @@ -29,7 +145,7 @@
fwrite($myfile, "th { background: #39baf1; color: #fff; }");
fwrite($myfile, "td { border: 1px solid white; }");
fwrite($myfile, "</style>");
fwrite($myfile, $_POST['configInfo']);
fwrite($myfile, $configInfo);
fclose($myfile);
echo( htmlspecialchars(json_encode({"status": "success"}), ENT_NOQUOTES, 'UTF-8'));
?>
Loading