-
-
Notifications
You must be signed in to change notification settings - Fork 332
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[mirotalksfu] - Improve security, add tests
- Loading branch information
1 parent
8d7901e
commit 7287363
Showing
6 changed files
with
310 additions
and
57 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -43,7 +43,7 @@ dependencies: { | |
* @license For commercial or closed source, contact us at [email protected] or purchase directly via CodeCanyon | ||
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970 | ||
* @author Miroslav Pejic - [email protected] | ||
* @version 1.5.59 | ||
* @version 1.5.60 | ||
* | ||
*/ | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,66 +1,96 @@ | ||
'use strict'; | ||
|
||
const xss = require('xss'); | ||
const { JSDOM } = require('jsdom'); | ||
const DOMPurify = require('dompurify'); | ||
const he = require('he'); | ||
|
||
// Initialize DOMPurify with jsdom | ||
const window = new JSDOM('').window; | ||
const purify = DOMPurify(window); | ||
|
||
const Logger = require('./Logger'); | ||
const log = new Logger('Xss'); | ||
|
||
const checkXSS = (dataObject) => { | ||
try { | ||
if (Array.isArray(dataObject)) { | ||
if (Object.keys(dataObject).length > 0 && typeof dataObject[0] === 'object') { | ||
dataObject.forEach((obj) => { | ||
for (const key in obj) { | ||
if (obj.hasOwnProperty(key)) { | ||
let objectJson = objectToJSONString(obj[key]); | ||
if (objectJson) { | ||
let jsonString = xss(objectJson); | ||
let jsonObject = JSONStringToObject(jsonString); | ||
if (jsonObject) { | ||
obj[key] = jsonObject; | ||
} | ||
} | ||
} | ||
} | ||
}); | ||
log.debug('XSS Array of Object sanitization done'); | ||
return dataObject; | ||
// Configure DOMPurify | ||
purify.setConfig({ | ||
ALLOWED_TAGS: ['a', 'img', 'div', 'span', 'svg', 'g', 'p'], // Allow specific tags | ||
ALLOWED_ATTR: ['href', 'src', 'title', 'id', 'class'], // Allow specific attributes | ||
ALLOWED_URI_REGEXP: /^(?!data:|javascript:|vbscript:|file:|view-source:).*/, // Disallow dangerous URIs | ||
}); | ||
|
||
// Clean problematic attributes | ||
function cleanAttributes(node) { | ||
if (node.nodeType === window.Node.ELEMENT_NODE) { | ||
// Remove dangerous attributes | ||
const dangerousAttributes = ['onerror', 'onclick', 'onload', 'onmouseover', 'onfocus', 'onchange', 'oninput']; | ||
dangerousAttributes.forEach((attr) => { | ||
if (node.hasAttribute(attr)) { | ||
node.removeAttribute(attr); | ||
} | ||
} else if (typeof dataObject === 'object') { | ||
let objectJson = objectToJSONString(dataObject); | ||
if (objectJson) { | ||
let jsonString = xss(objectJson); | ||
let jsonObject = JSONStringToObject(jsonString); | ||
if (jsonObject) { | ||
log.debug('XSS Object sanitization done'); | ||
return jsonObject; | ||
} | ||
}); | ||
|
||
// Handle special cases for 'data:' URIs | ||
const src = node.getAttribute('src'); | ||
if (src && src.startsWith('data:')) { | ||
node.removeAttribute('src'); | ||
} | ||
|
||
// Remove unsafe 'style' attributes | ||
if (node.hasAttribute('style')) { | ||
const style = node.getAttribute('style'); | ||
if (style.includes('javascript:') || style.includes('data:')) { | ||
node.removeAttribute('style'); | ||
} | ||
} else if (typeof dataObject === 'string' || dataObject instanceof String) { | ||
log.debug('XSS String sanitization done'); | ||
return xss(dataObject); | ||
} | ||
log.warn('XSS not sanitized', dataObject); | ||
return dataObject; | ||
} catch (error) { | ||
log.error('XSS error', { data: dataObject, error: error }); | ||
return dataObject; | ||
} | ||
}; | ||
|
||
function objectToJSONString(dataObject) { | ||
try { | ||
return JSON.stringify(dataObject); | ||
} catch (error) { | ||
return false; | ||
// Remove 'title' attribute if it contains dangerous content | ||
if (node.hasAttribute('title')) { | ||
const title = node.getAttribute('title'); | ||
if (title.includes('javascript:') || title.includes('data:') || title.includes('onerror')) { | ||
node.removeAttribute('title'); | ||
} | ||
} | ||
} | ||
} | ||
|
||
function JSONStringToObject(jsonString) { | ||
// Hook to clean specific attributes that can cause XSS | ||
purify.addHook('beforeSanitizeAttributes', cleanAttributes); | ||
|
||
// Main function to check and sanitize data | ||
const checkXSS = (dataObject) => { | ||
try { | ||
return JSON.parse(jsonString); | ||
return sanitizeData(dataObject); | ||
} catch (error) { | ||
return false; | ||
log.error('Sanitization error:', error); | ||
return dataObject; // Return original data in case of error | ||
} | ||
}; | ||
|
||
// Recursively sanitize data based on its type | ||
function sanitizeData(data) { | ||
if (typeof data === 'string') { | ||
// Decode HTML entities and URL encoded content | ||
const decodedData = he.decode(decodeURIComponent(data)); | ||
return purify.sanitize(decodedData); | ||
} | ||
|
||
if (Array.isArray(data)) { | ||
return data.map(sanitizeData); | ||
} | ||
|
||
if (data && typeof data === 'object') { | ||
return sanitizeObject(data); | ||
} | ||
|
||
return data; // For numbers, booleans, null, undefined | ||
} | ||
|
||
// Sanitize object properties | ||
function sanitizeObject(obj) { | ||
return Object.keys(obj).reduce((acc, key) => { | ||
acc[key] = sanitizeData(obj[key]); | ||
return acc; | ||
}, {}); | ||
} | ||
|
||
module.exports = checkXSS; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,7 +11,7 @@ if (location.href.substr(0, 5) !== 'https') location.href = 'https' + location.h | |
* @license For commercial or closed source, contact us at [email protected] or purchase directly via CodeCanyon | ||
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970 | ||
* @author Miroslav Pejic - [email protected] | ||
* @version 1.5.59 | ||
* @version 1.5.60 | ||
* | ||
*/ | ||
|
||
|
@@ -4444,7 +4444,7 @@ function showAbout() { | |
imageUrl: image.about, | ||
customClass: { image: 'img-about' }, | ||
position: 'center', | ||
title: 'WebRTC SFU v1.5.59', | ||
title: 'WebRTC SFU v1.5.60', | ||
html: ` | ||
<br /> | ||
<div id="about"> | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,7 +9,7 @@ | |
* @license For commercial or closed source, contact us at [email protected] or purchase directly via CodeCanyon | ||
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970 | ||
* @author Miroslav Pejic - [email protected] | ||
* @version 1.5.59 | ||
* @version 1.5.60 | ||
* | ||
*/ | ||
|
||
|
Oops, something went wrong.