Java - chuỗi thoát để ngăn SQL tiêm


146

Tôi đang cố gắng đưa một số tính năng chống sql vào vị trí trong java và tôi thấy rất khó để làm việc với hàm chuỗi "thay thế". Cuối cùng, tôi cần một chức năng mà sẽ chuyển đổi bất kỳ hiện \để \\, bất kỳ "để \", bất kỳ 'để \', và bất kỳ \nđể \\nsao cho khi chuỗi được đánh giá bằng cách tiêm MySQL SQL sẽ bị chặn.

Tôi đã kích hoạt một số mã mà tôi đang làm việc và tất cả \\\\\\\\\\\các chức năng đang khiến mắt tôi trở nên điên loạn. Nếu bất cứ ai tình cờ có một ví dụ về điều này, tôi sẽ đánh giá rất cao nó.


1
Được rồi, tôi đã đi đến kết luận rằng PreparedStatements là cách để đi, tuy nhiên dựa trên những phản đối hiện tại tôi cần tiến hành như dự kiến ​​ban đầu và chỉ cần đặt một bộ lọc vào thời điểm hiện tại và khi đạt được cột mốc hiện tại tôi có thể quay lại và cấu trúc lại cơ sở dữ liệu để chuẩn bị. Trong thời gian chờ đợi để duy trì động lực, có ai đó có một giải pháp để thoát hiệu quả các ký tự trên cho MySQL được cung cấp cho Java và hệ thống biểu thức chính quy của nó là một nỗi đau tuyệt đối để tìm ra số lần thoát cần thiết ....
Scott Bonner

2
Không phải tất cả các câu lệnh SQL đều có thể tham số hóa, ví dụ "SET ROLE role_name" hoặc "LISTEN channel_name"
Neil McGuigan

1
@NeilMcGuigan Yep. Hầu hết các trình điều khiển cũng sẽ từ chối tham số hóa một cái gì đó giống như CREATE VIEW myview AS SELECT * FROM mytable WHERE col = ?vì câu lệnh chính là phân đoạn DDL , mặc dù phần bạn đang cố gắng tham số hóa thực sự là DML .
SeldomNeedy

Câu trả lời:


249

PreparedStatements là cách để đi, bởi vì chúng làm cho việc tiêm SQL không thể thực hiện được. Đây là một ví dụ đơn giản lấy đầu vào của người dùng làm tham số:

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

Bất kể ký tự nào trong tên và email, những ký tự đó sẽ được đặt trực tiếp trong cơ sở dữ liệu. Chúng sẽ không ảnh hưởng đến tuyên bố INSERT dưới bất kỳ hình thức nào.

Có các phương thức thiết lập khác nhau cho các loại dữ liệu khác nhau - phương thức bạn sử dụng phụ thuộc vào trường cơ sở dữ liệu của bạn là gì. Ví dụ: nếu bạn có cột INTEGER trong cơ sở dữ liệu, bạn nên sử dụng một setIntphương thức. Tài liệu PreparedStatement liệt kê tất cả các phương thức khác nhau có sẵn để cài đặt và nhận dữ liệu.


1
thông qua phương pháp này bạn có thể coi mọi tham số là một chuỗi mà vẫn an toàn không? Tôi đang cố gắng tìm ra cách cập nhật kiến ​​trúc hiện tại của mình để an toàn mà không phải xây dựng lại toàn bộ lớp cơ sở dữ liệu ...
Scott Bonner

1
Tất cả SQL dynqmic chỉ là các chuỗi, vì vậy đó không phải là câu hỏi. Tôi không quen thuộc với PrepStatement, vì vậy câu hỏi thực sự là nó có tạo ra một truy vấn được tham số hóa để sau đó có thể được thực thi với ExecuteUpdate. Nếu có, điều đó tốt. Nếu không, thì nó chỉ đơn giản là che giấu vấn đề và bạn có thể không có bất kỳ tùy chọn bảo mật nào ngoại trừ thiết kế lại lớp cơ sở dữ liệu. Đối phó với SQL SQL là một trong những điều bạn phải thiết kế ngay từ đầu; nó không phải là thứ bạn có thể dễ dàng thêm vào sau này.
Cylon Cat

2
Nếu bạn đang chèn vào trường INTEGER, bạn sẽ muốn sử dụng 'setInt'. Tương tự, các trường cơ sở dữ liệu số khác sẽ sử dụng các setters khác. Tôi đã đăng một liên kết đến các tài liệu PreparedStatement liệt kê tất cả các loại setter.
Kaleb Brasee

2
Có Cylon, PreparedStatements tạo các truy vấn tham số.
Kaleb Brasee

2
@Kaleb Brasee, cảm ơn. Đó là điều tốt để biết. Các công cụ là khác nhau trong mọi môi trường, nhưng nhận được các truy vấn tham số là câu trả lời cơ bản.
Mèo Cylon

46

Cách duy nhất để ngăn chặn việc tiêm SQL là với SQL được tham số hóa. Đơn giản là không thể xây dựng bộ lọc thông minh hơn những người hack SQL để kiếm sống.

Vì vậy, sử dụng các tham số cho tất cả các đầu vào, cập nhật và các mệnh đề. Dynamic SQL đơn giản là một cánh cửa mở cho tin tặc và bao gồm SQL động trong các thủ tục được lưu trữ. Tham số hóa, tham số hóa, tham số hóa.


11
Và thậm chí SQL được tham số hóa không đảm bảo 100%. Nhưng đó là một khởi đầu rất tốt.
duffymo

2
@duffymo, tôi đồng ý rằng không có gì là an toàn 100%. Bạn có một ví dụ về SQL tiêm sẽ hoạt động ngay cả với SQL được tham số hóa không?
Cylon Cat

3
@Cylon Cat: Chắc chắn, khi một đoạn SQL (như @WhereClause hoặc @tableName) được truyền dưới dạng tham số, được nối vào SQL và được thực thi động. SQL tiêm xảy ra khi bạn cho phép người dùng viết mã của bạn. Không quan trọng bạn có nắm bắt mã của họ như một tham số hay không.
Steve Kass

16
BTW, tôi không biết tại sao điều này không được đề cập nhiều, nhưng làm việc với PreparedStatements cũng nhiều dễ dàng hơn và nhiều hơn có thể đọc được. Điều đó một mình có lẽ làm cho chúng trở thành mặc định cho mọi lập trình viên biết về họ.
Edan Maor

3
Xin lưu ý rằng PreparedStatements cho một số cơ sở dữ liệu RẤT tốn kém để tạo, vì vậy nếu bạn cần thực hiện nhiều trong số chúng, hãy đo cả hai loại.
Thorbjørn Ravn Andersen

36

Nếu thực sự bạn không thể sử dụng Tùy chọn phòng thủ 1: Báo cáo đã chuẩn bị (Truy vấn được tham số hóa) hoặc Tùy chọn phòng thủ 2: Quy trình được lưu trữ , đừng xây dựng công cụ của riêng bạn, hãy sử dụng API bảo mật doanh nghiệp của OWASP . Từ OWASP ESAPI được lưu trữ trên Google Code:

Đừng viết điều khiển bảo mật của riêng bạn! Phát minh lại bánh xe khi phát triển các điều khiển bảo mật cho mọi ứng dụng web hoặc dịch vụ web dẫn đến lãng phí thời gian và lỗ hổng bảo mật lớn. Bộ công cụ API bảo mật doanh nghiệp (ESAPI) của OWASP giúp các nhà phát triển phần mềm bảo vệ chống lại các lỗi thiết kế và triển khai liên quan đến bảo mật.

Để biết thêm chi tiết, hãy xem Ngăn chặn SQL Tiêm trong JavaSQL Cheat Sheet Cheat Sheet .

Đặc biệt chú ý đến Tùy chọn phòng thủ 3: Thoát tất cả đầu vào được cung cấp bởi người dùng giới thiệu dự án OWASP ESAPI ).


4
ESAPI dường như không còn tồn tại như ngày nay. Trên AWS có WAF có thể giúp chống lại SQL SQL, XSS, v.v. có bất kỳ sự thay thế nào khác vào thời điểm này không?
ChrisOdney

@ChrisOdney Một WAF có thể dễ dàng bỏ qua. Hầu hết các Framework đã thực hiện ngăn ngừa SQL-Injection của riêng chúng trong đó chúng tự động thoát các tham số. Các lựa chọn thay thế cho các dự án cũ: owasp.org/index.php/ trên
Javan R.

19

(Đây là câu trả lời cho nhận xét của OP theo câu hỏi ban đầu; tôi hoàn toàn đồng ý rằng PreparedStatement là công cụ cho công việc này, không phải regexes.)

Khi bạn nói \n, bạn có nghĩa là chuỗi \+ nhoặc một ký tự dòng thực tế? Nếu là \+ n, tác vụ khá đơn giản:

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

Để khớp một dấu gạch chéo ngược trong đầu vào, bạn đặt bốn trong số chúng vào chuỗi regex. Để đặt một dấu gạch chéo ngược trong đầu ra, bạn đặt bốn trong số chúng vào chuỗi thay thế. Điều này giả sử bạn đang tạo các biểu thức chính quy và thay thế dưới dạng các ký tự chuỗi Java. Nếu bạn tạo chúng theo bất kỳ cách nào khác (ví dụ: bằng cách đọc chúng từ một tệp), bạn không phải thực hiện tất cả các thao tác thoát kép đó.

Nếu bạn có một ký tự linefeed trong đầu vào và bạn muốn thay thế nó bằng một chuỗi thoát, bạn có thể thực hiện lần thứ hai qua đầu vào bằng:

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

Hoặc có thể bạn muốn hai dấu gạch chéo ngược (tôi không quá rõ về điều đó):

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

1
Cảm ơn vì nhận xét, tôi thích cách bạn đã làm tất cả các nhân vật trong một, tôi sẽ nói về nó theo cách diễn đạt ít thường xuyên hơn thay thế cho mỗi ... Tôi không chắc cách gán câu trả lời cho câu hỏi này ngay bây giờ . Cuối cùng PreparedStatements là câu trả lời, nhưng với mục tiêu hiện tại của tôi, câu trả lời của bạn là câu trả lời tôi cần, bạn có buồn không nếu tôi đưa ra câu trả lời cho một trong những câu trả lời được chuẩn bị trước đó, hoặc có cách nào để chia sẻ câu trả lời giữa một cặp vợ chồng không?
Scott Bonner

1
Vì đây chỉ là một loại bùn tạm thời, hãy tiếp tục và chấp nhận một trong những câu trả lời PreparedStatement.
Alan Moore

12

PreparedStatements là cách để đi trong hầu hết, nhưng không phải tất cả các trường hợp. Đôi khi bạn sẽ thấy mình trong một tình huống mà một truy vấn, hoặc một phần của nó, phải được xây dựng và lưu trữ dưới dạng một chuỗi để sử dụng sau này. Kiểm tra Bảng cheat SQL Ngăn chặn trên trang web OWASP để biết thêm chi tiết và API bằng các ngôn ngữ lập trình khác nhau.


1
Các cheatheets OWASP đã được chuyển đến GitHub. Bảng cheat SQL Injection hiện có tại đây: github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/
Kẻ

10

Báo cáo đã chuẩn bị là giải pháp tốt nhất, nhưng nếu bạn thực sự cần làm thủ công, bạn cũng có thể sử dụng StringEscapeUtilslớp từ thư viện Apache Commons-Lang. Nó có một escapeSql(String)phương thức mà bạn có thể sử dụng:

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


2
Để tham khảo: commons.apache.org/proper/commons-lang/javadocs/api-2.6/org/ , Dù sao, phương pháp này chỉ thoát khỏi dấu ngoặc kép và dường như không ngăn chặn các cuộc tấn công SQL Injection.
Paco Abato

11
Điều này đã bị xóa khỏi các phiên bản mới nhất vì nó chỉ thoát khỏi các trích dẫn duy nhất
Pini Cheyni

2
Câu trả lời này nên được xóa vì nó không ngăn chặn tiêm sql.
Javan R.

9

Sử dụng một biểu thức chính quy để loại bỏ văn bản có thể gây ra âm thanh tiêm SQL giống như câu lệnh SQL đang được gửi đến cơ sở dữ liệu thông qua Statementthay vì a PreparedStatement.

Một trong những cách dễ nhất để ngăn chặn việc tiêm SQL ở nơi đầu tiên là sử dụng một PreparedStatement, chấp nhận dữ liệu để thay thế vào câu lệnh SQL bằng cách giữ chỗ, không dựa vào nối chuỗi để tạo câu lệnh SQL để gửi đến cơ sở dữ liệu.

Để biết thêm thông tin, Sử dụng Báo cáo đã chuẩn bị từ Hướng dẫn Java sẽ là một nơi tốt để bắt đầu.


6

Trong trường hợp bạn đang xử lý một hệ thống cũ hoặc bạn có quá nhiều nơi để chuyển sang PreparedStatements trong thời gian quá ngắn - tức là nếu có một trở ngại trong việc sử dụng thực tiễn tốt nhất được đề xuất bởi các câu trả lời khác, bạn có thể thử AntiQueryFilter


5

Bạn cần mã dưới đây. Nhìn thoáng qua, nó có thể trông giống như bất kỳ mã cũ nào mà tôi tạo ra. Tuy nhiên, những gì tôi đã làm là xem mã nguồn cho http://grepcode.com/file/repo1.maven.org/maven2/mysql/mysql-connector-java/5.1.31/com/mysql/jdbc/PreparedStatement. java . Sau đó, tôi cẩn thận xem qua mã setString (int tham số Index, Chuỗi x) để tìm các ký tự mà nó thoát và tùy chỉnh mã này cho lớp của riêng tôi để nó có thể được sử dụng cho các mục đích mà bạn cần. Rốt cuộc, nếu đây là danh sách các nhân vật mà Oracle thoát ra, thì biết đây thực sự là an toàn cho sự an toàn. Có lẽ Oracle cần một chút khéo léo để thêm một phương thức tương tự như phương thức này cho bản phát hành Java lớn tiếp theo.

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();
    }
}

2
Tôi nghĩ mã này là phiên bản dịch ngược của mã nguồn trong liên kết trên. Bây giờ mới hơn mysql-connector-java-xxx, các báo cáo case '\u00a5'case '\u20a9'dường như đã bị xóa
zhy

Tôi đã thử sqlmap với mã của bạn và nó không bảo vệ tôi khỏi cuộc tấn công đầu tiên `Loại: mù dựa trên boolean Tiêu đề: VÀ mù dựa trên boolean - Mệnh đề WHERE hoặc HAVING Payload: q = 1% 'VÀ 5430 = 5430 VÀ'% ' = '`
shareef

Xin lỗi, nó hoạt động nhưng đang xem kết quả phiên được lưu trữ cuối cùng .. tôi đã giữ bình luận cho tương lai tương tự ..
shareef

Bạn có thể sử dụng org.ostermiller.utils.StringHelper.escapeSQL()hoặc com.aoindustries.sql.SQLUtility.escapeSQL().
Mohamed Ennahdi El Idrissi

1
Điều quan trọng cần lưu ý là giấy phép GPLv2 trên mã gốc đã được sao chép từ bất kỳ ai đi qua đây. Tôi không phải là luật sư nhưng tôi thực sự khuyên bạn không nên sử dụng câu trả lời này trong dự án của bạn trừ khi bạn hoàn toàn nhận thức được ý nghĩa của việc bao gồm mã được cấp phép này.
Nick Spacek

0

Sau khi tìm kiếm rất nhiều giải pháp thử nghiệm để ngăn chặn sqlmap từ việc tiêm sql, trong trường hợp hệ thống kế thừa không thể áp dụng các số liệu đã được chuẩn bị ở mọi nơi.

chủ đề java-security-cross-site-scripting-xss-and-sql-tiêm LÀ GIẢI PHÁP

tôi đã thử giải pháp của @Richard nhưng không hoạt động trong trường hợp của tôi. tôi đã sử dụng một bộ lọc

Mục tiêu của bộ lọc này là để bao bọc yêu cầu thành một trình bao bọc được mã hóa riêng MyHttpRequestWrapper để chuyển đổi:

các tham số HTTP có các ký tự đặc biệt (<,>, ',') thành mã HTML thông qua phương thức org.springframework.web.util.HtmlUtils.htmlEscape (Lỗi). Lưu ý: Có một phân nhóm tương tự trong Apache Commons: org.apache.commons.lang.StringEscapeUtils.escapeHtml (mật) các ký tự tiêm SQL (',,,) thông qua Apache Commons classe org.apache.commons.lang.StringEscapeUtils. escSql (tinh)

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

Nó có tốt java-security-cross-site-scripting-xss-and-sql-injection topickhông Tôi đang cố gắng tìm một giải pháp cho một ứng dụng cũ.
caot

0

Từ: [Nguồn]

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;

}


0

Nếu bạn đang sử dụng PL / SQL, bạn cũng có thể sử dụng DBMS_ASSERT nó có thể vệ sinh đầu vào của bạn để bạn có thể sử dụng nó mà không phải lo lắng về việc tiêm SQL.

xem câu trả lời này chẳng hạn: https://stackoverflow.com/a/21406499/1726419

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.