Merge pull request #6576 from atorralba/atorralba/android-cleartext-storage-filesystem

Java: Create new query Cleartext storage of sensitive information in Android filesystem
This commit is contained in:
Tony Torralba
2022-01-17 14:02:38 +01:00
committed by GitHub
13 changed files with 644 additions and 15 deletions

View File

@@ -115,6 +115,7 @@ private module Frameworks {
private import semmle.code.java.security.AndroidIntentRedirection
private import semmle.code.java.security.ResponseSplitting
private import semmle.code.java.security.InformationLeak
private import semmle.code.java.security.Files
private import semmle.code.java.security.GroovyInjection
private import semmle.code.java.security.JexlInjectionSinkModels
private import semmle.code.java.security.JndiInjection
@@ -258,20 +259,6 @@ private predicate sinkModelCsv(string row) {
"java.net;URLClassLoader;false;URLClassLoader;(String,URL[],ClassLoader);;Argument[1];open-url",
"java.net;URLClassLoader;false;URLClassLoader;(String,URL[],ClassLoader,URLStreamHandlerFactory);;Argument[1];open-url",
"java.net;URLClassLoader;false;newInstance;;;Argument[0];open-url",
// Create file
"java.io;FileOutputStream;false;FileOutputStream;;;Argument[0];create-file",
"java.io;RandomAccessFile;false;RandomAccessFile;;;Argument[0];create-file",
"java.io;FileWriter;false;FileWriter;;;Argument[0];create-file",
"java.nio.file;Files;false;move;;;Argument[1];create-file",
"java.nio.file;Files;false;copy;;;Argument[1];create-file",
"java.nio.file;Files;false;newOutputStream;;;Argument[0];create-file",
"java.nio.file;Files;false;newBufferedReader;;;Argument[0];create-file",
"java.nio.file;Files;false;createDirectory;;;Argument[0];create-file",
"java.nio.file;Files;false;createFile;;;Argument[0];create-file",
"java.nio.file;Files;false;createLink;;;Argument[0];create-file",
"java.nio.file;Files;false;createSymbolicLink;;;Argument[0];create-file",
"java.nio.file;Files;false;createTempDirectory;;;Argument[0];create-file",
"java.nio.file;Files;false;createTempFile;;;Argument[0];create-file",
// Bean validation
"javax.validation;ConstraintValidatorContext;true;buildConstraintViolationWithTemplate;;;Argument[0];bean-validation",
// Set hostname

View File

@@ -0,0 +1,101 @@
/**
* Provides classes and predicates to reason about cleartext storage in the Android filesystem
* (external or internal storage).
*/
import java
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.ExternalFlow
import semmle.code.java.security.CleartextStorageQuery
import semmle.code.java.security.Files
import semmle.code.xml.AndroidManifest
private class AndroidFilesystemCleartextStorageSink extends CleartextStorageSink {
AndroidFilesystemCleartextStorageSink() {
filesystemInput(_, this.asExpr()) and
// Make sure we are in an Android application.
exists(AndroidManifestXmlFile manifest)
}
}
/** A call to a method or constructor that may write to files to the local filesystem. */
class LocalFileOpenCall extends Storable {
LocalFileOpenCall() {
this = any(DataFlow::Node sink | sinkNode(sink, "create-file")).asExpr().(Argument).getCall()
}
override Expr getAnInput() {
exists(FilesystemFlowConfig conf, DataFlow::Node n |
filesystemInput(n, result) and
conf.hasFlow(DataFlow::exprNode(this), n)
)
}
override Expr getAStore() {
exists(FilesystemFlowConfig conf, DataFlow::Node n |
closesFile(n, result) and
conf.hasFlow(DataFlow::exprNode(this), n)
)
}
}
/** Holds if `input` is written into `file`. */
private predicate filesystemInput(DataFlow::Node file, Argument input) {
exists(DataFlow::Node write | sinkNode(write, "write-file") |
input = write.asExpr() or
isVarargs(input, write)
) and
if input.getCall().getCallee().isStatic()
then file.asExpr() = input.getCall()
else file.asExpr() = input.getCall().getQualifier()
}
/** Holds if `arg` is part of `varargs`. */
private predicate isVarargs(Argument arg, DataFlow::ImplicitVarargsArray varargs) {
arg.isVararg() and arg.getCall() = varargs.getCall()
}
/** Holds if `store` closes `file`. */
private predicate closesFile(DataFlow::Node file, Call closeCall) {
closeCall.getCallee() instanceof CloseFileMethod and
if closeCall.getCallee().isStatic()
then file.asExpr() = closeCall
else file.asExpr() = closeCall.getQualifier()
or
// try-with-resources automatically closes the file
any(TryStmt try).getAResource() = closeCall.(LocalFileOpenCall).getEnclosingStmt() and
closeCall = file.asExpr()
}
/** A method that closes a file, perhaps after writing some data. */
private class CloseFileMethod extends Method {
CloseFileMethod() {
this.hasQualifiedName("java.io", ["RandomAccessFile", "FileOutputStream", "PrintStream"],
"close")
or
this.getDeclaringType().getASupertype*().hasQualifiedName("java.io", "Writer") and
this.hasName("close")
or
this.hasQualifiedName("java.nio.file", "Files", ["write", "writeString"])
}
}
private class FilesystemFlowConfig extends DataFlow::Configuration {
FilesystemFlowConfig() { this = "FilesystemFlowConfig" }
override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof LocalFileOpenCall }
override predicate isSink(DataFlow::Node sink) {
filesystemInput(sink, _) or
closesFile(sink, _)
}
override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
// Add nested Writer constructors as extra data flow steps
exists(ClassInstanceExpr cie |
cie.getConstructedType().getASupertype*().hasQualifiedName("java.io", "Writer") and
node1.asExpr() = cie.getArgument(0) and
node2.asExpr() = cie
)
}
}

View File

@@ -0,0 +1,72 @@
/** Provides classes and predicates to work with File objects. */
import java
import semmle.code.java.dataflow.ExternalFlow
private class CreateFileSinkModels extends SinkModelCsv {
override predicate row(string row) {
row =
[
"java.io;FileOutputStream;false;FileOutputStream;;;Argument[0];create-file",
"java.io;RandomAccessFile;false;RandomAccessFile;;;Argument[0];create-file",
"java.io;FileWriter;false;FileWriter;;;Argument[0];create-file",
"java.io;PrintStream;false;PrintStream;(File);;Argument[0];create-file",
"java.io;PrintStream;false;PrintStream;(File,String);;Argument[0];create-file",
"java.io;PrintStream;false;PrintStream;(File,Charset);;Argument[0];create-file",
"java.io;PrintStream;false;PrintStream;(String);;Argument[0];create-file",
"java.io;PrintStream;false;PrintStream;(String,String);;Argument[0];create-file",
"java.io;PrintStream;false;PrintStream;(String,Charset);;Argument[0];create-file",
"java.io;PrintWriter;false;PrintWriter;(File);;Argument[0];create-file",
"java.io;PrintWriter;false;PrintWriter;(File,String);;Argument[0];create-file",
"java.io;PrintWriter;false;PrintWriter;(File,Charset);;Argument[0];create-file",
"java.io;PrintWriter;false;PrintWriter;(String);;Argument[0];create-file",
"java.io;PrintWriter;false;PrintWriter;(String,String);;Argument[0];create-file",
"java.io;PrintWriter;false;PrintWriter;(String,Charset);;Argument[0];create-file",
"java.nio.file;Files;false;copy;;;Argument[1];create-file",
"java.nio.file;Files;false;createDirectories;;;Argument[0];create-file",
"java.nio.file;Files;false;createDirectory;;;Argument[0];create-file",
"java.nio.file;Files;false;createFile;;;Argument[0];create-file",
"java.nio.file;Files;false;createLink;;;Argument[0];create-file",
"java.nio.file;Files;false;createSymbolicLink;;;Argument[0];create-file",
"java.nio.file;Files;false;createTempDirectory;;;Argument[0];create-file",
"java.nio.file;Files;false;createTempFile;(Path,String,String,FileAttribute[]);;Argument[0];create-file",
"java.nio.file;Files;false;move;;;Argument[1];create-file",
"java.nio.file;Files;false;newBufferedWriter;;;Argument[0];create-file",
"java.nio.file;Files;false;newOutputStream;;;Argument[0];create-file",
"java.nio.file;Files;false;write;;;Argument[0];create-file",
"java.nio.file;Files;false;writeString;;;Argument[0];create-file"
]
}
}
private class WriteFileSinkModels extends SinkModelCsv {
override predicate row(string row) {
row =
[
"java.io;FileOutputStream;false;write;;;Argument[0];write-file",
"java.io;RandomAccessFile;false;write;;;Argument[0];write-file",
"java.io;RandomAccessFile;false;writeBytes;;;Argument[0];write-file",
"java.io;RandomAccessFile;false;writeChars;;;Argument[0];write-file",
"java.io;RandomAccessFile;false;writeUTF;;;Argument[0];write-file",
"java.io;Writer;true;append;;;Argument[0];write-file",
"java.io;Writer;true;write;;;Argument[0];write-file",
"java.io;PrintStream;true;append;;;Argument[0];write-file",
"java.io;PrintStream;true;format;(String,Object[]);;Argument[0..1];write-file",
"java.io;PrintStream;true;format;(Locale,String,Object[]);;Argument[1..2];write-file",
"java.io;PrintStream;true;print;;;Argument[0];write-file",
"java.io;PrintStream;true;printf;(String,Object[]);;Argument[0..1];write-file",
"java.io;PrintStream;true;printf;(Locale,String,Object[]);;Argument[1..2];write-file",
"java.io;PrintStream;true;println;;;Argument[0];write-file",
"java.io;PrintStream;true;write;;;Argument[0];write-file",
"java.io;PrintStream;true;writeBytes;;;Argument[0];write-file",
"java.io;PrintWriter;false;format;(String,Object[]);;Argument[0..1];write-file",
"java.io;PrintWriter;false;format;(Locale,String,Object[]);;Argument[1..2];write-file",
"java.io;PrintWriter;false;print;;;Argument[0];write-file",
"java.io;PrintWriter;false;printf;(String,Object[]);;Argument[0..1];write-file",
"java.io;PrintWriter;false;printf;(Locale,String,Object[]);;Argument[1..2];write-file",
"java.io;PrintWriter;false;println;;;Argument[0];write-file",
"java.nio.file;Files;false;write;;;Argument[1];write-file",
"java.nio.file;Files;false;writeString;;;Argument[1];write-file"
]
}
}

View File

@@ -0,0 +1,36 @@
public void fileSystemStorageUnsafe(String name, String password) {
// BAD - sensitive data stored in cleartext
FileWriter fw = new FileWriter("some_file.txt");
fw.write(name + ":" + password);
fw.close();
}
public void filesystemStorageEncryptedFileSafe(Context context, String name, String password) {
// GOOD - the whole file is encrypted with androidx.security.crypto.EncryptedFile
File file = new File("some_file.txt");
String masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC);
EncryptedFile encryptedFile = new EncryptedFile.Builder(
file,
context,
masterKeyAlias,
EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build();
FileOutputStream encryptedOutputStream = encryptedFile.openFileOutput();
encryptedOutputStream.write(name + ":" + password);
}
public void fileSystemStorageSafe(String name, String password) {
// GOOD - sensitive data is encrypted using a custom method
FileWriter fw = new FileWriter("some_file.txt");
fw.write(name + ":" + encrypt(password));
fw.close();
}
private static String encrypt(String cleartext) {
// Use an encryption or strong hashing algorithm in the real world.
// The example below just returns a SHA-256 hash.
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(cleartext.getBytes(StandardCharsets.UTF_8));
String encoded = Base64.getEncoder().encodeToString(hash);
return encoded;
}

View File

@@ -0,0 +1,35 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<overview>
<p>
Android applications with the appropriate permissions can write files either to the device external storage or the application internal storage, depending on the application's needs. However, sensitive information should not be saved in cleartext. Otherwise it can be accessed by any process or user in rooted devices, or can be disclosed through chained vulnerabilities, like unexpected access to the private storage through exposed components.
</p>
</overview>
<recommendation>
<p>
Consider using the <code>EncryptedFile</code> class to work with files containing sensitive data. Alternatively, use encryption algorithms to encrypt the sensitive data being stored.
</p>
</recommendation>
<example>
<p>
In the first example, sensitive user information is stored in cleartext using a local file.
</p>
<p>
In the second and third examples, the code encrypts sensitive information before saving it to the filesystem.
</p>
<sample src="CleartextStorageAndroidFilesystem.java" />
</example>
<references>
<li>
Android Developers:
<a href="https://developer.android.com/topic/security/data">Work with data more securely</a>
</li>
<li>
Android Developers:
<a href="https://developer.android.com/reference/androidx/security/crypto/EncryptedFile">EncryptedFile</a>
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,23 @@
/**
* @name Cleartext storage of sensitive information in the Android filesystem
* @description Cleartext storage of sensitive information in the Android filesystem
* allows access for users with root privileges or unexpected exposure
* from chained vulnerabilities.
* @kind problem
* @problem.severity warning
* @precision medium
* @id java/android/cleartext-storage-filesystem
* @tags security
* external/cwe/cwe-312
*/
import java
import semmle.code.java.security.CleartextStorageAndroidFilesystemQuery
from SensitiveSource data, LocalFileOpenCall s, Expr input, Expr store
where
input = s.getAnInput() and
store = s.getAStore() and
data.flowsTo(input)
select store, "Local file $@ containing $@ is stored $@. Data was added $@.", s, s.toString(), data,
"sensitive data", store, "here", input, "here"

View File

@@ -0,0 +1,4 @@
---
category: newQuery
---
* A new query "Cleartext storage of sensitive information in the Android filesystem" (`java/android/cleartext-storage-filesystem`) has been added. This query finds instances of sensitive data being stored in local files without encryption, which may expose it to attackers or malicious applications.

View File

@@ -1,4 +1,3 @@
| java.io.PrintStream#println(Object) | 3 |
| java.lang.Class#isAssignableFrom(Class) | 1 |
| java.lang.String#length() | 1 |
| java.time.Duration#ofMillis(long) | 1 |

View File

@@ -0,0 +1,13 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.app"
android:installLocation="auto"
android:versionCode="1"
android:versionName="0.1" >
<application>
<activity android:name=".CleartextStorageAndroidDatabaseTest"></activity>
<activity android:name=".CleartextStorageAndroidFileSystemTest"></activity>
<activity android:name=".CleartextStorageSharedPrefsTest"></activity>
</application>
</manifest>

View File

@@ -0,0 +1,277 @@
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.util.Base64;
import java.util.List;
import java.util.Locale;
import android.app.Activity;
import android.content.Context;
import androidx.security.crypto.EncryptedFile;
import androidx.security.crypto.EncryptedFile.FileEncryptionScheme;
public class CleartextStorageAndroidFilesystemTest extends Activity {
public void testWriteLocalFile(Context context, String name, String password) throws Exception {
// FileOutputStream
{
// java.io;FileOutputStream;false;FileOutputStream;;;Argument[0];create-file
FileOutputStream os = new FileOutputStream("some_file.txt");
// Nested writers
Writer writer = new BufferedWriter(new OutputStreamWriter(os, "utf-8"));
// java.io;FileOutputStream;false;write;;;Argument[0];write-file
writer.write(name + ":" + password); // $ hasCleartextStorageAndroidFilesystem
writer.close();
}
// RandomAccessFile
{
// java.io;RandomAccessFile;false;RandomAccessFile;;;Argument[0];create-file
RandomAccessFile f = new RandomAccessFile("some_file.txt", "r");
String contents = name + ":" + password;
// java.io;RandomAccessFile;false;write;;;Argument[0];write-file
f.write(contents.getBytes()); // $ hasCleartextStorageAndroidFilesystem
f.close();
}
{
// try-with-resources
try (RandomAccessFile f = new RandomAccessFile(new File("some_file.txt"), "r")) {
// java.io;RandomAccessFile;false;writeBytes;;;Argument[0];write-file
f.writeBytes(name + ":" + password); // $ hasCleartextStorageAndroidFilesystem
}
}
{
RandomAccessFile f = new RandomAccessFile("some_file.txt", "r");
// java.io;RandomAccessFile;false;writeChars;;;Argument[0];write-file
f.writeChars(name + ":" + password); // $ hasCleartextStorageAndroidFilesystem
f.close();
}
{
RandomAccessFile f = new RandomAccessFile("some_file.txt", "r");
// java.io;RandomAccessFile;false;writeUTF;;;Argument[0];write-file
f.writeUTF(name + ":" + password); // $ hasCleartextStorageAndroidFilesystem
f.close();
}
// FileWriter
{
// java.io;FileWriter;false;FileWriter;;;Argument[0];create-file
FileWriter fw = new FileWriter("some_file.txt");
// java.io;Writer;true;append;;;Argument[0];write-file
fw.append(name + ":" + password); // $ hasCleartextStorageAndroidFilesystem
fw.close();
}
{
// try-with-resources
try (FileWriter fw = new FileWriter("some_file.txt")) {
// java.io;Writer;true;write;;;Argument[0];write-file
fw.write(name + ":" + password); // $ hasCleartextStorageAndroidFilesystem
}
}
// PrintStream
{
// java.io;PrintStream;false;PrintStream;(File);;Argument[0];create-file"
PrintStream ps = new PrintStream(new File("some_file.txt"));
// java.io;PrintStream;true;append;;;Argument[0];write-file
ps.append(name + ":" + password); // $ hasCleartextStorageAndroidFilesystem
ps.close();
}
{
// java.io;PrintStream;false;PrintStream;(File,String);;Argument[0];create-file
PrintStream ps = new PrintStream(new File("some_file.txt"), "utf-8");
// java.io;PrintStream;true;format;(String,Object[]);;Argument[0..1];write-file
ps.format("%s:" + password, name); // $ hasCleartextStorageAndroidFilesystem
ps.format("%s:%s", name, password); // $ hasCleartextStorageAndroidFilesystem
ps.close();
}
{
// java.io;PrintStream;false;PrintStream;(File,Charset);;Argument[0];create-file
PrintStream ps = new PrintStream(new File("some_file.txt"), Charset.defaultCharset());
// java.io;PrintStream;true;format;(Locale,String,Object[]);;Argument[1..2];write-file
ps.format(Locale.US, "%s:" + password, name); // $ hasCleartextStorageAndroidFilesystem
ps.format(Locale.US, "%s:%s", name, password); // $ hasCleartextStorageAndroidFilesystem
ps.close();
}
{
// java.io;PrintStream;false;PrintStream;(String);;Argument[0];create-file
PrintStream ps = new PrintStream("some_file.txt");
// java.io;PrintStream;true;print;;;Argument[0];write-file
ps.print(name + ":" + password); // $ hasCleartextStorageAndroidFilesystem
ps.close();
}
{
// java.io;PrintStream;false;PrintStream;(String,String);;Argument[0];create-file
PrintStream ps = new PrintStream("some_file.txt", "utf-8");
// java.io;PrintStream;true;printf;(String,Object[]);;Argument[0..1];write-file
ps.printf("%s:" + password, name); // $ hasCleartextStorageAndroidFilesystem
ps.printf("%s:%s", name, password); // $ hasCleartextStorageAndroidFilesystem
ps.close();
}
{
// java.io;PrintStream;false;PrintStream;(String,Charset);;Argument[0];create-file
PrintStream ps = new PrintStream("some_file.txt", Charset.defaultCharset());
// java.io;PrintStream;true;printf;(Locale,String,Object[]);;Argument[1..2];write-file
ps.printf(Locale.US, "%s:" + password, name); // $ hasCleartextStorageAndroidFilesystem
ps.printf(Locale.US, "%s:%s", name, password); // $ hasCleartextStorageAndroidFilesystem
ps.close();
}
{
PrintStream ps = new PrintStream("some_file.txt");
// java.io;PrintStream;true;println;;;Argument[0];write-file
ps.println(name + ":" + password); // $ hasCleartextStorageAndroidFilesystem
ps.close();
}
{
PrintStream ps = new PrintStream("some_file.txt");
String contents = name + ":" + password;
// java.io;PrintStream;true;write;;;Argument[0];write-file
ps.write(contents.getBytes()); // $ hasCleartextStorageAndroidFilesystem
ps.close();
}
{
PrintStream ps = new PrintStream("some_file.txt");
String contents = name + ":" + password;
// java.io;PrintStream;true;writeBytes;;;Argument[0];write-file
ps.writeBytes(contents.getBytes()); // $ hasCleartextStorageAndroidFilesystem
ps.close();
}
// PrintWriter
{
// java.io;PrintWriter;false;PrintWriter;(File);;Argument[0];create-file
PrintWriter pw = new PrintWriter(new File("some_file.txt"));
// java.io;Writer;true;append;;;Argument[0];write-file
pw.append(name + ":" + password); // $ hasCleartextStorageAndroidFilesystem
pw.close();
}
{
// try-with-resources
try (PrintWriter pw = new PrintWriter(new File("some_file.txt"))) {
// java.io;PrintWriter;false;format;(String,Object[]);;Argument[0..1];write-file
pw.format("%s:" + password, name); // $ hasCleartextStorageAndroidFilesystem
pw.format("%s:%s", name, password); // $ hasCleartextStorageAndroidFilesystem
}
}
{
// java.io;PrintWriter;false;PrintWriter;(File,String);;Argument[0];create-file
PrintWriter pw = new PrintWriter(new File("some_file.txt"), "utf-8");
// java.io;PrintWriter;false;format;(Locale,String,Object[]);;Argument[1..2];write-file
pw.format(Locale.US, "%s:" + password, name); // $ hasCleartextStorageAndroidFilesystem
pw.format(Locale.US, "%s:%s", name, password); // $ hasCleartextStorageAndroidFilesystem
pw.close();
}
{
// java.io;PrintWriter;false;PrintWriter;(File,Charset);;Argument[0];create-file
PrintWriter pw = new PrintWriter(new File("some_file.txt"), Charset.defaultCharset());
// java.io;PrintWriter;false;print;;;Argument[0];write-file
pw.print(name + ":" + password); // $ hasCleartextStorageAndroidFilesystem
pw.close();
}
{
// java.io;PrintWriter;false;PrintWriter;(String);;Argument[0];create-file
PrintWriter pw = new PrintWriter("some_file.txt");
// java.io;PrintWriter;false;printf;(String,Object[]);;Argument[0..1];write-file
pw.printf("%s:" + password, name); // $ hasCleartextStorageAndroidFilesystem
pw.printf("%s:%s", name, password); // $ hasCleartextStorageAndroidFilesystem
pw.close();
}
{
// java.io;PrintWriter;false;PrintWriter;(String,String);;Argument[0];create-file
PrintWriter pw = new PrintWriter("some_file.txt", "utf-8");
// java.io;PrintWriter;false;printf;(Locale,String,Object[]);;Argument[1..2];write-file
pw.printf(Locale.US, "%s:" + password, name); // $ hasCleartextStorageAndroidFilesystem
pw.printf(Locale.US, "%s:%s", name, password); // $ hasCleartextStorageAndroidFilesystem
pw.close();
}
{
// java.io;PrintWriter;false;PrintWriter;(String,Charset);;Argument[0];create-file
PrintWriter pw = new PrintWriter("some_file.txt", Charset.defaultCharset());
// java.io;PrintWriter;false;println;;;Argument[0];write-file
pw.println(name + ":" + password); // $ hasCleartextStorageAndroidFilesystem
pw.close();
}
{
PrintWriter pw = new PrintWriter("some_file.txt");
// java.io;Writer;true;write;;;Argument[0];write-file
pw.write(name + ":" + password); // $ hasCleartextStorageAndroidFilesystem
pw.close();
}
// java.nio.files.Files
{
// java.nio.file;Files;false;newBufferedWriter;;;Argument[0];create-file
BufferedWriter bw = Files.newBufferedWriter(Path.of("some_file.txt"));
bw.write(name + ":" + password); // $ hasCleartextStorageAndroidFilesystem
bw.close();
}
{
// java.nio.file;Files;false;newOutputStream;;;Argument[0];create-file
// try-with-resources
try (OutputStream os = Files.newOutputStream(Path.of("some_file.txt"))) {
String contents = name + ":" + password;
os.write(contents.getBytes());
}
}
{
Path path = Path.of("some_file.txt");
String contents = name + ":" + password;
// java.nio.file;Files;false;write;;;Argument[0];create-file
// java.nio.file;Files;false;write;;;Argument[1];write-file",
Files.write(path, contents.getBytes()); // $ hasCleartextStorageAndroidFilesystem
}
{
Path path = Path.of("some_file.txt");
String contents = name + ":" + password;
Files.write(path, List.of(contents)); // $ hasCleartextStorageAndroidFilesystem
}
{
Path path = Path.of("some_file.txt");
// java.nio.file;Files;false;writeString;;;Argument[0];create-file
// java.nio.file;Files;false;writeString;;;Argument[1];write-file"
Files.writeString(path, name + ":" + password); // $ hasCleartextStorageAndroidFilesystem
}
// Safe writes
{
FileWriter fw = new FileWriter("some_file.txt");
fw.write(name + ":" + hash(password)); // Safe - using a hash
fw.close();
}
{
Writer writer = new OutputStreamWriter(new ByteArrayOutputStream(), "utf-8");
writer.write(name + ":" + password); // Safe - not writing to a file
writer.close();
}
{
File file = new File("some_file.txt");
String contents = name + ":" + password;
EncryptedFile encryptedFile = new EncryptedFile.Builder(file, context, "some_key",
FileEncryptionScheme.AES256_GCM_HKDF_4KB).build();
FileOutputStream encryptedOutputStream = encryptedFile.openFileOutput();
encryptedOutputStream.write(contents.getBytes()); // Safe - using EncryptedFile
}
}
private static String hash(String cleartext) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(cleartext.getBytes(StandardCharsets.UTF_8));
String encoded = Base64.getEncoder().encodeToString(hash);
return encoded;
}
}

View File

@@ -0,0 +1,22 @@
import java
import semmle.code.java.security.CleartextStorageAndroidFilesystemQuery
import TestUtilities.InlineExpectationsTest
class CleartextStorageAndroidFilesystemTest extends InlineExpectationsTest {
CleartextStorageAndroidFilesystemTest() { this = "CleartextStorageAndroidFilesystemTest" }
override string getARelevantTag() { result = "hasCleartextStorageAndroidFilesystem" }
override predicate hasActualResult(Location location, string element, string tag, string value) {
tag = "hasCleartextStorageAndroidFilesystem" and
exists(SensitiveSource data, LocalFileOpenCall s, Expr input, Expr store |
input = s.getAnInput() and
store = s.getAStore() and
data.flowsTo(input)
|
input.getLocation() = location and
element = input.toString() and
value = ""
)
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package androidx.security.crypto;
import android.content.Context;
import androidx.annotation.NonNull;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
public final class EncryptedFile {
public enum FileEncryptionScheme {
AES256_GCM_HKDF_4KB;
}
public static final class Builder {
public Builder(@NonNull File file, @NonNull Context context, @NonNull String masterKeyAlias,
@NonNull FileEncryptionScheme fileEncryptionScheme) {}
public Builder(@NonNull Context context, @NonNull File file, @NonNull MasterKey masterKey,
@NonNull FileEncryptionScheme fileEncryptionScheme) {}
public Builder setKeysetPrefName(@NonNull String keysetPrefName) {
return null;
}
public Builder setKeysetAlias(@NonNull String keysetAlias) {
return null;
}
public EncryptedFile build() throws GeneralSecurityException, IOException {
return null;
}
}
public FileOutputStream openFileOutput() throws GeneralSecurityException, IOException {
return null;
}
public FileInputStream openFileInput()
throws GeneralSecurityException, IOException, FileNotFoundException {
return null;
}
}