Java - ciąg znaków zmiany znaczenia, aby zapobiec iniekcji SQL

146

Próbuję umieścić w javie jakiś iniekcję anti sql i jest mi bardzo trudno pracować z funkcją string „replaceAll”. Ostatecznie potrzebuję funkcji, która skonwertuje wszystkie istniejące \na \\, dowolne "na \", dowolne 'na \'i dowolne \nna \\ntak, że gdy ciąg jest oceniany przez wstrzyknięcia SQL MySQL, zostaną zablokowane.

Podłączyłem jakiś kod, z którym pracowałem, i cała \\\\\\\\\\\funkcja przyprawia mnie o szaleństwo. Byłbym bardzo wdzięczny, gdyby ktoś miał na to przykład.

Scott Bonner
źródło
1
Okay, doszedłem do wniosku, że gotowe zestawienia są drogą do zrobienia, jednak w oparciu o obecne cele muszę postępować zgodnie z pierwotnym planem i po prostu na razie założyć filtr i po osiągnięciu obecnego kamienia milowego mogę wróć i zrefaktoryzuj bazę danych pod kątem przygotowanej wypowiedzi. W międzyczasie, aby utrzymać tempo, czy ktoś ma rozwiązanie, aby skutecznie uciec przed powyższymi znakami dla MySQL, biorąc pod uwagę Javę, a jej system wyrażeń regularnych jest absolutnym problemem, aby obliczyć liczbę potrzebnych ucieczek ...
Scott Bonner
2
Nie wszystkie instrukcje SQL można parametryzować, na przykład „SET ROLE nazwa_roli” lub „LISTEN nazwa_kanału”
Neil McGuigan,
1
@NeilMcGuigan Yep. Większość sterowników odmówi również parametryzacji czegoś podobnego, CREATE VIEW myview AS SELECT * FROM mytable WHERE col = ?ponieważ instrukcja główna jest instrukcją DDL , nawet jeśli część, którą próbujesz sparametryzować, jest w rzeczywistości DML .
RzadkoNeedy

Odpowiedzi:

249

PreparedStatements to droga do zrobienia, ponieważ uniemożliwiają iniekcję SQL. Oto prosty przykład uwzględniający dane wejściowe użytkownika jako parametry:

public insertUser(String name, String email) {
   Connection conn = null;
   PreparedStatement stmt = null;
   try {
      conn = setupTheDatabaseConnectionSomehow();
      stmt = conn.prepareStatement("INSERT INTO person (name, email) values (?, ?)");
      stmt.setString(1, name);
      stmt.setString(2, email);
      stmt.executeUpdate();
   }
   finally {
      try {
         if (stmt != null) { stmt.close(); }
      }
      catch (Exception e) {
         // log this error
      }
      try {
         if (conn != null) { conn.close(); }
      }
      catch (Exception e) {
         // log this error
      }
   }
}

Bez względu na to, jakie znaki są w nazwie i e-mailu, zostaną one umieszczone bezpośrednio w bazie danych. Nie wpłyną one w żaden sposób na instrukcję INSERT.

Istnieją różne metody ustawiania dla różnych typów danych - to, której z nich używasz, zależy od tego, jakie są pola bazy danych. Na przykład, jeśli masz w bazie danych kolumnę INTEGER, powinieneś użyć setIntmetody. Dokumentacja PreparedStatement zawiera listę wszystkich dostępnych metod ustawiania i pobierania danych.

Kaleb Brasee
źródło
1
czy za pomocą tej metody możesz traktować każdy parametr jako ciąg znaków i nadal być bezpieczny? Próbuję znaleźć sposób na zaktualizowanie mojej istniejącej architektury, aby była bezpieczna bez konieczności przebudowywania całej warstwy bazy danych ...
Scott Bonner
1
Całe dynqmic SQL to tylko ciągi znaków, więc nie o to chodzi. Nie jestem zaznajomiony z PrepareStatement, więc prawdziwe pytanie brzmi: czy generuje sparametryzowane zapytanie, które można następnie wykonać za pomocą ExecuteUpdate. Jeśli tak, to dobrze. Jeśli nie, to po prostu ukrywa problem i możesz nie mieć żadnej bezpiecznej opcji poza przeprojektowaniem warstwy bazy danych. Radzenie sobie z iniekcją SQL jest jedną z tych rzeczy, które trzeba zaprojektować od samego początku; nie jest to coś, co można łatwo dodać później.
Cylon Cat
2
Jeśli wstawiasz do pola INTEGER, będziesz chciał użyć „setInt”. Podobnie inne numeryczne pola bazy danych używałyby innych metod ustawiających. Opublikowałem łącze do dokumentów PreparedStatement, które zawierają listę wszystkich typów ustawiających.
Kaleb Brasee
2
Tak, Cylon, PreparedStatements generuje sparametryzowane zapytania.
Kaleb Brasee
2
@Kaleb Brasee, dzięki. Dobrze wiedzieć. Narzędzia są różne w każdym środowisku, ale podstawową odpowiedzią jest przejście do zapytań parametrycznych.
Cylon Cat
46

Jedynym sposobem zapobiegania iniekcji SQL jest sparametryzowany SQL. Po prostu nie jest możliwe zbudowanie filtra, który byłby mądrzejszy niż ludzie, którzy zarabiają na życie hakowaniem SQL.

Dlatego użyj parametrów dla wszystkich klauzul input, updates i where. Dynamiczny SQL to po prostu otwarte drzwi dla hakerów, co obejmuje dynamiczny SQL w procedurach składowanych. Parametryzacja, parametryzacja, parametryzacja.

Cylon Cat
źródło
11
A nawet sparametryzowany SQL nie daje 100% gwarancji. Ale to bardzo dobry początek.
duffymo
2
@duffymo, zgadzam się, że nic nie jest w 100% bezpieczne. Czy masz przykład wstrzyknięcia SQL, który będzie działał nawet ze sparametryzowanym SQL?
Cylon Cat
3
@Cylon Cat: Oczywiście, gdy fragment kodu SQL (taki jak @WhereClause lub @tableName) jest przekazywany jako parametr, konkatenowany do kodu SQL i wykonywany dynamicznie. Wstrzyknięcie SQL ma miejsce, gdy pozwalasz użytkownikom pisać kod. Nie ma znaczenia, czy przechwycisz ich kod jako parametr, czy nie.
Steve Kass
16
Przy okazji, nie wiem, dlaczego nie ma o tym więcej wzmianek, ale praca z PreparedStatements jest też dużo łatwiejsza i dużo bardziej czytelna. Już samo to prawdopodobnie czyni je domyślnymi dla każdego programisty, który o nich wie.
Edan Maor
3
Zwróć uwagę, że przygotowanie przygotowanych zestawień dla niektórych baz danych jest BARDZO drogie w tworzeniu, więc jeśli musisz zrobić ich dużo, zmierz oba typy.
Thorbjørn Ravn Andersen
36

Jeśli naprawdę nie możesz użyć opcji obrony 1: gotowe instrukcje (zapytania parametryczne) lub opcji obrony 2: procedury składowane , nie twórz własnego narzędzia, użyj interfejsu API OWASP Enterprise Security . Z OWASP ESAPI hostowanego w Google Code:

Nie pisz własnych zabezpieczeń! Odkrywanie na nowo koła, jeśli chodzi o opracowywanie kontroli bezpieczeństwa dla każdej aplikacji internetowej lub usługi internetowej, prowadzi do marnowania czasu i ogromnych luk w zabezpieczeniach. Zestawy narzędzi OWASP Enterprise Security API (ESAPI) pomagają programistom chronić się przed błędami projektowymi i wdrożeniowymi związanymi z bezpieczeństwem.

Aby uzyskać więcej informacji, zobacz temat Zapobieganie iniekcjom SQL w języku Java i ściągawka dotycząca zapobiegania iniekcjom SQL .

Zwróć szczególną uwagę na opcję obrony 3: ucieczka od wszystkich danych wprowadzonych przez użytkownika, która wprowadza projekt OWASP ESAPI ).

Pascal Thivent
źródło
4
ESAPI wydaje się nie działać na dzień dzisiejszy. Na AWS jest WAF, który może pomóc w zapobieganiu wstrzykiwaniu SQL, XSS itp. Czy w tym momencie są jakieś inne alternatywy?
ChrisOdney
@ChrisOdney WAF można łatwo ominąć. Większość frameworków już implementuje własne zapobieganie iniekcjom SQL, w których automatycznie zmieniają parametry. Alternatywy dla starszych projektów: owasp.org/index.php/ ...
Javan R.,
19

(Jest to odpowiedź na komentarz OP w pierwotnym pytaniu; Całkowicie zgadzam się, że narzędzie PreparedStatement jest narzędziem do tego zadania, a nie wyrażeniami regularnymi).

Kiedy mówisz \n, czy masz na myśli sekwencję \+ nczy rzeczywisty znak nowego wiersza? Jeśli to \+ n, zadanie jest dość proste:

s = s.replaceAll("['\"\\\\]", "\\\\$0");

Aby dopasować jeden lewy ukośnik w danych wejściowych, należy umieścić cztery z nich w ciągu wyrażenia regularnego. Aby wstawić jeden odwrotny ukośnik na wyjściu, należy umieścić cztery z nich w ciągu zastępczym. Zakłada się, że tworzysz wyrażenia regularne i zamienniki w postaci literałów Java String. Jeśli utworzysz je w jakikolwiek inny sposób (np. Czytając je z pliku), nie musisz robić tego podwójnego znaku ucieczki.

Jeśli masz znak wysuwu wiersza na wejściu i chcesz go zastąpić sekwencją ucieczki, możesz wykonać drugie przejście przez wejście za pomocą tego:

s = s.replaceAll("\n", "\\\\n");

A może chcesz dwa odwrotne ukośniki (nie jestem tego pewien):

s = s.replaceAll("\n", "\\\\\\\\n");
Alan Moore
źródło
1
Dzięki za komentarz, podoba mi się sposób, w jaki zrobiłeś wszystkie znaki w jednym, chodziło mi o to, mniej regularny sposób zastępowania wszystkich dla każdego ... Nie jestem pewien, jak teraz przypisać odpowiedź na to pytanie . Ostatecznie przygotowane zestawienia jest odpowiedzią, ale dla mojego obecnego celu odpowiedź, której potrzebuję, jest Twoja. Czy byłbyś zdenerwowany, gdybym udzielił odpowiedzi na jedną z wcześniej przygotowanych odpowiedzi, czy jest sposób, aby podzielić się odpowiedzią między parą?
Scott Bonner
1
Ponieważ jest to tylko tymczasowy problem, śmiało zaakceptuj jedną z odpowiedzi w przygotowanym oświadczeniu.
Alan Moore
12

PreparedStatements to sposób postępowania w większości, ale nie we wszystkich przypadkach. Czasami znajdziesz się w sytuacji, w której zapytanie lub jego część musi zostać zbudowane i zapisane jako łańcuch do późniejszego wykorzystania. Zapoznaj się ze ściągawką dotyczącą zapobiegania iniekcjom SQL w witrynie OWASP, aby uzyskać więcej informacji i interfejsów API w różnych językach programowania.


źródło
1
Ściągawki OWASP zostały przeniesione do GitHub. Ściągawka
theferrit32
10

Przygotowane instrukcje są najlepszym rozwiązaniem, ale jeśli naprawdę musisz to zrobić ręcznie, możesz również użyć StringEscapeUtilsklasy z biblioteki Apache Commons-Lang. Ma escapeSql(String)metodę, której możesz użyć:

import org.apache.commons.lang.StringEscapeUtils; … String escapedSQL = StringEscapeUtils.escapeSql(unescapedSQL);

ToBe_HH
źródło
2
Dla porównania: commons.apache.org/proper/commons-lang/javadocs/api-2.6/org/… W każdym razie ta metoda ogranicza się tylko do cudzysłowów i nie wydaje się zapobiegać atakom typu SQL Injection.
Paco Abato
11
Zostało to usunięte z najnowszych wersji, ponieważ
unikało
2
Tę odpowiedź należy usunąć, ponieważ nie zapobiega ona iniekcji sql.
Javan R.
9

Użycie wyrażenia regularnego w celu usunięcia tekstu, który mógłby spowodować wstrzyknięcie SQL, brzmi tak, jakby instrukcja SQL była wysyłana do bazy danych za pośrednictwem, Statementa nie PreparedStatement.

Jednym z najłatwiejszych sposobów zapobiegania iniekcji SQL w pierwszej kolejności jest użycie a PreparedStatement, który akceptuje dane do zastąpienia w instrukcji SQL przy użyciu symboli zastępczych, która nie polega na konkatenacji ciągów w celu utworzenia instrukcji SQL do wysłania do bazy danych.

Aby uzyskać więcej informacji, dobrym początkiem byłoby użycie gotowych instrukcji z samouczków języka Java .

coobird
źródło
6

Jeśli masz do czynienia ze starszym systemem lub masz zbyt wiele miejsc, aby przełączyć się na PreparedStatements w zbyt krótkim czasie - tj. Jeśli napotkasz przeszkodę w zastosowaniu najlepszych praktyk sugerowanych przez inne odpowiedzi, możesz wypróbować AntiSQLFilter

Bozho
źródło
5

Potrzebujesz poniższego kodu. Na pierwszy rzut oka może to wyglądać jak każdy stary kod, który wymyśliłem. Jednak spojrzałem na kod źródłowy http://grepcode.com/file/repo1.maven.org/maven2/mysql/mysql-connector-java/5.1.31/com/mysql/jdbc/PreparedStatement. java . Następnie dokładnie przejrzałem kod setString (int parameterIndex, String x), aby znaleźć znaki, z których ucieka, i dostosowałem to do mojej własnej klasy, aby można go było użyć do celów, których potrzebujesz. W końcu, jeśli jest to lista postaci, które uciekają Oracle, to świadomość, że jest to naprawdę pocieszające pod względem bezpieczeństwa. Może Oracle potrzebuje zachęty, aby dodać metodę podobną do tej dla następnej większej wersji Java.

public class SQLInjectionEscaper {

    public static String escapeString(String x, boolean escapeDoubleQuotes) {
        StringBuilder sBuilder = new StringBuilder(x.length() * 11/10);

        int stringLength = x.length();

        for (int i = 0; i < stringLength; ++i) {
            char c = x.charAt(i);

            switch (c) {
            case 0: /* Must be escaped for 'mysql' */
                sBuilder.append('\\');
                sBuilder.append('0');

                break;

            case '\n': /* Must be escaped for logs */
                sBuilder.append('\\');
                sBuilder.append('n');

                break;

            case '\r':
                sBuilder.append('\\');
                sBuilder.append('r');

                break;

            case '\\':
                sBuilder.append('\\');
                sBuilder.append('\\');

                break;

            case '\'':
                sBuilder.append('\\');
                sBuilder.append('\'');

                break;

            case '"': /* Better safe than sorry */
                if (escapeDoubleQuotes) {
                    sBuilder.append('\\');
                }

                sBuilder.append('"');

                break;

            case '\032': /* This gives problems on Win32 */
                sBuilder.append('\\');
                sBuilder.append('Z');

                break;

            case '\u00a5':
            case '\u20a9':
                // escape characters interpreted as backslash by mysql
                // fall through

            default:
                sBuilder.append(c);
            }
        }

        return sBuilder.toString();
    }
}
Richard
źródło
2
Myślę, że ten kod jest zdekompilowaną wersją kodu źródłowego w powyższym linku. Teraz w nowych mysql-connector-java-xxx, az case '\u00a5'oraz case '\u20a9'sprawozdania wydają został usunięty
zhy
Próbowałem sqlmap z twoim kodem i nie chroniło mnie to przed pierwszym atakiem `Type: boolean-based blind Title: AND boolean based blind - WHERE or HAVING clause Payload: q = 1% 'AND 5430 = 5430 AND'% ' = '`
shareef
Przepraszam, że działa, ale przeglądałem ostatnie zapisane wyniki sesji .. Zachowałem komentarz na przyszłość podobny ..
shareef
Możesz użyć org.ostermiller.utils.StringHelper.escapeSQL()lub com.aoindustries.sql.SQLUtility.escapeSQL().
Mohamed Ennahdi El Idrissi
1
Ważne, aby zwrócić uwagę na licencję GPLv2 na oryginalny kod, z którego została skopiowana dla każdego, kto zetknie się z tym. Nie jestem prawnikiem, ale zdecydowanie odradzałbym używanie tej odpowiedzi w swoim projekcie, chyba że masz pełną świadomość konsekwencji dołączenia tego licencjonowanego kodu.
Nick Spacek
0

Po przeszukaniu wielu testowych rozwiązań zapobiegających wstrzykiwaniu sqlmap do sql, w przypadku starszego systemu, który nie może wszędzie zastosować przygotowanych instrukcji.

temat java-security-cross-site-scripting-xss-and-sql-injection BYŁO ROZWIĄZANIEM

wypróbowałem rozwiązanie @Richard, ale nie zadziałało w moim przypadku. użyłem filtra

Celem tego filtru jest umieszczenie żądania w zakodowanym przez siebie opakowaniu MyHttpRequestWrapper, który przekształca:

parametry HTTP ze znakami specjalnymi (<,>, ',…) do kodów HTML za pomocą metody org.springframework.web.util.HtmlUtils.htmlEscape (…). Uwaga: w Apache Commons występuje podobna klasa: org.apache.commons.lang.StringEscapeUtils.escapeHtml (…) znaki iniekcji SQL („,„,…) za pośrednictwem Apache Commons classe org.apache.commons.lang.StringEscapeUtils. escapeSql (…)

<filter>
<filter-name>RequestWrappingFilter</filter-name>
<filter-class>com.huo.filter.RequestWrappingFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>RequestWrappingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>




package com.huo.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletReponse;
import javax.servlet.http.HttpServletRequest;

public class RequestWrappingFilter implements Filter{

    public void doFilter(ServletRequest req, ServletReponse res, FilterChain chain) throws IOException, ServletException{
        chain.doFilter(new MyHttpRequestWrapper(req), res);
    }

    public void init(FilterConfig config) throws ServletException{
    }

    public void destroy() throws ServletException{
    }
}




package com.huo.filter;

import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

import org.apache.commons.lang.StringEscapeUtils;

public class MyHttpRequestWrapper extends HttpServletRequestWrapper{
    private Map<String, String[]> escapedParametersValuesMap = new HashMap<String, String[]>();

    public MyHttpRequestWrapper(HttpServletRequest req){
        super(req);
    }

    @Override
    public String getParameter(String name){
        String[] escapedParameterValues = escapedParametersValuesMap.get(name);
        String escapedParameterValue = null; 
        if(escapedParameterValues!=null){
            escapedParameterValue = escapedParameterValues[0];
        }else{
            String parameterValue = super.getParameter(name);

            // HTML transformation characters
            escapedParameterValue = org.springframework.web.util.HtmlUtils.htmlEscape(parameterValue);

            // SQL injection characters
            escapedParameterValue = StringEscapeUtils.escapeSql(escapedParameterValue);

            escapedParametersValuesMap.put(name, new String[]{escapedParameterValue});
        }//end-else

        return escapedParameterValue;
    }

    @Override
    public String[] getParameterValues(String name){
        String[] escapedParameterValues = escapedParametersValuesMap.get(name);
        if(escapedParameterValues==null){
            String[] parametersValues = super.getParameterValues(name);
            escapedParameterValue = new String[parametersValues.length];

            // 
            for(int i=0; i<parametersValues.length; i++){
                String parameterValue = parametersValues[i];
                String escapedParameterValue = parameterValue;

                // HTML transformation characters
                escapedParameterValue = org.springframework.web.util.HtmlUtils.htmlEscape(parameterValue);

                // SQL injection characters
                escapedParameterValue = StringEscapeUtils.escapeSql(escapedParameterValue);

                escapedParameterValues[i] = escapedParameterValue;
            }//end-for

            escapedParametersValuesMap.put(name, escapedParameterValues);
        }//end-else

        return escapedParameterValues;
    }
}
shareef
źródło
Czy to dobrze java-security-cross-site-scripting-xss-and-sql-injection topic? Próbuję znaleźć rozwiązanie dla starszej aplikacji.
caot
0

Od: [Źródło]

public String MysqlRealScapeString(String str){
  String data = null;
  if (str != null && str.length() > 0) {
    str = str.replace("\\", "\\\\");
    str = str.replace("'", "\\'");
    str = str.replace("\0", "\\0");
    str = str.replace("\n", "\\n");
    str = str.replace("\r", "\\r");
    str = str.replace("\"", "\\\"");
    str = str.replace("\\x1a", "\\Z");
    data = str;
  }
return data;

}

Billcountry
źródło
0

Jeśli używasz PL / SQL, możesz go również użyć DBMS_ASSERT , aby oczyścić dane wejściowe, dzięki czemu możesz z niego korzystać bez martwienia się o wstrzyknięcia SQL.

zobacz tę odpowiedź na przykład: https://stackoverflow.com/a/21406499/1726419

yossico
źródło