PHP代码审计分段讲解(14)
30题利用提交数组绕过逻辑
源码如下:
<?php $role = "guest"; $flag = "flag{test_flag}"; $auth = false; if(isset($_COOKIE["role"])){ $role = unserialize(base64_decode($_COOKIE["role"])); if($role === "admin"){ $auth = true; } else{ $auth = false; } } else{ $role = base64_encode(serialize($role)); setcookie('role',$role); } if($auth){ if(isset($_POST['filename'])){ $filename = $_POST['filename']; $data = $_POST['data']; if(preg_match('[<>?]', $data)) { die('No No No!'.$data); } else { $s = implode($data); if(!preg_match('[<>?]', $s)){ $flag='None.'; } $rand = rand(1,10000000); $tmp="./uploads/".md5(time() + $rand).$filename; file_put_contents($tmp, $flag); echo "your file is in " . $tmp; } } else{ echo "Hello admin, now you can upload something you are easy to forget."; echo "<br />there are the source.<br />"; echo '<textarea rows="10" cols="100">'; echo htmlspecialchars(str_replace($flag,'flag{???}',file_get_contents(__FILE__))); echo '</textarea>'; } } else{ echo "Sorry. You have no permissions."; } ?>
首先给出了$role和$auth的初始值
$role = "guest"; $auth = false;
如果在COOKIE中没有传值的话,就会进入else,将初始值设定在COOKIE里
else{ $role = base64_encode(serialize($role)); setcookie('role',$role); }
从后面的逻辑上看,我们需要令
$auth=true
所以需要手动传入role值,通过逻辑
if(isset($_COOKIE["role"])){ $role = unserialize(base64_decode($_COOKIE["role"])); if($role === "admin"){ $auth = true; } else{ $auth = false; } }
这里对传入的 role 进行base64解密后反序列化,将结果赋值给$role
$role = unserialize(base64_decode($_COOKIE["role"]));
然后想要令
$auth=true
前提条件为:
$role === "admin"
这个是我们可以控制的
编写代码
<?php $role='admin'; $role1=base64_encode(serialize($role)); echo $role1; ?>
得到
role=czo1OiJhZG1pbiI7
可以看到成功绕过了第一个点
继续往下看
当 $auth 为 true的时候,进行:
if(isset($_POST['filename'])){ $filename = $_POST['filename']; $data = $_POST['data']; if(preg_match('[<>?]', $data)) { die('No No No!'.$data); } else { $s = implode($data); if(!preg_match('[<>?]', $s)){ $flag='None.'; } $rand = rand(1,10000000); $tmp="./uploads/".md5(time() + $rand).$filename; file_put_contents($tmp, $flag); echo "your file is in " . $tmp; } }
可以看出来是上传文件的代码,具体为:
传入文件名和文件内容:filename 和 data
if(isset($_POST['filename'])){ $filename = $_POST['filename']; $data = $_POST['data'];
判断$data中是否有一句话木马标识,有的话则退出
if(preg_match('[<>?]', $data)) { die('No No No!'.$data); }
没有的话 else 结构,这里有一句
$s = implode($data);
应该是上传一句话木马的突破点。
关于 implode()函数,有:
定义:
implode()函数返回由数组元素组合成的字符串
示例:
<?php $arr = array('Hello','World!','Beautiful','Day!'); echo implode(" ",$arr); ?>输出:
Hello World! Beautiful Day!
而我们在前面的代码审计中,知道preg_match()函数只能处理字符串,当传入的变量是数组是会返回false,这里正好满足,可以编写代码测试
<?php $data[]='<?php phpinfo();?>'; if(preg_match('[<>?]', $data)) { die('No No No!'.$data); }else{ echo "yes!"; } ?>
输出为
yes! PHP Warning: preg_match() expects parameter 2 to be string, array given in /usercode/file.php on line 3
虽然有警告,但是还是成功绕过了。
这里的代码
if(!preg_match('[<>?]', $s)){ $flag='None.'; }
表示如果变量$s中没有匹配到特定字符的话就令$flag为空,这样在后面的文件写入时,也不能获取到flag了。
$rand = rand(1,10000000); $tmp="./uploads/".md5(time() + $rand).$filename; file_put_contents($tmp, $flag);
这里是生成一个随机的文件名,并且将 flag 内容写进去
最后是输出文件名
echo "your file is in " . $tmp;
我们绕过后 flag 会写入到 文件名随机生成的文件中,该文件名最后是可知的。
按照之前分析的过程,很容易可以构建出payload
访问获取flag
结束