it-swarm.com.de

Wie vermeide ich SQL-Injection, wenn die Anweisung einen dynamischen Tabellennamen hat?

Ich habe so etwas wie Code.

   final PreparedStatement stmt = connection
                .prepareStatement("delete from " + fullTableName
                    + " where name= ?");
   stmt.setString(1, addressName);

Berechnung von fullTableName ist so etwas wie:

 public String getFullTableName(final String table) {
    if (this.schemaDB != null) {
        return this.schemaDB + "." + table;
    }
    return table;
 }

Hier ist schemaDB der Name der Umgebung (der im Laufe der Zeit geändert werden kann) und table der Tabellenname (der festgelegt wird).

Der Wert für schemaDB stammt aus einer XML-Datei, die die Abfrage für die SQL-Injection anfällig macht.

Abfrage: Ich bin nicht sicher, wie der Tabellenname als vorbereitete Anweisung verwendet werden kann (wie die in diesem Beispiel verwendete name). Dies ist die 100% ige Sicherheitsmaßnahme gegen SQL-Injection.

Könnte mir bitte jemand einen Vorschlag machen, wie man damit umgehen könnte?

Hinweis: Wir können in Zukunft auf DB2 migriert werden, sodass die Lösung sowohl mit Oracle als auch mit DB2 kompatibel sein sollte (und wenn möglich datenbankunabhängig).

8
Gaurav

JDBC erlaubt es Ihnen leider nicht, den Tabellennamen in Anweisungen als gebundene Variable zu definieren. (Es hat seine Gründe dafür).

Sie können also nicht schreiben oder diese Art von Funktionalität erreichen:

connection.prepareStatement("SELECT * FROM ? where id=?", "TUSERS", 123);

Und TUSER muss an den Tabellennamen der Anweisung gebunden sein.

Daher ist Ihre einzige sichere Möglichkeit, die Benutzereingabe zu validieren. Am sichersten ist es jedoch, die Datenbank nicht zu validieren und Benutzereingaben zuzulassen, da Sie sicher sein können, dass ein Benutzer immer intelligenter ist als Ihre Validierung. Vertrauen Sie niemals einem dynamischen, benutzerdefinierten String, der in Ihrer Anweisung verkettet ist.

Was ist ein sicheres Validierungsmuster?

Muster 1: Erstellen Sie vorab sichere Abfragen

1) Erstellen Sie alle Ihre gültigen Anweisungen ein für alle Mal im Code.

Map<String, String> statementByTableName = new HashMap<>();
statementByTableName.put("table_1", "DELETE FROM table_1 where name= ?");
statementByTableName.put("table_2", "DELETE FROM table_2 where name= ?");

Bei Bedarf kann diese Erstellung selbst mit einer select * from ALL_TABLES;-Anweisung dynamisiert werden. ALL_TABLES gibt alle Tabellen zurück, auf die Ihr SQL-Benutzer Zugriff hat, und Sie können auch den Tabellennamen und den Schemanamen daraus abrufen.

2) Wählen Sie die Anweisung in der Karte aus

String unsafeUserContent = ...
String safeStatement = statementByTableName.get(usafeUserContent);
conn.prepareStatement(safeStatement, name);

Sehen Sie, wie die Variable unsafeUserContent niemals die Datenbank erreicht.

3) Führen Sie eine Richtlinie oder einen Komponententest durch, mit dem überprüft wird, ob alle von Ihnen statementByTableName für zukünftige Entwicklungen in Bezug auf Ihre Schemata gültig sind und ob keine Tabelle fehlt.

Muster 2: Doppelprüfung

Sie können 1) überprüfen, ob die Benutzereingabe tatsächlich ein Tabellenname ist, indem Sie eine injektionsfreie Abfrage verwenden (ich gebe hier Pseudo-SQL-Code ein, Sie müssen ihn anpassen, damit er funktioniert, da ich keine Oracle-Instanz habe, die tatsächlich überprüft werden muss Es klappt) :

select * FROM 
    (select schema_name || '.' || table_name as fullName FROM all_tables)
WHERE fullName = ?

Und binden Sie hier Ihren vollständigen Namen als vorbereitete Anweisungsvariable. Wenn Sie ein Ergebnis haben, ist es ein gültiger Tabellenname. Dann können Sie dieses Ergebnis verwenden, um eine sichere Abfrage zu erstellen.

Muster 3

Es ist eine Art Mischung zwischen 1 und 2. Sie erstellen eine Tabelle mit dem Namen "TABLES_ALLOWED_FOR_DELETION" und statisieren sie mit allen Tabellen, die zum Löschen geeignet sind.

Dann machen Sie Ihren Validierungsschritt

conn.prepareStatement(SELECT safe_table_name FROM TABLES_ALLOWED_FOR_DELETION WHERE table_name = ?", unsafeDynamicString);

Wenn dies zu einem Ergebnis führt, führen Sie den safe_table_name aus. Aus Sicherheitsgründen sollte diese Tabelle vom Standardanwender nicht beschreibbar sein.

Ich finde irgendwie, dass das erste Muster besser ist.

8
GPI

Sie können Angriffe vermeiden, indem Sie Ihren Tabellennamen mit einem regulären Ausdruck überprüfen:

if (fullTableName.matches("[_a-zA-Z0-9\\.]+")) {
    final PreparedStatement stmt = connection
                .prepareStatement("delete from " + fullTableName
                    + " where name= ?");
    stmt.setString(1, addressName);
}

Es ist unmöglich, SQL mit einem derart eingeschränkten Zeichensatz zu injizieren.

Außerdem können wir alle Anführungszeichen aus dem Tabellennamen entfernen und sie sicher zu unserer Abfrage hinzufügen:

fullTableName = StringEscapeUtils.escapeSql(fullTableName);
final PreparedStatement stmt = connection
            .prepareStatement("delete from " + fullTableName
                + " where name= ?");
stmt.setString(1, addressName);

StringEscapeUtils wird mit der Apache-Bibliothek commons-lang geliefert.

0
elyor
create table MYTAB(n number);
insert into MYTAB values(10);
commit;
select * from mytab;

N
10

create table TABS2DEL(tname varchar2(32));
insert into TABS2DEL values('MYTAB');
commit;
select * from TABS2DEL;

TNAME
MYTAB

create or replace procedure deltab(v in varchar2)
is

    LvSQL varchar2(32767);
    LvChk number;

begin
    LvChk := 0;
    begin
        select count(1)
          into LvChk
          from TABS2DEL
         where tname = v;

         if LvChk = 0 then
             raise_application_error(-20001, 'Input table name '||v||' is not a valid table name');
         end if;


    exception when others
              then raise;
    end;

    LvSQL := 'delete from '||v||' where n = 10';
    execute immediate LvSQL;
    commit;

end deltab;

begin
deltab('MYTAB');
end;

select * from mytab;

keine Zeilen gefunden

begin
deltab('InvalidTableName');
end;

ORA-20001: Input table name InvalidTableName is not a valid table name ORA-06512: at "SQL_PHOYNSAMOMWLFRCCFWUMTBQWC.DELTAB", line 21
ORA-06512: at "SQL_PHOYNSAMOMWLFRCCFWUMTBQWC.DELTAB", line 16
ORA-06512: at line 2
ORA-06512: at "SYS.DBMS_SQL", line 1721
0
ArtBajji

Ich denke, dass der beste Ansatz darin besteht, eine Reihe möglicher Tabellennamen zu erstellen und zu prüfen, ob diese vorhanden sind, bevor Sie eine Abfrage erstellen.

Set<String> validTables=.... // prepare this set yourself

    if(validTables.contains(fullTableName))
    {
       final PreparedStatement stmt = connection
                    .prepareStatement("delete from " + fullTableName
                        + " where name= ?");

    //and so on
    }else{
       // ooooh you nasty haker!
    }
0
Antoniossss