idekctf复现
好久没做题了,看了师傅们发的idek发现质量挺不错,就复现了一下
readme
签到题,但是以前没咋接触过这类,刚上手还有点懵
是一个go的程序,存在逻辑漏洞
先全文通读一下
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
}
经过一段跟进
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,这个时候就好办了
{
"orders":[
100,
100,
100,
99,
99,
99,
40
]
}
只要加起来等于12625就好了
这里注意一个点,请求头还是要注意的,要不然容易无法解析,一开始测试的时候就是没注意Content-Type
导致一直没法成功复现
最近比较忙,复现先放一放了~