Google's instructions for implementing the attestation API are:
Obtain a nonce. Request a SafetyNet attestation. Transfer the response to your server. Use the response on your server, along with your other anti-abuse signals, to control your app's behavior.
I understand the nonce should be obtained from the server. What's to stop an attacker from running two versions of the app - one on a legit device and one on an insecure device and doing the following:
- App on insecure device gets nonce from my server
- App on secure device calls Google's attestation API using this nonce
- App on secure device gets signed JWS response from Google
- Attacker transfers the JWS response to app on insecure device
- App on insecure device sends JWS response to my server
My app server would verify the JWS - including the nonce - and think that the app on the insecure device is actually secure.
It's not that simple as it sounds. If you, for example, use certificate pinning, a man in the middle attack would be hard to nearly impossible.
So now it may be an option to reverse engineer the app and capture the JWS, here you can add obfuscation to make it harder to reverse engineer.
If he gets his hands on a JWS you can limit the time available for a JWS to be stored in the backend and make it even harder to use (maybe allow a JWS to be only stored for 5 minutes or less) to "invalidate" it.
Also as mentioned by Google, if you build your nonce from some data the user will send to the server with the current request, the attacker also needs to have this data in the request, because you could simply reverse compute the nonce (if you have stored the final nonce + the random data you computed) and if this doesn't match you can simply reject the request.
Also if the device is secure, an attacker shouldn't be able to just extract the nonce from it.
Please take this all with a grain of salt, as I'm not a security expert.