gorm使用风险点
在使用gorm的同时看了下gorm的查询流程及其使用时避免不正确使用可能存在的SQLi点,mysql查询语句核心还是静态占位符方式避免SQLi。
0x01 GORM流程
GORM使用链式API(通过func clone()),而其中有三个重要的组成结构,DB/search/scope
- search 用来保存各种查询条件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21type search struct {
db *DB
whereConditions []map[string]interface{}
orConditions []map[string]interface{}
notConditions []map[string]interface{}
havingConditions []map[string]interface{}
joinConditions []map[string]interface{}
initAttrs []interface{}
assignAttrs []interface{}
selects map[string]interface{}
omits []string
orders []interface{}
preload []searchPreload
offset interface{}
limit interface{}
group string
tableName string
raw bool
Unscoped bool
ignoreOrderQuery bool
} - Scope 包含当前查询的作用域信息
1
2
3
4
5
6
7
8
9
10
11
12type Scope struct {
Search *search
Value interface{}
SQL string
SQLVars []interface{}
db *DB
instanceID string
primaryKeyField *Field
skipLeft bool
fields *[]*Field
selectAttrs *[]string
} - Prepare/State
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19func (db *DB) queryDC(ctx, txctx context.Context, dc *driverConn, releaseConn func(error), query string, args []interface{}) (*Rows, error) {
......
withLock(dc, func() {
si, err = ctxDriverPrepare(ctx, dc.ci, query)
})
if err != nil {
releaseConn(err)
return nil, err
}
ds := &driverStmt{Locker: dc, si: si}
rowsi, err := rowsiFromStatement(ctx, dc.ci, ds, args...)
if err != nil {
ds.Close()
releaseConn(err)
return nil, err
}
......
}
0x02 错误使用注入点
2.1 处理query语句逻辑
1 | func (scope *Scope) CombinedConditionSql() string { |
从CombinedConditionSql
函数及其search
结构体中可以筛选相对容易存在风险的输入点
2.2 静态占位符预编译
等同于PHP中 * 利用 1
2
3
4
5
......
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES,false);
......1
rows, err := db.Table("user").Debug().Where("username in (?) and updatexml(1,concat(0x7e, version()),1)", []string{"admin", ""}).Rows()
2.3 oder by点
- 利用
1
rows, _ := db.Table("user").Debug().Where("username in (?)", []string{"admin", ""}).Order("id and updatexml(1, concat(0x7e, version()),1)").Rows()
- src
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19func (scope *Scope) orderSQL() string {
if len(scope.Search.orders) == 0 || scope.Search.ignoreOrderQuery {
return ""
}
var orders []string
for _, order := range scope.Search.orders {
if str, ok := order.(string); ok {
orders = append(orders, scope.quoteIfPossible(str))
} else if expr, ok := order.(*expr); ok {
exp := expr.expr
for _, arg := range expr.args {
exp = strings.Replace(exp, "?", scope.AddToVars(arg), 1)
}
orders = append(orders, exp)
}
}
return " ORDER BY " + strings.Join(orders, ",")
}
2.4 group by点
- 利用
1
rows, _ := db.Table("user").Debug().Select("username, password").Group("usernme and updatexml(1, concat(0x7e, version()),1)").Rows()
- src
1
2
3
4
5
6func (scope *Scope) groupSQL() string {
if len(scope.Search.group) == 0 {
return ""
}
return " GROUP BY " + scope.Search.group
}
2.4 tablename
- 利用
1
rows, err := db.Table("user union select updatexml(1,concat(0x7e, version()),1)").Debug().Select("username, password").Rows()
- src
1
2
3
4
5
6
7
8
9
10func (scope *Scope) QuotedTableName() (name string) {
if scope.Search != nil && len(scope.Search.tableName) > 0 {
if strings.Contains(scope.Search.tableName, " ") {
return scope.Search.tableName
}
return scope.Quote(scope.Search.tableName)
}
return scope.Quote(scope.TableName())
}
防御
上述容易出现SQLi点中频繁/容易利用的依然是order by注入,updatexml()
或^(select version() regexp '^8')
也相对容易利用,针对order by 注入最好的方式还是白名单方式校验,在gorm中可以使用gorm.Expr("id = ? DESC", "and updatexml(1,concat(0x7e, version()),1)")
做表达式替换。