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.
This commit is contained in:
Spotlight 2020-12-25 01:45:38 -06:00
parent 7558aeed43
commit 8009f1a510
No known key found for this signature in database
GPG Key ID: 874AA355B3209BDC
3 changed files with 21 additions and 31 deletions

View File

@ -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' */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; /*!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` -- Table structure for table `owned_titles`
-- --
@ -43,21 +58,6 @@ CREATE TABLE `owned_titles` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
/*!40101 SET character_set_client = @saved_cs_client */; /*!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` -- Table structure for table `userbase`
-- --
@ -67,7 +67,8 @@ DROP TABLE IF EXISTS `userbase`;
/*!40101 SET character_set_client = utf8 */; /*!40101 SET character_set_client = utf8 */;
CREATE TABLE `userbase` ( CREATE TABLE `userbase` (
`DeviceId` varchar(10) NOT NULL, `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, `AccountId` varchar(9) NOT NULL,
`Region` varchar(3) NOT NULL, `Region` varchar(3) NOT NULL,
`Country` varchar(2) NOT NULL, `Country` varchar(2) NOT NULL,

13
ias.go
View File

@ -19,7 +19,6 @@ package main
import ( import (
"crypto/md5" "crypto/md5"
"crypto/sha256"
"database/sql" "database/sql"
"errors" "errors"
"fmt" "fmt"
@ -34,7 +33,7 @@ var registerUser *sql.Stmt
func iasInitialize() { func iasInitialize() {
var err error 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 { if err != nil {
log.Fatalf("ias initialize: error preparing statement: %v\n", err) 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. // Generate a random 9-digit number, padding zeros as necessary.
accountId := fmt.Sprintf("%9d", rand.Intn(999999999)) accountId := fmt.Sprintf("%9d", rand.Intn(999999999))
// This is where it gets hairy.
// Generate a device token, 21 characters... // Generate a device token, 21 characters...
deviceToken := RandString(21) 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))) 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.. // 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 { if err != nil {
// It's okay if this isn't a MySQL error, as perhaps other issues have come in. // It's okay if this isn't a MySQL error, as perhaps other issues have come in.
if driverErr, ok := err.(*mysql.MySQLError); ok { if driverErr, ok := err.(*mysql.MySQLError); ok {

View File

@ -1,7 +1,6 @@
package main package main
import ( import (
"crypto/sha256"
"database/sql" "database/sql"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@ -172,11 +171,8 @@ func checkAuthentication(e *Envelope) (bool, error) {
return false, nil 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. // Check using various input given.
row := routeVerify.QueryRow(hashedDeviceToken, accountId, e.DeviceId()) row := routeVerify.QueryRow(hash, accountId, e.DeviceId())
var throwaway string var throwaway string
err = row.Scan(&throwaway) err = row.Scan(&throwaway)