题目提供了源码跟docker环境
分析流程
init.go
中定义了路由
|
|
易得
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数组来储存
|
|
想到go的slice在append时会有奇怪的事情发生
|
|
最后输出的结果为
1
| [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
|
|
为账户添加金额
app/lottery.go
|
|
将用户加进lottery列表中
|
|
开奖
Add()
将account
拷贝进了l
中,evaluate()
将l
中的account
取出,并对其中的Amount
进行了append操作,所以如果能构造一个len=3 cap=4
的Amount
并使它以此经过AddAmount
和evaluate
的处理,不就会将最后一个元素修改为至少100w的值吗?于是利用链就出来了
利用链
先请求三次增加金额,当前账户的Amount
结构就为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | +---------------------------+ | | | 99 | | | +---------------------------+ | | | 99 | | | +---------------------------+ | | | 99 | | | len=3----------> +---------------------------+ | | | | | | cap=4----------> +---------------------------+ |
再请求/lottery/add
,将当前account
复制到l
中,现在l
中的account.Amount
的len=3
再请求/account/xxxx/amount
增加一次金额
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | +---------------------------+ | | | 99 | | | +---------------------------+ | | | 99 | | | +---------------------------+ | | | 99 | | | +---------------------------+ | | | 99 | | | cap=4+---------> +---------------------------+ <---------+ len=4 |
但是因为l
中的account.Amount
的len=3
,所以在开奖的时候,append(account.Amounts, randInt(999913, 3700000))
,不会对account.Amounts
进行扩容,所以还是对0x2(假设)的最后一个元素进行覆盖,所以将99覆盖成了100w以上的数,这样账户中的金额就轻轻松松超过100w,flag到手
这里我在这几个地方把地址打印出来,验证了最后len=3
的时候是对同一个地址进行操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | ---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
|
|
参考
https://www.zhihu.com/question/27161493 https://github.com/mwarzynski/confidence2019_teaser_lottery