Skip to content
Open
Show file tree
Hide file tree
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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 1.3.5
This is a security release.

Here's a full list of what's changed since the last release:
- Security: Fixed an authenticated (Contributor+) stored DOM-based XSS in the bundled Featherlight library (CVE-2024-5667). Image URLs are now assigned as attribute values instead of being concatenated into markup, the jQuery selector filter no longer parses input as HTML, and HTML lightbox content is sanitized to strip inline event handlers, `javascript:` URLs and `<script>` tags.
- Security: Hardened the caption feature (`addCaptions`) so caption markup is sanitized before insertion instead of being assigned via `innerHTML`, closing a second DOM-XSS sink reachable by users able to store raw caption HTML.

## 1.3.4
This is a maintenance release.

Expand Down
17 changes: 16 additions & 1 deletion assets/plugin/js/wpFeatherlight.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,22 @@

if ( 0 !== caption.length ) {
var $captionElm = $( '<div class="caption">' ).appendTo( object.find( '.featherlight-content' ) );
$captionElm[0].innerHTML = caption.html();
/* Sanitize caption markup before insertion: drop inline event handlers, javascript: URLs and <script> to prevent DOM XSS (CVE-2024-5667). */
var $captionHtml = $( $.parseHTML( '' + caption.html(), document, false ) );
$captionHtml.find( '*' ).addBack().each( function() {
if ( ! this.attributes ) {
return;
}
for ( var i = this.attributes.length - 1; i >= 0; i-- ) {
var attrName = this.attributes[ i ].name,
lowerName = attrName.toLowerCase(),
value = ( '' + this.attributes[ i ].value ).replace( /[\s ]+/g, '' ).toLowerCase();
if ( 0 === lowerName.indexOf( 'on' ) || ( ( 'href' === lowerName || 'src' === lowerName || 'xlink:href' === lowerName ) && 0 === value.indexOf( 'javascript:' ) ) ) {
this.removeAttribute( attrName );
}
}
});
$captionElm.append( $captionHtml );
}
};
}
Expand Down
2 changes: 1 addition & 1 deletion includes/class-plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class WP_Featherlight {
* @since 0.3.0
* @var string
*/
const VERSION = '1.3.4';
const VERSION = '1.3.5';

/**
* Property for storing a reference to the main plugin file.
Expand Down
17 changes: 16 additions & 1 deletion js/wpFeatherlight.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,22 @@

if ( 0 !== caption.length ) {
var $captionElm = $( '<div class="caption">' ).appendTo( object.find( '.featherlight-content' ) );
$captionElm[0].innerHTML = caption.html();
/* Sanitize caption markup before insertion: drop inline event handlers, javascript: URLs and <script> to prevent DOM XSS (CVE-2024-5667). */
var $captionHtml = $( $.parseHTML( '' + caption.html(), document, false ) );
$captionHtml.find( '*' ).addBack().each( function() {
if ( ! this.attributes ) {
return;
}
for ( var i = this.attributes.length - 1; i >= 0; i-- ) {
var attrName = this.attributes[ i ].name,
lowerName = attrName.toLowerCase(),
value = ( '' + this.attributes[ i ].value ).replace( /[\s ]+/g, '' ).toLowerCase();
if ( 0 === lowerName.indexOf( 'on' ) || ( ( 'href' === lowerName || 'src' === lowerName || 'xlink:href' === lowerName ) && 0 === value.indexOf( 'javascript:' ) ) ) {
this.removeAttribute( attrName );
}
}
});
$captionElm.append( $captionHtml );
}
};
}
Expand Down
2 changes: 1 addition & 1 deletion js/wpFeatherlight.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

40 changes: 36 additions & 4 deletions js/wpFeatherlight.pkgd.js
Original file line number Diff line number Diff line change
Expand Up @@ -448,15 +448,15 @@
jquery: {
regex: /^[#.]\w/, /* Anything that starts with a class name or identifiers */
test: function(elem) { return elem instanceof $ && elem; },
process: function(elem) { return this.persist !== false ? $(elem) : $(elem).clone(true); }
process: function(elem) { var $elem = $(document).find(elem); return this.persist !== false ? $elem : $elem.clone(true); }
},
image: {
regex: /\.(png|jpg|jpeg|gif|tiff?|bmp|svg)(\?\S*)?$/i,
process: function(url) {
var self = this,
deferred = $.Deferred(),
img = new Image(),
$img = $('<img src="'+url+'" alt="" class="'+self.namespace+'-image" />');
$img = $('<img alt="" class="'+self.namespace+'-image" />').attr( 'src', url );
img.onload = function() {
/* Store naturalWidth & height for IE8 */
$img.naturalWidth = img.width; $img.naturalHeight = img.height;
Expand All @@ -469,7 +469,24 @@
},
html: {
regex: /^\s*<[\w!][^<]*>/, /* Anything that starts with some kind of valid tag */
process: function(html) { return $(html); }
process: function(html) {
/* Sanitize untrusted markup: drop <script>, inline event handlers and javascript: URLs to prevent DOM XSS (CVE-2024-5667). */
var $html = $($.parseHTML('' + html, document, false));
$html.find('*').addBack().each(function() {
if (!this.attributes) {
return;
}
for (var i = this.attributes.length - 1; i >= 0; i--) {
var attrName = this.attributes[i].name,
lowerName = attrName.toLowerCase(),
value = ('' + this.attributes[i].value).replace(/[\s ]+/g, '').toLowerCase();
if (0 === lowerName.indexOf('on') || (('href' === lowerName || 'src' === lowerName || 'xlink:href' === lowerName) && 0 === value.indexOf('javascript:'))) {
this.removeAttribute(attrName);
}
}
});
return $html;
}
},
ajax: {
regex: /./, /* At this point, any content is assumed to be an URL */
Expand Down Expand Up @@ -1019,7 +1036,22 @@

if ( 0 !== caption.length ) {
var $captionElm = $( '<div class="caption">' ).appendTo( object.find( '.featherlight-content' ) );
$captionElm[0].innerHTML = caption.html();
/* Sanitize caption markup before insertion: drop inline event handlers, javascript: URLs and <script> to prevent DOM XSS (CVE-2024-5667). */
var $captionHtml = $( $.parseHTML( '' + caption.html(), document, false ) );
$captionHtml.find( '*' ).addBack().each( function() {
if ( ! this.attributes ) {
return;
}
for ( var i = this.attributes.length - 1; i >= 0; i-- ) {
var attrName = this.attributes[ i ].name,
lowerName = attrName.toLowerCase(),
value = ( '' + this.attributes[ i ].value ).replace( /[\s ]+/g, '' ).toLowerCase();
if ( 0 === lowerName.indexOf( 'on' ) || ( ( 'href' === lowerName || 'src' === lowerName || 'xlink:href' === lowerName ) && 0 === value.indexOf( 'javascript:' ) ) ) {
this.removeAttribute( attrName );
}
}
});
$captionElm.append( $captionHtml );
}
};
}
Expand Down
Loading