PHP黑魔法

古娜拉黑暗之神🪄

PHP基础函数

接着上一篇小bug,把那些也提溜过来继续写🧐

extract()

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

语法

extract(array,extract_rules,prefix)

参数 描述
array(数组名) 规定要使用的数组
extract_rules(提取规则) 可省略。extract() 函数将检查每个键名是否为合法的变量名,同时也检查和符号表中已存在的变量名是否冲突。对不合法和冲突的键名的处理将根据此参数决定。
prefix(前缀) 可省略。请注意 prefix 仅在 extract_type 的值是 EXTR_PREFIX_SAME,EXTR_PREFIX_ALL,EXTR_PREFIX_INVALID 或 EXTR_PREFIX_IF_EXISTS 时需要。如果附加了前缀后的结果不是合法的变量名,将不会导入到符号表中。

extract_rules可能的值:

  • EXTR_OVERWRITE - 默认。如果有冲突,则覆盖已有的变量
  • EXTR_SKIP - 如果有冲突,不覆盖已有的变量。
  • EXTR_PREFIX_SAME - 如果有冲突,在变量名前加上前缀 prefix
  • EXTR_PREFIX_ALL - 给所有变量名加上前缀 prefix
  • EXTR_PREFIX_INVALID - 仅在不合法或数字变量名前加上前缀 prefix
  • EXTR_IF_EXISTS - 仅在当前符号表中已有同名变量时,覆盖它们的值。其它的都不处理。
  • EXTR_PREFIX_IF_EXISTS - 仅在当前符号表中已有同名变量时,建立附加了前缀的变量名,其它的都不处理。
  • EXTR_REFS - 将变量作为引用提取。导入的变量仍然引用了数组参数的值。
1
2
3
4
5
6
7
#实例演示1
<?php
$a = "Apple";
$my_array = array("a" => "cat","b"=> "dog","d" =>"horse");
extract($my_array);
echo "$a = $a;$b = $b;$c = $c";
?>
1
2
#输出演示
$a = cat;$b = dog;$c = horse
1
2
3
4
5
6
7
#实例演示2
<?php
$a = "Apple";
$my_array = array("a" => "cat","b"=> "dog","d" =>"horse");
extract($my_array,EXTR_PREFIX_SAME, "dup");
echo "$a = $a;$b = $b;$c = $c";
?>
1
2
#输出演示
$a = Original; $b = Dog; $c = Horse; $dup_a = Cat

isset()

isset() 函数用于检测变量是否已设置并且非 NULL。若使用 isset() 测试一个被设置成 NULL 的变量,将返回 FALSE。

语法

bool isset ( mixed $var [, mixed $... ] )

$var为要测试的变量

如果一次传入多个参数,那么 isset() 只有在全部参数都被设置时返回 TRUE,计算过程从左至右,中途遇到没有设置的变量时就会立即停止。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#实例演示:
<?php
$test='';
if(isset($test)){
echo "变量已设置"
}
//结果为true,将打印文本

//使用var_dump输出isset()的返回值。
$a="Mod"
$b="Modifier"

var_dump(isset($a));
var_dump(isset($b));

unset($a);//删除变量a

var_dump(isset($a));
var_dump(isset($b));

$c=NULL;
var_dump(isset($c));
1
2
3
4
5
6
7
#输出演示
变量已设置
bool(true)
bool(true)
bool(false)
bool(true)
bool(flase)

file_get_contents()

file_get_contents() 把整个文件读入一个字符串中。

1
2
3
4
#实例演示
<?php
echo file_get_contens("test.txt");
?>
1
2
3
#输出演示
This is an example for test
(输出文件中的所有文本)

strcmp()

strcmp()把两个字符串以二进制方式进行比较,且该函数比较时区分大小写。

语法

strcmp(string1, string2)

返回值

如果返回值小于0,则str1小于str2;

如果返回值大于0,则str1大于str2;

如果返回值等于0,则str1等于str2;

有资料说返回值也不仅是-1和1,也可能是其他数据

1
2
3
4
5
#实例演示:
<?php
echo strmp("hello","hello"); //输出0
echo strcmp("Hello","hello"); //输出1
?>

还有个函数strcasecmp(),用法与strcmp()类似,只是不区分大小写

ereg/preg_match

ereg()preg_match()是PHP中常用的正则表达式。

紧急补课—正则表达式

在编写处理字符串的程序或者网页的时候,经常会查找符合某些复杂规则的字符串的需要,正则表达式就是用于描述这些规则的语法。

作用: 分割, 匹配, 查找, 替换

例如: 验证邮箱地址格式, 手机号码格式等等

语法

preg_match(mode, string subject, array matches);

ereg(mode,string,subject,array regs);

mode : 正则表达式

subject : 需要验证的字符串

matches/regs : 匹配后得到的结果,以数组的方式存储

返回值

返回值是falsetrue

1
2
3
4
5
6
7
8
9
#实例演示1:
<?php
if (preg_math("/php/i","I think PHP is really hard!")){
//模式分隔符后的i标记这是一个大小写不敏感的搜索
echo "查找到匹配字符串"
}else{
echo "未发现匹配字符串"
}
?>
1
2
#输出演示1:
查找到匹配字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#实例演示2:
//查找固定的某个单词
<?php
if (preg_math("/\hard\b/i","I think PHP is really hard!")){
//模式中的 \b 标记一个单词边界,所以只有独立的单词会被匹配,而不会匹配单词的部分内容
echo "查找到匹配字符串\n";
}else{
echo "未发现匹配字符串\n";
}

if (preg_math("/\hard\b/i","I think PHP is harder than others!")){
echo "查找到匹配字符串\n";
}else{
echo "未发现匹配字符串\n";
}
1
2
3
#输出演示2:
查找到匹配字符串
未发现匹配字符串
1
2
3
4
5
6
7
8
9
10
11
#实例演示3:
<?php
// 从URL中获取主机名称
preg_match('@^(?:http://)?([^/]+)@i',
"http://shmodifier.github.io", $matches);
$host = $matches[1];

// 获取主机名称的后面两部分
preg_match('/[^.]+\.[^.]+$/', $host, $matches);
echo "domain name is: {$matches[0]}\n";
?>
1
2
#输出演示3:
domain name is: github.io

乱七八糟一堆符号我一个都看不懂我好得很哇

魔法部分

警告:麻瓜请自觉退出🚫

01 strcmp绕过

1
2
3
4
5
6
7
8
9
#题目源代码:
<?php

define('FLAG', 'Midifer终将否极泰来');
if (strcmp($_GET['flag'], FLAG) == 0) {
echo "相信我:" . FLAG;
}

?>

ps:从ws那里copy来的(x

解题思路

条件是strcmp($_GET['flag'], FLAG) == 0,就是要**$_GET变量和FLAG**相等,但是我们除了严刑逼供出题人,根本就不知道FLAG是什么。

$_GET[‘flag’]意思是从url获取一个叫flag的GET参数

不要害怕!

当 strcmp() 比较出错时会返回 NULL;而返回 NULL 即为返回 0,我们只需要palyload一个非字符串变量和字符串FLAG比较就会出错并返回0。

欸,相等辣!

最终palyload?flag[]=0

02 md5绕过

1
2
3
4
5
6
7
#题目源代码:
<?php
define('FLAG', 'Midifer终将否极泰来');
if (($_GET['s1']) != $_GET['s2'] && md5($_GET['s1']) == $_GET['s2']) {
echo "相信我 :" . FLAG;
}
?>

解题思路

条件是($_GET['s1'])!=$-GET['s2'] && md5($GET[s1])==$_get['s2'],就是变量s1和s2不能相等但是他们的md5要相等

因为s2和s2不能相等,所以单纯md5加密得出的密文一定也不完全一样,这时就要利用一些歪门邪道(不是)

紧急补课—md5

md5一种密码散列函数。MD5算法的原理可简要的叙述为:MD5码以512位分组来处理输入的信息,且每一分组又被划分为16个32位子分组,经过了一系列的处理后,算法的输出由四个32位分组组成,将这四个32位分组级联后将生成一个128位散列值。

补不了我看不懂🥲

001 科学计数法绕过

字符串经过md5加密后,会生成既有数字又有字母的密文,可以利用科学计数法的语法规律反推明文。

了解到md5是不可逆的所以应该不能解码,但是可以根据加密规律找满足需要的明文

0无论乘十的几次方都仍然为零,例如'0e123456'=='0e654321'==0。所以只需要找到两个经过md5加密后以”0e“开头的字符串充当变量即可。

copy了一些加密后是0e开头的值:

  • QNKCDZO
  • 240610708
  • s878926199a
  • s155964671a
  • s214587387a
  • s214587387a
  • 0e215962017

最终playload?s1=QNKCDZO&s2=240610708

没有成功截图😣

不知道为什么自己运行的时候没成功,页面一片空白什么都没输出。

紧急补课

在php中,**’xey‘** 意为x乘10的y次方。例如:1000 == ‘1e3’。

002 数组绕过

冲浪冲到的

php中的md5函数不能用来加密数组,所以会出现结果NULL。例如md5([1,2]) == md5([3,4]) == NULL

最终playload?s1[]=1&s2[]=2

但是在我的电脑上复现会报错而不是直接null绕过。

03 extract 变量覆盖绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#题目源代码:
<?php

$flag='xxx';
extract($_GET);
if(isset($shiyan))
{
$content=trim(file_get_contents($flag));
if($shiyan==$content)
{
echo'Modifier终将否极泰来';
}
else
{
echo'Oh.no';
}
}

?>

解题思路

题目使用extract($_GET)接受请求,并将其键名和键值转换为变量名和变量值,随后进行if条件的判断。$flag是一个输入的变量,file_get_contents($flag)打开的一定是空文件,所以$content一定为空

解题关键在于令$shiyan==$content

使用GET提交参数和值,利用extract($_GET)进行覆盖,使flag和shiyan的值都为空,从而满足if条件,输出flag。

最终payload?flag=&shiyan=

04 绕过过滤的空白字符

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#题目源代码
<?php

$info = "";
$req = [];
$flag="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

ini_set("display_error", false);
error_reporting(0);

if(!isset($_GET['number'])){
header("hint:26966dc52e85af40f59b4fe73d8c323a.txt");

die("have a fun!!");

}

foreach([$_GET, $_POST] as $global_var) {
foreach($global_var as $key => $value) {
$value = trim($value);
is_string($value) && $req[$key] = addslashes($value);
}
}


function is_palindrome_number($number) {
$number = strval($number);
$i = 0;
$j = strlen($number) - 1;
while($i < $j) {
if($number[$i] !== $number[$j]) {
return false;
}
$i++;
$j--;
}
return true;
}


if(is_numeric($_REQUEST['number']))
{

$info="sorry, you cann't input a number!";

}
elseif($req['number']!=strval(intval($req['number'])))
{

$info = "number must be equal to it's integer!! ";

}
else
{

$value1 = intval($req["number"]);
$value2 = intval(strrev($req["number"]));

if($value1!=$value2){
$info="no, this is not a palindrome number!";
}
else
{

if(is_palindrome_number($req["number"])){
$info = "nice! {$value1} is a palindrome number!";
}
else
{
$info=$flag;
}
}

}

echo $info;
?>

解题思路

这个代码好长让我来认真看一下🧐

从最后看echo $info$info=$flag,我们知道如果满足前面的条件,flag会被赋值给$info并输出。

再往上找条件

要不满足条件,即is_palindrome_number($req["number"])为假。

前面代码有写is_palindrome_number函数用来判断回文数字。

要满足$value1=$value2,即intval($req["number"])=intval(strrev($req["number"])),$value反转后不能和原来相等。

strrev()函数反转字符串

is_numberic($REAUEST['NUMBER']需要为假,且$req['number']==strval(intval(eq['number'])

再看看上面

我们要在url传入一个名为number的变量

这段的意思是把我们所有的输入收集到$global_var列表里,此时列表**$global_var=[$number]。再进入下一步循环,将键名赋值给$key,变量的值赋值给$value,即$key=0,$value=$number**。

进入循环内部后,trim()函数去除变量首尾的空白字符或其他字符后需满足条件is_string($value) && $req[$key] = addslashes($value),即$value为字符串并给字符串加反斜线。

判断条件如下

综上,要传入一个字符串,字符串不能是数字还必须是整数,不是回文数字还要是回文数字😅。

悖论!烦死了不做了去暴打出题人

没关系我们可以绕过。

intval()is_numeric()函数在开始判断前,会先跳过所有空白字符,但是is_palindrome_number()不会,可以利用这个特点,在一个回文数字之前加上一个空白字符,比如\f121就是%0c121

再用%00绕过is_numeric($_REQUEST['number'])

最终playload?number=%00%0c121

这个怎么还显示代码捏

05 ereg/preg_match 正则 %00 截断

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
#题目源代码
<?php

$flag = "flag";

if (isset ($_GET['password']))
{
if (ereg ("^[a-zA-Z0-9]+$", $_GET['password']) === FALSE)
{
echo '<p>You password must be alphanumeric</p>';
}
else if (strlen($_GET['password']) < 8 && $_GET['password'] > 99999999)
{
if (strpos ($_GET['password'], '*-*') !== FALSE) //strpos — 查找字符串首次出现的位置
{
die('Flag: ' . $flag);
}
else
{
echo('<p>*-* have not been found</p>');
}
}
else
{
echo '<p>Invalid password</p>';
}
}
?>

解题思路

001 %00截断

需要满足条件ereg ("^[a-zA-Z0-9]+$", $_GET['password'])不为false,即输入的值必须有大小写字母或者数字

还需满足strlen($_GET['password']) < 8 && $_GET['password'] > 99999999,即输入值长度小于8大于99999999

最后要满足strpos ($_GET['password'], '*-*') !== FALSE,也就是输入中必须有***-***。但是这条和第一条相悖,不慌,姐已经见过大世面了,姐可以绕过它。

当ereg语句遇到%00的时候就会认为是休止符,不再往后看

可以在字符串中添加%00,在它之后加*-*,骗一下第一个判断条件。

第二个条件可以使用科学计数法,比如1e10。

最终playload?password=1e10%00*-*

本地复现没成功,我好得很哇我好的很哇😅

报错

本地复现不成功是打不倒我的,我擅长使用百度。

翻译了一下是“调用了未定义函数”。在php5.3以上的版本将不再支持eregi()和ereg()函数,看了一下的我的版本号是7.3。

问题也不是不可避免,只需要改一下格式,把ereg()改成erge_match()就行。

但我不会改😣

002 数组绕过

冲浪冲到的另外一种方法

在true和false之外还有一个返回值是null。可以利用这一特点进行绕过。

ereg() 只能处理字符串,遇到数组会返回null,而且null !== false。同时,strlen()也不能处理数组,也会返回null,null的长度小于8。

因为要输入数组,数组大于整数,所以一定会返回true。

最终playload?password=[]=1

06 sha()函数比较绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#题目源代码
<?php

$flag = "Modifer终将否极泰来";

if (isset($_GET['name']) and isset($_GET['password']))
{
if ($_GET['name'] == $_GET['password'])
echo '<p>Your password can not be your name!</p>';
else if (sha1($_GET['name']) === sha1($_GET['password']))
die('Flag: '.$flag);
else
echo '<p>Invalid password.</p>';
}
else
echo '<p>Login first!</p>';
?>

解题思路

要满足两个条件:($_GET['name'] !== $_GET['password']

sha1($_GET['name']) === sha1($_GET['password'])

就是要让输入的两个变量不相等但是sha1加密后相等。

不同的字符串经过sha1加密后一定不同,但是sha1不能加密数组,会报错返回null,如果让两个变量同时返回null就会相等辣。

最终playload?name[]=1&password[]=2

07 session 验证绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#题目源代码
<?php

$flag = "Modifier终将否极泰来";

session_start();
if (isset ($_GET['password'])) {
if ($_GET['password'] == $_SESSION['password'])
die ('Flag: '.$flag);
else
print '<p>Wrong guess.</p>';
}
mt_srand((microtime() ^ rand(1, 10000)) % rand(1, 10000) + rand(1, 10000));
?>

题目分析

要满足条件$_GET['password'] == $_SESSION['password']

$_SESSION()存储会话信息

这里session中的password需要我们自己传入,如果不传就是null,同时password也传空,二者就相等啦

最终playload?password=

08 urldecode 二次编码绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#题目源代码
<?php
if(eregi("hackerDJ",$_GET['id'])) {
echo("<p>not allowed!</p>");
exit();
}

$_GET['id'] = urldecode($_GET['id']);
if($_GET['id'] == "hackerDJ")
{
echo "<p>Access granted!</p>";
echo "<p>flag: *****************} </p>";
}
?>
题目分析

需要满足两个条件:eregi("hackerDJ",$_GET['id'])为false;经过urldecode编码后满足$_GET['id'] == "hackerDJ"

也就是说传入值不能是hackerDJ,但是传入值经过urldecode解码后要和hackerDJ相等。

所以传入时要将hackerDJ的url编码再编码一次。

最终playload?id=%2568%2561%2563%256b%2544%254a


总觉得少了点什么原来是把这个笔记忘记喽

发上来防止自己手贱删掉🦭

把万师傅的原博放这里

PHP 入门基础漏洞 | 芜风 (drun1baby.github.io)


PHP黑魔法
https://shmodifier.github.io/2023/03/01/PHP黑魔法/
作者
Modifier
发布于
2023年3月1日
许可协议