【问题标题】:Firebase Realtime database was manipulatedFirebase 实时数据库被操纵
【发布时间】:2022-02-06 00:01:22
【问题描述】:

我正在将 firebase 实时数据库用于汇款应用程序,其中用户创建具有唯一用户名的帐户并可以发送/接收资金。后端只是从一个帐户中减去数字并添加到另一个帐户。一切正常,直到攻击者创建了大约 3 个帐户。然后合法地将钱存入一个帐户,但随后将相同的金额同时发送到其他两个帐户。就上下文而言,他创建了账户 A、B、C,合法地向“A”添加了 10 美元,然后将这 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)
    }
  }
}

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

【问题讨论】:

    标签: android firebase kotlin firebase-realtime-database


    【解决方案1】:

    很遗憾得知您的数据库中存在恶意用户。

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

    将其与database transaction 组合以执行写入,以便传输双方自动执行或失败。

    【讨论】:

      猜你喜欢
      • 2021-05-08
      • 2020-11-25
      • 2018-12-12
      • 2020-07-05
      • 2021-07-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-25
      相关资源
      最近更新 更多