'Firebase Realtime database was manipulated
I am using firebase realtime database for a money transfer app where a user creates an account with a unique username and gets to send/receive money. The backend just subtracts numbers from one account and adds to another account. Everything worked fine until an attacker created about 3 accounts. Then added money to one legally but then sent the same amount to the other two accounts at the same time. For context, He created account A,B,C, added $10 to 'A' legally, then sent that $10 to 'B' & 'C' at the same time making 'B' & 'C' have $10 each. Then repeated the process allover.
Each transaction has a timestamp but the difference between the two transactions was just milliseconds apart which seems like it was in a forloop and could have been executed by a human cause of delay.
How my database is structured
The transaction dates between the two shows just milliseconds apart which shouldn't be possible sending to two different accounts

Below is my kotlin code
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)
}
}
}
My last solution was coming here as I have been trying for a while. Thank you so much for taking your time out to help. For further clarification please leave a comment
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|

