Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add some patterns support to blacklist [T660] #226

Merged
merged 23 commits into from
Aug 1, 2018
Merged

Add some patterns support to blacklist [T660] #226

merged 23 commits into from
Aug 1, 2018

Conversation

storojs72
Copy link
Contributor

  1. Add support of %%SELECT%%
  2. Add support of SELECT %%COLUMN%% .. %%COLUMN%%
  3. Add support of SELECT A,B FROM T %%WHERE%%

I added some stubs (commented some places in code) to whitelist workflow - would like to know from reviewers if blacklist now will work as expected

@storojs72
Copy link
Contributor Author

  1. Add support of SELECT a, b FROM t1 WHERE userID=%%VALUE%%


func TestBlacklistPatterns(t *testing.T) {
//test %%SELECT%% pattern
testBlacklistPattern0(t)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better to use meaningful names for functions:

  • testBlacklistSelectPattern
  • testBlacklistColumnPattern
  • testBlacklistWherePattern
  • testBlacklistValuePattern

//Queries that should be blocked by first pattern have indexes: [0 .. 12] (all select queries)
for i, query := range queries {
err := censor.HandleQuery(query)
if i <= 12 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const SelectQueryCount = 12
for ....
  if i < SelectQueryCount {}
....

It is less readable when we need to find comment some line above that explain what the number mean in if clause

testBlacklistPattern3(t)

}
func testBlacklistPattern0(t *testing.T) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be good to add some general comment about whole test case like "test that all 'select' queries will be blocked by blacklist handler". after that future reader will understand whole logic and know what expect
or use verbose function names :)

queries := []string{
"SELECT A",
"SELECT A, B",
"SELECT A, B, C",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better to use two arrays of positive and negative values to check them in a loop.
in a future when someone will need to add some special value to check he will need just to add new query to correct array
or copy-paste one more if block and increase code size without need
plus it's more readable when you see

for _, query := range AcceptableQueries {
  // do something
  if err != nil {
     t.Log("unexpected error")
  }
}
for _, blockableQuery := range BlockableQueries { 
  // do something
  if err == nil {
     t.Log("we expect error")
  }
}

t.Fatal(err)
}
queries := []string{
"SELECT a, b, c FROM x WHERE a = 'someValue'",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add cases with two/three conditions: a='some value' and b=2 or c between 20 and 30

if err != nil {
log.WithField(logging.FieldKeyEventCode, logging.EventCodeErrorCensorQueryParseError).WithError(err).Errorln("Can't parse query to add rule to blacklist handler")
return ErrQuerySyntaxError
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this type of error specially handled on acra-censor level to ignore errors when we can't parse sql queries

patternDetected := false
matchOccurred := false
patternDetected, matchOccurred = handleSelectPattern(queryNodes, patternNodes)
if patternDetected {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not if patternDetected && matchOccurred?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and for what we use patternDetected? why not just check that matchOccurred?

func handleSelectColumnPattern(queryNodes, patternNodes []sqlparser.SQLNode) (patternDetected bool, matchOccurred bool) {
var emptySelect *sqlparser.Select
if reflect.TypeOf(queryNodes[0]) == reflect.TypeOf(emptySelect) && reflect.TypeOf(patternNodes[0]) == reflect.TypeOf(emptySelect) {
if strings.Contains(sqlparser.String(patternNodes[0].(*sqlparser.Select).SelectExprs), ColumnConfigPlaceholderReplacer) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we somehow to remember result of it and don't check it always? for example on config load and remembering patterns that contains such pattern and check only them with this filter?

var emptySelect *sqlparser.Select
if reflect.TypeOf(queryNodes[0]) == reflect.TypeOf(emptySelect) && reflect.TypeOf(patternNodes[0]) == reflect.TypeOf(emptySelect) {
if strings.Contains(sqlparser.String(patternNodes[0].(*sqlparser.Select).SelectExprs), ColumnConfigPlaceholderReplacer) {
if len(patternNodes[0].(*sqlparser.Select).SelectExprs) == len(queryNodes[0].(*sqlparser.Select).SelectExprs) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how we check that places of pattern %%COLUMN%% the same as in query? how it handle cases like:
pattern: select a, %%COLUMN%% from table
query: select b, blabla from table ?
We doesn't calculate count of %%column%% pattern to compare it with columns count in query

//handle SELECT a, b from t %%WHERE%% pattern
func handleSelectWherePattern(queryNodes, patternNodes []sqlparser.SQLNode) (patternDetected bool, matchOccurred bool) {
var emptySelect *sqlparser.Select
if reflect.TypeOf(queryNodes[0]) == reflect.TypeOf(emptySelect) && reflect.TypeOf(patternNodes[0]) == reflect.TypeOf(emptySelect) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we use this check > 1 times. what about to add some helper like func isSelectQuery(node sqlparser.SQLNode) and then use:
if isSelectQuery(queryNodes[0]) && isSelectQuery(patternNodes[0]) ? it's more readable and we encapsulate logic of detecting query type

@Lagovas
Copy link
Collaborator

Lagovas commented Jul 30, 2018

select %%COLUMN%%, %%COLUMN%% great pattern support but we need select %%SKIP_ALL_COLUMNS%% too. It will provide easy way to skip any permutations of columns in select clause and different column count. Actually, we planned it at start in T660

@storojs72 storojs72 changed the base branch from master to acracensor July 30, 2018 11:11
@@ -160,153 +157,208 @@ func (handler *BlacklistHandler) handleParenTables(statement *sqlparser.ParenTab
return nil
}

// Reset blacklist rules
func (handler *BlacklistHandler) Reset() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed comment :(

}

// Release / reset blacklist rules
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed comment :(

func (handler *BlacklistHandler) Release() {
handler.Reset()
}

// AddQueries adds queries to the list that should be blacklisted
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed comment :(

@vixentael
Copy link
Collaborator

Please don't remove golint comments, it took me some time to add them.

}

// NewBlacklistHandler creates new blacklist handler
func NewBlacklistHandler() *BlacklistHandler {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still removed

if index == 0 || reflect.DeepEqual(patternNode, queryNodes[index]) {
continue
}
if patternNodeColName, ok := patternNode.(*sqlparser.ColName); ok && patternNodeColName != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if type of node in query != type of node in pattern and it's not ColName? Can we check that they both has same type and then check value of .Name?

if queryWhereNode, ok := queryNodes[index].(*sqlparser.Where); ok && queryWhereNode != nil {
queryWhereDetected = true
}
if patternWhereDetected == queryWhereDetected && patternWhereDetected {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

true == true && true ?)
better to leave only patternWhereDetected && queryWhereDetected, because it's true only when both are true

@@ -88,9 +89,10 @@ func (handler *BlacklistHandler) CheckQuery(query string) (bool, error) {
}
//Check patterns
if len(handler.patterns) != 0 {
//fmt.Println("here")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

git add -p acra-censor/handlers/blacklist_handler.go and you can not include this :)

"SELECT * FROM company WHERE AGE NOT IN ( 25, 27 )",
"SELECT * FROM company WHERE AGE > (SELECT AGE FROM COMPANY WHERE SALARY > 65000)",
"SELECT AGE FROM company WHERE EXISTS (SELECT AGE FROM company WHERE SALARY > 65000)",
"SELECT age FROM company11 WHERE EXISTS (SELECT AGE FROM company WHERE SALARY > 65000)",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this query blockable too? company != company11

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because internal query inside EXISTS block try to access to 'company'

queryTables = querySelectStatement.From
}
}
if patternStarDetected {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we move this check after walking through patternNodes and write if !patternStarDetected{ return false} to avoid processing second loop?

if querySelectStatement, ok := queryNode.(*sqlparser.Select); ok && querySelectStatement != nil {
queryTables = querySelectStatement.From
if queryWhereNode, ok := queryNodes[index+queryNodeOffset].(*sqlparser.Where); ok && queryWhereNode != nil {
queryWhereDetected = true
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we return true here and false in else block and don't check queryWhereDetected && partternWhereDetected?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if nodes are not both *sqlparser.Where?

starDetected := false
sqlparser.Walk(func(node sqlparser.SQLNode) (bool, error) {
if _, ok := node.(*sqlparser.StarExpr); ok {
starDetected = true
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we stop walking here somehow? return error or false maybe

}
}
}
//func (handler *WhitelistHandler) RemoveRules(rules []string) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if it's commented, remove it?

@@ -10,15 +10,13 @@ import (
log "github.com/sirupsen/logrus"
)

// WhitelistHandler shows handler structure
type WhitelistHandler struct {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment missing

type WhitelistHandler struct {
queries map[string]bool
tables map[string]bool
rules []string
logger *log.Entry
}

// NewWhitelistHandler creates new white handler
func NewWhitelistHandler() *WhitelistHandler {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment missing

@@ -27,8 +25,6 @@ func NewWhitelistHandler() *WhitelistHandler {
return handler
}

// CheckQuery checks each query, returns false and error if query is not in a white list or
// if query tries to access to non-whitelisted table
func (handler *WhitelistHandler) CheckQuery(query string) (bool, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment missing

@@ -191,48 +187,41 @@ func (handler *WhitelistHandler) handleParenTables(statement *sqlparser.ParenTab
return nil
}

// Reset whitelist rules
func (handler *WhitelistHandler) Reset() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment missing

func (handler *WhitelistHandler) Reset() {
handler.queries = make(map[string]bool)
handler.tables = make(map[string]bool)
handler.rules = nil
}

// Release / reset whitelist rules
func (handler *WhitelistHandler) Release() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment missing

func (handler *WhitelistHandler) Release() {
handler.Reset()
}

// AddQueries adds queries to the list that should be whitelisted
func (handler *WhitelistHandler) AddQueries(queries []string) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment missing

func (handler *WhitelistHandler) AddQueries(queries []string) {
for _, query := range queries {
handler.queries[query] = true
}
}

// RemoveQueries removes queries from the list that should be whitelisted
func (handler *WhitelistHandler) RemoveQueries(queries []string) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment missing

func (handler *WhitelistHandler) RemoveQueries(queries []string) {
for _, query := range queries {
delete(handler.queries, query)
}
}

// AddTables adds tables that should be whitelisted
func (handler *WhitelistHandler) AddTables(tableNames []string) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment missing

func (handler *WhitelistHandler) AddTables(tableNames []string) {
for _, tableName := range tableNames {
handler.tables[tableName] = true
}
}

// RemoveTables removes whitelisted tables
func (handler *WhitelistHandler) RemoveTables(tableNames []string) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment missing

func (handler *WhitelistHandler) RemoveTables(tableNames []string) {
for _, tableName := range tableNames {
delete(handler.tables, tableName)
}
}

// AddRules adds rules that should be whitelisted
func (handler *WhitelistHandler) AddRules(rules []string) error {
func (handler *WhitelistHandler) AddPatterns(rules []string) error {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment missing

t.Fatal(err)
}
}
//whitelistHandler.RemovePatterns(testSecurityRules)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is it commented?

)

// BlacklistHandler shows handler structure
type BlacklistHandler struct {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment missing

handler.logger = log.WithField("handler", "blacklist")
return handler
}

// CheckQuery checks each query, returns false and error if query is blacklisted or
// if query tries to access to forbidden table
//CheckQuery checks input query
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new comment doesn't look very helpful, however old one was.

maybe update old comment?

@@ -105,8 +103,9 @@ func (handler *BlacklistHandler) CheckQuery(query string) (bool, error) {
func (handler *BlacklistHandler) handleAliasedTables(statement *sqlparser.AliasedTableExpr) error {
if handler.tables[sqlparser.String(statement.Expr)] {
return ErrAccessToForbiddenTableBlacklist
} else {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

golint has a rule: if method ends on

if 
   return
else 
   return

it should be changed to (remove else)

if 
  return

return

@storojs72
Copy link
Contributor Author

I think that we can now merge (into acracensor branch), and more carefully look for a golint when will merge into master

@vixentael
Copy link
Collaborator

vixentael commented Aug 1, 2018 via email

@storojs72 storojs72 merged commit 9632850 into cossacklabs:acracensor Aug 1, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants