idekctf复现

好久没做题了,看了师傅们发的idek发现质量挺不错,就复现了一下

readme

签到题,但是以前没咋接触过这类,刚上手还有点懵

是一个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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
package main

import (
"bufio"
"bytes"
"context"
"crypto/sha256"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"math/rand"
"net/http"
"os"
"time"
)

var password = sha256.Sum256([]byte("idek"))//生成了个hash散列
var randomData []byte

const (
MaxOrders = 10
)

func initRandomData() {
rand.Seed(1337)//Seed使用提供的seed值将发生器初始化为确定性状态
randomData = make([]byte, 24576)//给randomdata分配了24567内存
if _, err := rand.Read(randomData); err != nil {
panic(err)
}//rand.read(randomData)生成了随机数并赋值给randomdata
copy(randomData[12625:], password[:])//将password的hash散列赋值到randomdata的12625位之后
}

type ReadOrderReq struct {
Orders []int `json:"orders"`
}
//这里是唯一的路由,走到底就有flag
func justReadIt(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()//延迟调用r.body.close,方便执行完返回
//解析数据
body, err := ioutil.ReadAll(r.Body)
if err != nil {
w.WriteHeader(500)
w.Write([]byte("bad request\n"))
return
}
//绑定数据
reqData := ReadOrderReq{}
if err := json.Unmarshal(body, &reqData); err != nil {
w.WriteHeader(500)
w.Write([]byte("invalid body\n"))
return
}
//最多不能超过10条order
if len(reqData.Orders) > MaxOrders {
w.WriteHeader(500)
w.Write([]byte("whoa there, max 10 orders!\n"))
return
}

reader := bytes.NewReader(randomData)//创建了个新的reader
validator := NewValidator()//new了一个验证器

ctx := context.Background()//获取空context
for _, o := range reqData.Orders {
if err := validator.CheckReadOrder(o); err != nil {
w.WriteHeader(500)
w.Write([]byte(fmt.Sprintf("error: %v\n", err)))
return
}
//检查是否在0-100之间,也就是我们每个order都不能大于100
ctx = WithValidatorCtx(ctx, reader, int(o))//为context赋了两个值,一个reader一个order
_, err := validator.Read(ctx)
if err != nil {
w.WriteHeader(500)
w.Write([]byte(fmt.Sprintf("failed to read: %v\n", err)))
return
}
}
//这里读取不能出错
if err := validator.Validate(ctx); err != nil {
w.WriteHeader(500)
w.Write([]byte(fmt.Sprintf("validation failed: %v\n", err)))
return
}
//开始验证,跟进到validate
w.WriteHeader(200)
w.Write([]byte(os.Getenv("FLAG")))
}

func main() {
if _, exists := os.LookupEnv("LISTEN_ADDR"); !exists {
panic("env LISTEN_ADDR is required")
}
if _, exists := os.LookupEnv("FLAG"); !exists {
panic("env FLAG is required")
}
//初始化读取环境变量
initRandomData()//初始化randomdata

http.HandleFunc("/just-read-it", justReadIt)

srv := http.Server{
Addr: os.Getenv("LISTEN_ADDR"),
ReadTimeout: 5 * time.Second,
WriteTimeout: 5 * time.Second,
}

fmt.Printf("Server listening on %s\n", os.Getenv("LISTEN_ADDR"))
if err := srv.ListenAndServe(); err != nil {
panic(err)
}
}

type Validator struct{}

func NewValidator() *Validator {
return &Validator{}
}

func (v *Validator) CheckReadOrder(o int) error {
if o <= 0 || o > 100 {
return fmt.Errorf("invalid order %v", o)
}
return nil
}
//根据context中给的reader和size读取
func (v *Validator) Read(ctx context.Context) ([]byte, error) {
r, s := GetValidatorCtxData(ctx)
buf := make([]byte, s)//为buf分配了size大小的内存
_, err := r.Read(buf)//将reader里的数据读到buf
if err != nil {
return nil, fmt.Errorf("read error: %v", err)
}
return buf, nil
}

func (v *Validator) Validate(ctx context.Context) error {
r, _ := GetValidatorCtxData(ctx)//读一下传入的context中的值,此时我们的context里面有size和reader信息,接收了reader
buf, err := v.Read(WithValidatorCtx(ctx, r, 32))//这里写死了读取32位,我们的password也是32位,也就是要求我们从12625位开始读,也就是说一开始的循环中我们就要读掉12625位
if err != nil {
return err
}
if bytes.Compare(buf, password[:]) != 0 {
return errors.New("invalid password")
}
//这里进行对比
return nil
}

const (
reqValReaderKey = "readerKey"
reqValSizeKey = "reqValSize"
)

func GetValidatorCtxData(ctx context.Context) (io.Reader, int) {
reader := ctx.Value(reqValReaderKey).(io.Reader)
size := ctx.Value(reqValSizeKey).(int)
//获取到reader和size
if size >= 100 {
reader = bufio.NewReader(reader)
}
//这题的关键点来了,如果size大于100,就会重新new一个bufio的reader,这个reader的默认size值是4096
return reader, size
}

func WithValidatorCtx(ctx context.Context, r io.Reader, size int) context.Context {
ctx = context.WithValue(ctx, reqValReaderKey, r)
ctx = context.WithValue(ctx, reqValSizeKey, size)
return ctx
}

经过一段跟进

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for _, o := range reqData.Orders {
if err := validator.CheckReadOrder(o); err != nil {
w.WriteHeader(500)
w.Write([]byte(fmt.Sprintf("error: %v\n", err)))
return
}
//检查是否在0-100之间,也就是我们每个order都不能大于100
ctx = WithValidatorCtx(ctx, reader, int(o))//为context赋了两个值,一个reader一个order
_, err := validator.Read(ctx)
if err != nil {
w.WriteHeader(500)
w.Write([]byte(fmt.Sprintf("failed to read: %v\n", err)))
return
}
}

最终确定关键在这个循环,在这个循环中对每个order都当作size读取一次,也就是在这10次order内我们要读掉12625位,但是10*100最多1000位,这个时候就要提到GetValidatorCtxData这里如果size大于等于100就会重新new一个bufio的reader,他的默认值是4096,也就是说我们输入100,size其实不是100,而是4096,这个时候就好办了

1
2
3
4
5
6
7
8
9
10
11
{
"orders":[
100,
100,
100,
99,
99,
99,
40
]
}

只要加起来等于12625就好了

这里注意一个点,请求头还是要注意的,要不然容易无法解析,一开始测试的时候就是没注意Content-Type导致一直没法成功复现

image-20230130151525853

最近比较忙,复现先放一放了~