java-james / flutter_dotenv

Loads environment variables from `.env`.
https://pub.dartlang.org/packages/flutter_dotenv
MIT License
209 stars 46 forks source link

Reverse engineering bug #100

Open yhbsh opened 2 months ago

yhbsh commented 2 months ago

I was able to get all my .env files with all secrect keys from the apk, is it normal or when publishing to google play it will be different?

just do

unzip -l app.apk

under flutter_assets folder, you will find all assets, include .env files

ccosnean commented 3 weeks ago

@yhbsh yes, hence the "add the .env" into your pubspec.yml step. This approach is flawed in its core, sadly.

assets:
  - .env
yhbsh commented 3 weeks ago

@yhbsh yes, hence the "add the .env" into your pubspec.yml step. This approach is flawed in its core, sadly.

assets:
  - .env

That's kinda sad

ccosnean commented 3 weeks ago

@yhbsh agree. I found this package envied it uses the build_runner to take the .env contents and bake them into a generated file.

I added the .env and .g.dart files to .gitignore. And the package has a obfuscation feature. The secrets are still in the binary, and you could reverse engineer it, but more hidden... still looking for a better solution, if there is any...

looking for a solution with --dart-define.

UPDATE:

Never mind, after more research --dart-define also bakes the the secrets into the binary (makes sense since it is a compile flag... 🤣 ), there's no way around it and it seems that the only mitigation for it is code obfuscation.

yhbsh commented 3 weeks ago

@yhbsh agree. I found this package envied it uses the build_runner to take the .env contents and bake them into a generated file.

I added the .env and .g.dart files to .gitignore. And the package has a obfuscation feature. The secrets are still in the binary, and you could reverse engineer it, but more hidden... still looking for a better solution, if there is any...

looking for a solution with --dart-define.

UPDATE:

Never mind, after more research --dart-define also bakes the the secrets into the binary (makes sense since it is a compile flag... 🤣 ), there's no way around it and it seems that the only mitigation for it is code obfuscation.

Were you able to extract the secrets from the binary? Using --dart-define

ccosnean commented 3 weeks ago

@yhbsh I haven't tried, i read it in this article

The main advantage of using --dart-define is that we're no longer hardcoding sensitive keys in the source code. But when we compile our app, the keys are still going to be baked in in the release binary:

Screenshot 2024-06-04 at 22 27 24

API keys and source code are combined to produce the release binary

This makes total sense that the keys are ending up in the final binary, in fact that is always the case with compiled binaries, unless you are downloading them from a remote source at runtime.

yhbsh commented 3 weeks ago

@yhbsh I haven't tried, i read it in this article

The main advantage of using --dart-define is that we're no longer hardcoding sensitive keys in the source code. But when we compile our app, the keys are still going to be baked in in the release binary:

Screenshot 2024-06-04 at 22 27 24

API keys and source code are combined to produce the release binary

This makes total sense that the keys are ending up in the final binary, in fact that is always the case with compiled binaries, unless you are downloading them from a remote source at runtime.

True, but I am not sure we can extract them easily from the release binary, please try and see where it goes, if that's true then obfuscating is a must

ccosnean commented 3 weeks ago

@yhbsh it is not easy, with obfuscation it is even harder. But still theoretically possible.

maeddin commented 2 weeks ago

Why not simply use --dart-define-from-file=.env? I don't quite understand the point of third-party packages here.

You can never completely prevent users from being able to read the secrets. Client secrets should only be an additional security hurdle.

ccosnean commented 2 weeks ago

Why not simply use --dart-define-from-file=.env? I don't quite understand the point of third-party packages here.

You can never completely prevent users from being able to read the secrets. Client secrets should only be an additional security hurdle.

@maeddin you can use what's best for you.

I use flutter_dotenv or envied now, cause it gives you insight on your env at build time, and you have a Typed env to use, that i find really handy.

With --dart-define you will know about a missing env var at Runtime, with String.fromEnvironment.

maeddin commented 2 weeks ago

Why not simply use --dart-define-from-file=.env? I don't quite understand the point of third-party packages here. You can never completely prevent users from being able to read the secrets. Client secrets should only be an additional security hurdle.

@maeddin you can use what's best for you.

I use flutter_dotenv or envied now, cause it gives you insight on your env at build time, and you have a Typed env to use, that i find really handy.

With --dart-define you will know about a missing env var at Runtime, with String.fromEnvironment.

Why should env variables be missing at runtime? They are defined in advance at compile time, so I don't see the point of defining them again at runtime.

ccosnean commented 2 weeks ago

They are defined in advance at compile time

That's wrong. You can build you app without --dart-define flags and it will produce a valid build, with empty strings.

Screenshot 2024-06-13 at 18 10 10
maeddin commented 2 weeks ago

They are defined in advance at compile time

That's wrong. You can build you app without --dart-define flags and it will produce a valid build, with empty strings.

Screenshot 2024-06-13 at 18 10 10

Of course they are only defined at compile time if you use const variables for this (that's what the const keyword is for in the first place). And if the variable doesn't exist, an empty String is set, but I don't see how that contradicts my statement. It just sets an empty String because that is the default value in the method definition: external const factory String.fromEnvironment(String name, {String defaultValue = ""});

"This constructor is only guaranteed to work when invoked as const. It may work as a non-constant invocation on some platforms which have access to compiler options at run-time, but most ahead-of-time compiled platforms will not have this information."

https://api.flutter.dev/flutter/dart-core/String/String.fromEnvironment.html

maeddin commented 2 weeks ago

I use flutter_dotenv or envied now, cause it gives you insight on your env at build time, and you have a Typed env to use, that i find really handy.

With --dart-define you will know about a missing env var at Runtime, with String.fromEnvironment.

You can also use typed values with int.fromEnvironment and bool.fromEnvironment without parsing it yourself. Or am I getting this wrong?

ccosnean commented 2 weeks ago

this comment With --dart-define you will know about a missing env var at Runtime, with String.fromEnvironment.

@maeddin again, with --dart-define you will know about a missing env only during runtime, when the String.fromEnvironment is executed.

envied uses build_runner and parses the variables before you run the app, while generating files. your app will not compile if the env is wrong. I find this really helpful, and that's why i use it.

maeddin commented 2 weeks ago

this comment With --dart-define you will know about a missing env var at Runtime, with String.fromEnvironment.

@maeddin again, with --dart-define you will know about a missing env only during runtime, when the String.fromEnvironment is executed.

envied uses build_runner and parses the variables before you run the app, while generating files. your app will not compile if the env is wrong. I find this really helpful, and that's why i use it.

Okay, now I understand what you mean. I thought you were referring to flutter_dotenv. My mistake. I personally also find envied more useful.