CVE-2017-16031 (JavaScript, published 2018-06-04T19:29Z)
Description: value: Socket.io is a realtime application framework that provides communication via websockets. Because socket.io 0.9.6 and earlier depends on `Math.random()` to create socket IDs, the IDs are predictable. An attacker is able to guess the socket ID and gain access to socket.io servers, potentially obtaining sensitive information.
Severity: MEDIUM (CVSS v3 base score: 7.5)
Weakness: CWE-330 \u2014 Use of Insufficiently Random Values
The product uses insufficiently random numbers or values in a security context that depends on unpredictable numbers.
Repository: https://github.com/socketio/socket.io
Files changed:
lib/manager.js
test/manager.test.js
Commit message: Making ID generation securely random


Vulnerable code:
Manager.prototype.generateId = function () {
  return Math.abs(Math.random() * Math.random() * Date.now() | 0).toString()
    + Math.abs(Math.random() * Math.random() * Date.now() | 0).toString();
      var id = self.generateId()
    this.get('authorization').call(this, data, function (err, authorized) {
      fn(err, authorized);

Fixed code:
  , crypto = require('crypto')
  this.sequenceNumber = Date.now() | 0;
 
Manager.prototype.generateId = function (data) {
  var rand = new Buffer(15); // multiple of 3 for base64
  this.sequenceNumber = (this.sequenceNumber + 1) | 0;
  rand.writeInt32BE(this.sequenceNumber, 11);
  crypto.randomBytes(12).copy(rand);
  return rand.toString('base64').replace(/\//g, '_').replace(/\+/g, '-');
      var id = self.generateId(newData || handshakeData)
    this.get('authorization').call(this, data, function (err, authorized, newData) {
      fn(err, authorized, newData);
  'test authorization gets handshake data': function (done) {
    var port = ++ports
      , io = sio.listen(port)
      , cl = client(port);

    io.configure(function () {
      function auth (data, fn) {
        data.query.should.have.foo;
        data.query.foo.should.eql('bar');
        fn(null, false);
      };

      io.set('authorization', auth);
    });

    cl.get('/socket.io/{protocol}/?foo=bar', function (res, data) {
      res.statusCode.should.eql(403);
      data.should.match(/handshake unauthorized/);

      cl.end();
      io.server.close();
      done();
    });
  },

  'test that authorization can view handshake data': function (done) {
    var port = ++ports
      , io = sio.listen(port)
      , cl = client(port);

    io.configure(function () {
      function auth (data, fn) {
        data.query.should.have.foo;
        data.query.foo.should.eql('bar');
        fn(null, true);
      };

      io.set('authorization', auth);
    });

    cl.get('/socket.io/{protocol}/?foo=bar', function (res, data) {
      res.statusCode.should.eql(200);

      cl.end();
      io.server.close();
      done();
    });
  },

  'test that authorization can change handshake data': function (done) {
    var port = ++ports
      , io = sio.listen(port)
      , cl = client(port);

    io.configure(function () {
      function auth (data, fn) {
        var replacement = { baz: 'qu' };
        for (i in data) {
          if (data.hasOwnProperty(i)) {
            replacement[i] = data[i];
          }
        }
        fn(null, true, replacement);
      };

      io.set('authorization', auth);
    });

    cl.get('/socket.io/{protocol}/', function (res, data) {
      var id = data.split(':', 2)[0];
      res.statusCode.should.eql(200);
      io.handshaken[id].should.have.baz;
      io.handshaken[id].baz.should.eql('qu');

      cl.end();
      io.server.close();
      done();
    });
  },