[MM-18036] Sanitize sql LIKE terms on search endpoints (#12044)

* Sanitize sql LIKE terms on search endpoints

* Add search term sanitization in additional places
This commit is contained in:
Claudio Costa
2019-09-11 10:56:12 +02:00
committed by Daniel Schalla
parent 8b969426f0
commit 814c234443
9 changed files with 62 additions and 32 deletions

View File

@@ -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) {
likeTerm = 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)
}
likeTerm = sanitizeSearchTerm(term, "*")
if likeTerm == "" {
return
@@ -2429,6 +2419,7 @@ func (s SqlChannelStore) getSearchGroupChannelsQuery(userId, term string, isPost
for idx, term := range terms {
argName := fmt.Sprintf("Term%v", idx)
term = sanitizeSearchTerm(term, "\\")
likeClauses = append(likeClauses, fmt.Sprintf(baseLikeClause, ":"+argName))
args[argName] = "%" + term + "%"
}

View File

@@ -90,6 +90,7 @@ func (s SqlComplianceStore) ComplianceExport(job *model.Compliance) ([]*model.Co
keywordQuery = "AND ("
for index, keyword := range keywords {
keyword = sanitizeSearchTerm(keyword, "\\")
if index >= 1 {
keywordQuery += " OR LOWER(Posts.Message) LIKE :Keyword" + strconv.Itoa(index)
} else {

View File

@@ -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) {
var emojis []*model.Emoji
name = sanitizeSearchTerm(name, "\\")
term := ""
if !prefixOnly {
term = "%"

View File

@@ -853,7 +853,7 @@ func (s *SqlGroupStore) groupsBySyncableBaseQuery(st model.GroupSyncableType, t
}
if len(opts.Q) > 0 {
pattern := fmt.Sprintf("%%%s%%", opts.Q)
pattern := fmt.Sprintf("%%%s%%", sanitizeSearchTerm(opts.Q, "\\"))
operatorKeyword := "ILIKE"
if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
operatorKeyword = "LIKE"
@@ -919,7 +919,7 @@ func (s *SqlGroupStore) GetGroups(page, perPage int, opts model.GroupSearchOpts)
}
if len(opts.Q) > 0 {
pattern := fmt.Sprintf("%%%s%%", opts.Q)
pattern := fmt.Sprintf("%%%s%%", sanitizeSearchTerm(opts.Q, "\\"))
operatorKeyword := "ILIKE"
if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
operatorKeyword = "LIKE"

View File

@@ -293,6 +293,8 @@ func (s SqlTeamStore) GetByName(name string) (*model.Team, *model.AppError) {
func (s SqlTeamStore) SearchAll(term string) ([]*model.Team, *model.AppError) {
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 {
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) {
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 {
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) {
var teams []*model.Team
term = sanitizeSearchTerm(term, "\\")
query :=
`SELECT *
FROM

View File

@@ -179,6 +179,7 @@ func (s SqlUserAccessTokenStore) GetByUser(userId string, offset, limit int) ([]
}
func (s SqlUserAccessTokenStore) Search(term string) ([]*model.UserAccessToken, *model.AppError) {
term = sanitizeSearchTerm(term, "\\")
tokens := []*model.UserAccessToken{}
params := map[string]interface{}{"Term": term + "%"}
query := `

View File

@@ -409,11 +409,13 @@ func applyRoleFilter(query sq.SelectBuilder, role string, isPostgreSQL bool) sq.
return query
}
roleParam := fmt.Sprintf("%%%s%%", role)
if isPostgreSQL {
roleParam := fmt.Sprintf("%%%s%%", sanitizeSearchTerm(role, "\\"))
return query.Where("u.Roles LIKE LOWER(?)", roleParam)
}
roleParam := fmt.Sprintf("%%%s%%", sanitizeSearchTerm(role, "*"))
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)
}
var escapeLikeSearchChar = []string{
"%",
"_",
}
var ignoreLikeSearchChar = []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) {
// These chars must be removed from the like query.
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)
}
term = sanitizeSearchTerm(term, "*")
searchType := USER_SEARCH_TYPE_NAMES_NO_FULL_NAME
if options.AllowEmails {

View File

@@ -8,11 +8,27 @@ import (
"database/sql"
"fmt"
"strconv"
"strings"
"github.com/mattermost/gorp"
"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
// be used as part of a SQL query.
func MapStringsToQueryParams(list []string, paramPrefix string) (string, map[string]interface{}) {

View File

@@ -2,6 +2,8 @@ package sqlstore
import (
"testing"
"github.com/stretchr/testify/require"
)
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)
}