[mirotalk] - improve security, add unit-tests, add chatgpt img
This commit is contained in:
@@ -6,8 +6,26 @@ on:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '22.13.0' # LTS
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
|
||||
- name: Run unit tests
|
||||
run: npm test
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
needs: test # This ensures the build job only runs if the test job succeeds
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
+18
-1
@@ -39,7 +39,7 @@ dependencies: {
|
||||
* @license For commercial use or closed source, contact us at license.mirotalk@gmail.com or purchase directly from CodeCanyon
|
||||
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-p2p-webrtc-realtime-video-conferences/38376661
|
||||
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
|
||||
* @version 1.4.51
|
||||
* @version 1.4.55
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -62,6 +62,7 @@ const fs = require('fs');
|
||||
const checkXSS = require('./xss.js');
|
||||
const ServerApi = require('./api');
|
||||
const mattermostCli = require('./mattermost.js');
|
||||
const Validate = require('./validate');
|
||||
const Host = require('./host');
|
||||
const Logs = require('./logs');
|
||||
const log = new Logs('server');
|
||||
@@ -552,6 +553,12 @@ app.get('/join/', async (req, res) => {
|
||||
return res.status(401).json({ message: 'Direct Room Join: Missing mandatory room parameter!' });
|
||||
}
|
||||
|
||||
if (!Validate.isValidRoomName(room)) {
|
||||
return res.status(400).json({
|
||||
message: 'Invalid Room name!\nPath traversal pattern detected!',
|
||||
});
|
||||
}
|
||||
|
||||
const allowRoomAccess = isAllowedRoomAccess('/join/params', req, hostCfg, peers, room);
|
||||
|
||||
if (!allowRoomAccess && !token) {
|
||||
@@ -622,6 +629,11 @@ app.get('/join/:roomId', function (req, res) {
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
if (!Validate.isValidRoomName(roomId)) {
|
||||
log.warn('/join/:roomId invalid', roomId);
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
const allowRoomAccess = isAllowedRoomAccess('/join/:roomId', req, hostCfg, peers, roomId);
|
||||
|
||||
if (allowRoomAccess) {
|
||||
@@ -1176,6 +1188,11 @@ io.sockets.on('connect', async (socket) => {
|
||||
peer_info,
|
||||
} = config;
|
||||
|
||||
if (!Validate.isValidRoomName(channel)) {
|
||||
log.warn('[' + socket.id + '] - Invalid room name', channel);
|
||||
return socket.emit('unauthorized');
|
||||
}
|
||||
|
||||
if (channel in socket.channels) {
|
||||
return log.debug('[' + socket.id + '] [Warning] already joined', channel);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
'use strict';
|
||||
|
||||
const checkXSS = require('./xss.js');
|
||||
|
||||
function isValidRoomName(input) {
|
||||
if (typeof input !== 'string') {
|
||||
return false;
|
||||
}
|
||||
const room = checkXSS(input);
|
||||
return !room ? false : !hasPathTraversal(room);
|
||||
}
|
||||
|
||||
function hasPathTraversal(input) {
|
||||
const pathTraversalPattern = /(\.\.(\/|\\))+/;
|
||||
return pathTraversalPattern.test(input);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isValidRoomName,
|
||||
hasPathTraversal,
|
||||
};
|
||||
+86
-57
@@ -1,72 +1,101 @@
|
||||
'use strict';
|
||||
|
||||
const xss = require('xss');
|
||||
const Logs = require('./logs');
|
||||
const log = new Logs('xss');
|
||||
const { JSDOM } = require('jsdom');
|
||||
const DOMPurify = require('dompurify');
|
||||
const he = require('he');
|
||||
|
||||
/**
|
||||
* Prevent XSS injection by client side
|
||||
*
|
||||
* @param {object} dataObject
|
||||
* @returns sanitized object
|
||||
*/
|
||||
// Initialize DOMPurify with jsdom
|
||||
const window = new JSDOM('').window;
|
||||
const purify = DOMPurify(window);
|
||||
|
||||
const Logger = require('./logs');
|
||||
const log = new Logger('Xss');
|
||||
|
||||
// Configure DOMPurify
|
||||
purify.setConfig({
|
||||
ALLOWED_TAGS: ['a', 'img', 'div', 'span', 'svg', 'g', 'p'], // Allow specific tags
|
||||
ALLOWED_ATTR: ['href', 'src', 'title', 'id', 'class', 'target'], // 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);
|
||||
}
|
||||
});
|
||||
|
||||
// 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');
|
||||
}
|
||||
}
|
||||
|
||||
// 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');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hook to clean specific attributes that can cause XSS
|
||||
purify.addHook('beforeSanitizeAttributes', cleanAttributes);
|
||||
|
||||
// Main function to check and sanitize data
|
||||
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;
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
} 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;
|
||||
return sanitizeData(dataObject);
|
||||
} catch (error) {
|
||||
log.error('XSS error', { data: dataObject, error: error });
|
||||
return dataObject;
|
||||
log.error('Sanitization error:', error);
|
||||
return dataObject; // Return original data in case of error
|
||||
}
|
||||
};
|
||||
|
||||
function objectToJSONString(dataObject) {
|
||||
try {
|
||||
return JSON.stringify(dataObject);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
function needsDecoding(str) {
|
||||
const urlEncodedPattern = /%[0-9A-Fa-f]{2}/g;
|
||||
return urlEncodedPattern.test(str);
|
||||
}
|
||||
|
||||
function JSONStringToObject(jsonString) {
|
||||
try {
|
||||
return JSON.parse(jsonString);
|
||||
} catch (error) {
|
||||
return false;
|
||||
// Recursively sanitize data based on its type
|
||||
function sanitizeData(data) {
|
||||
if (typeof data === 'string') {
|
||||
// Decode HTML entities and URL encoded content
|
||||
const decodedData = needsDecoding(data) ? he.decode(decodeURIComponent(data)) : he.decode(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;
|
||||
|
||||
+11
-5
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "mirotalk",
|
||||
"version": "1.4.51",
|
||||
"version": "1.4.55",
|
||||
"description": "A free WebRTC browser-based video call",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"start": "node app/src/server.js",
|
||||
"start-dev": "nodemon app/src/server.js",
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"test": "mocha tests/*.js",
|
||||
"lint": "npx prettier --write .",
|
||||
"docker-build": "docker build --tag mirotalk/p2p:latest .",
|
||||
"docker-rmi": "docker images |grep '<none>' |awk '{print $3}' |xargs docker rmi",
|
||||
@@ -48,9 +48,12 @@
|
||||
"compression": "^1.7.5",
|
||||
"cors": "^2.8.5",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dompurify": "^3.2.3",
|
||||
"dotenv": "^16.4.7",
|
||||
"express": "^4.21.2",
|
||||
"express-openid-connect": "^2.17.1",
|
||||
"he": "^1.2.0",
|
||||
"jsdom": "^26.0.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"js-yaml": "^4.1.0",
|
||||
"ngrok": "^5.0.0-beta.2",
|
||||
@@ -59,12 +62,15 @@
|
||||
"qs": "^6.13.1",
|
||||
"socket.io": "^4.8.1",
|
||||
"swagger-ui-express": "^5.0.1",
|
||||
"uuid": "11.0.5",
|
||||
"xss": "^1.0.15"
|
||||
"uuid": "11.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "^11.0.1",
|
||||
"node-fetch": "^3.3.2",
|
||||
"nodemon": "^3.1.9",
|
||||
"prettier": "3.4.2"
|
||||
"prettier": "3.4.2",
|
||||
"proxyquire": "^2.1.3",
|
||||
"should": "^13.2.3",
|
||||
"sinon": "^19.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
+4
-3
@@ -15,7 +15,7 @@
|
||||
* @license For commercial use or closed source, contact us at license.mirotalk@gmail.com or purchase directly from CodeCanyon
|
||||
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-p2p-webrtc-realtime-video-conferences/38376661
|
||||
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
|
||||
* @version 1.4.51
|
||||
* @version 1.4.55
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -34,6 +34,7 @@ const myRoomUrl = window.location.origin + '/join/' + roomId; // share room url
|
||||
// Images
|
||||
const images = {
|
||||
caption: '../images/caption.png',
|
||||
chatgpt: '../images/chatgpt.png',
|
||||
confirmation: '../images/image-placeholder.png',
|
||||
share: '../images/share.png',
|
||||
locked: '../images/locked.png',
|
||||
@@ -7923,8 +7924,8 @@ function appendMessage(from, img, side, msg, privateMsg, msgId = null) {
|
||||
|
||||
// sanitize all params
|
||||
const getFrom = filterXSS(from);
|
||||
const getImg = filterXSS(img);
|
||||
const getSide = filterXSS(side);
|
||||
const getImg = isChatGPTOn && getSide === 'left' ? images.chatgpt : filterXSS(img);
|
||||
const getMsg = filterXSS(msg);
|
||||
const getPrivateMsg = filterXSS(privateMsg);
|
||||
const getMsgId = filterXSS(msgId);
|
||||
@@ -11036,7 +11037,7 @@ function showAbout() {
|
||||
Swal.fire({
|
||||
background: swBg,
|
||||
position: 'center',
|
||||
title: '<strong>WebRTC P2P v1.4.51</strong>',
|
||||
title: '<strong>WebRTC P2P v1.4.55</strong>',
|
||||
imageAlt: 'mirotalk-about',
|
||||
imageUrl: images.about,
|
||||
customClass: { image: 'img-about' },
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
'use strict';
|
||||
|
||||
// npx mocha test-validator.js
|
||||
|
||||
require('should');
|
||||
|
||||
const checkValidator = require('../app/src/validate');
|
||||
|
||||
describe('test-validate', () => {
|
||||
describe('1. Handling invalid room name', () => {
|
||||
it('should return false for non-string inputs', () => {
|
||||
checkValidator.isValidRoomName(123).should.be.false();
|
||||
checkValidator.isValidRoomName({}).should.be.false();
|
||||
checkValidator.isValidRoomName([]).should.be.false();
|
||||
checkValidator.isValidRoomName(null).should.be.false();
|
||||
checkValidator.isValidRoomName(undefined).should.be.false();
|
||||
});
|
||||
|
||||
it('should return false for xss injection inputs', () => {
|
||||
checkValidator.isValidRoomName('<script>alert("xss")</script>').should.be.false();
|
||||
});
|
||||
|
||||
it('should return true for valid room name', () => {
|
||||
checkValidator.isValidRoomName('Room1').should.be.true();
|
||||
checkValidator.isValidRoomName('ConferenceRoom').should.be.true();
|
||||
checkValidator.isValidRoomName('Room_123').should.be.true();
|
||||
checkValidator.isValidRoomName('30521HungryHat').should.be.true();
|
||||
checkValidator.isValidRoomName('dbc4a9d9-6879-479a-b8fe-cedaad176b0d').should.be.true();
|
||||
});
|
||||
|
||||
it('should return false for room name with path traversal', () => {
|
||||
checkValidator.isValidRoomName('../etc/passwd').should.be.false();
|
||||
checkValidator.isValidRoomName('..\\etc\\passwd').should.be.false();
|
||||
checkValidator.isValidRoomName('Room/../../etc').should.be.false();
|
||||
checkValidator.isValidRoomName('Room\\..\\..\\etc').should.be.false();
|
||||
});
|
||||
|
||||
it('should return true for room names with special characters that do not imply path traversal', () => {
|
||||
checkValidator.isValidRoomName('Room_@!#$%^&*()').should.be.true();
|
||||
checkValidator.isValidRoomName('Room-Name').should.be.true();
|
||||
checkValidator.isValidRoomName('Room.Name').should.be.true();
|
||||
});
|
||||
});
|
||||
|
||||
describe('3. Handle path traversal', () => {
|
||||
it('should return false for strings without path traversal', () => {
|
||||
checkValidator.hasPathTraversal('Room1').should.be.false();
|
||||
checkValidator.hasPathTraversal('Rec_Test.webm').should.be.false();
|
||||
checkValidator.hasPathTraversal('simple/path').should.be.false();
|
||||
});
|
||||
|
||||
it('should return true for strings with path traversal', () => {
|
||||
checkValidator.hasPathTraversal('../etc/passwd').should.be.true();
|
||||
checkValidator.hasPathTraversal('..\\etc\\passwd').should.be.true();
|
||||
checkValidator.hasPathTraversal('Room/../../etc').should.be.true();
|
||||
checkValidator.hasPathTraversal('Room\\..\\..\\etc').should.be.true();
|
||||
});
|
||||
|
||||
it('should return false for strings with ".." that do not indicate path traversal', () => {
|
||||
checkValidator.hasPathTraversal('Room..').should.be.false();
|
||||
checkValidator.hasPathTraversal('Rec..webm').should.be.false();
|
||||
checkValidator.hasPathTraversal('NoPathTraversalHere..').should.be.false();
|
||||
});
|
||||
|
||||
it('should return true for complex path traversal patterns', () => {
|
||||
checkValidator.hasPathTraversal('....//').should.be.true();
|
||||
checkValidator.hasPathTraversal('..\\..\\').should.be.true();
|
||||
checkValidator.hasPathTraversal('.../../').should.be.true();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,218 @@
|
||||
'use strict';
|
||||
|
||||
// npx mocha test-xss.js
|
||||
|
||||
require('should');
|
||||
|
||||
const checkXSS = require('../app/src/xss');
|
||||
|
||||
describe('test-xss', () => {
|
||||
describe('1. Basic Data Types Handling', () => {
|
||||
it('should return numbers and booleans unchanged', () => {
|
||||
checkXSS(42).should.equal(42);
|
||||
checkXSS(true).should.equal(true);
|
||||
});
|
||||
|
||||
it('should return null and undefined unchanged', () => {
|
||||
should.not.exist(checkXSS(null));
|
||||
should.not.exist(checkXSS(undefined));
|
||||
});
|
||||
});
|
||||
|
||||
describe('2. Simple String Handling', () => {
|
||||
it('should sanitize strings with XSS injections', () => {
|
||||
const maliciousString = '<script>alert("xss")</script>';
|
||||
const sanitizedString = checkXSS(maliciousString);
|
||||
sanitizedString.should.not.containEql('<script>');
|
||||
sanitizedString.should.not.containEql('alert');
|
||||
sanitizedString.should.not.containEql('</script>');
|
||||
});
|
||||
|
||||
it('should sanitize complex XSS injections', () => {
|
||||
const complexString = '<svg><g/onload=alert(2)//<p>';
|
||||
const sanitizedString = checkXSS(complexString);
|
||||
sanitizedString.should.not.containEql('onload');
|
||||
sanitizedString.should.equal('<svg><g></g></svg>');
|
||||
});
|
||||
|
||||
it('should sanitize HTML attributes', () => {
|
||||
const maliciousHtml = '<a href="javascript:alert(\'xss\')">click me</a>';
|
||||
const sanitizedHtml = checkXSS(maliciousHtml);
|
||||
sanitizedHtml.should.not.containEql('javascript:');
|
||||
sanitizedHtml.should.containEql('<a>click me</a>');
|
||||
});
|
||||
|
||||
it('should sanitize embedded scripts in HTML', () => {
|
||||
const maliciousHtml = '<div><script>alert("xss")</script></div>';
|
||||
const sanitizedHtml = checkXSS(maliciousHtml);
|
||||
sanitizedHtml.should.not.containEql('<script>');
|
||||
sanitizedHtml.should.containEql('<div></div>');
|
||||
});
|
||||
|
||||
it('should handle encoded XSS payloads', () => {
|
||||
const encodedXss = '%3Cscript%3Ealert%28%27xss%27%29%3C%2Fscript%3E';
|
||||
const sanitizedString = checkXSS(encodedXss);
|
||||
sanitizedString.should.not.containEql('alert');
|
||||
sanitizedString.should.equal('');
|
||||
});
|
||||
|
||||
it('should handle special characters used in XSS attacks', () => {
|
||||
const specialCharsXss = "<div title=\"<img src='x' onerror='alert(1)'/>\">Test</div>";
|
||||
const sanitizedSpecialChars = checkXSS(specialCharsXss);
|
||||
sanitizedSpecialChars.should.not.containEql('onerror');
|
||||
sanitizedSpecialChars.should.containEql('<div>Test</div>');
|
||||
});
|
||||
});
|
||||
|
||||
describe('3. Handling Objects, Arrays, and Nested Structures', () => {
|
||||
it('should sanitize objects with XSS injections', () => {
|
||||
const maliciousObject = {
|
||||
key1: '<script>alert("xss")</script>',
|
||||
key2: 'normal string',
|
||||
};
|
||||
const sanitizedObject = checkXSS(maliciousObject);
|
||||
sanitizedObject.key1.should.not.containEql('<script>');
|
||||
sanitizedObject.key1.should.not.containEql('alert');
|
||||
sanitizedObject.key1.should.not.containEql('</script>');
|
||||
sanitizedObject.key2.should.equal('normal string');
|
||||
});
|
||||
|
||||
it('should sanitize arrays with XSS injections', () => {
|
||||
const maliciousArray = ['<script>alert("xss")</script>', 'normal string'];
|
||||
const sanitizedArray = checkXSS(maliciousArray);
|
||||
sanitizedArray[0].should.not.containEql('<script>');
|
||||
sanitizedArray[0].should.not.containEql('alert');
|
||||
sanitizedArray[0].should.not.containEql('</script>');
|
||||
sanitizedArray[1].should.equal('normal string');
|
||||
});
|
||||
|
||||
it('should handle nested objects and arrays with XSS injections', () => {
|
||||
const nestedData = {
|
||||
key1: [
|
||||
'<script>alert("xss")</script>',
|
||||
{
|
||||
key2: '<img src="x" onerror="alert(\'xss\')">',
|
||||
},
|
||||
],
|
||||
};
|
||||
const sanitizedData = checkXSS(nestedData);
|
||||
sanitizedData.key1[0].should.not.containEql('<script>');
|
||||
sanitizedData.key1[0].should.not.containEql('alert');
|
||||
sanitizedData.key1[0].should.not.containEql('</script>');
|
||||
sanitizedData.key1[1].key2.should.not.containEql('onerror');
|
||||
sanitizedData.key1[1].key2.should.equal('<img src="x">');
|
||||
});
|
||||
|
||||
it('should handle XSS in nested HTML elements', () => {
|
||||
const nestedXss = '<div><span onclick="alert(\'xss\')">Click me</span></div>';
|
||||
const sanitizedNestedXss = checkXSS(nestedXss);
|
||||
sanitizedNestedXss.should.not.containEql('onclick');
|
||||
sanitizedNestedXss.should.containEql('<div><span>Click me</span></div>');
|
||||
});
|
||||
|
||||
it('should handle XSS through malicious attributes in different tags', () => {
|
||||
const maliciousAttributes =
|
||||
'<a href="#" onclick="alert(\'xss\')">Link</a><iframe src="javascript:alert(\'xss\')"></iframe>';
|
||||
const sanitizedAttributes = checkXSS(maliciousAttributes);
|
||||
sanitizedAttributes.should.not.containEql('onclick');
|
||||
sanitizedAttributes.should.not.containEql('javascript:');
|
||||
sanitizedAttributes.should.not.containEql('alert');
|
||||
});
|
||||
});
|
||||
|
||||
describe('4. Handling Specific Formats (JSON, Base64, etc.)', () => {
|
||||
it('should handle XSS in JSON data', () => {
|
||||
const maliciousJson = '{"key": "<img src=\'x\' onerror=\'alert(1)\'>"}';
|
||||
const sanitizedJson = checkXSS(JSON.parse(maliciousJson));
|
||||
sanitizedJson.key.should.not.containEql('onerror');
|
||||
sanitizedJson.key.should.equal('<img src="x">');
|
||||
});
|
||||
|
||||
it('should sanitize base64 encoded content', () => {
|
||||
const maliciousBase64 = '<img src="data:image/svg+xml;base64,PHN2ZyBvbmxvYWQ9YWxlcnQoJ3hzcicpPg==">';
|
||||
const sanitizedBase64 = checkXSS(maliciousBase64);
|
||||
sanitizedBase64.should.not.containEql('onload');
|
||||
sanitizedBase64.should.equal('<img>');
|
||||
});
|
||||
|
||||
it('should sanitize encoded HTML entities', () => {
|
||||
const encodedHtmlEntities = '<script>alert('xss')</script>';
|
||||
const sanitizedEntities = checkXSS(encodedHtmlEntities);
|
||||
sanitizedEntities.should.not.containEql('<script>');
|
||||
sanitizedEntities.should.equal('');
|
||||
});
|
||||
|
||||
it('should sanitize encoded and obfuscated payloads', () => {
|
||||
const obfuscatedXss = '<img src="x" onerror="eval(String.fromCharCode(97,108,101,114,116) + \'(1)\')">';
|
||||
const sanitizedObfuscatedXss = checkXSS(obfuscatedXss);
|
||||
sanitizedObfuscatedXss.should.not.containEql('eval');
|
||||
sanitizedObfuscatedXss.should.equal('<img src="x">');
|
||||
});
|
||||
});
|
||||
|
||||
describe('5. Handling CSS and JavaScript URL XSS', () => {
|
||||
it('should sanitize JavaScript and CSS injections', () => {
|
||||
const jsInjection = '<div style="background-image: url(javascript:alert(\'xss\'))"></div>';
|
||||
const sanitizedJsInjection = checkXSS(jsInjection);
|
||||
sanitizedJsInjection.should.not.containEql('javascript:');
|
||||
sanitizedJsInjection.should.containEql('<div></div>');
|
||||
});
|
||||
|
||||
it('should handle JavaScript URL XSS', () => {
|
||||
const jsUrlXss = '<a href="javascript:alert(\'xss\')">Click me</a>';
|
||||
const sanitizedJsUrl = checkXSS(jsUrlXss);
|
||||
sanitizedJsUrl.should.not.containEql('javascript:');
|
||||
sanitizedJsUrl.should.containEql('<a>Click me</a>');
|
||||
});
|
||||
|
||||
it('should sanitize `javascript:` URLs in CSS attributes', () => {
|
||||
const maliciousCss = '<div style="background:url(javascript:alert(\'xss\'))"></div>';
|
||||
const sanitizedCss = checkXSS(maliciousCss);
|
||||
sanitizedCss.should.not.containEql('javascript:');
|
||||
sanitizedCss.should.equal('<div></div>');
|
||||
});
|
||||
});
|
||||
|
||||
describe('6. Handling SVG, MathML, and Data URIs', () => {
|
||||
it('should handle XSS in SVG and MathML', () => {
|
||||
const svgXss = '<svg><script>alert("xss")</script></svg>';
|
||||
const sanitizedSvgXss = checkXSS(svgXss);
|
||||
sanitizedSvgXss.should.not.containEql('<script>');
|
||||
sanitizedSvgXss.should.equal('<svg></svg>');
|
||||
});
|
||||
|
||||
it('should sanitize data URIs in HTML attributes', () => {
|
||||
const maliciousHtml = '<img src="data:image/svg+xml,<svg onload=alert(\'xss\')>">';
|
||||
const sanitizedHtml = checkXSS(maliciousHtml);
|
||||
sanitizedHtml.should.not.containEql('onload');
|
||||
sanitizedHtml.should.equal('<img>');
|
||||
});
|
||||
|
||||
it('should handle data URL XSS', () => {
|
||||
const dataUrlXss =
|
||||
'<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNAAAAABJRU5ErkJggg==">';
|
||||
const sanitizedDataUrl = checkXSS(dataUrlXss);
|
||||
sanitizedDataUrl.should.not.containEql('data:');
|
||||
sanitizedDataUrl.should.containEql('<img>');
|
||||
});
|
||||
});
|
||||
|
||||
describe('7. Handling Dynamic Content', () => {
|
||||
it('should handle XSS in dynamic content', () => {
|
||||
const dynamicXss =
|
||||
'<div id="dynamicContent"></div><script>document.getElementById("dynamicContent").innerHTML = "<img src=\'x\' onerror=\'alert(1)\'>" </script>';
|
||||
const sanitizedDynamicXss = checkXSS(dynamicXss);
|
||||
sanitizedDynamicXss.should.not.containEql('onerror');
|
||||
sanitizedDynamicXss.should.containEql('<div id="dynamicContent"></div>');
|
||||
});
|
||||
});
|
||||
|
||||
describe('8. Handling Mixed Content', () => {
|
||||
it('should sanitize mixed content', () => {
|
||||
const mixedContent = '<div>Normal text <script>alert("xss")</script> more text</div>';
|
||||
const sanitizedContent = checkXSS(mixedContent);
|
||||
sanitizedContent.should.not.containEql('<script>');
|
||||
sanitizedContent.should.containEql('<div>Normal text more text</div>');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user