golang mysql 诊断之旅(2000万开房数据被曝光引发的血案)
最近由于某某漏洞原因,2000万开房数据被曝光,数据是csv格式,打开慢的要死,于是想把这2000w的开房数据导入mysql,然后用go写个简单的查询工具。
悲剧开始了: 第一步,下载 mysql模块,go get github.com/go-sql-driver/mysql, 第二步,写个小例子测试下 package main import ( "database/sql" //这包一定要引用 "encoding/json" "fmt" //这个前面一章讲过 _ "github.com/go-sql-driver/mysql" //这就是刚才下载的包 ) // 定义一个结构体,需要大写开头哦,字段名也需要大写开头哦,否则json模块会识别不了 // 结构体成员仅大写开头外界才能访问 type User struct { User string `json:"user"` Password string `json:"password"` Host string `json:"host"` } // 一如既往的main方法 func main() { // 格式有点怪,@tcp 是指网络协议(难道支持udp?),然后是域名和端口 db,e := sql.Open("mysql","root:@tcp(192.168.7.15:3306)/mysql?charset=utf8") if e != nil { //如果连接出错,e将不是nil的 print("ERROR?") return } defer db.Close() // 提醒一句,运行到这里,并不代表数据库连接是完全OK的,因为发送第一条SQL才会校验密码 汗~! rows,e := db.Query("select user,password,host from mysql.user") if e != nil { fmt.Printf("query error!!%vn",e) return } if rows == nil { print("Rows is nil") return } fmt.Println("DB rows.Next") for rows.Next() { //跟java的ResultSet一样,需要先next读取 user := new(User) // rows貌似只支持Scan方法 继续汗~! 当然,可以通过GetColumns()来得到字段顺序 row_err := rows.Scan(&user.User,&user.Password,&user.Host) if row_err != nil { print("Row error!!") return } b,_ := json.Marshal(user) fmt.Println(string(b)) // 这里没有判断错误,呵呵,一般都不会有错吧 } fmt.Println("Done") } 结果一直报错: panic: runtime error: index out of range goroutine 1 [running]: github.com/go-sql-driver/mysql.readLengthEncodedInteger(0x10fb0037,0x1,0xfc9,0x0,...) E:/go/src/github.com/go-sql-driver/mysql/utils.go:406 +0x3e8 github.com/go-sql-driver/mysql.skipLengthEnodedString(0x10fb0037,0x2,...) E:/go/src/github.com/go-sql-driver/mysql/utils.go:366 +0x38 github.com/go-sql-driver/mysql.(*mysqlConn).readColumns(0x10f88230,0x10f86500,...) E:/go/src/github.com/go-sql-driver/mysql/packets.go:482 +0x389 github.com/go-sql-driver/mysql.(*mysqlConn).getSystemVar(0x10f88230,0x530b88,0x12,...) E:/go/src/github.com/go-sql-driver/mysql/connection.go:228 +0x118 github.com/go-sql-driver/mysql.(*mysqlDriver).Open(0x5f0bf4,0x547aa8,0x2f,0x10f9f900,...) E:/go/src/github.com/go-sql-driver/mysql/driver.go:70 +0x2de database/sql.(*DB).conn(0x10f85e40,0x10f50228,0xff014c,0x5) C:/Users/ADMINI~1/AppData/Local/Temp/2/bindist465310315/go/src/pkg/database/sql/sql.go:484 +0x15e database/sql.(*DB).query(0x10f85e40,0x527b68,0x8,...) C:/Users/ADMINI~1/AppData/Local/Temp/2/bindist465310315/go/src/pkg/database/sql/sql.go:708 +0x58 database/sql.(*DB).Query(0x10f85e40,...) C:/Users/ADMINI~1/AppData/Local/Temp/2/bindist465310315/go/src/pkg/database/sql/sql.go:699 +0x6b main.main() E:/go/src/testmysql/testmysql.go:54 +0x89 goroutine 3 [syscall]: syscall.Syscall6(0x7c80a7bd,0x5,0xf70,0x10f86420,0x10f50280,...) C:/Users/ADMINI~1/AppData/Local/Temp/2/bindist465310315/go/src/pkg/runtime/zsyscall_windows_windows_386.c:97 +0x49 syscall.GetQueuedCompletionStatus(0xf70,0x10f50278,0xffffffff,...) C:/Users/ADMINI~1/AppData/Local/Temp/2/bindist465310315/go/src/pkg/syscall/zsyscall_windows_386.go:507 +0x7e net.(*resultSrv).Run(0x10f50260) C:/Users/ADMINI~1/AppData/Local/Temp/2/bindist465310315/go/src/pkg/net/fd_windows.go:150 +0x11a created by net.startServer C:/Users/ADMINI~1/AppData/Local/Temp/2/bindist465310315/go/src/pkg/net/fd_windows.go:285 +0xde goroutine 4 [select]: net.(*ioSrv).ProcessRemoteIO(0x10f50268) C:/Users/ADMINI~1/AppData/Local/Temp/2/bindist465310315/go/src/pkg/net/fd_windows.go:183 +0x171 created by net.startServer C:/Users/ADMINI~1/AppData/Local/Temp/2/bindist465310315/go/src/pkg/net/fd_windows.go:293 +0x163 exit status 2 刚开始怀疑windows没有装mysql驱动,可是兴趣来了想分析下堆栈信息,正好学点新东西, log大法,跟踪readLengthEncodedInteger,并加入如下调试代码,打印b[]byte的内存信息,发现b[0] = 0xfe,但是后面却没有数据了,所以造成了数组b的index溢出,完整代码如下: func readLengthEncodedInteger(b []byte) (num uint64,isNull bool,n int) { fmt.Println(b) fmt.Printf("0x%02xn",b[0]) switch b[0] { // 251: NULL case 0xfb: n = 1 isNull = true return // 252: value of following 2 case 0xfc: num = uint64(b[1]) | uint64(b[2])<<8 n = 3 return // 253: value of following 3 case 0xfd: num = uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16 n = 4 return // 254: value of following 8 case 0xfe: num = uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16 | uint64(b[4])<<24 | uint64(b[5])<<32 | uint64(b[6])<<40 | uint64(b[7])<<48 | uint64(b[8])<<54 n = 9 return } // 0-250: value of first byte num = uint64(b[0]) n = 1 return } 继续跟踪发现:package.go的readColumns中有一个很奇怪的退出,但是没有这个0xfe的处理,而且奇怪的是for循环只有这里可以正常return。。。 func (mc *mysqlConn) readColumns(count int) (columns []mysqlField,err error) { var data []byte var i,pos,n int var name []byte columns = make([]mysqlField,count) fmt.Println("count:") fmt.Println(count) for { data,err = mc.readPacket() if err != nil { return } // EOF Packet if data[0] == iEOF && len(data) == 5 { if i != count { err = fmt.Errorf("ColumnsCount mismatch n:%d len:%d",count,len(columns)) } return }
发现重要说明,这个Column的包后面有一个EOF_Packet,也就是上面的0xfe,于是怀疑这是go mysql driver的一个bug
|