提取React前端路由

提取React前端路由

写了个脚本,用于提取使用React和react-router模块编写的页面的前端路由。脚本在Github上。

脚本执行步骤

  1. 找到React Container:翻阅React源码,用于存储Container的属性名在不同React版本下有些不同:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    https://github.com/facebook/react/blob/v16.14.0/packages/react-dom/src/client/ReactDOMComponentTree.js#L39 :
    const internalInstanceKey = '__reactFiber$' + randomKey;
    const internalContainerInstanceKey = '__reactContainer$' + randomKey;

    https://github.com/facebook/react/blob/v16.13.1/packages/react-dom/src/client/ReactDOMComponentTree.js#L21 :
    const internalInstanceKey = '__reactInternalInstance$' + randomKey;
    const internalContainerInstanceKey = '__reactContainere$' + randomKey;

    https://github.com/facebook/react/blob/v16.9.0/packages/react-dom/src/client/ReactDOM.js#L556 :
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(xx)
  2. 递归遍历Container的属性,识别Routes组件或RouterProvider组件:

    • Routes组件里包裹一个或多个Route组件,而Route组件的属性见源码 (React Route 版本6), React Route 版本5的在这里,这两个版本的Route组件属性差别不大,通常就用到path属性和指定渲染组件的属性,elementcomponentrender
    • RouterProvider组件的属性见源码 ,这个组件是从react router 6版本开始支持的。
  3. 提取路由里的路径信息,并处理嵌套路由的情况

测试

根据React Route官方文档编写一个标准的、用于测试的前端项目:https://github.com/Ovi3/react-router-test 。 再全网搜索基于React的前端,去测试,修改,尽量适配、兼容多种情况。

还是会有些缺陷:

  1. 该脚本只处理基于react-router-dom、react-router模块的路由, 不处理其它的,如BGmi使用的 @generouted/react-router 库, 一些站点(fofa query:title="DC Base")使用 @tanstack/react-location 模块等。
  2. 暂未考虑一个页面里存在多个Container的情况
  3. 子路由Routes组件通过动态返回(return)时,获取不到该子路由信息。 (当页面渲染了该子路由组件是可以获取,该脚本未实现。 或者可通过其它方式获取?)

从提取结果上看有时不如使用linkfinder直接正则匹配, 不过提取前端路由的优点是知道这个路径是前端路由,知道要用浏览器去访问,去渲染页面。 且知道是用url路径还是hash路径去访问。换个角度说,在burpsuite里爆破linkfinder匹配出来路径很多都响应同一个html页面时,这些路径可能就是前端路由,可以尝试用浏览器去访问看看。

其它

看了几天React跟React Router,有时还不如直接运行一个递归遍历全局对象的js脚本,直接打印出可能是url路径的字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 广度优先算法
function traverseObject(obj, prefix = '', maxDepth = 32) {
let pathes = [];
let queue = [{ obj, prefix, depth: 0 }];
let visited = new WeakSet();

while (queue.length > 0) {
let { obj, prefix, depth } = queue.shift();

if (depth > maxDepth || visited.has(obj)) {
continue;
}

visited.add(obj);

for (let prop in obj) {
let newPrefix = prefix.length > 0 ? prefix + "." + prop : prop;

try {
if (['parent', 'window', 'top', 'self', 'parentNode', 'parentElement', 'ownerDocument', 'ownerElement',
'previousElementSibling', 'previousSibling', 'offsetParent',
'_parentVnode', '$parent', '_renderProxy', // vue
'return', 'containerInfo', '_debugOwner', '_owner' // react
].includes(prop)) {
continue;
}

if (typeof obj[prop] === 'object' && obj[prop] !== null) {
queue.push({ obj: obj[prop], prefix: newPrefix, depth: depth + 1 });
} else if (typeof obj[prop] === 'string' && obj[prop].length > 0) {
if (obj[prop].indexOf(" ") === -1 && (obj[prop].indexOf("/") === 0 || newPrefix.indexOf("route") !== -1 || newPrefix.indexOf("path") !== -1)) {
console.log(newPrefix + ": " + obj[prop]);
pathes.push(obj[prop]);
}
}
} catch (e) {
console.log('访问 ' + newPrefix + ' 时错误: ' + e);
}
}
}

pathes = pathes.filter(function(value, index, self) {
return self.indexOf(value) === index;
});

return pathes;
}

console.table(traverseObject(document));

参考


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!