Java - escape string untuk mencegah injeksi SQL

146

Saya mencoba untuk menempatkan beberapa injeksi anti sql di Jawa dan saya merasa sangat sulit untuk bekerja dengan fungsi string "replaceAll". Pada akhirnya saya membutuhkan fungsi yang akan mengkonversi semua yang ada \ke \\, "ke \", 'ke \', dan \nke apa pun \\nsehingga ketika string dievaluasi oleh MySQL, suntikan SQL akan diblokir.

Saya telah mendongkrak beberapa kode yang sedang saya kerjakan dan semua \\\\\\\\\\\fungsinya membuat mata saya jadi gila. Jika ada orang yang memiliki contoh ini, saya akan sangat menghargainya.

Scott Bonner
sumber
1
Oke, saya sampai pada kesimpulan bahwa PreparedStatements adalah jalan yang harus saya tempuh, namun berdasarkan pada objek yang ada saat ini, saya perlu melanjutkan seperti yang direncanakan sebelumnya dan hanya meletakkan filter di tempat untuk saat ini dan setelah tonggak pencapaian saat ini tercapai saya dapat kembali dan refactor database untuk pernyataan yang disiapkan. Sementara itu untuk menjaga momentum, apakah seseorang memiliki solusi untuk secara efektif melarikan diri dari karakter-karakter di atas untuk MySQL yang diberikan Java dan sistem ekspresi regulernya adalah rasa sakit mutlak untuk menghitung jumlah pelarian yang diperlukan ....
Scott Bonner
2
Tidak semua pernyataan SQL parameterable, misalnya "SET ROLE role_name" atau "LISTEN channel_name"
Neil McGuigan
1
@NeilMcGuigan Yep. Sebagian besar driver juga akan menolak melakukan parameterisasi CREATE VIEW myview AS SELECT * FROM mytable WHERE col = ?karena pernyataan utama adalah pernyataan- DDL , meskipun bagian yang Anda coba untuk parameterisasi sebenarnya adalah DML .
SeldomNeedy

Jawaban:

249

PreparedStatements adalah cara yang harus dilakukan, karena mereka membuat injeksi SQL tidak mungkin. Berikut ini contoh sederhana mengambil input pengguna sebagai parameter:

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
      }
   }
}

Tidak peduli karakter apa yang ada dalam nama dan email, karakter-karakter itu akan ditempatkan secara langsung dalam database. Mereka tidak akan mempengaruhi pernyataan INSERT dengan cara apa pun.

Ada beberapa metode kumpulan untuk tipe data yang berbeda - yang Anda gunakan tergantung pada bidang database Anda. Misalnya, jika Anda memiliki kolom INTEGER dalam database, Anda harus menggunakan setIntmetode. Dokumentasi PreparedStatement mencantumkan semua metode berbeda yang tersedia untuk mengatur dan mendapatkan data.

Kaleb Brasee
sumber
1
via metode ini dapatkah Anda memperlakukan setiap parameter sebagai string dan tetap aman? Saya mencoba untuk mencari cara untuk memperbarui arsitektur yang ada untuk menjadi aman tanpa harus membangun kembali lapisan database seluruh ...
Scott Bonner
1
Semua Dynqmic SQL hanyalah string, jadi itu bukan pertanyaan untuk ditanyakan. Saya tidak terbiasa dengan ReadyStatement, jadi pertanyaan sebenarnya adalah apakah ia menghasilkan permintaan parameter yang kemudian dapat dieksekusi dengan ExecuteUpdate. Jika ya, itu bagus. Jika tidak, maka itu hanya menyembunyikan masalah, dan Anda mungkin tidak memiliki opsi aman kecuali mendesain ulang lapisan database. Berurusan dengan injeksi SQL adalah salah satu hal yang harus Anda rancang sejak awal; itu bukan sesuatu yang bisa Anda tambahkan dengan mudah nanti.
Cylon Cat
2
Jika Anda memasukkan ke dalam bidang INTEGER, Anda ingin menggunakan 'setInt'. Demikian juga, bidang basis data numerik lainnya akan menggunakan setter lainnya. Saya memasang tautan ke dokumen PreparedStatement yang mencantumkan semua jenis penyetel.
Kaleb Brasee
2
Ya Cylon, PreparedStatements menghasilkan pertanyaan parameter.
Kaleb Brasee
2
@ Kaleb Brasee, terima kasih. Senang mendengarnya. Alat-alat berbeda di setiap lingkungan, tetapi turun ke pertanyaan parameter adalah jawaban mendasar.
Cylon Cat
46

Satu-satunya cara untuk mencegah injeksi SQL adalah dengan SQL parameter. Sangat tidak mungkin untuk membangun filter yang lebih pintar daripada orang yang meretas SQL untuk mencari nafkah.

Jadi gunakan parameter untuk semua input, pembaruan, dan klausa mana. Dynamic SQL hanyalah pintu terbuka bagi peretas, dan itu termasuk SQL dinamis dalam prosedur tersimpan. Parameterisasi, parameterisasi, parameterisasi.

Kucing Cylon
sumber
11
Dan bahkan parameterized SQL bukanlah jaminan 100%. Tapi ini awal yang sangat bagus.
duffymo
2
@duffymo, saya setuju bahwa tidak ada yang 100% aman. Apakah Anda memiliki contoh injeksi SQL yang akan berfungsi bahkan dengan SQL parameterized?
Cylon Cat
3
@Cylon Cat: Tentu, ketika sepotong SQL (seperti @WhereClause atau @tableName) dilewatkan sebagai parameter, disatukan ke dalam SQL, dan dieksekusi secara dinamis. Injeksi SQL terjadi ketika Anda membiarkan pengguna menulis kode Anda. Tidak masalah apakah Anda menangkap kodenya sebagai parameter atau tidak.
Steve Kass
16
BTW, saya tidak tahu mengapa hal ini tidak disebutkan lebih, tapi bekerja dengan PreparedStatements juga banyak lebih mudah dan banyak lebih mudah dibaca. Itu saja mungkin membuat mereka default untuk setiap programmer yang tahu tentang mereka.
Edan Maor
3
Harap dicatat bahwa PreparedStatements untuk beberapa database SANGAT mahal untuk dibuat, jadi jika Anda perlu melakukan banyak dari itu, ukur kedua jenis.
Thorbjørn Ravn Andersen
36

Jika Anda benar-benar tidak dapat menggunakan Opsi Pertahanan 1: Pernyataan yang Disiapkan (Kueri Parameterized) atau Opsi Pertahanan 2: Prosedur Tersimpan , jangan membuat alat Anda sendiri, gunakan API Keamanan OWASP Enterprise . Dari OWASP ESAPI yang dihosting di Google Code:

Jangan tulis kontrol keamanan Anda sendiri! Menemukan kembali roda ketika datang untuk mengembangkan kontrol keamanan untuk setiap aplikasi web atau layanan web menyebabkan waktu terbuang dan lubang keamanan besar. Toolkit OWASP Enterprise Security API (ESAPI) membantu pengembang perangkat lunak berjaga-jaga terhadap kelemahan terkait desain dan implementasi yang terkait dengan keamanan.

Untuk detail lebih lanjut, lihat Mencegah Injeksi SQL di Java dan Lembar Cheat Pencegahan Injeksi SQL .

Berikan perhatian khusus pada Opsi Pertahanan 3: Melarikan Diri dari Semua Input yang Disediakan Pengguna yang memperkenalkan proyek OWASP ESAPI ).

Thivent Pascal
sumber
4
ESAPI tampaknya tidak berfungsi pada hari ini. Pada AWS ada WAF yang dapat membantu melawan injeksi SQL, XSS dll. Adakah alternatif lain saat ini?
ChrisOdney
@ ChrisOdney WAF dapat dengan mudah dilewati. Sebagian besar Kerangka sudah menerapkan pencegahan SQL-Injection mereka sendiri di mana mereka melarikan diri parameter secara otomatis oleh mereka sendiri. Alternatif untuk proyek lawas: owasp.org/index.php/…
Javan R.
19

(Ini sebagai jawaban atas komentar OP di bawah pertanyaan awal; Saya sepenuhnya setuju bahwa PreparedStatement adalah alat untuk pekerjaan ini, bukan regex.)

Ketika Anda mengatakan \n, apakah maksud Anda urutan \+ natau karakter linefeed yang sebenarnya? Jika itu \+ n, tugasnya cukup mudah:

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

Untuk mencocokkan satu garis miring terbalik di input, Anda menempatkan empat di string regex. Untuk memasukkan satu backslash ke dalam output, Anda memasukkan empat dari mereka ke dalam string pengganti. Ini dengan asumsi Anda membuat regex dan penggantian dalam bentuk literal Java String. Jika Anda membuatnya dengan cara lain (misalnya, dengan membacanya dari file), Anda tidak perlu melakukan semua itu dengan melarikan diri dua kali.

Jika Anda memiliki karakter linefeed di input dan Anda ingin menggantinya dengan urutan escape, Anda dapat membuat pass kedua melewati input dengan ini:

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

Atau mungkin Anda ingin dua backslash (saya tidak terlalu jelas tentang itu):

s = s.replaceAll("\n", "\\\\\\\\n");
Alan Moore
sumber
1
Terima kasih atas komentarnya, saya suka cara Anda melakukan semua karakter dalam satu, saya akan melakukannya dengan cara ekspresi kurang teratur untuk mengganti semua untuk masing-masing ... Saya tidak yakin bagaimana memberikan jawaban untuk pertanyaan ini sekarang . Akhirnya PreparedStatements adalah jawabannya, tetapi untuk tujuan saya saat ini, jawaban Anda adalah jawaban yang saya butuhkan, apakah Anda akan marah jika saya memberikan jawaban atas salah satu jawaban pernyataan sebelumnya, atau adakah cara untuk membagikan jawaban di antara pasangan?
Scott Bonner
1
Karena ini hanyalah kludge sementara, silakan dan terima salah satu jawaban PreparedStatement.
Alan Moore
12

PreparedStatements adalah cara untuk melakukannya, tetapi tidak semua kasus. Terkadang Anda akan menemukan diri Anda dalam situasi di mana kueri, atau bagian dari itu, harus dibangun dan disimpan sebagai string untuk digunakan nanti. Lihat Lembar Cheat Pencegahan Injeksi SQL di Situs OWASP untuk detail lebih lanjut dan API dalam berbagai bahasa pemrograman.


sumber
1
Cheatsheet OWASP telah dipindahkan ke GitHub. Lembar contekan SQL Injection sekarang ada di sini: github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/…
theferrit32
10

Pernyataan yang Disiapkan adalah solusi terbaik, tetapi jika Anda benar-benar perlu melakukannya secara manual Anda juga bisa menggunakan StringEscapeUtilskelas dari perpustakaan Apache Commons-Lang. Ini memiliki escapeSql(String)metode, yang dapat Anda gunakan:

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

ToBe_HH
sumber
2
Untuk referensi: commons.apache.org/proper/commons-lang/javadocs/api-2.6/org/... Bagaimanapun, metode ini hanya lolos dari penawaran dan sepertinya tidak mencegah serangan SQL Injection.
Paco Abato
11
Ini telah dihapus dari versi terbaru karena itu hanya lolos tanda kutip tunggal
Pini Cheyni
2
Jawaban ini harus dihapus karena tidak mencegah injeksi sql.
Javan R.
9

Menggunakan ekspresi reguler untuk menghapus teks yang dapat menyebabkan injeksi SQL terdengar seperti pernyataan SQL dikirim ke database melalui Statementbukan PreparedStatement.

Salah satu cara termudah untuk mencegah injeksi SQL di tempat pertama adalah dengan menggunakan PreparedStatement, yang menerima data untuk menggantikan pernyataan SQL menggunakan placeholder, yang tidak bergantung pada rangkaian string untuk membuat pernyataan SQL untuk dikirim ke database.

Untuk informasi lebih lanjut, Menggunakan Pernyataan Disiapkan dari Tutorial Java akan menjadi tempat yang baik untuk memulai.

coobird
sumber
6

Jika Anda berurusan dengan sistem lama, atau Anda memiliki terlalu banyak tempat untuk beralih PreparedStatementdalam waktu yang terlalu sedikit - yaitu jika ada kendala untuk menggunakan praktik terbaik yang disarankan oleh jawaban lain, Anda dapat mencoba AntiSQLFilter

Bozho
sumber
5

Anda memerlukan kode berikut di bawah ini. Sekilas, ini mungkin terlihat seperti kode lama yang saya buat. Namun, yang saya lakukan adalah melihat kode sumber http://grepcode.com/file/repo1.maven.org/maven2/mysql/mysql-connector-java/5.1.31/com/mysql/jdbc/PreparedStatement. java . Kemudian setelah itu, saya hati-hati melihat melalui kode setString (int parameterIndex, String x) untuk menemukan karakter yang lolos dan mengkustomisasi ini ke kelas saya sendiri sehingga dapat digunakan untuk keperluan yang Anda butuhkan. Lagi pula, jika ini adalah daftar karakter yang lolos dari Oracle, maka mengetahui ini benar-benar menghibur dari segi keamanan. Mungkin Oracle membutuhkan dorongan untuk menambahkan metode yang mirip dengan yang ini untuk rilis utama Java berikutnya.

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
sumber
2
Saya pikir kode ini adalah versi kode sumber yang didekompilasi dalam tautan di atas. Sekarang di yang lebih baru mysql-connector-java-xxx, case '\u00a5'dan case '\u20a9'pernyataan tampaknya telah dihapus
zhy
saya mencoba sqlmap dengan kode Anda dan itu tidak melindungi saya dari serangan frist `Jenis: boolean-based blind Judul: AND boolean-blind - WHERE atau HAVING klausa Payload: q = 1% 'DAN 5430 = 5430 AND'% ' = '`
shareef
Maaf ini berfungsi tetapi sedang melihat hasil sesi tersimpan terakhir .. saya menyimpan komentar untuk serupa di masa depan ..
shareef
Anda bisa menggunakan org.ostermiller.utils.StringHelper.escapeSQL()atau com.aoindustries.sql.SQLUtility.escapeSQL().
Mohamed Ennahdi El Idrissi
1
Penting untuk dicatat lisensi GPLv2 pada kode asli tempat ini disalin dari siapa pun yang menemukan ini. Saya bukan pengacara tetapi saya sangat merekomendasikan untuk tidak menggunakan jawaban ini dalam proyek Anda kecuali Anda sepenuhnya menyadari implikasi dari memasukkan kode lisensi ini.
Nick Spacek
0

Setelah mencari banyak pengujian solusi untuk mencegah sqlmap dari injeksi sql, dalam kasus sistem warisan yang tidak dapat menerapkan statments disiapkan di mana-mana.

topik java-security-cross-site-scripting-xss-dan-sql-injeksi ADALAH SOLUSI

saya mencoba solusi @Richard tetapi tidak berhasil dalam kasus saya. saya menggunakan filter

Tujuan filter ini adalah untuk membungkus permintaan menjadi pembungkus berkode sendiri, MyHttpRequestWrapper yang mengubah:

parameter HTTP dengan karakter khusus (<,>, ', ...) ke dalam kode HTML melalui metode org.springframework.web.util.HtmlUtils.htmlEscape (…). Catatan: Ada classe yang sama di Apache Commons: org.apache.commons.lang.StringEscapeUtils.escapeHtml (...) karakter injeksi SQL (', “,…) melalui 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
sumber
Apakah ini bagus java-security-cross-site-scripting-xss-and-sql-injection topic? Saya mencoba mencari solusi untuk aplikasi lawas.
caot
0

Dari: [Sumber]

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
sumber
0

Jika Anda menggunakan PL / SQL, Anda juga dapat menggunakannya DBMS_ASSERT untuk membersihkan input Anda sehingga Anda dapat menggunakannya tanpa khawatir tentang suntikan SQL.

lihat jawaban ini misalnya: https://stackoverflow.com/a/21406499/1726419

yossico
sumber