Skip to content

Commit c4ae030

Browse files
committed
improve svg sanitization
1 parent be985f0 commit c4ae030

File tree

3 files changed

+144
-4
lines changed

3 files changed

+144
-4
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
"require": {
5656
"php": ">=7.4",
5757
"codeinwp/themeisle-sdk": "^3.3",
58-
"codeinwp/optimole-sdk": "^1.0"
58+
"codeinwp/optimole-sdk": "^1.0",
59+
"enshrined/svg-sanitize": "^0.18.0"
5960
}
6061
}

composer.lock

Lines changed: 46 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

inc/admin.php

Lines changed: 96 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
* @author Optimole <[email protected]>
1111
*/
1212

13+
use enshrined\svgSanitize\Sanitizer;
1314
/**
1415
* Class Optml_Admin
1516
*/
@@ -87,12 +88,105 @@ public function __construct() {
8788
'upload_mimes',
8889
[
8990
$this,
90-
'allow_meme_types',
91+
'allow_svg',
9192
]
9293
); // phpcs:ignore WordPressVIPMinimum.Hooks.RestrictedHooks.upload_mimes
94+
add_filter( 'wp_handle_upload_prefilter', [ $this, 'check_svg_and_sanitize' ] );
9395
}
9496
}
97+
/**
98+
* Check if the file is an SVG, if so handle appropriately
99+
*
100+
* @param array $file An array of data for a single file.
101+
*
102+
* @return mixed
103+
*/
104+
public function check_svg_and_sanitize( $file ) {
105+
// Ensure we have a proper file path before processing.
106+
if ( ! isset( $file['tmp_name'] ) ) {
107+
return $file;
108+
}
109+
110+
$file_name = isset( $file['name'] ) ? $file['name'] : '';
111+
$wp_filetype = wp_check_filetype_and_ext( $file['tmp_name'], $file_name );
112+
$type = ! empty( $wp_filetype['type'] ) ? $wp_filetype['type'] : '';
113+
114+
if ( 'image/svg+xml' === $type ) {
115+
if ( ! current_user_can( 'upload_files' ) ) {
116+
$file['error'] = 'Invalid';
117+
return $file;
118+
}
119+
120+
if ( ! $this->sanitize_svg( $file['tmp_name'] ) ) {
121+
$file['error'] = 'Invalid';
122+
}
123+
}
124+
125+
return $file;
126+
}
127+
128+
/**
129+
* Sanitize the SVG
130+
*
131+
* @param string $file Temp file path.
132+
*
133+
* @return bool|int
134+
*/
135+
protected function sanitize_svg( $file ) {
136+
// We can ignore the phpcs warning here as we're reading and writing to the Temp file.
137+
$dirty = file_get_contents( $file ); // phpcs:ignore
138+
139+
// Is the SVG gzipped? If so we try and decode the string.
140+
$is_zipped = $this->is_gzipped( $dirty );
141+
if ( $is_zipped && ( ! function_exists( 'gzdecode' ) || ! function_exists( 'gzencode' ) ) ) {
142+
return false;
143+
}
95144

145+
if ( $is_zipped ) {
146+
$dirty = gzdecode( $dirty );
147+
148+
// If decoding fails, bail as we're not secure.
149+
if ( false === $dirty ) {
150+
return false;
151+
}
152+
}
153+
154+
$sanitizer = new Sanitizer();
155+
$clean = $sanitizer->sanitize( $dirty );
156+
157+
if ( false === $clean ) {
158+
return false;
159+
}
160+
161+
// If we were gzipped, we need to re-zip.
162+
if ( $is_zipped ) {
163+
$clean = gzencode( $clean );
164+
}
165+
166+
// We can ignore the phpcs warning here as we're reading and writing to the Temp file.
167+
file_put_contents( $file, $clean ); // phpcs:ignore
168+
169+
return true;
170+
}
171+
172+
/**
173+
* Check if the contents are gzipped
174+
*
175+
* @see http://www.gzip.org/zlib/rfc-gzip.html#member-format
176+
*
177+
* @param string $contents Content to check.
178+
*
179+
* @return bool
180+
*/
181+
protected function is_gzipped( $contents ) {
182+
// phpcs:disable Generic.Strings.UnnecessaryStringConcat.Found
183+
if ( function_exists( 'mb_strpos' ) ) {
184+
return 0 === mb_strpos( $contents, "\x1f" . "\x8b" . "\x08" );
185+
} else {
186+
return 0 === strpos( $contents, "\x1f" . "\x8b" . "\x08" );
187+
}
188+
// phpcs:enable
189+
}
96190
/**
97191
* Schedules the hourly cron that starts the querying for images alt/title attributes
98192
*
@@ -1824,7 +1918,7 @@ private function get_dashboard_strings() {
18241918
* @access public
18251919
* @uses filter:upload_mimes
18261920
*/
1827-
public function allow_meme_types( $mimes ) {
1921+
public function allow_svg( $mimes ) {
18281922
$mimes['svg'] = 'image/svg+xml';
18291923

18301924
return $mimes;

0 commit comments

Comments
 (0)