Firebase 实时数据库被操纵

Firebase Realtime database was manipulated

我正在将 firebase 实时数据库用于汇款应用程序,用户在其中创建一个具有唯一用户名的帐户并获得 send/receive 资金。后端只是从一个帐户中减去数字并添加到另一个帐户。一切正常,直到攻击者创建了大约 3 个帐户。然后合法地将钱添加到一个帐户中,但同时将相同的金额发送到其他两个帐户。对于上下文,他创建了帐户 A、B、C,合法地将 10 美元添加到 'A',然后将这 10 美元发送到 'B' & 'C',同时使 'B' & 'C' 每人10美元。然后从头到尾重复这个过程。

每笔交易都有一个时间戳,但两笔交易之间的差异仅相差几毫秒,这似乎是在一个 forloop 中,可能是由人为延迟执行的。

我的数据库是如何构建的

两者之间的交易日期仅相差几毫秒,不可能发送到两个不同的账户

下面是我的kotlin代码

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.pay_activity_review_local)

    ref  = Firebase.database.getReference("UsersAccount")
    progressHud = SVProgressHUD(this)

    nameTV.text = recipientFullName
    tagTV.text = userTag
    currencyCodeTV.text = recipientCurrencyCode
    amountTV.text = "${CurrentUser.currencySymbol}${sendReceiveAmount.commaRepresentation()}"
    swipeBtn.setText("Swipe to $sendRequestText")

    swipeBtn.setOnStateChangeListener {

        if (it) {

           sendBtnPressed()
        } 
    }
}

private fun sendBtnPressed() {

    val valueEventListener = object : ValueEventListener {

        override fun onDataChange(dataSnapshot: DataSnapshot) {

            if (dataSnapshot.exists()) {

                currentUserBalance = dataSnapshot.child("AvailableAmount").getValue(Double::class.java)
                Log.i("xxx currentUserBalance", "$currentUserBalance")

                val desc = descriptionET.text.trim().toString()

                if (desc.isNotEmpty()) {

                    if (isRequest) {
                        // issa request
                        generateRequest(recipientId, desc, isRequest, sendReceiveAmount)

                    } else {
                        // issa send
                        if (currentUserBalance!! >= sendReceiveAmount) {

                            sendAmountToRecipientAccount(sendReceiveAmount, recipientId, desc)

                        } else {
                            // insufficient
                            progressHud.dismissImmediately()
                            extensions.infoAlertDialog(this@ReviewLocalActivity, "Why?", "Your funds are insufficient, deposit funds in your wallet to continue", false)
                        }
                    }

                } else {
                    // description is empty
                    progressHud.dismissImmediately()
                    extensions.infoAlertDialog(this@ReviewLocalActivity, "Description field empty", "Enter description of payment", false)
                }

            } else {
                return
            }
        }

        override fun onCancelled(databaseError: DatabaseError) {
            // Getting Post failed, log a message
            Log.i("xxx onCan", databaseError.toException().toString())
        }
    }

    ref = Firebase.database.getReference("UsersAccount")
    ref.child(CurrentUser.userId).addListenerForSingleValueEvent(valueEventListener) 
}

private fun sendAmountToRecipientAccount(amountToSend: Double, recipientId: String, desc: String) {

    progressHud.showWithStatus("")

    val valueEventListener = object : ValueEventListener {
        override fun onDataChange(snapshot: DataSnapshot) {

            if (snapshot.exists()) {

                val recipientBalance = snapshot.child("AvailableAmount").getValue(Double::class.java)!!

                //add amount to recipient current account
                val newBalance = recipientBalance + amountToSend

                val values: MutableMap<String, Any> = HashMap()
                values["AvailableAmount"] = newBalance

                ref.child(recipientId).updateChildren(values).addOnSuccessListener {

                    // debiting current user account
                    val amountToDebit = currentUserBalance!! - amountToSend //force unwrap cos value def exists

                    val values2: MutableMap<String, Any> = HashMap()
                    values2["AvailableAmount"] = amountToDebit

                    ref.child(CurrentUser.userId).updateChildren(values2).addOnSuccessListener {

                        //sending push notification to recipient
                        val notifyObject = JSONObject()
                        val notifyInfo = JSONObject()

                        notifyInfo.put("title","Credit")
                        notifyInfo.put("body","${CurrentUser.firstName} sent you ${CurrentUser.currencySymbol}${amountToSend.commaRepresentation()}.")
                        notifyInfo.put("badge",1)
                        notifyInfo.put("sound","default")

                        notifyObject.put("to", recipientRegToken)
                        notifyObject.put("notification", notifyInfo)

                        extensions.sendFirebasePushNotification(notifyObject, this@ReviewLocalActivity)

                        // creating transaction receipt
                        generateTransactionReceipt(recipientId, desc, isRequest, amountToSend)

                    }.addOnFailureListener {

                        Log.e("xxx firebase", "Error getting data", it)
                    }

                }.addOnFailureListener {

                    Log.e("xxx firebase", "Error getting data", it)
                }
            }
        }

        override fun onCancelled(error: DatabaseError) {

            Log.i("xxx onCancelled", error.message)
        }

    }
    ref.child(recipientId).addListenerForSingleValueEvent(valueEventListener)
}

private fun generateTransactionReceipt(recipientId: String, desc: String, isRequest: Boolean, amount: Double) {

    val currentUserFullName = "${CurrentUser.firstName} ${CurrentUser.lastName}"
    val transactionDate = ServerValue.TIMESTAMP
    val transactionId = ref.child(CurrentUser.userId).push().key

    // updating currentUser transaction history
    val values: MutableMap<String, Any> = HashMap()
    values["TransactionDate"] = transactionDate
    values["SenderId"] = recipientId
    values["SenderName"] = recipientFullName // recipientName bcos historyActivity will display it for current user
    values["SenderEmail"] = recipientEmail
    values["Description"] = desc
    values["ImageUrl"] = recipientImgUrl
    values["Amount"] = amount
    values["CurrencyCode"] = recipientCurrencyCode
    values["IsRequest"] = isRequest
    values["IsCredit"] = false
    values["IsWithdraw"] = false
    values["IsDeclined"] = false

    ref.child(CurrentUser.userId).child("TransactionHistory").child("$transactionId").updateChildren(values)
            .addOnSuccessListener {

                extensions.claimFirstTimeBonus(CurrentUser.userId) //claiming user first time bonus

                // updating recipient transaction history
                val values2: MutableMap<String, Any> = HashMap()
                values2["TransactionDate"] = transactionDate
                values2["SenderId"] = CurrentUser.userId
                values2["SenderName"] = currentUserFullName
                values2["SenderEmail"] = CurrentUser.email
                values2["Description"] = desc
                values2["ImageUrl"] = CurrentUser.imageUrl
                values2["Amount"] = amount
                values2["CurrencyCode"] = CurrentUser.currencyCode
                values2["IsRequest"] = isRequest // will be true
                values2["IsCredit"] = true
                values2["IsWithdraw"] = false
                values2["IsDeclined"] = false

                ref.child(recipientId).child("TransactionHistory").child("$transactionId").updateChildren(values2)
                        .addOnSuccessListener {

                            progressHud.dismissImmediately()

                            val intent = Intent(this, SentActivity::class.java)

                            intent.putExtra("senderAmount", amount)
                            intent.putExtra("senderCurrCode", CurrentUser.currencyCode) // both recip/sender have same code for local transaction in Sent Activity
                            intent.putExtra("recipientCurrCode", recipientCurrencyCode)
                            intent.putExtra("sendOrRequestText", sendRequestText)
                            intent.putExtra("recipientImgUrl", recipientImgUrl)
                            intent.putExtra("recipientFullName", recipientFullName)
                            intent.putExtra("isRequest", isRequest)
                            intent.putExtra("isVerified", isVerified)
                            this.startActivity(intent)

                        }.addOnFailureListener {

                            Log.e("xxx firebase", "Error getting data", it)
                        }

            }.addOnFailureListener {

                Log.e("xxx firebase", "Error getting data", it)
            }
}

private fun generateRequest(recipientId: String,
                            desc: String,
                            isRequest: Boolean,
                            amount: Double) {

    progressHud.showWithStatus("")

    val currentUserFullName = "${CurrentUser.firstName} ${CurrentUser.lastName}"
    val requestDate = ServerValue.TIMESTAMP
    val transactionId = ref.child(CurrentUser.userId).push().key

    // goes to recipient & checks if current userId isBlocked by recipient
    ref.child(recipientId).child("BlockedLists").child(CurrentUser.userId).get().addOnSuccessListener {

        val isBlocked = it.exists()

        if (isBlocked) {

            progressHud.dismissImmediately()
            extensions.infoAlertDialog(this, "Blocked", "You have been blocked by this user.", false)

        } else {

            // generating currentUser request
            val values: MutableMap<String, Any> = HashMap()
            values["TransactionDate"] = requestDate
            values["SenderId"] = recipientId
            values["SenderName"] = recipientFullName // recipientName bcos historyActivity will display it for current user
            values["SenderEmail"] = recipientEmail
            values["Description"] = desc
            values["ImageUrl"] = recipientImgUrl
            values["Amount"] = amount
            values["CurrencyCode"] = recipientCurrencyCode
            values["IsRequest"] = isRequest // will be true
            values["IsCredit"] = false
            values["IsWithdraw"] = false
            values["IsDeclined"] = false

            ref.child(CurrentUser.userId).child("TransactionHistory").child("$transactionId").updateChildren(values)
                .addOnSuccessListener {

                //sending push notification to recipient
                val notifyObject = JSONObject()
                val notifyInfo = JSONObject()

                notifyInfo.put("title","Request")
                notifyInfo.put("body","${CurrentUser.firstName} requested ${CurrentUser.currencySymbol}${amount.commaRepresentation()} from you.")
                notifyInfo.put("badge",1)
                notifyInfo.put("sound","default")

                notifyObject.put("to", recipientRegToken)
                notifyObject.put("notification", notifyInfo)

                extensions.sendFirebasePushNotification(notifyObject, this)

                // generating recipient transaction history
                val values2: MutableMap<String, Any> = HashMap()
                    values2["TransactionDate"] = requestDate
                    values2["SenderId"] = CurrentUser.userId
                    values2["SenderName"] = currentUserFullName
                    values2["SenderEmail"] = CurrentUser.email
                    values2["Description"] = desc
                    values2["ImageUrl"] = CurrentUser.imageUrl
                    values2["Amount"] = amount
                    values2["CurrencyCode"] = CurrentUser.currencyCode
                    values2["IsRequest"] = isRequest // will be true
                    values2["IsCredit"] = true
                    values2["IsWithdraw"] = false
                    values2["IsDeclined"] = false
                    values2["IsVerified"] = isVerified

                ref.child(recipientId).child("TransactionHistory").child("$transactionId").updateChildren(values2)
                    .addOnSuccessListener {

                        progressHud.dismissImmediately()

                        val intent = Intent(this, SentActivity::class.java)

                        intent.putExtra("senderAmount", sendReceiveAmount)
                        intent.putExtra("recipientCurrCode", recipientCurrencyCode)
                        intent.putExtra("sendOrRequestText", sendRequestText)
                        intent.putExtra("recipientImgUrl", recipientImgUrl)
                        intent.putExtra("recipientFullName", recipientFullName)
                        intent.putExtra("isRequest", isRequest)
                        intent.putExtra("isVerified", isVerified)
                        this.startActivity(intent)


                    }.addOnFailureListener {
                        Log.i("xxx firebase", "Error getting data", it)
                    }

                }
                .addOnFailureListener {
                    Log.i("xxx firebase", "Error getting data", it)
                }
        }

    }.addOnFailureListener {

        Log.i("xxx firebase", "Error getting data", it)
    }
  }
}

我已经尝试了一段时间,所以最后一个解决方案是来到这里。非常感谢您抽出宝贵时间提供帮助。如需进一步说明,请发表评论

很抱歉得知您的数据库中有恶意用户。

听起来您需要implement security rules确保每笔交易都是平衡的,以便添加到一个帐户的金额与从另一个帐户删除的金额相匹配。

将它与 database transaction 结合起来执行写入,以便传输的双方自动执行或失败。