1. 任务
1.1.1.1.1.1. 知识部分
- rce看【之前的笔记?】
- php的知识点学习继续
- jwt token好像是比赛的题目考察内容,我看看
- php伪协议
1.1.1.1.1.2. 题目
- 参加iscc比赛【五一】
- rce题目
1.1.1.1.1.3. 环境配置
- 把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";
$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,邮箱,名称
#### 2.1.7. php 时间
`string date ( string $format [, int $timestamp ] )`
|-----------|-----------------------|
| 参数 | 描述 |
| format | 必需。规定时间戳的格式。 |
| timestamp | 可选。规定时间戳。默认是当前的日期和时间。 |
date() 函数的第一个必需参数 *format* 规定了如何格式化日期/时间。

|---------------|----------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------|
| `format` 字符 | 说明 | 返回值例子 |
| *日* | --- | --- |
| *d* | 月份中的第几天,有前导零的 2 位数字 | *01* 到 *31* |
| *D* | 星期中的第几天,文本表示,3 个字母 | *Mon* 到 *Sun* |
| *j* | 月份中的第几天,没有前导零 | *1* 到 *31* |
| *l*("L"的小写字母) | 星期几,完整的文本格式 | *Sunday* 到 *Saturday* |
| *N* | ISO-8601 格式数字表示的星期中的第几天(PHP 5.1.0 新加) | *1* (表示星期一)到 *7*(表示星期天) |
| *S* | 每月天数后面的英文后缀,2 个字符 | *st* ,*nd* ,*rd* 或者 *th* 。可以和 *j* 一起用 |
| *w* | 星期中的第几天,数字表示 | *0* (表示星期天)到 *6*(表示星期六) |
| *z* | 年份中的第几天 | *0* 到 *365* |
| *星期* | --- | --- |
| *W* | ISO-8601 格式年份中的第几周,每周从星期一开始(PHP 4.1.0 新加的) | 例如:*42*(当年的第 42 周) |
| *月* | --- | --- |
| *F* | 月份,完整的文本格式,例如 January 或者 March | *January* 到 *December* |
| *m* | 数字表示的月份,有前导零 | *01* 到 *12* |
| *M* | 三个字母缩写表示的月份 | *Jan* 到 *Dec* |
| *n* | 数字表示的月份,没有前导零 | *1* 到 *12* |
| *t* | 给定月份所应有的天数 | *28* 到 *31* |
| *年* | --- | --- |
| *L* | 是否为闰年 | 如果是闰年为 *1* ,否则为 *0* |
| *o* | ISO-8601 格式年份数字。这和 *Y* 的值相同,只除了如果 ISO 的星期数(*W*)属于前一年或下一年,则用那一年。(PHP 5.1.0 新加) | Examples: *1999* or *2003* |
| *Y* | 4 位数字完整表示的年份 | 例如:*1999* 或 *2003* |
| *y* | 2 位数字表示的年份 | 例如:*99* 或 *03* |
| *时间* | --- | --- |
| *a* | 小写的上午和下午值 | *am* 或 *pm* |
| *A* | 大写的上午和下午值 | *AM* 或 *PM* |
| *B* | Swatch Internet 标准时 | *000* 到 *999* |
| *g* | 小时,12 小时格式,没有前导零 | *1* 到 *12* |
| *G* | 小时,24 小时格式,没有前导零 | *0* 到 *23* |
| *h* | 小时,12 小时格式,有前导零 | *01* 到 *12* |
| *H* | 小时,24 小时格式,有前导零 | *00* 到 *23* |
| *i* | 有前导零的分钟数 | *00* 到 *59*\> |
| *s* | 秒数,有前导零 | *00* 到 *59*\> |
| *u* | 毫秒 (PHP 5.2.2 新加)。需要注意的是 **date()** 函数总是返回 *000000* 因为它只接受 integer 参数, 而 DateTime::format() 才支持毫秒。 | 示例: *654321* |
| *时区* | --- | --- |
| *e* | 时区标识(PHP 5.1.0 新加) | 例如:*UTC* ,*GMT* ,*Atlantic/Azores* |
| *I* | 是否为夏令时 | 如果是夏令时为 *1* ,否则为 *0* |
| *O* | 与格林威治时间相差的小时数 | 例如:*+0200* |
| *P* | 与格林威治时间(GMT)的差别,小时和分钟之间有冒号分隔(PHP 5.1.3 新加) | 例如:*+02:00* |
| *T* | 本机所在的时区 | 例如:*EST* ,*MDT*(【译者注】在 Windows 下为完整文本格式,例如"Eastern Standard Time",中文版会显示"中国标准时间")。 |
| *Z* | 时差偏移量的秒数。UTC 西边的时区偏移量总是负的,UTC 东边的时区偏移量总是正的。 | *-43200* 到 *43200* |
| *完整的日期/时间* | --- | --- |
| *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. 验证输入
上面的实例有一个通过 "GET" 方法传送的输入变量 (email):
1. 检测是否存在 "GET" 类型的 "email" 输入变量
2. 如果存在输入变量,检测它是否是有效的 e-mail 地址

##### 2.1.8.5. 净化输入
上面的实例有一个通过 "GET" 方法传送的输入变量 (url):
1. 检测是否存在 "GET" 类型的 "url" 输入变量
2. 如果存在此输入变量,对其进行净化(删除非法字符),并将其存储在 $url 变量中

##### 2.1.8.6. 过滤多个输入
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 之间。
");
}
elseif(!$result["email"])
{
echo("E-Mail 不合法
");
}
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?
查博客
* [token与JWT详细介绍_jwt token-CSDN博客](https://blog.csdn.net/m0_56750901/article/details/125493359?ops_request_misc=elastic_search_misc&request_id=3fb593d6064a4909eba9bb42df5ea3a4&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-125493359-null-null.142^v102^pc_search_result_base3&utm_term=jwt%20token&spm=1018.2226.3001.4187 "token与JWT详细介绍_jwt token-CSDN博客")
* [基于jwt的token验证、原理及流程_jwt token-CSDN博客](https://blog.csdn.net/z_ssyy/article/details/130971321?ops_request_misc=elastic_search_misc&request_id=3fb593d6064a4909eba9bb42df5ea3a4&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_click~default-2-130971321-null-null.142^v102^pc_search_result_base3&utm_term=jwt%20token&spm=1018.2226.3001.4187 "基于jwt的token验证、原理及流程_jwt token-CSDN博客")
* [一篇了解什么是Token、什么是Jwt_jwt token-CSDN博客](https://blog.csdn.net/weixin_44147535/article/details/135167800?ops_request_misc=&request_id=&biz_id=102&utm_term=jwt%20token&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-0-135167800.142^v102^pc_search_result_base3&spm=1018.2226.3001.4187 "一篇了解什么是Token、什么是Jwt_jwt token-CSDN博客")
* [JWT(JSON Web Token)全维度渗透测试实战与防御体系构建_cve-2020-26160-CSDN博客](https://blog.csdn.net/weixin_42376192/article/details/157578373?ops_request_misc=&request_id=&biz_id=102&utm_term=jwt%20token%E5%AE%9E%E6%88%98&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-1-157578373.142^v102^pc_search_result_base3&spm=1018.2226.3001.4187 "JWT(JSON Web Token)全维度渗透测试实战与防御体系构建_cve-2020-26160-CSDN博客")
应该可能使用的工具
[JWT在线工具 - kjson在线工具](https://www.kjson.com/jwt/ "JWT在线工具 - kjson在线工具")
[在线JWT Token生成](https://www.lddgo.net/encrypt/jwt-generate "在线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.php``beautify.php`发现这两个地方也是不可访问,可能是没写完整


##### 3.2.2.4. 第四步,根据`preview.php`要求,查找`preview.php`源代码
使用**伪协议** ,去看`preview.php`的源代码,因为之前只看见`beautify.php`的源代码

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

找到源代码了
\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了,
1.
declare(strict_types=1);
开启PHP严格类型模式,强制函数参数和返回值必须匹配声明的类型,
避免隐式类型转换导致的安全问题,是现代PHP安全编码的标准写法。
1.
header('Content-Type: text/plain; charset=utf-8');
header('X-Powered-By: JSON Preview');
第一行:强制响应内容为纯文本UTF8编码,避免乱码
第二行:自定义响应头,模拟成JSON预览工具的后端接口
1.
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`\[伪协议绕过
-
漏洞点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解码

