[Android] Prevent OTP flooding
Nicolas Pomepuy
git at videolan.org
Mon Feb 23 12:42:02 UTC 2026
vlc-android | branch: master | Nicolas Pomepuy <nicolas at videolabs.io> | Wed Feb 11 09:43:50 2026 +0100| [84dd7c6e4a63e64ad833db7608e00f6170d8499e] | committer: Nicolas Pomepuy
Prevent OTP flooding
> https://code.videolan.org/videolan/vlc-android/commit/84dd7c6e4a63e64ad833db7608e00f6170d8499e
---
.../vlc/remoteaccessserver/RemoteAccessOTP.kt | 9 ++++++++
.../vlc/remoteaccessserver/RemoteAccessRouting.kt | 27 ++++++++++++++++++++++
2 files changed, 36 insertions(+)
diff --git a/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessOTP.kt b/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessOTP.kt
index 981443d5fd..6d4591d84c 100644
--- a/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessOTP.kt
+++ b/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessOTP.kt
@@ -30,6 +30,7 @@ import org.videolan.vlc.gui.helpers.NotificationHelper
import org.videolan.vlc.gui.helpers.REMOTE_ACCESS_CODE_ID
import org.videolan.vlc.remoteaccessserver.ssl.SecretGenerator
import org.videolan.vlc.remoteaccessserver.utils.CypherUtils
+import org.videolan.vlc.util.RemoteAccessUtils
import java.security.SecureRandom
@@ -117,6 +118,14 @@ object RemoteAccessOTP {
cancel(REMOTE_ACCESS_CODE_ID)
}
}
+
+ suspend fun removeAllCodes(appContext: Context) {
+ codes.clear()
+ with(NotificationManagerCompat.from(appContext)) {
+ cancel(REMOTE_ACCESS_CODE_ID)
+ }
+ RemoteAccessUtils.otpFlow.emit(null)
+ }
}
data class OTPCode(val code: String, val challenge: String, val expiration: Long)
\ No newline at end of file
diff --git a/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessRouting.kt b/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessRouting.kt
index 91c48b8e29..a31420007d 100644
--- a/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessRouting.kt
+++ b/application/remote-access-server/src/main/java/org/videolan/vlc/remoteaccessserver/RemoteAccessRouting.kt
@@ -50,6 +50,7 @@ import io.ktor.server.application.ApplicationCall
import io.ktor.server.application.call
import io.ktor.server.auth.authenticate
import io.ktor.server.http.content.staticFiles
+import io.ktor.server.plugins.origin
import io.ktor.server.request.receiveMultipart
import io.ktor.server.request.receiveParameters
import io.ktor.server.response.header
@@ -64,6 +65,7 @@ import io.ktor.server.routing.post
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.TimeoutCancellationException
+import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout
@@ -151,6 +153,8 @@ import java.text.DateFormat
import java.util.Date
import java.util.Locale
+private const val TAG = "VLC/HttpSharingServer"
+
/**
* Setup the server routing
*
@@ -197,6 +201,11 @@ fun Route.setupRouting(appContext: Context, scope: CoroutineScope) {
call.respondRedirect("/")
return at post
}
+ if (isFlooding(appContext, call.request.origin.remoteAddress)) {
+ Log.w(TAG, "Too many requests from ${call.request.origin.remoteAddress}")
+ call.respond(HttpStatusCode.TooManyRequests)
+ return at post
+ }
call.respondRedirect("/index.html#/login/error")
}
// Main end point redirect to index.html
@@ -1462,6 +1471,24 @@ fun Route.setupRouting(appContext: Context, scope: CoroutineScope) {
}
}
+val attempts:ArrayList<Pair<String, Long>> = arrayListOf()
+private suspend fun isFlooding(appContext: Context, ip:String): Boolean {
+ val now = System.currentTimeMillis()
+ attempts.add(Pair(ip, now))
+ attempts.removeIf { System.currentTimeMillis() - it.second > 3_600_000L }
+ val nbAttemptsBySec = attempts.filter { it.first == ip && it.second > now - 1_000L }.size
+ if (nbAttemptsBySec > 1) {
+ delay((nbAttemptsBySec - 1) * 500L)
+ }
+ val nbAttemptsByMin = attempts.filter { it.first == ip && it.second > now - 60_000L }.size
+ if (nbAttemptsByMin > 10) {
+ RemoteAccessOTP.removeAllCodes(appContext)
+ delay((nbAttemptsByMin - 10) * 2_000L)
+ }
+ val nbAttempts = attempts.filter { it.first == ip }.size
+ return nbAttempts > 20
+}
+
/**
* The the list of all the log files
*
More information about the Android
mailing list