[MM-18036] Sanitize sql LIKE terms on search endpoints (#12044)
* Sanitize sql LIKE terms on search endpoints * Add search term sanitization in additional places
Этот коммит содержится в:
коммит произвёл
Daniel Schalla
родитель
8b969426f0
Коммит
814c234443
@@ -2259,17 +2259,7 @@ func (s SqlChannelStore) SearchMore(userId string, teamId string, term string) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s SqlChannelStore) buildLIKEClause(term string, searchColumns string) (likeClause, likeTerm string) {
|
func (s SqlChannelStore) buildLIKEClause(term string, searchColumns string) (likeClause, likeTerm string) {
|
||||||
likeTerm = term
|
likeTerm = sanitizeSearchTerm(term, "*")
|
||||||
|
|
||||||
// These chars must be removed from the like query.
|
|
||||||
for _, c := range ignoreLikeSearchChar {
|
|
||||||
likeTerm = strings.Replace(likeTerm, c, "", -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// These chars must be escaped in the like query.
|
|
||||||
for _, c := range escapeLikeSearchChar {
|
|
||||||
likeTerm = strings.Replace(likeTerm, c, "*"+c, -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if likeTerm == "" {
|
if likeTerm == "" {
|
||||||
return
|
return
|
||||||
@@ -2429,6 +2419,7 @@ func (s SqlChannelStore) getSearchGroupChannelsQuery(userId, term string, isPost
|
|||||||
|
|
||||||
for idx, term := range terms {
|
for idx, term := range terms {
|
||||||
argName := fmt.Sprintf("Term%v", idx)
|
argName := fmt.Sprintf("Term%v", idx)
|
||||||
|
term = sanitizeSearchTerm(term, "\\")
|
||||||
likeClauses = append(likeClauses, fmt.Sprintf(baseLikeClause, ":"+argName))
|
likeClauses = append(likeClauses, fmt.Sprintf(baseLikeClause, ":"+argName))
|
||||||
args[argName] = "%" + term + "%"
|
args[argName] = "%" + term + "%"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ func (s SqlComplianceStore) ComplianceExport(job *model.Compliance) ([]*model.Co
|
|||||||
keywordQuery = "AND ("
|
keywordQuery = "AND ("
|
||||||
|
|
||||||
for index, keyword := range keywords {
|
for index, keyword := range keywords {
|
||||||
|
keyword = sanitizeSearchTerm(keyword, "\\")
|
||||||
if index >= 1 {
|
if index >= 1 {
|
||||||
keywordQuery += " OR LOWER(Posts.Message) LIKE :Keyword" + strconv.Itoa(index)
|
keywordQuery += " OR LOWER(Posts.Message) LIKE :Keyword" + strconv.Itoa(index)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -147,6 +147,8 @@ func (es SqlEmojiStore) Delete(emoji *model.Emoji, time int64) *model.AppError {
|
|||||||
func (es SqlEmojiStore) Search(name string, prefixOnly bool, limit int) ([]*model.Emoji, *model.AppError) {
|
func (es SqlEmojiStore) Search(name string, prefixOnly bool, limit int) ([]*model.Emoji, *model.AppError) {
|
||||||
var emojis []*model.Emoji
|
var emojis []*model.Emoji
|
||||||
|
|
||||||
|
name = sanitizeSearchTerm(name, "\\")
|
||||||
|
|
||||||
term := ""
|
term := ""
|
||||||
if !prefixOnly {
|
if !prefixOnly {
|
||||||
term = "%"
|
term = "%"
|
||||||
|
|||||||
@@ -853,7 +853,7 @@ func (s *SqlGroupStore) groupsBySyncableBaseQuery(st model.GroupSyncableType, t
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(opts.Q) > 0 {
|
if len(opts.Q) > 0 {
|
||||||
pattern := fmt.Sprintf("%%%s%%", opts.Q)
|
pattern := fmt.Sprintf("%%%s%%", sanitizeSearchTerm(opts.Q, "\\"))
|
||||||
operatorKeyword := "ILIKE"
|
operatorKeyword := "ILIKE"
|
||||||
if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
|
if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
|
||||||
operatorKeyword = "LIKE"
|
operatorKeyword = "LIKE"
|
||||||
@@ -919,7 +919,7 @@ func (s *SqlGroupStore) GetGroups(page, perPage int, opts model.GroupSearchOpts)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(opts.Q) > 0 {
|
if len(opts.Q) > 0 {
|
||||||
pattern := fmt.Sprintf("%%%s%%", opts.Q)
|
pattern := fmt.Sprintf("%%%s%%", sanitizeSearchTerm(opts.Q, "\\"))
|
||||||
operatorKeyword := "ILIKE"
|
operatorKeyword := "ILIKE"
|
||||||
if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
|
if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
|
||||||
operatorKeyword = "LIKE"
|
operatorKeyword = "LIKE"
|
||||||
|
|||||||
@@ -293,6 +293,8 @@ func (s SqlTeamStore) GetByName(name string) (*model.Team, *model.AppError) {
|
|||||||
func (s SqlTeamStore) SearchAll(term string) ([]*model.Team, *model.AppError) {
|
func (s SqlTeamStore) SearchAll(term string) ([]*model.Team, *model.AppError) {
|
||||||
var teams []*model.Team
|
var teams []*model.Team
|
||||||
|
|
||||||
|
term = sanitizeSearchTerm(term, "\\")
|
||||||
|
|
||||||
if _, err := s.GetReplica().Select(&teams, "SELECT * FROM Teams WHERE Name LIKE :Term OR DisplayName LIKE :Term", map[string]interface{}{"Term": term + "%"}); err != nil {
|
if _, err := s.GetReplica().Select(&teams, "SELECT * FROM Teams WHERE Name LIKE :Term OR DisplayName LIKE :Term", map[string]interface{}{"Term": term + "%"}); err != nil {
|
||||||
return nil, model.NewAppError("SqlTeamStore.SearchAll", "store.sql_team.search_all_team.app_error", nil, "term="+term+", "+err.Error(), http.StatusInternalServerError)
|
return nil, model.NewAppError("SqlTeamStore.SearchAll", "store.sql_team.search_all_team.app_error", nil, "term="+term+", "+err.Error(), http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
@@ -303,6 +305,8 @@ func (s SqlTeamStore) SearchAll(term string) ([]*model.Team, *model.AppError) {
|
|||||||
func (s SqlTeamStore) SearchOpen(term string) ([]*model.Team, *model.AppError) {
|
func (s SqlTeamStore) SearchOpen(term string) ([]*model.Team, *model.AppError) {
|
||||||
var teams []*model.Team
|
var teams []*model.Team
|
||||||
|
|
||||||
|
term = sanitizeSearchTerm(term, "\\")
|
||||||
|
|
||||||
if _, err := s.GetReplica().Select(&teams, "SELECT * FROM Teams WHERE Type = 'O' AND AllowOpenInvite = true AND (Name LIKE :Term OR DisplayName LIKE :Term)", map[string]interface{}{"Term": term + "%"}); err != nil {
|
if _, err := s.GetReplica().Select(&teams, "SELECT * FROM Teams WHERE Type = 'O' AND AllowOpenInvite = true AND (Name LIKE :Term OR DisplayName LIKE :Term)", map[string]interface{}{"Term": term + "%"}); err != nil {
|
||||||
return nil, model.NewAppError("SqlTeamStore.SearchOpen", "store.sql_team.search_open_team.app_error", nil, "term="+term+", "+err.Error(), http.StatusInternalServerError)
|
return nil, model.NewAppError("SqlTeamStore.SearchOpen", "store.sql_team.search_open_team.app_error", nil, "term="+term+", "+err.Error(), http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
@@ -313,6 +317,8 @@ func (s SqlTeamStore) SearchOpen(term string) ([]*model.Team, *model.AppError) {
|
|||||||
func (s SqlTeamStore) SearchPrivate(term string) ([]*model.Team, *model.AppError) {
|
func (s SqlTeamStore) SearchPrivate(term string) ([]*model.Team, *model.AppError) {
|
||||||
var teams []*model.Team
|
var teams []*model.Team
|
||||||
|
|
||||||
|
term = sanitizeSearchTerm(term, "\\")
|
||||||
|
|
||||||
query :=
|
query :=
|
||||||
`SELECT *
|
`SELECT *
|
||||||
FROM
|
FROM
|
||||||
|
|||||||
@@ -179,6 +179,7 @@ func (s SqlUserAccessTokenStore) GetByUser(userId string, offset, limit int) ([]
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s SqlUserAccessTokenStore) Search(term string) ([]*model.UserAccessToken, *model.AppError) {
|
func (s SqlUserAccessTokenStore) Search(term string) ([]*model.UserAccessToken, *model.AppError) {
|
||||||
|
term = sanitizeSearchTerm(term, "\\")
|
||||||
tokens := []*model.UserAccessToken{}
|
tokens := []*model.UserAccessToken{}
|
||||||
params := map[string]interface{}{"Term": term + "%"}
|
params := map[string]interface{}{"Term": term + "%"}
|
||||||
query := `
|
query := `
|
||||||
|
|||||||
@@ -409,11 +409,13 @@ func applyRoleFilter(query sq.SelectBuilder, role string, isPostgreSQL bool) sq.
|
|||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
|
|
||||||
roleParam := fmt.Sprintf("%%%s%%", role)
|
|
||||||
if isPostgreSQL {
|
if isPostgreSQL {
|
||||||
|
roleParam := fmt.Sprintf("%%%s%%", sanitizeSearchTerm(role, "\\"))
|
||||||
return query.Where("u.Roles LIKE LOWER(?)", roleParam)
|
return query.Where("u.Roles LIKE LOWER(?)", roleParam)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
roleParam := fmt.Sprintf("%%%s%%", sanitizeSearchTerm(role, "*"))
|
||||||
|
|
||||||
return query.Where("u.Roles LIKE ? ESCAPE '*'", roleParam)
|
return query.Where("u.Roles LIKE ? ESCAPE '*'", roleParam)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1222,15 +1224,6 @@ func (us SqlUserStore) SearchInChannel(channelId string, term string, options *m
|
|||||||
return us.performSearch(query, term, options)
|
return us.performSearch(query, term, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
var escapeLikeSearchChar = []string{
|
|
||||||
"%",
|
|
||||||
"_",
|
|
||||||
}
|
|
||||||
|
|
||||||
var ignoreLikeSearchChar = []string{
|
|
||||||
"*",
|
|
||||||
}
|
|
||||||
|
|
||||||
var spaceFulltextSearchChar = []string{
|
var spaceFulltextSearchChar = []string{
|
||||||
"<",
|
"<",
|
||||||
">",
|
">",
|
||||||
@@ -1265,15 +1258,7 @@ func generateSearchQuery(query sq.SelectBuilder, terms []string, fields []string
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (us SqlUserStore) performSearch(query sq.SelectBuilder, term string, options *model.UserSearchOptions) ([]*model.User, *model.AppError) {
|
func (us SqlUserStore) performSearch(query sq.SelectBuilder, term string, options *model.UserSearchOptions) ([]*model.User, *model.AppError) {
|
||||||
// These chars must be removed from the like query.
|
term = sanitizeSearchTerm(term, "*")
|
||||||
for _, c := range ignoreLikeSearchChar {
|
|
||||||
term = strings.Replace(term, c, "", -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// These chars must be escaped in the like query.
|
|
||||||
for _, c := range escapeLikeSearchChar {
|
|
||||||
term = strings.Replace(term, c, "*"+c, -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
searchType := USER_SEARCH_TYPE_NAMES_NO_FULL_NAME
|
searchType := USER_SEARCH_TYPE_NAMES_NO_FULL_NAME
|
||||||
if options.AllowEmails {
|
if options.AllowEmails {
|
||||||
|
|||||||
@@ -8,11 +8,27 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/mattermost/gorp"
|
"github.com/mattermost/gorp"
|
||||||
"github.com/mattermost/mattermost-server/mlog"
|
"github.com/mattermost/mattermost-server/mlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var escapeLikeSearchChar = []string{
|
||||||
|
"%",
|
||||||
|
"_",
|
||||||
|
}
|
||||||
|
|
||||||
|
func sanitizeSearchTerm(term string, escapeChar string) string {
|
||||||
|
term = strings.Replace(term, escapeChar, "", -1)
|
||||||
|
|
||||||
|
for _, c := range escapeLikeSearchChar {
|
||||||
|
term = strings.Replace(term, c, escapeChar+c, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return term
|
||||||
|
}
|
||||||
|
|
||||||
// Converts a list of strings into a list of query parameters and a named parameter map that can
|
// Converts a list of strings into a list of query parameters and a named parameter map that can
|
||||||
// be used as part of a SQL query.
|
// be used as part of a SQL query.
|
||||||
func MapStringsToQueryParams(list []string, paramPrefix string) (string, map[string]interface{}) {
|
func MapStringsToQueryParams(list []string, paramPrefix string) (string, map[string]interface{}) {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package sqlstore
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMapStringsToQueryParams(t *testing.T) {
|
func TestMapStringsToQueryParams(t *testing.T) {
|
||||||
@@ -30,3 +32,29 @@ func TestMapStringsToQueryParams(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSanitizeSearchTerm(t *testing.T) {
|
||||||
|
term := "test"
|
||||||
|
result := sanitizeSearchTerm(term, "\\")
|
||||||
|
require.Equal(t, result, term)
|
||||||
|
|
||||||
|
term = "%%%"
|
||||||
|
expected := "\\%\\%\\%"
|
||||||
|
result = sanitizeSearchTerm(term, "\\")
|
||||||
|
require.Equal(t, result, expected)
|
||||||
|
|
||||||
|
term = "%\\%\\%"
|
||||||
|
expected = "\\%\\%\\%"
|
||||||
|
result = sanitizeSearchTerm(term, "\\")
|
||||||
|
require.Equal(t, result, expected)
|
||||||
|
|
||||||
|
term = "%_test_%"
|
||||||
|
expected = "\\%\\_test\\_\\%"
|
||||||
|
result = sanitizeSearchTerm(term, "\\")
|
||||||
|
require.Equal(t, result, expected)
|
||||||
|
|
||||||
|
term = "**test_%"
|
||||||
|
expected = "test*_*%"
|
||||||
|
result = sanitizeSearchTerm(term, "*")
|
||||||
|
require.Equal(t, result, expected)
|
||||||
|
}
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user