mirror of
https://github.com/pocketbase/pocketbase.git
synced 2026-10-17 19:48:14 +02:00
added randomized trottle for failed list filter requests
This commit is contained in:
1 parent
8144ad1dda
commit
0d34c00d2e
1 file changed
+43
-1
+43
-1
@@ -1,9 +1,12 @@
|
|||||||
package apis
|
package apis
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
cryptoRand "crypto/rand"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"math/big"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/labstack/echo/v5"
|
"github.com/labstack/echo/v5"
|
||||||
"github.com/pocketbase/dbx"
|
"github.com/pocketbase/dbx"
|
||||||
@@ -71,7 +74,13 @@ func (api *recordApi) list(c echo.Context) error {
|
|||||||
|
|
||||||
records := []*models.Record{}
|
records := []*models.Record{}
|
||||||
|
|
||||||
result, err := searchProvider.ParseAndExec(c.QueryParams().Encode(), &records)
|
// note: in v0.23.0 this has been migrated as option check in the search.Provider
|
||||||
|
queryStr := c.QueryParams().Encode()
|
||||||
|
if len(queryStr) > 2048 {
|
||||||
|
return NewBadRequestError("query string is too large", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := searchProvider.ParseAndExec(queryStr, &records)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return NewBadRequestError("", err)
|
return NewBadRequestError("", err)
|
||||||
}
|
}
|
||||||
@@ -91,10 +100,43 @@ func (api *recordApi) list(c echo.Context) error {
|
|||||||
api.app.Logger().Debug("Failed to enrich list records", slog.String("error", err.Error()))
|
api.app.Logger().Debug("Failed to enrich list records", slog.String("error", err.Error()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// note: in v0.23.0 this is combined with extra check for repeated attempts
|
||||||
|
//
|
||||||
|
// Add a randomized throttle in case of empty search filter attempts.
|
||||||
|
//
|
||||||
|
// This is just for extra precaution since security researches raised concern regarding the possibity of eventual
|
||||||
|
// timing attacks because the List API rule acts also as filter and executes in a single run with the client-side filters.
|
||||||
|
// This is by design and it is an accepted tradeoff between performance, usability and correctness.
|
||||||
|
//
|
||||||
|
// While technically the below doesn't fully guarantee protection against filter timing attacks, in practice combined with the network latency it makes them even less feasible.
|
||||||
|
// A properly configured rate limiter or individual fields Hidden checks are better suited if you are really concerned about eventual information disclosure by side-channel attacks.
|
||||||
|
//
|
||||||
|
// In all cases it doesn't really matter that much because it doesn't affect the builtin PocketBase security sensitive fields (e.g. password and tokenKey) since they
|
||||||
|
// are not client-side filterable and in the few places where they need to be compared against an external value, a constant time check is used.
|
||||||
|
if requestInfo.Admin == nil &&
|
||||||
|
(collection.ListRule != nil && *collection.ListRule != "") &&
|
||||||
|
(requestInfo.Query["filter"] != "") &&
|
||||||
|
len(e.Records) == 0 {
|
||||||
|
api.app.Logger().Debug("Randomized throttle because of failed filter search", "collectionId", collection.Id)
|
||||||
|
randomizedThrottle(100)
|
||||||
|
}
|
||||||
|
|
||||||
return e.HttpContext.JSON(http.StatusOK, e.Result)
|
return e.HttpContext.JSON(http.StatusOK, e.Result)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func randomizedThrottle(softMax int64) {
|
||||||
|
var timeout int64
|
||||||
|
randRange, err := cryptoRand.Int(cryptoRand.Reader, big.NewInt(softMax))
|
||||||
|
if err == nil {
|
||||||
|
timeout = randRange.Int64()
|
||||||
|
} else {
|
||||||
|
timeout = softMax
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(time.Duration(timeout) * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
func (api *recordApi) view(c echo.Context) error {
|
func (api *recordApi) view(c echo.Context) error {
|
||||||
collection, _ := c.Get(ContextCollectionKey).(*models.Collection)
|
collection, _ := c.Get(ContextCollectionKey).(*models.Collection)
|
||||||
if collection == nil {
|
if collection == nil {
|
||||||
|
|||||||
Reference in new issue
Block a user