Skip to content

Webpack 渗透思路

BX

Webpack 渗透思路

目前了解到的就是这些 Webpack 是一个现代 JavaScript 应用程序的静态模块打包器。当 webpack 处理应用程序时,它会在内部构建一个依赖关系图,映射项目需要的每个模块,然后生成一个或多个 bundle。

img

所以说整体思路就是

“识别特征—还原源码—定位关键逻辑—安全分析”

如何分辨 webpack 打包网站

fofa 语句

body="chunk.js" || body="runtime" || body="
__webpack_require__
"
body="/static/js/" && body=".chunk.js"

典型的案例如下

<!-- webpack 打包后的典型 HTML 结构 -->
<!DOCTYPE html>
<html>
<head>
    <title>App</title>
    <!-- webpack 生成的 CSS 文件,通常带有 hash -->
    <link href="/static/css/main.a1b2c3d4.css" rel="stylesheet">
</head>
<body>
    <div id="root"></div>
    <!-- webpack 生成的 JS 文件,通常带有 hash -->
    <script src="/static/js/runtime-main.1a2b3c4d.js"></script>
    <script src="/static/js/2.e5f6g7h8.chunk.js"></script>
    <script src="/static/js/main.i9j0k1l2.chunk.js"></script>
</body>
</html>

例如

img

img

控制台源代码就有Webpack://

webpack 目前支持多种调试模式,不同模式的区别在于编译后代码和源码之间的映射方式不同。具体来说,源码是通过不同的方式来映射的。有些模式会生成一个 .map 文件(用于源码映射),有些模式会通过注释的形式包含源码映射信息,还有些模式会使用 DataUrl 的方式(即把映射信息以 data URI 的形式嵌入到生成的文件中)

几种常见的映射打包方式

  1. source-map

生成独立的 .map 文件,方便调试,调试时可以还原到源代码。

适合用于生产环境,便于排查问题,但会暴露源码。

  1. inline-source-map

将 source map 以 DataUrl 的形式内联到编译后的文件中,不生成单独的 .map 文件。

适合开发环境,方便查看源码但文件体积较大。

  1. eval-source-map

将 source map 以 DataUrl 的形式嵌入到每个模块中(通过 eval),构建速度快,调试体验好。

适合开发环境,不推荐用于生产环境。

  1. cheap-module-source-map

提供行映射而不是列映射,忽略模块中的 loader source map,生成速度较快。

适用于开发环境,如果只关心行号,可以提升效率。

  1. hidden-source-map

生成 .map 文件,但不会在编译后的文件中引用,适合用于只需要 sourcemap 供错误收集工具使用,不直接暴露给用户。

img

存在泄露

Sources—> Page—> Webpack://中查看到Webpack项目源码

可以采用下面的反解 webpack

然后就可以分析项目结构和源代码审计等等了

自动化工具检测

https://github.com/rtcatc/Packer-Fuzzer

这个工具可以对 webpack 网站进行一个扫描,漏洞探查

python .\PackerFuzzer.py -u "..."

img

会生成一个 doc 文档,扫描报告

img

Webpack 案例

把相关代码放入 GitHub 仓库上面了

https://github.com/bx33661/Webpack-demo/tree/main

npm run dev
npm run build

img

会在 dist 文件夹中生成两个文件

  1. bundle.js

bundle.js 是webpack打包后生成的JavaScript文件,它包含了项目中所有JavaScript模块和依赖的集合。

主要特点:

  1. index.html
(() => {
  "use strict";
  var n = {
    56: (n, e, t) => {
      n.exports = function(n) {
        var e = t.nc;
        e && n.setAttribute("nonce", e)
      }
    },
    72: n => {
      var e = [];
      function t(n) {
        for (var t = -1, r = 0; r < e.length; r++)
          if (e[r].identifier === n) {
            t = r;
            break
          } return t
      }
      function r(n, r) {
        for (var a = {}, i = [], c = 0; c < n.length; c++) {
          var s = n[c],
            d = r.base ? s[0] + r.base : s[0],
            u = a[d] || 0,
            p = "".concat(d, " ").concat(u);
          a[d] = u + 1;
          var l = t(p),
            f = {
              css: s[1],
              media: s[2],
              sourceMap: s[3],
              supports: s[4],
              layer: s[5]
            };
          if (-1 !== l) e[l].references++, e[l].updater(f);
          else {
            var v = o(f, r);
            r.byIndex = c, e.splice(c, 0, {
              identifier: p,
              updater: v,
              references: 1
            })
          }
          i.push(p)
        }
        return i
      }
      function o(n, e) {
        var t = e.domAPI(e);
        return t.update(n),
          function(e) {
            if (e) {
              if (e.css === n.css && e.media === n.media && e.sourceMap === n.sourceMap && e.supports === n.supports && e.layer === n.layer) return;
              t.update(n = e)
            } else t.remove()
          }
      }
      n.exports = function(n, o) {
        var a = r(n = n || [], o = o || {});
        return function(n) {
          n = n || [];
          for (var i = 0; i < a.length; i++) {
            var c = t(a[i]);
            e[c].references--
          }
          for (var s = r(n, o), d = 0; d < a.length; d++) {
            var u = t(a[d]);
            0 === e[u].references && (e[u].updater(), e.splice(u, 1))
          }
          a = s
        }
      }
    },
    113: n => {
      n.exports = function(n, e) {
        if (e.styleSheet) e.styleSheet.cssText = n;
        else {
          for (; e.firstChild;) e.removeChild(e.firstChild);
          e.appendChild(document.createTextNode(n))
        }
      }
    },
    208: (n, e, t) => {
      t.d(e, {
        A: () => c
      });
      var r = t(601),
        o = t.n(r),
        a = t(314),
        i = t.n(a)()(o());
      i.push([n.id, "* {\n  margin: 0;\n  padding: 0;\n  box-sizing: border-box;\n}\n\nbody {\n  font-family: Arial, sans-serif;\n  line-height: 1.6;\n  color: #333;\n  background-color: #f4f4f4;\n}\n\n#app {\n  max-width: 800px;\n  margin: 0 auto;\n  padding: 20px;\n}\n\n.title {\n  color: #2c3e50;\n  text-align: center;\n  margin-bottom: 20px;\n  padding-bottom: 10px;\n  border-bottom: 1px solid #eee;\n}\n\n.content {\n  background: white;\n  padding: 20px;\n  border-radius: 5px;\n  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);\n}\n\nbutton {\n  background: #3498db;\n  color: white;\n  border: none;\n  padding: 10px 15px;\n  border-radius: 4px;\n  cursor: pointer;\n  margin-top: 10px;\n  font-size: 16px;\n  transition: background 0.3s;\n}\n\nbutton:hover {\n  background: #2980b9;\n}\n\n#result {\n  margin-top: 20px;\n  padding: 15px;\n  background: #f9f9f9;\n  border-left: 4px solid #3498db;\n  display: none;\n}\n\n#result.active {\n  display: block;\n  animation: fadeIn 0.5s;\n}\n\n@keyframes fadeIn {\n  from { opacity: 0; }\n  to { opacity: 1; }\n}", ""]);
                const c = i
            },
            314: n => {
                n.exports = function(n) {
                    var e = [];
                    return e.toString = function() {
                        return this.map((function(e) {
                            var t = "",
                                r = void 0 !== e[5];
                            return e[4] && (t += "@supports (".concat(e[4], ") {")), e[2] && (t += "@media ".concat(e[2], " {")), r && (t += "@layer".concat(e[5].length > 0 ? " ".concat(e[5]) : "", " {")), t += n(e), r && (t += "}"), e[2] && (t += "}"), e[4] && (t += "}"), t
                        })).join("")
                    }, e.i = function(n, t, r, o, a) {
                        "string" == typeof n && (n = [
                            [null, n, void 0]
                        ]);
                        var i = {};
                        if (r)
                            for (var c = 0; c < this.length; c++) {
                                var s = this[c][0];
                                null != s && (i[s] = !0)
                            }
                        for (var d = 0; d < n.length; d++) {
                            var u = [].concat(n[d]);
                            r && i[u[0]] || (void 0 !== a && (void 0 === u[5] || (u[1] = "@layer".concat(u[5].length > 0 ? " ".concat(u[5]) : "", " {").concat(u[1], "}")), u[5] = a), t && (u[2] ? (u[1] = "@media ".concat(u[2], " {").concat(u[1], "}"), u[2] = t) : u[2] = t), o && (u[4] ? (u[1] = "@supports (".concat(u[4], ") {").concat(u[1], "}"), u[4] = o) : u[4] = "".concat(o)), e.push(u))
                        }
                    }, e
                }
            },
            540: n => {
                n.exports = function(n) {
                    var e = document.createElement("style");
                    return n.setAttributes(e, n.attributes), n.insert(e, n.options), e
                }
            },
            601: n => {
                n.exports = function(n) {
                    return n[1]
                }
            },
            659: n => {
                var e = {};
                n.exports = function(n, t) {
                    var r = function(n) {
                        if (void 0 === e[n]) {
                            var t = document.querySelector(n);
                            if (window.HTMLIFrameElement && t instanceof window.HTMLIFrameElement) try {
                                t = t.contentDocument.head
                            } catch (n) {
                                t = null
                            }
                            e[n] = t
                        }
                        return e[n]
                    }(n);
                    if (!r) throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");
                    r.appendChild(t)
                }
            },
            825: n => {
                n.exports = function(n) {
                    if ("undefined" == typeof document) return {
                        update: function() {},
                        remove: function() {}
                    };
                    var e = n.insertStyleElement(n);
                    return {
                        update: function(t) {
                            ! function(n, e, t) {
                                var r = "";
                                t.supports && (r += "@supports (".concat(t.supports, ") {")), t.media && (r += "@media ".concat(t.media, " {"));
                                var o = void 0 !== t.layer;
                                o && (r += "@layer".concat(t.layer.length > 0 ? " ".concat(t.layer) : "", " {")), r += t.css, o && (r += "}"), t.media && (r += "}"), t.supports && (r += "}");
                                var a = t.sourceMap;
                                a && "undefined" != typeof btoa && (r += "\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(a)))), " */")), e.styleTagTransform(r, n, e.options)
                            }(e, n, t)
                        },
                        remove: function() {
                            ! function(n) {
                                if (null === n.parentNode) return !1;
                                n.parentNode.removeChild(n)
                            }(e)
                        }
                    }
                }
            }
        },
        e = {};
    function t(r) {
        var o = e[r];
        if (void 0 !== o) return o.exports;
        var a = e[r] = {
            id: r,
            exports: {}
        };
        return n[r](a, a.exports, t), a.exports
    }
    t.n = n => {
        var e = n && n.__esModule ? () => n.default : () => n;
        return t.d(e, {
            a: e
        }), e
    }, t.d = (n, e) => {
        for (var r in e) t.o(e, r) && !t.o(n, r) && Object.defineProperty(n, r, {
            enumerable: !0,
            get: e[r]
        })
    }, t.o = (n, e) => Object.prototype.hasOwnProperty.call(n, e), t.nc = void 0;
    var r = t(72),
        o = t.n(r),
        a = t(825),
        i = t.n(a),
        c = t(659),
        s = t.n(c),
        d = t(56),
        u = t.n(d),
        p = t(540),
        l = t.n(p),
        f = t(113),
        v = t.n(f),
        m = t(208),
        b = {};
    b.styleTagTransform = v(), b.setAttributes = u(), b.insert = s().bind(null, "head"), b.domAPI = i(), b.insertStyleElement = l(), o()(m.A, b), m.A && m.A.locals && m.A.locals, document.addEventListener("DOMContentLoaded", (() => {
        console.log("页面已加载");
        const n = document.getElementById("clickBtn"),
            e = document.getElementById("result");
        n && e && n.addEventListener("click", (() => {
            const n = 
你好,Webpack! 当前时间: ${(new Date).toLocaleTimeString()}
;
            e.textContent = n, e.classList.add("active")
        }))
    }))
})();

修改一下,添加devtool 选项:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  // 入口文件
  entry: './src/index.js',

  // 添加devtool配置来启用source map
  devtool: 'source-map', // 这将生成完整的source map文件

  // 输出配置
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
    clean: true // 每次构建前清理dist文件夹
  },

  // ... 其余配置保持不变 ...
};

这样的话我们

❯ npm run build

这样可以生成一个bundle.js.map文件

反解 webpack

有很多工具,这里使用reverse-sourcemap

npm install --global reverse-sourcemap

看一下用法

❯ reverse-sourcemap -h
reverse-sourcemap - Reverse engineering JavaScript and CSS sources from sourcemaps
Usage: reverse-sourcemap [options] <file|directory>
  -h, --help               Help and usage instructions
  -V, --version            Version number
  -v, --verbose            Verbose output, will print which file is currently being

                           processed
  -o, --output-dir String  Output directory - default: .
  -M, --match String       Regular expression for matching and filtering files -
                           default: \.map$
  -r, --recursive          Recursively search matching files
Version 1.0.4

直接逆向出来

❯ reverse-sourcemap --output-dir ./ bundle.js.map

img

效果如下

img

可以看到与我们原本项目结构相比,结构基本一致

img

效果还是不错的

img

所以说有的时候,我们会发现一些网站泄露了一些”map”文件,我们可以达到意想不到的利用

编辑这篇文章

评论区

使用 GitHub Discussions 驱动,欢迎留言交流。

上一篇
哥斯拉流量分析和溯源
下一篇
Windows应急响应和安全