利用Elixir中的原子特性 + 错误消息泄露 -- Atom Bomb

发布于:2025-05-09 ⋅ 阅读:(18) ⋅ 点赞:(0)

题目信息: This new atom bomb early warning system is quite strange…

题目使用 elixir 语言

一开始,我们会访问 /page.html

<!DOCTYPE html>
<!-- 设定文档语言为英语 -->
<html lang="en">
<head>
    <!-- 设定字符编码为UTF-8 -->
    <meta charset="UTF-8">
    <!-- 适配不同屏幕尺寸 -->
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- 网页标题 -->
    <title>Atom Bomb Alert System</title>
    <style>
        body {
            /* 设置字体为Arial,若不可用则使用无衬线字体 */
            font-family: Arial, sans-serif;
            /* 深灰色背景 */
            background-color: #1a1a1a; 
            /* 浅灰色文字 */
            color: #e0e0e0; 
            /* 外边距为0 */
            margin: 0;
            /* 内边距为20px */
            padding: 20px;
            /* 使用弹性布局 */
            display: flex;
            /* 垂直排列子元素 */
            flex-direction: column;
            /* 水平居中内容 */
            align-items: center; 
        }

        button {
            /* 红色背景 */
            background-color: #d9534f; 
            /* 白色文字 */
            color: white;
            /* 无边框 */
            border: none;
            /* 内边距 */
            padding: 10px 20px;
            /* 圆角边框 */
            border-radius: 5px;
            /* 字体大小 */
            font-size: 16px;
            /* 鼠标悬停时显示手型光标 */
            cursor: pointer;
            /* 按钮下方间距 */
            margin-bottom: 20px; 
            /* 背景颜色过渡效果 */
            transition: background-color 0.3s; 
        }

        button:hover {
            /* 鼠标悬停时更深的红色 */
            background-color: #c9302c; 
        }

        div {
            /* 宽度占容器的100% */
            width: 100%; 
            /* 最大宽度为400px */
            max-width: 400px; 
            /* 元素下方间距 */
            margin-bottom: 20px; 
            /* 更深的卡片背景色 */
            background-color: #2a2a2a; 
            /* 内边距 */
            padding: 15px;
            /* 深色边框 */
            border: 1px solid #444; 
            /* 圆角边框 */
            border-radius: 5px;
            /* 深色阴影 */
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3); 
        }

        h2 {
            /* 红色标题,强调紧急性 */
            color: #d9534f; 
            /* 标题居中 */
            text-align: center;
        }

        h6 {
            /* 浅灰色副标题 */
            color: #bbb; 
            /* 上方间距 */
            margin-top: 20px;
            /* 下方间距 */
            margin-bottom: 5px;
        }

        p {
            /* 段落深色背景 */
            background-color: #333; 
            /* 段落深色边框 */
            border: 1px solid #555; 
            /* 段落内边距 */
            padding: 10px;
            /* 段落圆角边框 */
            border-radius: 5px;
            /* 段落外边距 */
            margin: 5px 0 20px 0;
        }

        img {
            /* 图片最大宽度为容器的100% */
            max-width: 100%;
            /* 图片高度自适应 */
            height: auto;
            /* 图片深色边框 */
            border: 1px solid #444; 
            /* 图片圆角边框 */
            border-radius: 5px;
        }

        #danger {
            /* 字体加粗 */
            font-weight: bold;
            /* 字体大小为1.2倍 */
            font-size: 1.2em;
            /* 红色文字 */
            color: #d9534f; 
            /* 文字居中 */
            text-align: center;
        }

        /* 当屏幕宽度小于等于600px时的样式 */
        @media (max-width: 600px) {
            body {
                /* 减小内边距 */
                padding: 10px;
            }

            button {
                /* 按钮宽度占满容器 */
                width: 100%;
            }
        }
    </style>
</head>
<body>
    <!-- 主标题 -->
    <h2>Welcome to Atom Bomb Alert System</h2>

    <!-- 点击按钮触发检查炸弹警报的函数 -->
    <button onclick="check_alert()">Check for bomb alert</button>

    <div>
        <!-- 炸弹详情标题 -->
        <h2>Bomb Details</h2>
        <!-- 炸弹位置副标题 -->
        <h6>Bomb Location</h6>
        <!-- 用于显示炸弹位置的段落 -->
        <p id="location"></p>
        <!-- 炸弹高度副标题 -->
        <h6>Bomb Altitude</h6>
        <!-- 用于显示炸弹高度的段落 -->
        <p id="altitude"></p>
        <!-- 炸弹威力副标题 -->
        <h6>Bomb Power</h6>
        <!-- 用于显示炸弹威力的段落 -->
        <p id="power"></p>
    </div>

    <div>
        <!-- 危险评估标题 -->
        <h2>Danger Assessment</h2>
        <!-- 用于显示危险评估信息的段落 -->
        <p id="danger"></p>

        <!-- 用于显示原子炸弹爆炸图片的元素 -->
        <img id="explosion" alt="Atom Bomb Explosion">
    </div>
</body>
</html>

<script>
/**
 * 从服务器获取炸弹信息
 * @returns {Promise<Object|null>} 包含炸弹信息的对象,如果出错或响应失败则返回null
 */
async function get_bomb() {
    try {
        // 发送请求获取炸弹信息
        const responce = await fetch("/atom_bomb");

        if (responce.ok) {
            // 若响应成功,解析响应为JSON格式并返回
            return await responce.json();
        } else {
            // 若响应失败,返回null
            return null;
        }
    } catch (error) {
        // 捕获并打印错误信息
        console.error(error.message);
        // 出错时返回null
        return null;
    }
}

/**
 * 检查炸弹的危险程度
 * @param {Object} bomb - 包含炸弹信息的对象
 * @returns {Promise<string|null>} 危险评估信息,如果出错或响应失败则返回null
 */
async function check_bomb_danger(bomb) {
    // 将高度转换为特定格式(此处代码可能有误,推测是注释错误,原意可能不是转换为原子)
    bomb.altitude = ":" + bomb.altitude;

    // 构建请求体
    payload = {
        impact: {
            bomb: bomb
        }
    };

    try {
        // 发送POST请求检查炸弹危险程度
        const responce = await fetch("/bomb_impacts", {
            method: "POST",
            body: JSON.stringify(payload),
            headers: {
                "Content-Type": "application/json",
            },
        });

        if (responce.ok) {
            // 若响应成功,解析响应并返回危险评估信息
            return (await responce.json()).message;
        } else {
            // 若响应失败,返回null
            return null;
        }
    } catch (error) {
        // 捕获并打印错误信息
        console.error(error.message);
        // 出错时返回null
        return null;
    }
}

/**
 * 检查炸弹警报并更新页面信息
 */
async function check_alert() {
    // 获取炸弹信息
    const bomb = await get_bomb();

    // 获取用于显示炸弹位置、高度和威力的元素
    const location = document.getElementById("location");
    const altitude = document.getElementById("altitude");
    const power = document.getElementById("power");

    // 更新页面上的炸弹位置、高度和威力信息
    location.innerHTML = bomb.location;
    altitude.innerHTML = bomb.altitude;
    power.innerHTML = bomb.power;

    // 获取用于显示爆炸图片的元素
    const explosion = document.getElementById("explosion");
    // 更新爆炸图片的源地址
    explosion.src = `/images/atom${bomb.explosion_type}.png`;

    // 获取炸弹危险评估信息
    const message = await check_bomb_danger(bomb);

    // 获取用于显示危险评估信息的元素
    const danger = document.getElementById("danger");
    // 更新页面上的危险评估信息
    danger.innerHTML = message;
}

// 页面加载时自动检查炸弹警报
check_alert();
</script>
// 定义一个名为 AtomBomb.Router 的模块,用于处理路由逻辑
defmodule AtomBomb.Router do
  // 使用 Phoenix.Router 模块,并禁用助手功能
  use Phoenix.Router, helpers: false

  // 导入 Plug.Conn 模块,用于处理连接相关操作
  import Plug.Conn
  // 导入 Phoenix.Controller 模块,用于处理控制器相关操作
  import Phoenix.Controller

  // 定义一个名为 :browser 的管道,用于处理浏览器请求
  pipeline :browser do
    // 配置该管道接受的请求格式为 HTML
    plug :accepts, ["html"]
    // 设置安全的浏览器头信息
    plug :put_secure_browser_headers
  end

  // 定义一个名为 :api 的管道,用于处理 API 请求
  pipeline :api do
    // 配置该管道接受的请求格式为 JSON
    plug :accepts, ["json"]
  end

  // 定义一个路由作用域,所有路由路径都以根路径 "/" 开头,控制器命名空间为 AtomBomb
  scope "/", AtomBomb do
    // 将该作用域下的请求通过 :browser 管道进行处理
    pipe_through :browser

    // 定义一个 GET 请求路由,当访问根路径 "/" 时,调用 PageController 模块的 :home 动作
    get "/", PageController, :home
  end

  // 定义另一个路由作用域,所有路由路径都以根路径 "/" 开头,控制器命名空间为 AtomBomb
  scope "/", AtomBomb do
    // 将该作用域下的请求通过 :api 管道进行处理
    pipe_through :api

    // 定义一个 GET 请求路由,当访问 "/atom_bomb" 路径时,调用 PageController 模块的 :get_atom_bomb 动作
    get "/atom_bomb", PageController, :get_atom_bomb
    // 定义一个 POST 请求路由,当访问 "/bomb_impacts" 路径时,调用 PageController 模块的 :get_bomb_impacts 动作
    post "/bomb_impacts", PageController, :get_bomb_impacts
  end
end

似乎只有此处存在输入

POST /bomb_impacts HTTP/2
Host: atom-bomb.atreides.b01lersc.tf
Content-Length: 99
Sec-Ch-Ua-Platform: "Windows"
Accept-Language: zh-CN,zh;q=0.9
Sec-Ch-Ua: "Not:A-Brand";v="24", "Chromium";v="134"
Content-Type: application/json
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Accept: */*
Origin: https://atom-bomb.atreides.b01lersc.tf
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://atom-bomb.atreides.b01lersc.tf/page.html
Accept-Encoding: gzip, deflate, br
Priority: u=1, i

{"impact":{"bomb":{"location":"idaho","power":1026,"altitude":":low_altitude","explosion_type":7}}}

继续分析其后端逻辑

  // 定义一个名为 get_bomb_impacts 的控制器动作,用于获取炸弹影响信息
  // conn: 连接结构体,包含请求和响应的相关信息
  // params: 表示请求参数
  def get_bomb_impacts(conn, params) do
    // 调用 AtomBomb.atomizer 函数对请求参数进行处理
    params = AtomBomb.atomizer(params)

    // 调用 AtomBomb.calculate_bomb_danger_level 函数计算炸弹的危险等级,并将结果赋值给 danger_message 变量
    danger_message = AtomBomb.calculate_bomb_danger_level(params.impact.bomb)

    // 渲染名为 :danger_level 的视图模板,并传递危险等级信息作为 :danger_message 参数
    render(conn, :danger_level, danger_message: danger_message)
  @doc """
  Converts params to atoms
  """
  # 定义一个函数 atomizer,用于将映射类型的参数中的键转换为原子
  def atomizer(params) when is_map(params) do
    # 遍历映射中的每个键值对
    Enum.map(params, fn {key, val} -> 
      # 根据 string_to_atom 函数的结果进行模式匹配
      case string_to_atom(key) do
        {:ok, key} -> 
          # 如果转换成功,递归调用 atomizer 处理值,并返回新的键值对
          {key, atomizer(val)}
        :error -> 
          # 如果转换失败,返回 nil
          nil
      end
    end)
    |> Enum.filter(fn val -> val != nil end)
    |> Map.new
  end

  # 定义一个函数 atomizer,用于处理列表类型的参数,递归调用 atomizer 处理列表中的每个元素
  def atomizer(params) when is_list(params) do
    Enum.map(params, &atomizer/1)
  end

  # 定义一个函数 atomizer,用于处理二进制类型的参数
  def atomizer(params) when is_binary(params) do
    # 检查字符串是否以 : 开头
    if String.at(params, 0) == ":" do
      # convert string to atom if it starts with :
      # 移除字符串开头的 :
      atom_string = String.slice(params, 1..-1//1)

      # 根据 string_to_atom 函数的结果进行模式匹配
      case string_to_atom(atom_string) do
        {:ok, val} -> 
          # 如果转换成功,返回原子
          val
        :error -> 
          # 如果转换失败,返回 nil
          nil
      end
    else
      # 如果不以 : 开头,直接返回原字符串
      params
    end
  end

  # 定义一个函数 atomizer,用于处理其他类型的参数,直接返回原参数
  # any other value is left as is
  def atomizer(params) do
    params
  end
  @doc """
  Calculates the danger level of the atom bomb for the given location
  """
  # 定义一个函数 calculate_bomb_danger_level,根据炸弹信息计算炸弹的危险等级
  def calculate_bomb_danger_level(bomb) do
    # 根据炸弹的高度确定缩放系数
    scaling = case bomb.altitude do
      :underground -> 0.05
      :surface -> 1.5
      :low_altitude -> 3.0
      :high_altitude -> 1.2
      :space -> 0.03
    end

    # 计算炸弹的实际威力
    power = scaling * bomb.power

    # 根据实际威力判断危险等级
    cond do
      power < 200.0 -> "there is not much danger"
      power < 400.0 -> "you might get cancer"
      power < 800.0 -> "you should hide underground"
      power < 1300.0 -> "your house will be blown away"
      true -> "you might be cooked"
    end
  end

我们的目标应该是执行此处函数

  # 定义一个函数 bomb,尝试读取 flag.txt 文件的内容,并返回包含炸弹信息的字符串
  def bomb() do
    # 尝试读取 flag.txt 文件
    flag = case File.read("flag.txt") do
      {:ok, flag} -> 
        # 如果读取成功,返回文件内容
        flag
      {:error, _} -> 
        # 如果读取失败,返回默认值
        "bctf{REDACTED}"
    end

    "The atom bomb detonated, and left in the crater there is a chunk of metal inscribed with #{flag}"
  end

根本搭不起来调试环境进,放弃 -------------------------------------------------------------------------------------------------------------------------------------

赛后

b01lers-ctf-2025-public/src/web/atombomb/solve at main · b01lers/b01lers-ctf-2025-public · GitHub

在 Elixir 中,原子(Atom) 是一种基本数据类型,用于表示固定值,其名称直接作为自身的值。原子是不可变的、常量,且通常用于代码中的标识符、状态标记或模式匹配。

核心特性:

  1. 名称即值
    原子的值就是它的名字,例如 :ok:error:hello。不需要额外的赋值操作。

  2. 常量且高效
    原子在内存中以唯一的形式存储(通过原子表),多次使用同一个原子不会重复占用内存。例如,无论使用多少次 :ok,内存中只有一份。

  3. 语法形式

    • 简单原子:以冒号开头,后接小写字母、数字、下划线或 @,例如 :ok:status_code
    • 带特殊字符的原子:用双引号包裹,例如 :"hello world!":"123@email.com"

常见用途:

  1. 模式匹配与函数返回值

    case File.read("file.txt") do
      {:ok, content} -> IO.puts("成功读取:#{content}")
      {:error, reason} -> IO.puts("失败原因:#{reason}")
    end
    

    函数常用 :ok/:error 表示操作结果。

  2. 作为标识符

    用于标识选项或配置,例如:

    String.split("a,b,c", ",", trim: true)
    # `trim: true` 中的 `:trim` 是原子
    
  3. 模块名称
    模块名本质是原子。例如 IO.puts/1 中的 IO 是原子 Elixir.IO 的语法糖:

    :"Elixir.IO".puts("Hello")  # 等同于 IO.puts("Hello")
    

在 Elixir 中,原子可以直接或间接用于调用函数

漏洞的核心逻辑

def atomizer(params) when is_map(params) do
  Enum.map(params, fn {key, val} -> case string_to_atom(key) do
    {:ok, key} -> {key, atomizer(val)}
    :error -> nil
  end
  end)
  |> Enum.filter(fn val -> val != nil end)
  |> Map.new
end

def atomizer(params) when is_list(params) do
  Enum.map(params, &atomizer/1)
end

def atomizer(params) when is_binary(params) do
  if String.at(params, 0) == ":" do
    # convert string to atom if it starts with :
    # remove leading :
    atom_string = String.slice(params, 1..-1//1)

    case string_to_atom(atom_string) do
      {:ok, val} -> val
      :error -> nil
    end
  else
    params
  end
end

# any other value is left as is
def atomizer(params) do
  params
end
  1. 服务器如何处理请求

    • 当你发送一个JSON请求到 /bomb_impacts 接口时,服务器会调用 AtomBomb.atomizer 函数处理参数。
    • 这个函数会将参数中的键(key)和以冒号开头的值(value)转换为原子(比如 ":apple":apple)。
  2. 危险的转换

    • 如果你发送一个参数值为 ":Elixir.AtomBomb",它会被转换为原子 :Elixir.AtomBomb
    • 在Elixir中,Module.function() 本质是调用原子 :Elixir.Modulefunction 方法。
    • 所以 params.impact.bomb 会变成调用 :Elixir.AtomBomb.bomb() 函数,而这个函数直接返回了包含flag的字符串!
  3. 触发错误泄露flag

    • 服务器后续代码试图访问 bomb.altitude(认为 bomb 是一个map)。
    • 但实际上 bomb 此时是一个字符串(flag就在这个字符串里),访问不存在的字段会报错。
    • 服务器的错误处理直接把错误信息返回给用户,于是你就能看到flag了!

攻击步骤

  1. 构造一个特殊的JSON

    {"impact": ":Elixir.AtomBomb"}
    
    • 这里的 ":Elixir.AtomBomb" 会被服务器转换为原子 :Elixir.AtomBomb
  2. 发送这个JSON到服务器

    curl -X POST http://localhost:6888/bomb_impacts \
         -H "Content-Type: application/json" \
         --data '{"impact": ":Elixir.AtomBomb"}'
    
  3. 服务器处理过程

  # 定义 get_bomb_impacts 函数,处理获取炸弹影响信息的请求
  def get_bomb_impacts(conn, params) do
    # 调用 AtomBomb.atomizer 函数处理传入的参数
    params = AtomBomb.atomizer(params)

    # 调用 AtomBomb.calculate_bomb_danger_level 函数计算炸弹的危险等级,并获取危险信息
    danger_message = AtomBomb.calculate_bomb_danger_level(params.impact.bomb)

    # 渲染 :danger_level 视图并传递危险信息
    render(conn, :danger_level, danger_message: danger_message)
  end
  • 参数处理 → 将 impact 转换为 :Elixir.AtomBomb
  • 试图调用 :Elixir.AtomBomb.bomb() → 返回包含flag的字符串
  • 后续代码访问 bomb.altitude 失败 → 报错信息中包含这个字符串
  1. 最终结果
    服务器返回的错误信息中会直接显示:
    "The atom bomb detonated...bctf{n0w_w3_ar3_a1l_d3ad_:(_8cd12c17102ac269}"
    

类比理解

假设有一个自动售货机:

  1. 正常操作:投入硬币 → 选择饮料(比如输入 {"drink": "cola"}
  2. 漏洞利用:输入一个特殊指令 {"drink": ":giveMeMoney"}
  3. 售货机错误地执行了内部函数 :giveMeMoney() → 直接吐钱

网站公告

今日签到

点亮在社区的每一天
去签到