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 结合起来执行写入,以便传输的双方自动执行或失败。
我正在将 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 结合起来执行写入,以便传输的双方自动执行或失败。