[MyBB] ReDoS Resulting in Authenticated RCE

We discussed this vulnerability during Episode 217 on 09 October 2023

Awesome abuse of an Regex DoS to bypass a security check in MyBB resulting in an RCE.

Take a look at the following code from MyBB’s check_template function:

<?php 
// ...
if(preg_match("~\\{\\$.+?\\}~s", preg_replace('~\\{\\$+[a-zA-Z_][a-zA-Z_0-9]*((?:-\\>|\\:\\:)\\$*[a-zA-Z_][a-zA-Z_0-9]*|\\[\s*\\$*([\'"]?)[a-zA-Z_ 0-9 ]+\\2\\]\s*)*\\}~', '', $template)))
{
  return true;
}

In this case its basically just trying to take the original template, and then remove all the normal looking variable expansions inside of it like {$xyz} will be removed by the preg_replace but anything more complex than a variable access will not be removed. Then the preg_match looks if there are any remaining expansions, if they are they are unsafe.

Logically the check should work, but the preg_* functions will return error values rather than raising an exception, these errors should always be checked though. By abusing the backtracking present in the regex. Once the backtracking limit is reached, preg_replace will simply return null. So the preg_match will be matching against nothing, which natually passes as it finds no matches.

Allowing for a template containing the backtracking and a problematic state to bypass the security check.


Because we haven’t covered ReDoS in a podcast summary before I want to touch on how you can determine if a regex is vulnerable:

The general rule for determining if a regex is vulnerable to backtracking issues is to see if the regex meets the following conditions:

  1. Repetition operators are applied to a sub-expression: (abc)+ or (x)*
  2. The sub-expression can match the same input value multiple times: (a|a)+ or (a+)*
  3. There must be a match case following the sub-expression that isn’t matched by the sub-expression: (a+)*b

The problematic sub-expression in this case is towards the end: ((?:-\>|\:\:)\$*[a-zA-Z_][a-zA-Z_0-9]*|\[\s*\$*([\'"]?)[a-zA-Z_ 0-9 ]+\2\]\s*)specifically the second case after the | (or) operator: \[\s*\$*([\'"]?)[a-zA-Z_ 0-9 ]+\2\]\s* Looking at the three conditions:

  1. It ends with a * which is a repetition operation
  2. The subexpression can match the same input for example: [0][0][0] the same expression would match [0] or [0][0] from that input
  3. The last part of the regex is to match a }

So by repeating that array access you can get the regex engine into an exponential backtracking situation. Atleast that’s how I understand this regex, no one likes reading this stuff. Most of the time I’ll toss things into a ReDos detector like: https://devina.io/redos-checker though there are plenty of other options too that provide other features.