好久没做题了,看了师傅们发的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导致一直没法成功复现

image-20230130151525853

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