题目链接: https://challenge-0422.intigriti.io/

分析

站点使用了mithril.js,版本为2.0.4最新版

https://unpkg.com/[email protected]/mithril.js

这里简单贴一下题目关键部分。

function main() {
          const qs = m.parseQueryString(location.search)

          let appConfig = Object.create(null)
          appConfig["version"] = 1337
          appConfig["mode"] = "production"
          appConfig["window-name"] = "Window"
          appConfig["window-content"] = "default content"
          appConfig["window-toolbar"] = ["close"]
          appConfig["window-statusbar"] = false
          appConfig["customMode"] = false

          if (qs.config) {
            merge(appConfig, qs.config)
            appConfig["customMode"] = true
          }

          let devSettings = Object.create(null)
          devSettings["root"] = document.createElement('main')
          devSettings["isDebug"] = false
          devSettings["location"] = 'challenge-0422.intigriti.io'
          devSettings["isTestHostOrPort"] = false

          if (checkHost()) {
            devSettings["isTestHostOrPort"] = true
            merge(devSettings, qs.settings)
          }

          if (devSettings["isTestHostOrPort"] || devSettings["isDebug"]) {
            console.log('appConfig', appConfig)
            console.log('devSettings', devSettings)
          }

          if (!appConfig["customMode"]) {
            m.mount(devSettings.root, App)
          } else {
            m.mount(devSettings.root, {view: function() {
              return m(CustomizedApp, {
                name: appConfig["window-name"],
                content: appConfig["window-content"] ,
                options: appConfig["window-toolbar"],
                status: appConfig["window-statusbar"]
              })
            }})
          }

          document.body.appendChild(devSettings.root)
        }

        function checkHost() {
          const temp = location.host.split(':')
          const hostname = temp[0]
          const port = Number(temp[1]) || 443
          return hostname === 'localhost' || port === 8080
        }

        function isPrimitive(n) {
          return n === null || n === undefined || typeof n === 'string' || typeof n === 'boolean' || typeof n === 'number'
        }

        function merge(target, source) {
          let protectedKeys = ['__proto__', "mode", "version", "location", "src", "data", "m"]

          for(let key in source) {
            if (protectedKeys.includes(key)) continue

            if (isPrimitive(target[key])) {
              target[key] = sanitize(source[key])
            } else {
              merge(target[key], source[key])
            }
          }
        }
        function sanitize(data) {
          if (typeof data !== 'string') return data
          return data.replace(/[<>%&\\$\\s\\\\]/g, '_').replace(/script/gi, '_')
        }

        main()
      })()

站点主要做了以下几件事:

这里注意到有以下几个问题:

Prototype Pollution

看到有merge很容易想到原型链污染,qs首先经过mithril中的parseQueryString处理,我们看一下源码

var parseQueryString = function(string) {
        if (string === "" || string == null) return {}
        if (string.charAt(0) === "?") string = string.slice(1)
        var entries = string.split("&"), counters = {}, data0 = {}
        for (var i = 0; i < entries.length; i++) {
            var entry = entries[i].split("=")
            var key5 = decodeURIComponent(entry[0])
            var value2 = entry.length === 2 ? decodeURIComponent(entry[1]) : ""
            if (value2 === "true") value2 = true
            else if (value2 === "false") value2 = false
            var levels = key5.split(/\\]\\[?|\\[/)
            var cursor = data0
            if (key5.indexOf("[") > -1) levels.pop()
            for (var j0 = 0; j0 < levels.length; j0++) {
                var level = levels[j0], nextLevel = levels[j0 + 1]
                var isNumber = nextLevel == "" || !isNaN(parseInt(nextLevel, 10))
                if (level === "") {
                    var key5 = levels.slice(0, j0).join()
                    if (counters[key5] == null) {
                        counters[key5] = Array.isArray(cursor) ? cursor.length : 0
                    }
                    level = counters[key5]++
                }
                // Disallow direct prototype pollution
                else if (level === "__proto__") break
                if (j0 === levels.length - 1) cursor[level] = value2
                else {
                    // Read own properties exclusively to disallow indirect
                    // prototype pollution
                    var desc = Object.getOwnPropertyDescriptor(cursor, level)
                    if (desc != null) desc = desc.value
                    if (desc == null) cursor[level] = desc = isNumber ? [] : {}
                    cursor = desc
                }
            }
        }
        return data0
    }

可以发现mithril中的parseQueryString已经对原型链污染做了防御