-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Java: Add query for sensitive data exposed in text fields #15396
Java: Add query for sensitive data exposed in text fields #15396
Conversation
QHelp previews: java/ql/src/Security/CWE/CWE-200/AndroidSensitiveNotifications.qhelpExposure of sensitive information to notificationsSensitive information such as passwords or two-factor authentication (2FA) codes should not be exposed in a system notification. Notifications should not be considered secure, as other untrusted applications may be able to use a RecommendationDo not expose sensitive data in notifications. ExampleIn the following sample, the // BAD: `password` is exposed in a notification.
void confirmPassword(String password) {
NotificationManager manager = NotificationManager.from(this);
manager.send(
new Notification.Builder(this, CHANNEL_ID)
.setContentText("Your password is: " + password)
.build());
} References
|
|
45f414a
to
40cff8c
Compare
QHelp previews: java/ql/src/Security/CWE/CWE-200/AndroidSensitiveTextField.qhelpExposure of sensitive information to UI text views.Sensitive information such as passwords should not be displayed in UI components unless explicitly required, to mitigate shoulder-surfing attacks. RecommendationFor editable text fields containing sensitive information, the ExampleIn the following (bad) case, sensitive information in TextView pwView = getViewById(R.id.pw_text);
pwView.setText("Your password is: " + password); In the following (good) case, the user must press a button to reveal sensitive information. TextView pwView = findViewById(R.id.pw_text);
pwView.setVisibility(View.INVISIBLE);
pwView.setText("Your password is: " + password);
Button showButton = findViewById(R.id.show_pw_button);
showButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
pwView.setVisibility(View.VISIBLE);
}
}); References
|
40cff8c
to
22d8269
Compare
…w is masked, and find more instances of findViewById.
22d8269
to
3abd670
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks quite good to me! I like the introduction of the Layout.qll
library and moving everything layout-related there.
I added some minor comments for your consideration. As always, DCA, MRVA and docs review will have the final say.
private class MaskCall extends MethodCall { | ||
MaskCall() { | ||
this.getMethod().getAnOverride*().hasQualifiedName("android.widget", "TextView", "setInputType") | ||
or | ||
this.getMethod().getAnOverride*().hasQualifiedName("android.view", "View", "setVisibility") | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it worth checking the arguments of the method call? Either that it's View.INVISIBLE
or its literal value, or some InputType.*PASSWORD*
variation, its literal value, or a binary OR operation with one of those as an operand.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a potential precision improvement to reduce FNs, but I'm not sure it's worthwhile. For setVisibility
, it seems redundant; as the visibility is either being set from invisible to visible, or from visible to invisible; either implying it would have been invisible at some point. For setInputType
, ignoring it yields a few more results in just one project, which set input type to values other than passwords so would appear with a more precise check here, but i'm not sure whether they are TPs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, let's just roll with it and revisit this in the future if we start seeing FNs.
} | ||
|
||
/** Holds if data can flow from `e1` to `e2` with local flow steps as well as flow through fields containing `View`s */ | ||
private predicate localViewFieldExprFlow(Expr e1, Expr e2) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit unsure about how this will perform. If you see any indications of this performing badly in DCA let me know, there are a couple of tricks we can use as alternative to this kind of local flow (the modules SimpleGlobal
and LocalTaintFlow
), and we can also tweak the field read-write step to use the FieldValueNode
instead. But only really needed if this predicate gives problems.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Performance does not seem to be much of an issue at the moment. DCA looks fine and MRVA runs take about 6 mins, the same as other queries like sql injection.
id_field.getDeclaringType() = r_id and | ||
id_field.hasName(name) | ||
| | ||
DataFlow::localExprFlow(id_field.getAnAccess(), result.getArgument(0)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider whether SimpleGlobal
flow may be more appropriate (if you think you need more than local flow for tracking the ID).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've not seen any cases where local flow is insufficient to find the id used by a call to findViewById
} | ||
|
||
/** A local flow step that also flows through access to fields containing `View`s */ | ||
private predicate localViewFieldFlowStep(DataFlow::Node node1, DataFlow::Node node2) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to confirm, this is for tracking something like the following, right?
EditText test15;
void test1() {
test15 = findViewById(R.id.test15);
}
void test2(String password) {
test15.setVisibility(View.INVISIBLE);
test15.setText(password);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed; it was a common FP in one of my MRVA runs for a view defined in xml as textPassword
to be stored in a field this way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, thank you! Only two very small comments 👍
java/ql/src/Security/CWE/CWE-200/AndroidSensitiveTextField.qhelp
Outdated
Show resolved
Hide resolved
java/ql/src/Security/CWE/CWE-200/AndroidSensitiveTextField.qhelp
Outdated
Show resolved
Hide resolved
Co-authored-by: Ben Ahmady <32935794+subatoi@users.noreply.github.com>
@subatoi Those changes have been made - can you re-approve? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you!
This query checks for sensitive data being exposed to text fields.
Fields that have their visibility or input type set (dynamically or via xml) are considered safe and excluded.