Skip to content

Commit 2bd8bdb

Browse files
committed
Added: getShortcodeAtts mutation to extract shortcodes from a request.
Fixed: potential PHP error in the array_key_value matching type.
1 parent 4b25caa commit 2bd8bdb

4 files changed

Lines changed: 129 additions & 0 deletions

File tree

src/Processor.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,10 @@ public function matchParameterValue($match, $value)
376376
// If a specific parameter key matches a sub-match condition.
377377
if ($matchType == 'array_key_value' && isset($match['key'], $match['match'])) {
378378
$values = $this->request->getParameterValues($match['key'], $value);
379+
if (!is_array($values)) {
380+
return false;
381+
}
382+
379383
foreach ($values as $value) {
380384
if ($this->matchParameterValue($match['match'], $value)) {
381385
return true;

src/Request.php

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,10 @@ public function applyMutation($mutations, $value)
196196
'getArrayValues' => [
197197
'args' => [],
198198
'type' => 'is_array'
199+
],
200+
'getShortcodeAtts' => [
201+
'args' => [],
202+
'type' => 'is_string'
199203
]
200204
];
201205

@@ -217,6 +221,8 @@ public function applyMutation($mutations, $value)
217221
// Call the function with given arguments.
218222
if ($mutation == 'getArrayValues') {
219223
$value = $this->getArrayValues($value);
224+
} elseif ($mutation == 'getShortcodeAtts') {
225+
$value = $this->getShortcodeAtts($value);
220226
} else {
221227
$value = call_user_func_array($mutation, array_merge([$value], $allowed[$mutation]['args']));
222228
}
@@ -323,4 +329,55 @@ public function getArrayValues($data, $glue = '&', $type = 'string')
323329

324330
return $ret;
325331
}
332+
333+
/**
334+
* Given a string, fetch all shortcodes and its attributes.
335+
*
336+
* @param string $value
337+
* @return array
338+
*/
339+
public function getShortcodeAtts($value)
340+
{
341+
// For rare cases where this may not be defined.
342+
if (!function_exists('shortcode_parse_atts')) {
343+
return [];
344+
}
345+
346+
// The regular expression used by WordPress core to fetch shortcodes and its attributes.
347+
preg_match_all(
348+
'/\[(\[?)(.*?)(?![\w-])([^\]\/]*(?:\/(?!\])[^\]\/]*)*?)(?:(\/)\]|\](?:([^\[]*+(?:\[(?!\/\2\])[^\[]*+)*+)\[\/\2\])?)(\]?)/',
349+
$value,
350+
$shortcodes,
351+
PREG_SET_ORDER
352+
);
353+
354+
// No matches.
355+
if (count($shortcodes) == 0) {
356+
return [];
357+
}
358+
359+
// Iterate through all shortcodes and fetch their attributes.
360+
$return = [];
361+
foreach ($shortcodes as $shortcode) {
362+
if (!isset($shortcode[2], $shortcode[3], $shortcode[5])) {
363+
continue;
364+
}
365+
366+
// Merge together if the shortcode occurs more than once.
367+
if (isset($return[$shortcode[2]])) {
368+
$atts = @shortcode_parse_atts($shortcode[3]);
369+
foreach ($atts as $key => $value) {
370+
if (isset($return[$shortcode[2]][$key])) {
371+
$return[$shortcode[2]][$key] .= $value;
372+
} else {
373+
$return[$shortcode[2]][$key] = $value;
374+
}
375+
}
376+
} else {
377+
$return[$shortcode[2]] = @shortcode_parse_atts($shortcode[3]);
378+
}
379+
}
380+
381+
return $return;
382+
}
326383
}

tests/FirewallTest.php

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,5 +316,65 @@ public function testRules()
316316
);
317317
$this->assertFalse($this->processor->launch(false));
318318
$this->alterPayload();
319+
320+
// Determine if a POST parameter contains a shortcode with a bad attribute.
321+
// Import the WordPress functions.
322+
function get_shortcode_atts_regex() {
323+
return '/([\w-]+)\s*=\s*"([^"]*)"(?:\s|$)|([\w-]+)\s*=\s*\'([^\']*)\'(?:\s|$)|([\w-]+)\s*=\s*([^\s\'"]+)(?:\s|$)|"([^"]*)"(?:\s|$)|\'([^\']*)\'(?:\s|$)|(\S+)(?:\s|$)/';
324+
}
325+
326+
function shortcode_parse_atts( $text ) {
327+
$atts = array();
328+
$pattern = get_shortcode_atts_regex();
329+
$text = preg_replace( "/[\x{00a0}\x{200b}]+/u", ' ', $text );
330+
if ( preg_match_all( $pattern, $text, $match, PREG_SET_ORDER ) ) {
331+
foreach ( $match as $m ) {
332+
if ( ! empty( $m[1] ) ) {
333+
$atts[ strtolower( $m[1] ) ] = stripcslashes( $m[2] );
334+
} elseif ( ! empty( $m[3] ) ) {
335+
$atts[ strtolower( $m[3] ) ] = stripcslashes( $m[4] );
336+
} elseif ( ! empty( $m[5] ) ) {
337+
$atts[ strtolower( $m[5] ) ] = stripcslashes( $m[6] );
338+
} elseif ( isset( $m[7] ) && strlen( $m[7] ) ) {
339+
$atts[] = stripcslashes( $m[7] );
340+
} elseif ( isset( $m[8] ) && strlen( $m[8] ) ) {
341+
$atts[] = stripcslashes( $m[8] );
342+
} elseif ( isset( $m[9] ) ) {
343+
$atts[] = stripcslashes( $m[9] );
344+
}
345+
}
346+
347+
// Reject any unclosed HTML elements.
348+
foreach ( $atts as &$value ) {
349+
if ( false !== strpos( $value, '<' ) ) {
350+
if ( 1 !== preg_match( '/^[^<]*+(?:<[^>]*+>[^<]*+)*+$/', $value ) ) {
351+
$value = '';
352+
}
353+
}
354+
}
355+
} else {
356+
$atts = ltrim( $text );
357+
}
358+
359+
return $atts;
360+
}
361+
362+
$this->setUpFirewallProcessor([$this->rules[20]]);
363+
$this->alterPayload(
364+
['POST' => [
365+
'content' => 'This is my post content and a shortcode with bad attribute value. [learn_press_featured_courses order_by="post_date" order="desc"]'
366+
]]
367+
);
368+
$this->assertTrue($this->processor->launch(false));
369+
$this->alterPayload();
370+
371+
$this->setUpFirewallProcessor([$this->rules[20]]);
372+
$this->alterPayload(
373+
['POST' => [
374+
'content' => 'This is my post content with a legitimate shortcode. [learn_press_featured_courses order_by="post_date" order="\',(select sleep(10))"]'
375+
]]
376+
);
377+
$this->assertFalse($this->processor->launch(false));
378+
$this->alterPayload();
319379
}
320380
}

tests/data/Rules.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,5 +158,13 @@
158158
"cat":"TEST",
159159
"type":"BLOCK",
160160
"type_params":null
161+
},
162+
{
163+
"id":21,
164+
"title":"Block a specific WordPress shortcode attribute from containing a bad value.",
165+
"rules":[{"parameter":"post.content","mutations":["getShortcodeAtts"],"match":{"type":"array_key_value","key":"learn_press_featured_courses.order","match":{"type":"contains","value":")"}}}],
166+
"cat":"TEST",
167+
"type":"BLOCK",
168+
"type_params":null
161169
}
162170
]

0 commit comments

Comments
 (0)