如何防止通过控制台进行集合修改以实现安全的更新操作?

时间:2014-09-18 14:33:24

标签: meteor

我对Meteor相当新,我只想弄清楚流星的安全性。

我正在编写一个测验应用,允许登录用户保存他们的分数。我创建了一个由用户ID和分数数组组成的集合。我公开推新分数的方式是服务器端的一种方法:

Meteor.methods({ 
  'pushScore' : function(playerId, playerScore) {
    UserScores.upsert({ userId : playerId}, {$push : {scores : playerScore}});
  }
});

我从客户端点击按钮调用该方法,如下所示:

if (Meteor.userId()){
  Meteor.call('pushScore', Meteor.userId(), Session.get("score"));
}

我在这里有以下问题:

  • 显然,用户可以操纵“会话”中的得分值并欺骗系统。什么是备用安全机制,以便在进行测验时跟踪运行得分?
  • 另一个可能是一个更大的问题。如何阻止用户仅对我的方法“pushScore”发出控制台调用,并再次通过添加100分来欺骗系统?
  • 我在这里设计的方式是否存在固有的缺陷?

这只是一个示例应用程序,但我可以轻松想象一个可以模仿这个的真实场景。在这种情况下,最好的做法是什么?

提前致谢。

干杯..

3 个答案:

答案 0 :(得分:4)

正如@Peppe建议的那样,你应该以某种方式将逻辑移到服务器上。 Meteor安全(以及一般的Web安全)的主要规则是

您无法信任客户。

原因就是你已经提到的:如果客户端可以做什么,那么就没有办法阻止流氓用户从浏览器控制台做同样的事情,甚至写自己的恶意客户端将利用泄漏。

在您的情况下,这意味着如果客户端能够将分数添加到分数,那么无论您采用何种安全措施,用户都可以这样做。你可以或多或少地做到这一点,但你的系统有一个设计的泄漏,不能完全关闭。

因此,唯一的防弹解决方案是让服务器决定何时分配点数。我假设在测验应用中,用户在选择问题的正确答案时获得积分。因此,如果答案是正确的,那么不要在客户端上检查,而是创建一个服务器端方法,该方法将接收问题ID,答案ID并增加用户分数。然后确保用户不能仅使用所有可能的答案调用此方法,其方式与您的测验设计相对应 - 例如,如果选择了错误答案则给出负点,或者允许在一段时间内仅回答一次相同的问题。

最后,确保客户端不仅在收到的数据中获得正确的答案ID。

答案 1 :(得分:2)

简而言之,您的问题有两种常见的解决方法:

  1. 如果你使用Meteor.method不传递Meteor.call中的任何参数,服务器可以而且应该收集它计划在服务器端插入/更新的数据。

  2. 您可以使用集合“allow”方法向集合中添加验证函数,以验证来自客户端的任何更新,在这种情况下,您不需要Meteor.method,只需从客户端进行更新即可验证服务器端。

答案 2 :(得分:2)

meteor中的安全性(插入/更新/删除操作)与任何其他框架中的安全性相同:在执行用户采取的操作之前,请确保用户有权执行该操作。安全性可能在Meteor中显得有些弱点,但它不会受到其它框架的影响(但是,通过控制台在Meteor中更容易利用它)。

解决问题的最佳方式可能因情况而异,但这里有一个例子:如果用户发帖,用户应获得5分。这是一个解决问题的坏方法:

if(Meteor.isClient){

    // Insert the post and increase points.
    Posts.insert({userId: Meteor.userId(), post: "The post."})
    Meteor.users.update(Meteor.userId(), {$inc: {'profile.points': 5}})

}

if(Meteor.isServer){

    Posts.allow({
        insert: function(userId, doc){

            check(doc, {
                _id: String,
                userId: String,
                post: String
            })

            // You must be yourself.
            if(doc.userId != userId){
                return false
            }

            return true

        }
    })

    Meteor.users.allow({
        update: function(userId, doc, fieldNames, modifier){

            check(modifier, {
                $inc: {
                    'profile.points': Number
                }
            })

            if(modifier.$inc['profile.points'] != 5){
                return false
            }

            return true

        }
    })

}

是什么让它变坏?用户可以在不发布帖子的情况下增加积分。这是一个更好的解决方案:

if(Meteor.isClient){

    // Insert the post and increase points.
    Method.call('postAndIncrease', {userId: Meteor.userId(), post: "The post."})

}

if(Meteor.isServer){

    Meteor.methods({
        postAndIncrease: function(post){

            check(post, {
                userId: String,
                post: String
            })

            // You must be yourself.
            if(post.userId != this.userId){
                return false
            }

            Posts.insert(post)
            Meteor.users.update(this.userId, {$inc: {'profile.points': 5}})

        }
    })

}

更好,但仍然很糟糕。为什么?由于延迟(帖子是在服务器上创建的,而不是客户端)。这是一个更好的解决方案:

if(Meteor.isClient){

    // Insert the post and increase points.
    Posts.insert({userId: Meteor.userId(), post: "The post."})

}

if(Meteor.isServer){

    Posts.allow({
        insert: function(userId, doc){

            check(doc, {
                _id: String,
                userId: String,
                post: String
            })

            // You must be yourself.
            if(doc.userId != userId){
                return false
            }

            return true

        }
    })

    Posts.find().observe({
        added: function(post){

            // When new posts are added, the user gain the points.
            Meteor.users.update(post.userId, {$inc: {'profile.points': 5}})

        }
    })

}

这个解决方案唯一的缺点是点数增加的延迟,但这是我们必须忍受的事情(至少在目前)。在服务器上使用observe也可能是一个缺点,但我认为你可以通过使用包集合钩子来传递它。