# SQL Server Android App: 'int java.lang.String.length()' on a null object reference



## Detlef_Retlef (20. Aug 2020)

Hallo Leute,

ich stehe hier vor einem Problem. Eine kurze Zusammenfassung zu dem, was ich eigentlich vorhabe:
Ich schreibe eine Android App und möchte mit dieser Reihen von Tabelle A zu Tabelle B übertragen durch einen INSERT INTO Befehl. An sich also nichts spektakuläres. Tabelle A wird über einen SELECT Befehl ausgelesen und die Werte in eine ArrayList und später dann in Strings gespeichert. Die Methode "suchen" funktioniert einwandfrei beim ersten Mal. Bei zweiten Mal passiert nichts. Ich kriege lediglich die Fehlermeldung:
Attempt to invoke virtual method 'int java.lang.String.length()' on a null object reference
Mir ist bewusst, dass irgendwo ein null Wert übergeben wird aber ich weiß nicht genau wo, weil mir keine Zeile angezeigt wird in der Meldung. Verbesserungsvorschläge wären sehr hilfreich.

Anbei ist mein Code, der leicht zensiert wurde.

MFG Detlef_Retlef


```
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.StrictMode;
import android.support.design.widget.TextInputEditText;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.View;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;

public class SQLInsertInto extends AppCompatActivity {

    private static String ip = "...";
    private static String port = "...";
    private static String Klasse = "net.sourceforge.jtds.jdbc.Driver";
    private static String database = "...";
    private static String username = "...";
    private static String password = "...";
    private static String url = "jdbc:jtds:sqlserver://"+ip+":"+port+"/"+database;
    private Connection connection = null;
    private TextInputEditText suchleiste;
    private ArrayList<String> A = new ArrayList<>();
    private ArrayList<String> B = new ArrayList<>();
    private ArrayList<String> C = new ArrayList<>();
    private ArrayList<String> D = new ArrayList<>();
    private String eingabestring = null;
    private String aVar;
    private String bVar;
    private String cVar;
    private String dVar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sql_insert_into);

        suchleiste = (TextInputEditText) findViewById(R.id.suchleiste);

        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.INTERNET}, PackageManager.PERMISSION_GRANTED);

        StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
        StrictMode.setThreadPolicy(policy);
    }


    public void suchen(View view) {
            if (connection != null) {
                Statement statement;
                try {
                    A.clear();
                    B.clear();
                    C.clear();
                    D.clear();
                    getString();
                    statement = connection.createStatement();
                    ResultSet resultset = statement.executeQuery("SELECT A, B, C, D\n" +
                            "FROM Tabelle1 WHERE E = '" + eingabestring + "'");
                    while(resultset.next()) {
                        for (int i = 1; i <= 1; i++) {
                            A.add(resultset.getString(1));
                            B.add(resultset.getString(2));
                            C.add(resultset.getString(3));
                            D.add(resultset.getString(4));
                        }
                    }
                            aVar = A.get(0);
                            bVar = B.get(0);
                            cVar = C.get(0);
                            dVar = D.get(0);
                    
                } catch (SQLException e) {
                    e.printStackTrace();
                }
                try {
                    getString();
                    statement = connection.createStatement();
                    statement.executeUpdate("INSERT INTO Tabelle2\n" +
                            "(A, B, C, D)" +
                            "VALUES ('" + aVar + "', '" + bVar + "', '" + cVar + "', '" + dVar + "')");
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            Intent intent = new Intent(getBaseContext(), SQLInsertInto.class);
            startActivity(intent); }
    }

    private void getString() {
        eingabestring = suchleiste.getText().toString();
    }
}
```


```
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="SQLInsertInto">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="155dp"
        android:layout_marginStart="60dp"
        android:onClick="suchen"
        android:text="Suchen" />

    <android.support.design.widget.TextInputEditText
        android:id="@+id/suchleiste"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="75dp"/>


</RelativeLayout>
```


----------



## kneitzel (20. Aug 2020)

Also da fällt einiges direkt auf:
SQL Abfragen mit Usereingabe so zusammen zu setzen ist grob fahrlässig und öffnet Tür und Tor für SQL Injection Attacken (und gefährdet die Funktionalität. Gib auch mal ein ' ein beim Suchstring ....)
==> PreparedStatement mit Parameter nutzen: https://docs.oracle.com/javase/tutorial/jdbc/basics/prepared.html

Die for Schleife mit i=1 und i<=1 ist so unnötig und kann weg.

Wenn a,b,c und d zusammen gehören, dann würde ich da eine eigene Klasse für schreiben.

Statt die Werte zu lesen und dann zu schreiben könntest du direkt ein SELECT ... INTO ... durchführen.

Was den Fehler angeht: der Stacktrace sollte Dir doch genau aufzeigen, wo der Fehler auftritt. So auf Anhieb sehe ich nicht, wo der Fehler her kommen könnte.


----------



## Detlef_Retlef (20. Aug 2020)

JustNobody hat gesagt.:


> Also da fällt einiges direkt auf:
> SQL Abfragen mit Usereingabe so zusammen zu setzen ist grob fahrlässig und öffnet Tür und Tor für SQL Injection Attacken (und gefährdet die Funktionalität. Gib auch mal ein ' ein beim Suchstring ....)
> ==> PreparedStatement mit Parameter nutzen: https://docs.oracle.com/javase/tutorial/jdbc/basics/prepared.html
> 
> ...


Danke für die schnelle Antwort. Wenn ich die for-Schleife weglasse wird überall "null" eingetragen in der Datenbank. Mit ihr wird alles korrekt übertragen.
Die restlichen Tipps werde ich mal ausprobieren.

MFG Detlef_Retlef


----------



## kneitzel (20. Aug 2020)

Nur die äußere for Schleife sollte web, nicht der Inhalt. Der Inhalt wird zum Inhalt der while Schleife.

Damit sollte sich an dem Ablauf nichts ändern, denn die for Schleife wird ja genau ein Mal ausgeführt und das hast du ja auch, wenn der Code in der for Schleife einfach nur da stehen würde ...

Und was auch noch auffällt: du schließt nichts. Alles was (Auto)Closable ist solltest Du nach der Benutzung schließen. Bei dir wäre das Statement und Resultat am Ende zu schließen. Mein Tipp wäre hier try with resources zu nutzen.


----------



## Detlef_Retlef (20. Aug 2020)

JustNobody hat gesagt.:


> Nur die äußere for Schleife sollte web, nicht der Inhalt. Der Inhalt wird zum Inhalt der while Schleife.
> 
> Damit sollte sich an dem Ablauf nichts ändern, denn die for Schleife wird ja genau ein Mal ausgeführt und das hast du ja auch, wenn der Code in der for Schleife einfach nur da stehen würde ...
> 
> Und was auch noch auffällt: du schließt nichts. Alles was (Auto)Closable ist solltest Du nach der Benutzung schließen. Bei dir wäre das Statement und Resultat am Ende zu schließen. Mein Tipp wäre hier try with resources zu nutzen.



Wenn ich abfragen lasse, ob ein '-Zeichen enthalten ist in der Suchleiste, und es dann blockiere, sollte es doch vorerst sicher sein oder? Öffnen kann man es ja dann nicht mehr. Ist es möglich innerhalb eines '...' einen verschachtelten Befehl einzubauen? Also z.B.:

... WHERE E = 'SELECT * FROM ... usw' (So in etwa. Ich bin nur ein SQL-Anfänger)

Ich würde es erst gerne zum laufen bringen und mich dann zu einem späteren Zeitpunkt in die PreparedStatements reinlesen.


----------



## kneitzel (20. Aug 2020)

Verschachtelte SQL Abfragen sind möglich, aber natürlich nicht in ', weil das dann ein Literal wäre.

SELECT * from SomeTable WHERE someField IN (SELECT aField FROM OtherTable)

Das wäre so ein Beispiel einer verschachtelten SQL Anweisung.


----------



## Detlef_Retlef (20. Aug 2020)

JustNobody hat gesagt.:


> Verschachtelte SQL Abfragen sind möglich, aber natürlich nicht in ', weil das dann ein Literal wäre.
> 
> SELECT * from SomeTable WHERE someField IN (SELECT aField FROM OtherTable)
> 
> Das wäre so ein Beispiel einer verschachtelten SQL Anweisung.



Ich habe jetzt mal testweise ein PreparedStatement geschrieben. Dieses Mal als UPDATE und es funktioniert sogar. Jedoch erkenne ich da keinen Unterschied in Punkto Sicherheit gegenüber meiner ersten Version.

Anbei der Code:


```
public void suchen(View view) {
if (connection !=null) {
            try {
                PreparedStatement updateTabelle1 = connection.prepareStatement(
                        "UPDATE Tabelle1 SET A = ? WHERE B = 'Irgendwas'");
                getString();
                updateTabelle1.setString(1, eingabestring);
                updateTabelle1.executeUpdate();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
}
```


----------



## kneitzel (20. Aug 2020)

PreparedStatement kennt Parameter. Schau Dir diese doch noch einmal an (z.B. in dem Link, den ich dazu gepostet habe).


----------



## mihe7 (21. Aug 2020)

Detlef_Retlef hat gesagt.:


> Jedoch erkenne ich da keinen Unterschied in Punkto Sicherheit gegenüber meiner ersten Version.


Deine Version baut den SQL-String selbst zusammen, z. B.

```
"UPDATE Tabelle1 SET A = '" + eingabe + "' WHERE B = 'Irgendwas'"
```
Wenn ich als Benutzer Deiner Software nun folgendes eingebe: `X', B='Y`, dann wird aus Deinem String: `"UPDATE Tabelle1 SET A='X', B='Y' WHERE B = 'Irgendwas'"`. Oops.

Das nennt sich SQL-Injection und neuer Code, der so etwas zulässt, sollte mit Zuchthaus bestraft werden


----------



## Detlef_Retlef (21. Aug 2020)

mihe7 hat gesagt.:


> Deine Version baut den SQL-String selbst zusammen, z. B.
> 
> ```
> "UPDATE Tabelle1 SET A = '" + eingabe + "' WHERE B = 'Irgendwas'"
> ...


Hehe
Ich arbeite mittlerweile schon mit PreparedStatements. Aber das obige kann man auch einfach umgehen, wenn man keine '-Zeichen erlaubt im Suchfeld.


----------



## LimDul (21. Aug 2020)

Damit macht man aber nicht umbedingt alle Angriffe unmöglich, sondern nur den offensichtlichsten. 
Man sieht hier, was alles escaped wird: https://www.php.net/manual/de/pdo.quote.php


----------



## kneitzel (21. Aug 2020)

Also wie @LimDul schon schrieb: Das ist nur ein offensichtlicher Angriff.

Generell ist es so, dass es extrem viele Angriffsvektoren gibt. Und da sollte man sich auf gewisse Experten verlassen, die sich da intensiv mit beschäftigen. Daher macht es durchaus Sinn, sicherheitsrelevante Dinge zu nutzen, die bewährt sind und die von Experten begutachtet wurden.

Aber hier geht es auch noch um ganz andere Dinge: Die Darstellung von Werten als Zeichenkette ist problematisch.
a) Ungenauigkeiten bei der Darstellung: 17.001 / 17,001 - was für Zahlen sind das? Es gibt das Tausender-Trennzeichen und es gibt das "Komma" für nicht ganze Zahlen. Je nach Regionalisierung sind das unterschiedliche Dinge. Beim Datum kommen noch mehr Schreibweisen hinzu. Diese Regionalisierung kann schnell Probleme bereiten, wenn Du z.B. auf einem Gerät mit deutschen Settings arbeitest, aber der Server auf einem System mit US Settings läuft. 
b) Wie stellst Du ein Byte Array dar? Oder die Übergabe einer Tabelle? Es gibt also durchaus Datentypen, die nicht so einfach darstellbar sind.

Also es gibt Best Practices und das ist eine ganz wichtige. Und das mit dem Zuchthaus ist natürlich übertrieben, aber mach sowas im beruflichen Umfeld und du würdest Konsequenzen erfahren fürchte ich....

Daher: schau dir den Link noch einmal im Detail an. Speziell auf die ? im SQl Query und dann die setInt / setString Aufrufe, die danach folgen, achten.


----------



## Detlef_Retlef (21. Aug 2020)

@LimDul @JustNobody 
Aber dann wäre es ja sicher, wenn ich PreparedStatements verwende oder? Online steht überall, dass das die beste Abwehr gegen SQL Injections ist.


----------



## mihe7 (21. Aug 2020)

Detlef_Retlef hat gesagt.:


> Aber das obige kann man auch einfach umgehen, wenn man keine '-Zeichen erlaubt im Suchfeld.


Wozu, wenn es einen einfachen und "sicheren" Weg gibt?

SQL-Injection ist auch heute noch ein Problem. Selbst SAP bleibt davon nicht verschont: https://www.cvedetails.com/cve/CVE-2016-6818/ 

100 %-ig sicher kann man sich nie sein (hat der JDBC-Treiber evtl. einen Bug?) Aber alles ist besser als eine Frickellösung, die Eingaben des Benutzers in einen SQL-String selbst einbaut. Das ist als ob der Maurer die Ziegel mit dem Uhu-Klebestift zusammenklebt.



JustNobody hat gesagt.:


> Und das mit dem Zuchthaus ist natürlich übertrieben


Geringfügig  Wobei man sagen muss, dass durch solche Scherze großer Schaden entstehen kann und die Frage ggf. wird, ob hier grobe Fahrlässigkeit unterstellt werden muss.


----------



## mihe7 (21. Aug 2020)

Detlef_Retlef hat gesagt.:


> @LimDul @JustNobody
> Aber dann wäre es ja sicher, wenn ich PreparedStatements verwende oder? Online steht überall, dass das die beste Abwehr gegen SQL Injections ist.


Ja, wobei Du natürlich Parameter verwenden musst, sonst bringt das wenig.


----------



## Detlef_Retlef (21. Aug 2020)

mihe7 hat gesagt.:


> Ja, wobei Du natürlich Parameter verwenden musst, sonst bringt das wenig.


Sehr gut dann bin ich ja auf dem richtigen Weg. Danke für die Hilfe.


----------

