From 8009f1a510145caf819eea5ecce66e9be873406d Mon Sep 17 00:00:00 2001 From: Spotlight Date: Fri, 25 Dec 2020 01:45:38 -0600 Subject: [PATCH] Reduce security Nintendo makes the unfortunate decision of sending the plaintext token to the client in a few requests. Instead of storing a SHA-256 hash of the MD5 we expect from the Wii, we now store both the computed MD5 and plaintext generated token during registration. The author of this commit apologizes. --- database.sql | 33 +++++++++++++++++---------------- ias.go | 13 +++---------- route.go | 6 +----- 3 files changed, 21 insertions(+), 31 deletions(-) diff --git a/database.sql b/database.sql index b48983d..d79f974 100644 --- a/database.sql +++ b/database.sql @@ -25,6 +25,21 @@ Follow and practice proper security practices before handling user data. /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; +-- +-- Table structure for table `shop_titles` +-- + +DROP TABLE IF EXISTS `shop_titles`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `shop_titles` ( + `title_id` varchar(16) NOT NULL, + `version` int(11) NOT NULL, + `description` mediumtext DEFAULT 'yada yada', + PRIMARY KEY (`title_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +/*!40101 SET character_set_client = @saved_cs_client */; + -- -- Table structure for table `owned_titles` -- @@ -43,21 +58,6 @@ CREATE TABLE `owned_titles` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; /*!40101 SET character_set_client = @saved_cs_client */; --- --- Table structure for table `shop_titles` --- - -DROP TABLE IF EXISTS `shop_titles`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `shop_titles` ( - `title_id` varchar(16) NOT NULL, - `version` int(11) NOT NULL, - `description` mediumtext DEFAULT 'yada yada', - PRIMARY KEY (`title_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -/*!40101 SET character_set_client = @saved_cs_client */; - -- -- Table structure for table `userbase` -- @@ -67,7 +67,8 @@ DROP TABLE IF EXISTS `userbase`; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `userbase` ( `DeviceId` varchar(10) NOT NULL, - `DeviceToken` varchar(64) NOT NULL COMMENT 'This token should be considered a secret, so after generation only the sha256sum of the md5 the Wii sends is inserted.', + `DeviceTokenUnhashed` varchar(21) NOT NULL COMMENT 'Sadly, sometimes we must return the device token in plaintext.', + `DeviceToken` varchar(32) NOT NULL COMMENT 'The MD5 of the device token is sent for most requests. We store it to avoid wasting compute time.', `AccountId` varchar(9) NOT NULL, `Region` varchar(3) NOT NULL, `Country` varchar(2) NOT NULL, diff --git a/ias.go b/ias.go index 7bbaff9..547d4d6 100644 --- a/ias.go +++ b/ias.go @@ -19,7 +19,6 @@ package main import ( "crypto/md5" - "crypto/sha256" "database/sql" "errors" "fmt" @@ -34,7 +33,7 @@ var registerUser *sql.Stmt func iasInitialize() { var err error - registerUser, err = db.Prepare(`INSERT INTO userbase (DeviceId, DeviceToken, AccountId, Region, Country, Language, SerialNo, DeviceCode) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`) + registerUser, err = db.Prepare(`INSERT INTO userbase (DeviceId, DeviceTokenUnhashed, DeviceToken, AccountId, Region, Country, Language, SerialNo, DeviceCode) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`) if err != nil { log.Fatalf("ias initialize: error preparing statement: %v\n", err) } @@ -120,19 +119,13 @@ func register(e *Envelope) { // Generate a random 9-digit number, padding zeros as necessary. accountId := fmt.Sprintf("%9d", rand.Intn(999999999)) - // This is where it gets hairy. // Generate a device token, 21 characters... deviceToken := RandString(21) - // ...and then its md5, because the Wii sends this... + // ...and then its md5, because the Wii sends this for most requests. md5DeviceToken := fmt.Sprintf("%x", md5.Sum([]byte(deviceToken))) - // ...and then the sha256 of that md5. - // We'll store this in our database, as storing the md5 itself is effectively the token. - // It would not be good for security to directly store the token either. - // This is the hash of the md5 represented as a string, not individual byte values. - doublyHashedDeviceToken := fmt.Sprintf("%x", sha256.Sum256([]byte(md5DeviceToken))) // Insert all of our obtained values to the database.. - _, err = registerUser.Exec(e.DeviceId(), doublyHashedDeviceToken, accountId, e.Region(), e.Country(), e.Language(), serialNo, deviceCode) + _, err = registerUser.Exec(e.DeviceId(), deviceToken, md5DeviceToken, accountId, e.Region(), e.Country(), e.Language(), serialNo, deviceCode) if err != nil { // It's okay if this isn't a MySQL error, as perhaps other issues have come in. if driverErr, ok := err.(*mysql.MySQLError); ok { diff --git a/route.go b/route.go index a7693d7..33bbc3d 100644 --- a/route.go +++ b/route.go @@ -1,7 +1,6 @@ package main import ( - "crypto/sha256" "database/sql" "fmt" "io/ioutil" @@ -172,11 +171,8 @@ func checkAuthentication(e *Envelope) (bool, error) { return false, nil } - // The Wii sends us a MD5 of its given token. We store a SHA-256 hash of that. - hashedDeviceToken := fmt.Sprintf("%x", sha256.Sum256([]byte(hash))) - // Check using various input given. - row := routeVerify.QueryRow(hashedDeviceToken, accountId, e.DeviceId()) + row := routeVerify.QueryRow(hash, accountId, e.DeviceId()) var throwaway string err = row.Scan(&throwaway)