MoeCTF 2024 WriteUp Web方向(详细)
MoeCTF2024 Write UP
CTFerName:fifker
题目链接:https://ctf.xidian.edu.cn/training/10
文章内容有些是大一刚学习的时候写的,有些是后来补写的,没有仔细核验校改。如果出现有误或者语言风格不统一请见谅!
Web
1.Web渗透测试与审计入门指北

下载得到两个文件,先看看HTML。

只有一行字
看看HTML的源代码,结合PHP文件内容来看,只需要配置一下phpstudy,把这两个文件都扔到phpstudy下面的WWW里面。
然后使用http打开HTML文件。
即可获得flag
2.弗拉格之地的入口

由题目可以得知一个叫做‘爬虫’的生物,由此可以想到机器人协议。

直接修改URL,查看disallow

就可以获得flag了。
3.垫刀之路01: MoeCTF?启动!

题目告诉我们已经获得了shell权限。

那么根据习惯,我们直接查看根目录/,发现了flag的踪迹。

cat查看一下,得知需要查看环境变量:1
env

查看环境变量可得flag。
4.ImageCloud前置

?URL是一个很经典的漏洞,开放重定向漏洞,因此我们可以控制重定向的目标。

得知flag所在地。

那就直接查看即可。

获得flag。
5.ez_http
一道经典考察http请求包的题目,但是流程较长,使用Hackbar容易崩溃。(包括使用者)
想都不用想,直接点击Hit the question setter
然后就会得到回复Big 胆
因此按照提示,使用POST方法传参。
由于使用Hackbar经常没办法坚持到流程结束,所以使用burp suite抓包。
(如图显示render的界面是发送后界面)

抓包并发送至repeater,选择render

如图所示,修改请求包,发送

同理

如图所示
这里涉及到两个知识点。
第一个是同时要使用POST方法和GET方法的时候,在HTTP请求包中使用POST方法。
第二个是GET方法传输汉字需要经过URL编码。

涉及到源头source,则使用referer朔源

设置曲奇饼干

修改代理浏览器

最后一步,本地访问,获得flag
6.ProveYourLove
(此题还有个七夕限定7分)
这里flag提示说’your love is not yet fulfilled’,那就代表要按照题目所说的表白300份

正常提交一份之后会发现无法再提交了。

因此打开bp,提交表白,寻找POST请求头的Http包。

将请求包发送至爆破模块

发送300+1条表白,表白成功,获得flag
7.弗拉格之地的挑战
这一题的流程也较长,题目难度从浅到深,很有意思,好玩!

首先看到下面的一个HTML文件,直接url转跳进去,开始第一关。

我们得知第一题的考点在HTML,又提到了一个关键词,空白。
那么我们学习HTML第一课就会提到注释这种东西。
F12查看源代码。

记下flag1,并且通过url转跳到flag2

flag2,习惯性看下源代码
既然说到了HTTP,那就代表需要抓包,bp启动!

抓包,然后扔到repeater,记下flag2,进入flag3

这个要求有点经典

GET的读取方法就在url里面

然后用hackbar POST一个参数b
(很好hackbar崩溃了,最后一步用bp来完成)

把认证从用户改成管理员即可。
记下flag3,继续向着flag4前进吧。

朔源,使用referer。

正式进入flag4。

听声辩位,要回答9,但是并没有。
阅读源代码,我们发现用POST方法传输一个’method=get’就可以了

(这里插播一个发现的预期解)
将按钮8的id改为9。


收好flag4,出发去flag5啦。

很明显直接输入’I want flag’是不行的。

阅读源代码,发现一个函数content。

同flag4,直接POST一个即可。

需要看代码了,来看看什么意思。
首先需要GET和POST方法都得到一个参数moe,并且要对这个moe里面的内容进行检索。
其次是参数moe里面有flag,但是又不能有flag。
那么我们就需要知道/i 的意思是大小写不敏感。
因此如果检索到有flag原原本本这四个字母,则会die。
但是如果输入Flag,fLag,在大小写敏感的情况下会检索不出来,但是不敏感的时候可以,则会echo出答案。

准备最后一关了。

最后一步,一个eval函数就已经告诉你了,这么危险的函数怎么不用下试试?

使用代码:1
system()
或者1
echo shell_exec()
(注意,括号里的命令是字符串。)
直接看一眼根目录有什么东西,看到了flag7。

拿到flag7。
我们可以发现7个flag都是一段字母,我们可以给它拼起来。
我们发现flag7的结尾是=,那么可以推测是Base64或者Base32。
放入在线解码器,获得flag。
至此7关完结,7关环环相扣,获得难度从低到高由浅入深,又涵盖了web方向常见的题目,用故事的方式引导参赛者一步一步学习前进,出的确实很巧妙。
8.垫刀之路02: 普通的文件上传

阅读题目,只需要写一个简单的木马就行。
我们可以写一个经典的一句话木马。1
<?php @eval($_POST['muma']); ?>
导入进去就可以获得shell权限了。
使用hackbar用POST方法或者蚁剑都可以,连接。(以下使用hackbar)
导入木马。

根据上一题的经验,直接查看环境变量,获得flag。
9.垫刀之路03: 这是一个图床
由题目可知,这次只能上传图片了,那么我们就将上一题的木马后缀改为jpg。
导入的时候使用bp抓包,在http请求包中修改后缀位php。


如图所示。

后面的内容与第二题一样的,获得flag。
10.垫刀之路04: 一个文件浏览器

readme告诉你了,这些东西都没用,直接看看上一级文件。

可以看到一些其他东西,那么就继续一层一层往上爬。

找到了万恶的cfbb的藏宝点。

获得flag。
11.静态网页

直接攻击看板娘。
既然说了后端,那就尝试抓包。

抓一个,发现并不是。

抓到了下一步的php题了。


代码审计,如图所示即可获得flag。
12.垫刀之路05: 登陆网站

输入万能密码。1
'or 1='1
13.垫刀之路06: pop base mini moe
1 |
|
审计一下源代码:
通过GET方法获取参数data,然后对data进行反序列化的操作。
因为这几个PHP题目是后来补写的,这里就不详细说明各个魔术方法的作用了。根据类A的__destruct()方法可以推测出A是本道POP链子的入口点,因此:
1 | $a = new A(); |
然后为了让链子能和类B相连,考虑到__invoke($c)魔术方法,因此就需要把类A的this->$a赋值为类B。然后我们会发现:
1 | private $a = new B(); |
会报错。为什么?因为PHP的类属性的默认值只能接受字面量或常量,不能接受表达式(包括对象实例化)。
因此你可以给a赋值一个null、布尔值、整数、浮点数、字符串、数组。但是就是不能使用函数、方法调用或new操作符,因为这些表达式在编译时无法求值。
因此我们只能在运行的时候动态赋值:
1 | function __construct() { |
这样在调用__destruct()方法的时候:
1 | function __destruct() { |
a作为一个类B的对象就会被当作函数调用从而触发__invoke()方法:
1 | function __invoke($c) { |
类A的evil属性就会作为类B中的c属性被当作参数传入这个“函数b”。那么我们只需要将b设置为system函数,就可以实现RCE了!
最后的exp(直接在源代码上面审计,修改是一个很好的方法,因此我只在源代码基础上修改了需要改的部分):
1 |
|
拿到flag。

后知后觉:这一题的class B根本用不上。我们重新审计代码:
1 | function __destruct() { |
我们会发现这里我们已经满足了RCE的全部条件。函数$s和参数$this->evil的内容我们全部可控。因此这样即可:
1 | private $evil = "cat /flag"; |
可能是为了考上面那个知识点吧。这里也告诉我们一道题未必所有类都要用上(有可能出题人有失误)。
14.垫刀之路07: 泄漏的密码

URL里直接跳转/console。

习惯性从根目录获得flag,但是被预判了。

放弃绝对路径,直接cat flag。
15.勇闯铜人阵
脚本题,写个脚本即可,脚本如下:
写出脚本,运行。

获得flag。
16.电院_Backend

dirsearch扫一下发现有robots.txt,发现admin路由。
进入后就是登陆界面了,看上去就是Sql注入入口。题目给了源码,看一眼:

1 |
|
这一看就是一个Sql注入,注入的位置在邮箱这里。来看看这里面的逻辑:
1 | preg_match("/[a-zA-Z0-9]+@[a-zA-Z0-9]+\\.[a-zA-Z0-9]+/", $email) |
这是一个正则表达式,对邮箱的格式进行了一个限制,相信会看这篇文章的同学应该还没有接触过正则表达式。所以我们先来看看这个这个表达式是什么意思:
| 符号 | 含义 | 说明 |
|---|---|---|
/ |
定界符 | 正则表达式的开始和结束 |
[a-zA-Z0-9] |
字符类 | 匹配任意单个字母(大小写)或数字 |
+ |
量词 | 匹配前面的字符类至少1次(1次或多次) |
@ |
字面量 | 匹配字符 @ |
[a-zA-Z0-9]+ |
同上 | 匹配域名部分(@后面到点之前) |
\\. |
转义的点 | 匹配字面量点号 .(因为点在正则中是特殊字符) |
[a-zA-Z0-9]+ |
同上 | 匹配顶级域名(com、cn等) |
因此,只要满足这个要求的邮箱都可以通过,比如:
| 邮箱 | 拆解 |
|---|---|
| abc@def.com | abc + @ + def + . + com |
| a1@b2.c3 | a1 + @ + b2 + . + c3 |
| 123@456.789 | 纯数字也OK |
| admin@localhost.com | 正常邮箱 |
| 1@1.1 | 最短情况(1个字符@1个字符.1个字符) |
但是问题来了!这个正则表达式是没有锚定边界的。
补充说明:什么是锚定边界?
锚定边界就是使用^和$来限制匹配的位置。
当前代码是这样的:
1 | "/[a-zA-Z0-9]+@[a-zA-Z0-9]+\\.[a-zA-Z0-9]+/" |
意思是在字符串的任意位置找到这个模式就算匹配成功,可以匹配字符串的开头、中间或结尾。
这就意味着这些全部能匹配成功!你可以在邮箱前面后面塞入任何你想塞入的代码。
| 邮箱 | 拆解 |
|---|---|
| xxx abc@def.com xxx | 前后有字符 |
| 1@1.1’ or 1=1 | 后面拼接SQL |
| 恶意代码1@1.1恶意代码 | 前后都可以 |
我们来对比一下有锚定边界的安全写法:
1 | "/^[a-zA-Z0-9]+@[a-zA-Z0-9]+\\.[a-zA-Z0-9]+$/" |
| 符号 | 含义 |
|---|---|
^ |
匹配字符串开头 |
$ |
匹配字符串结尾 |
这意味着整个字符串必须完全符合这个模式,不能多任何字符。
| 邮箱 | 拆解 |
|---|---|
| abc@def.com | 纯邮箱 |
| xxx abc@def.com | 不行,开头多了字符 |
| abc@def.com xxx | 不行,结尾多了字符 |
| 1@1.1’ or 1=1 | 不行,后面拼接SQL |
因此,这个没有锚定边界的正则就可以直接无视,进行Sql注入。
然后是对于or的限制:
1 | preg_match("/or/i", $email) |
这里的参数i意味着大小写不敏感,也就是or、Or、OR、oR都不可以通过。这其实很简单,使用||就可以了…
所以最后的exp:
1 | 1@1.1' || 1=1 # |
