idekCTF 2022-web-Readme

yoobi·2023년 1월 25일
0

두괄식 info

  • >= 등 1의 차이를 잘 보자

FLAG 획득 방법론

func justReadIt(w http.ResponseWriter, r *http.Request) {
	defer 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
	}

	if len(reqData.Orders) > MaxOrders {
		w.WriteHeader(500)
		w.Write([]byte("whoa there, max 10 orders!\n"))
		return
	}

	reader := bytes.NewReader(randomData)
	validator := NewValidator()

	ctx := context.Background()
	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
		}

		ctx = WithValidatorCtx(ctx, reader, int(o))
		_, 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
	}

	w.WriteHeader(200)
	w.Write([]byte(os.Getenv("FLAG")))
}
  • 위의 여러 필터 및 에러 처리를 모두 우회하면 os.Getenv("FLAG")를 통해 환경 변수에 저장된 flag 획득이 가능합니다.

func Validate()

func (v *Validator) Validate(ctx context.Context) error {
	r, _ := GetValidatorCtxData(ctx)
	buf, err := v.Read(WithValidatorCtx(ctx, r, 32))
	if err != nil {
		return err
	}
	if bytes.Compare(buf, password[:]) != 0 {
		return errors.New("invalid password")
	}
	return nil
}
  • 최종적으로는 buf의 값과 passowrd의 값을 비교하여 같을 때만 flag를 확인할 수 있습니다

input

type ReadOrderReq struct {
	Orders []int `json:"orders"`
}
func justReadIt()
{
...
	reqData := ReadOrderReq{}
	if err := json.Unmarshal(body, &reqData); err != nil {
		w.WriteHeader(500)
		w.Write([]byte("invalid body\n"))
		return
	}
...
}
  • 저희가 입력할 수 있는 값은 int 배열 형태의 값으로 orders 변수에 넣어서 전달해야 합니다
const (
	MaxOrders = 10
)
func justReadIt()
{
...
	if len(reqData.Orders) > MaxOrders {
		w.WriteHeader(500)
		w.Write([]byte("whoa there, max 10 orders!\n"))
		return
	}
...
}
  • 입력할 수 있는 int 배열 내 데이터 개수는 10개로 제한되어 있습니다.
func (v *Validator) CheckReadOrder(o int) error {
	if o <= 0 || o > 100 {
		return fmt.Errorf("invalid order %v", o)
	}
	return nil
}
  • 또한, 각 하나의 배열 요소를 가지고와서 o <= 0 || o > 100을 확인합니다. 즉 0~100의 데이터만 입력이 가능합니다
  • 최종적으로는 0~100 사이의 int 값 10개를 입력할 수 있습니다

문제 풀이

func initRandomData() {
	rand.Seed(1337)
	randomData = make([]byte, 24576)
	if _, err := rand.Read(randomData); err != nil {
		panic(err)
	}
	copy(randomData[12625:], password[:])
}
  • copy(randomData[12625:], password[:])를 진행하므로 randomData[12625:]에는 password가 복사되어 있습니다
func justReadIt()
{
...
	reader := bytes.NewReader(randomData)
...
}
  • randomData는 justReadIt() 내에서 reader라는 변수로 저장되어 사용됩니다
func justReadIt()
{
...
	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
		}

		ctx = WithValidatorCtx(ctx, reader, int(o))
		_, err := validator.Read(ctx)
		if err != nil {
			w.WriteHeader(500)
			w.Write([]byte(fmt.Sprintf("failed to read: %v\n", err)))
			return
		}
	}
...
}
  • orders 내 데이터의 수 만큼 for 문을 실행하게 되는데, 이 때 validator.Read(ctx)가 실행되게 됩니다.
func (v *Validator) Read(ctx context.Context) ([]byte, error) {
	r, s := GetValidatorCtxData(ctx)
	buf := make([]byte, s)
	_, err := r.Read(buf)
	if err != nil {
		return nil, fmt.Errorf("read error: %v", err)
	}
	return buf, nil
}
  • read() 함수를 통해 r 로 저장되어 있는 randomData의 값을 계속 읽게되는데, 이 때 randomData 내의 값을 가르키는 포인터가 이동하게 됩니다.
  • 최종적으로 12625만큼 이동하게 만들면 randomData 내 저장되었던 password 데이터를 정확하게 buf에 저장하여 두 데이터(buf, password)를 비교하는 구문을 성공시킬 수 있습니다
  • 그러나, 0~100 사이의 int 값, 최대 10개만 입력할 수 있는 상황입니다. (100 * 10 = 1000)
  • 즉, 단순 입력 값 만큼 이동하는 값 외에 다른 방법으로 12625만큼 이동시켜야하는 문제입니다
func GetValidatorCtxData(ctx context.Context) (io.Reader, int) {
	reader := ctx.Value(reqValReaderKey).(io.Reader)
	size := ctx.Value(reqValSizeKey).(int)
	if size >= 100 {
		reader = bufio.NewReader(reader)
	}
	return reader, size
}
  • GetValidatorCtxData() 함수는 size가 100 일 때, reader = bufio.NewReader(reader)를 수행합니다. 결과적으로 bufio.NewReader()가 1번 실행될 때마다 데이터를 가르키는 포인터는 4096만큼 이동하게 됩니다. 이렇게 동작하는 이유는 아래와 같습니다.
// bufio.go
...
const (
	defaultBufSize = 4096
)
...
...
func NewReader(rd io.Reader) *Reader {
	return NewReaderSize(rd, defaultBufSize)
}
func NewReaderSize(rd io.Reader, size int) *Reader {
	// Is it already a Reader?
	b, ok := rd.(*Reader)
	if ok && len(b.buf) >= size {
		return b
	}
	if size < minReadBufferSize {
		size = minReadBufferSize
	}
	r := new(Reader)
	r.reset(make([]byte, size), rd)
	return r
}
...
  • 따라서, 100->4096의 이동이 가능함을 알았습니다.
  • 총 12625의 이동이 필요하기 때문에, orders=[4096(100), 4096(100), 99, 99, 99, 40]를 통해 flag 획득이 가능합니다.

사진

profile
this is yoobi

0개의 댓글