mirror of
https://github.com/github/codeql.git
synced 2026-05-01 19:55:15 +02:00
Query for detecting Local Android DoS caused by NFE
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
public class NFEAndroidDoS extends Activity {
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_view);
|
||||
|
||||
// BAD: Uncaught NumberFormatException due to remote user inputs
|
||||
{
|
||||
String minPriceStr = getIntent().getStringExtra("priceMin");
|
||||
double minPrice = Double.parseDouble(minPriceStr);
|
||||
}
|
||||
|
||||
// GOOD: Use the proper Android method to get number extra
|
||||
{
|
||||
int width = getIntent().getIntExtra("width", 0);
|
||||
int height = getIntent().getIntExtra("height", 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>NumberFormatException (NFE) thrown but not caught by an Android application will crash the application. If the application allows external inputs, an attacker can send an invalid number as intent extra to trigger NFE, which introduces local Denial of Service (Dos) attack.</p>
|
||||
<p>
|
||||
This is a common problem in Android development since Android components don't have
|
||||
<code>throw Exception(...)</code>
|
||||
in their class and method definitions.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
Use the Android methods intended to get number extras e.g.
|
||||
<code>Intent.getFloatExtra(String name, float defaultValue)</code>
|
||||
since they have the built-in try/catch processing, or explicitly do try/catch in the application.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>The following example shows both 'BAD' and 'GOOD' configurations. In the 'BAD' configuration, number value is retrieved as string extra then parsed to double. In the 'GOOD' configuration, number value is retrieved as integer extra.</p>
|
||||
<sample src="NFEAndroidDoS.java" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>
|
||||
CWE:
|
||||
<a href="https://cwe.mitre.org/data/definitions/749.html">CWE-755: Improper Handling of Exceptional Conditions</a>
|
||||
</li>
|
||||
<li>
|
||||
Android Developers:
|
||||
<a href="https://developer.android.com/topic/performance/vitals/crash">Android Crashes</a>
|
||||
</li>
|
||||
<li>
|
||||
Google Analytics:
|
||||
<a href="https://developers.google.com/analytics/devguides/collection/android/v4/exceptions">Crash and Exception Measurement Using the Google Analytics SDK</a>
|
||||
</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
107
java/ql/src/experimental/Security/CWE/CWE-755/NFEAndroidDoS.ql
Normal file
107
java/ql/src/experimental/Security/CWE/CWE-755/NFEAndroidDoS.ql
Normal file
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* @name Local Android DoS Caused By NumberFormatException
|
||||
* @id java/android/nfe-local-android-dos
|
||||
* @description NumberFormatException thrown but not caught by an Android application that allows external inputs can crash the application, which is a local Denial of Service (Dos) attack.
|
||||
* @kind path-problem
|
||||
* @tags security
|
||||
* external/cwe/cwe-755
|
||||
*/
|
||||
|
||||
import java
|
||||
import semmle.code.java.frameworks.android.Intent
|
||||
import semmle.code.java.frameworks.android.WebView
|
||||
import semmle.code.java.dataflow.FlowSources
|
||||
import DataFlow::PathGraph
|
||||
|
||||
/** Code from java/ql/src/Violations of Best Practice/Exception Handling/NumberFormatException.ql */
|
||||
private class SpecialMethodAccess extends MethodAccess {
|
||||
predicate isValueOfMethod(string klass) {
|
||||
this.getMethod().getName() = "valueOf" and
|
||||
this.getQualifier().getType().(RefType).hasQualifiedName("java.lang", klass) and
|
||||
this.getAnArgument().getType().(RefType).hasQualifiedName("java.lang", "String")
|
||||
}
|
||||
|
||||
predicate isParseMethod(string klass, string name) {
|
||||
this.getMethod().getName() = name and
|
||||
this.getQualifier().getType().(RefType).hasQualifiedName("java.lang", klass)
|
||||
}
|
||||
|
||||
predicate throwsNFE() {
|
||||
this.isParseMethod("Byte", "parseByte") or
|
||||
this.isParseMethod("Short", "parseShort") or
|
||||
this.isParseMethod("Integer", "parseInt") or
|
||||
this.isParseMethod("Long", "parseLong") or
|
||||
this.isParseMethod("Float", "parseFloat") or
|
||||
this.isParseMethod("Double", "parseDouble") or
|
||||
this.isParseMethod("Byte", "decode") or
|
||||
this.isParseMethod("Short", "decode") or
|
||||
this.isParseMethod("Integer", "decode") or
|
||||
this.isParseMethod("Long", "decode") or
|
||||
this.isValueOfMethod("Byte") or
|
||||
this.isValueOfMethod("Short") or
|
||||
this.isValueOfMethod("Integer") or
|
||||
this.isValueOfMethod("Long") or
|
||||
this.isValueOfMethod("Float") or
|
||||
this.isValueOfMethod("Double")
|
||||
}
|
||||
}
|
||||
|
||||
private class SpecialClassInstanceExpr extends ClassInstanceExpr {
|
||||
predicate isStringConstructor(string klass) {
|
||||
this.getType().(RefType).hasQualifiedName("java.lang", klass) and
|
||||
this.getAnArgument().getType().(RefType).hasQualifiedName("java.lang", "String") and
|
||||
this.getNumArgument() = 1
|
||||
}
|
||||
|
||||
predicate throwsNFE() {
|
||||
this.isStringConstructor("Byte") or
|
||||
this.isStringConstructor("Short") or
|
||||
this.isStringConstructor("Integer") or
|
||||
this.isStringConstructor("Long") or
|
||||
this.isStringConstructor("Float") or
|
||||
this.isStringConstructor("Double")
|
||||
}
|
||||
}
|
||||
|
||||
class NumberFormatException extends RefType {
|
||||
NumberFormatException() { this.hasQualifiedName("java.lang", "NumberFormatException") }
|
||||
}
|
||||
|
||||
private predicate catchesNFE(TryStmt t) {
|
||||
exists(CatchClause cc, LocalVariableDeclExpr v |
|
||||
t.getACatchClause() = cc and
|
||||
cc.getVariable() = v and
|
||||
v.getType().(RefType).getASubtype*() instanceof NumberFormatException
|
||||
)
|
||||
}
|
||||
|
||||
private predicate throwsNFE(Expr e) {
|
||||
e.(SpecialClassInstanceExpr).throwsNFE() or e.(SpecialMethodAccess).throwsNFE()
|
||||
}
|
||||
|
||||
/**
|
||||
* Taint configuration tracking flow from untrusted inputs to number conversion calls.
|
||||
*/
|
||||
class NFELocalDoSConfiguration extends TaintTracking::Configuration {
|
||||
NFELocalDoSConfiguration() { this = "NFELocalDoSConfiguration" }
|
||||
|
||||
/** Holds if source is a remote flow source */
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
/** Holds if NFE is thrown but not caught */
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
exists(Expr e |
|
||||
throwsNFE(e) and
|
||||
not exists(TryStmt t |
|
||||
t.getBlock() = e.getEnclosingStmt().getEnclosingStmt*() and
|
||||
catchesNFE(t)
|
||||
) and
|
||||
sink.asExpr() = e
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
from DataFlow::PathNode source, DataFlow::PathNode sink, NFELocalDoSConfiguration conf
|
||||
where conf.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Local Android Denial of Service due to $@.", source.getNode(),
|
||||
"user-provided value"
|
||||
24
java/ql/test/experimental/query-tests/security/CWE-755/AndroidManifest.xml
Executable file
24
java/ql/test/experimental/query-tests/security/CWE-755/AndroidManifest.xml
Executable file
@@ -0,0 +1,24 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.app"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="1"
|
||||
android:versionName="0.1" >
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme" >
|
||||
<activity
|
||||
android:name=".NFEAndroidDoS"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,22 @@
|
||||
edges
|
||||
| NFEAndroidDoS.java:13:24:13:34 | getIntent(...) : Intent | NFEAndroidDoS.java:14:21:14:51 | parseDouble(...) |
|
||||
| NFEAndroidDoS.java:22:21:22:31 | getIntent(...) : Intent | NFEAndroidDoS.java:23:15:23:40 | parseInt(...) |
|
||||
| NFEAndroidDoS.java:25:22:25:32 | getIntent(...) : Intent | NFEAndroidDoS.java:26:16:26:42 | parseInt(...) |
|
||||
| NFEAndroidDoS.java:43:24:43:34 | getIntent(...) : Intent | NFEAndroidDoS.java:44:21:44:43 | new Double(...) |
|
||||
| NFEAndroidDoS.java:43:24:43:34 | getIntent(...) : Intent | NFEAndroidDoS.java:47:21:47:47 | valueOf(...) |
|
||||
nodes
|
||||
| NFEAndroidDoS.java:13:24:13:34 | getIntent(...) : Intent | semmle.label | getIntent(...) : Intent |
|
||||
| NFEAndroidDoS.java:14:21:14:51 | parseDouble(...) | semmle.label | parseDouble(...) |
|
||||
| NFEAndroidDoS.java:22:21:22:31 | getIntent(...) : Intent | semmle.label | getIntent(...) : Intent |
|
||||
| NFEAndroidDoS.java:23:15:23:40 | parseInt(...) | semmle.label | parseInt(...) |
|
||||
| NFEAndroidDoS.java:25:22:25:32 | getIntent(...) : Intent | semmle.label | getIntent(...) : Intent |
|
||||
| NFEAndroidDoS.java:26:16:26:42 | parseInt(...) | semmle.label | parseInt(...) |
|
||||
| NFEAndroidDoS.java:43:24:43:34 | getIntent(...) : Intent | semmle.label | getIntent(...) : Intent |
|
||||
| NFEAndroidDoS.java:44:21:44:43 | new Double(...) | semmle.label | new Double(...) |
|
||||
| NFEAndroidDoS.java:47:21:47:47 | valueOf(...) | semmle.label | valueOf(...) |
|
||||
#select
|
||||
| NFEAndroidDoS.java:14:21:14:51 | parseDouble(...) | NFEAndroidDoS.java:13:24:13:34 | getIntent(...) : Intent | NFEAndroidDoS.java:14:21:14:51 | parseDouble(...) | Local Android Denial of Service due to $@. | NFEAndroidDoS.java:13:24:13:34 | getIntent(...) | user-provided value |
|
||||
| NFEAndroidDoS.java:23:15:23:40 | parseInt(...) | NFEAndroidDoS.java:22:21:22:31 | getIntent(...) : Intent | NFEAndroidDoS.java:23:15:23:40 | parseInt(...) | Local Android Denial of Service due to $@. | NFEAndroidDoS.java:22:21:22:31 | getIntent(...) | user-provided value |
|
||||
| NFEAndroidDoS.java:26:16:26:42 | parseInt(...) | NFEAndroidDoS.java:25:22:25:32 | getIntent(...) : Intent | NFEAndroidDoS.java:26:16:26:42 | parseInt(...) | Local Android Denial of Service due to $@. | NFEAndroidDoS.java:25:22:25:32 | getIntent(...) | user-provided value |
|
||||
| NFEAndroidDoS.java:44:21:44:43 | new Double(...) | NFEAndroidDoS.java:43:24:43:34 | getIntent(...) : Intent | NFEAndroidDoS.java:44:21:44:43 | new Double(...) | Local Android Denial of Service due to $@. | NFEAndroidDoS.java:43:24:43:34 | getIntent(...) | user-provided value |
|
||||
| NFEAndroidDoS.java:47:21:47:47 | valueOf(...) | NFEAndroidDoS.java:43:24:43:34 | getIntent(...) : Intent | NFEAndroidDoS.java:47:21:47:47 | valueOf(...) | Local Android Denial of Service due to $@. | NFEAndroidDoS.java:43:24:43:34 | getIntent(...) | user-provided value |
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.example.app;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
|
||||
/** Android activity that tests app crash by NumberFormatException */
|
||||
public class NFEAndroidDoS extends Activity {
|
||||
// BAD - parse string extra to double
|
||||
public void testOnCreate1(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(-1);
|
||||
|
||||
String minPriceStr = getIntent().getStringExtra("priceMin");
|
||||
double minPrice = Double.parseDouble(minPriceStr);
|
||||
}
|
||||
|
||||
// BAD - parse string extra to integer
|
||||
public void testOnCreate2(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(-1);
|
||||
|
||||
String widthStr = getIntent().getStringExtra("width");
|
||||
int width = Integer.parseInt(widthStr);
|
||||
|
||||
String heightStr = getIntent().getStringExtra("height");
|
||||
int height = Integer.parseInt(heightStr);
|
||||
}
|
||||
|
||||
// GOOD - parse int extra to integer
|
||||
public void testOnCreate3(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(-1);
|
||||
|
||||
int width = getIntent().getIntExtra("width", 0);
|
||||
int height = getIntent().getIntExtra("height", 0);
|
||||
}
|
||||
|
||||
// BAD - convert string extra to double
|
||||
public void testOnCreate4(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(-1);
|
||||
|
||||
String minPriceStr = getIntent().getStringExtra("priceMin");
|
||||
double minPrice = new Double(minPriceStr);
|
||||
|
||||
String maxPriceStr = getIntent().getStringExtra("priceMax");
|
||||
double maxPrice = Double.valueOf(minPriceStr);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
experimental/Security/CWE/CWE-755/NFEAndroidDoS.ql
|
||||
@@ -0,0 +1 @@
|
||||
// semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/google-android-9.0.0
|
||||
Reference in New Issue
Block a user