Hiển thị các bài đăng có nhãn SQL. Hiển thị tất cả bài đăng
Hiển thị các bài đăng có nhãn SQL. Hiển thị tất cả bài đăng

28 tháng 12, 2017

SQL Injection là gì? Ngăn chặn SQL Injection như thế nào?

1. SQL Injection là gì?

SQL Injection xảy ra khi kẻ tấn công có thể giả mạo các thao tác truy vấn để nhằm mục đích thực hiện một câu lệnh truy vấn SQL khác so với những gì mà nhà phát triển ứng dụng đã dự định ban đầu.

Khi thực thi câu lệnh truy vấn, chúng ta có hai tùy chọn cơ bản là:

  • Sử dụng statement (ví dụ, java.sql.Statement)
  • Hoặc sử dụng prepared statement (ví dụ, java.sql.PreparedStatement)

Khi xây dựng các câu truy vấn căn bản, nếu chúng ta thực hiện việc nối chuỗi thì cả hai loại java.sql.Statementjava.sql.PreparedStatement đều rất dễ bị tấn công SQL Injection.

Để có thể thực hiện một câu lệnh truy vấn thì trong hai lớp java.sql.Statementjava.sql.PreparedStatement có định nghĩa ra hai phương thức là:

  • executeQuery(String sql) để thực thi câu lệnh SQL SELECT
  • executeUpdate(String sql) để thực thi các câu lệnh SQL INSERT, UPDATE, DELETE

Tùy thuộc vào sự kết hợp của Statement/PreparedStatementexecuteQuery/executeUpdate, mà mục tiêu tấn công SQL Injection sẽ thay đổi, được thể hiện bằng các kịch bản dưới đây.

2. Statement và executeUpdate

Đây là sự kết hợp dễ bị tấn công nhất. Giả định, chúng ta có một phương thức dùng để cập nhật giá trị column review của một bản ghi thuộc bảng post_comment:

public void updatePostCommentReviewUsingStatement(Long id, String review) {
    doInJPA(entityManager -> {
        Session session = entityManager.unwrap(Session.class);
        session.doWork(connection -> {
            try(Statement statement = connection.createStatement()) {
                statement.executeUpdate(
                    "UPDATE post_comment " +
                    "SET review = '" + review + "' " +
                    "WHERE id = " + id);
            }
        });
    });
}

Và chúng ta sẽ thường gọi phương thức trên như sau:

updatePostCommentReviewUsingStatement(1L, "Awesome");

Một kẻ tấn công giả mạo chỉ đơn giả thực hiện cuộc tấn công SQL Injection như sau:

updatePostCommentReviewUsingStatement(1L, "'; DROP TABLE post_comment; -- '");

Và đay là những gì mà database sẽ thực thi:

Query:["UPDATE post_comment SET review = ''; DROP TABLE post_comment; -- '' WHERE id = 1"], Params:[]
  • Câu lệnh UPDATE sẽ được thực thi trước tiên
  • Sau đó sẽ là câu lệnh DROP
  • Cú pháp comment -- sẽ đảm bảo bỏ qua điều kiện trong mệnh đề WHERE trong phần còn lại của câu truy vấn.

Sau khi thực hiện tấn công SQL Injection trên, hãy thử truy vấn lại dữ liệu từ bảng post_comment để xem câu lệnh tấn công SQL Injection của chúng ta có thành công hay không.

ORACLE

Trên Oracle 11g, câu lệnh SQL Injection phía trên sẽ bị lỗi, vì JDBC driver không nhận ra dấu ;

Query:["UPDATE post_comment SET review = ''; DROP TABLE post_comment; -- '' WHERE id = 1"], Params:[]
WARN  [Alice]: o.h.e.j.s.SqlExceptionHelper - SQL Error: 911, SQLState: 22019
ERROR [Alice]: o.h.e.j.s.SqlExceptionHelper - ORA-00911: invalid character
 
Query:["select p.id as id1_1_0_, p.post_id as post_id3_1_0_, p.review as review2_1_0_ from post_comment p where p.id=?"], Params:[(1)]

SQL Server

Trên SQL Server 2014, câu lệnh SQL Injection sẽ được thực thi thành công và bảng post_comment đã bị xóa.

Query:["UPDATE post_comment SET review = ''; DROP TABLE post_comment; -- '' WHERE id = 1"], Params:[]
 
Query:["select p.id as id1_1_0_, p.post_id as post_id3_1_0_, p.review as review2_1_0_ from post_comment p where p.id=?"], Params:[(1)]
 
WARN  [Alice]: o.h.e.j.s.SqlExceptionHelper - SQL Error: 208, SQLState: S0002
ERROR [Alice]: o.h.e.j.s.SqlExceptionHelper - Invalid object name 'post_comment'.
INFO  [Alice]: o.h.e.i.DefaultLoadEventListener - HHH000327: Error performing load command : org.hibernate.exception.SQLGrammarException: could not extract ResultSet

PostgreSQL

Trên PostgreSQL 9.5, câu lệnh SQL Injection sẽ được thực thi thành công và bảng post_comment đã bị xóa.

Query:["UPDATE post_comment SET review = ''; DROP TABLE post_comment; -- '' WHERE id = 1"], Params:[]
 
Query:["select p.id as id1_1_0_, p.post_id as post_id3_1_0_, p.review as review2_1_0_ from post_comment p where p.id=?"], Params:[(1)]
WARN  [Alice]: o.h.e.j.s.SqlExceptionHelper - SQL Error: 0, SQLState: 42P01
ERROR [Alice]: o.h.e.j.s.SqlExceptionHelper - ERROR: relation "post_comment" does not exist

MySQL

Trên MySQL 5.7, câu lệnh SQL Injection sẽ bị lỗi vì JDBC driver không biên dịch đúng nhiều câu lệnh DML

Query:["UPDATE post_comment SET review = ''; DROP TABLE post_comment; -- '' WHERE id = 1"], Params:[]
WARN  [Alice]: o.h.e.j.s.SqlExceptionHelper - SQL Error: 1064, SQLState: 42000
ERROR [Alice]: o.h.e.j.s.SqlExceptionHelper - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'DROP TABLE post_comment; -- '' WHERE id = 1' at line 1
 
Query:["select p.id as id1_1_0_, p.post_id as post_id3_1_0_, p.review as review2_1_0_ from post_comment p where p.id=?"], Params:[(1)]

Mặc dù, lần tấn công SQL Injection đầu tiên không thành công trên tất cả các database, nhưng bạn sẽ nhận thấy rằng mọi cơ sở dữ liệu đều có ít nhất một biến thể của SQL Injection.

3. PreparedStatement và executeUpdate

Chúng ta sẽ thay đổi ví dụ trước để sử dụng PreparedStatement nhưng sẽ tránh sử dụng đến tham số ràng buộc

public void updatePostCommentReviewUsingPreparedStatement(Long id, String review) {
    doInJPA(entityManager -> {
        Session session = entityManager.unwrap(Session.class);
        session.doWork(connection -> {
            String sql = 
                "UPDATE post_comment " +
                "SET review = '" + review + "' " +
                "WHERE id = " + id;
            try(PreparedStatement statement = connection.prepareStatement(sql)) {
                statement.executeUpdate();
            }
        });
    });
}

Và thự hiện lại test case trước:

updatePostCommentReviewUsingPreparedStatement(
    1L, "'; DROP TABLE post_comment; -- '");
 
doInJPA(entityManager -> {
    PostComment comment = entityManager.find(
        PostComment.class, 1L);
    assertNotNull(comment);
});

Chúng ta cùng xem kết quả sau khi thực hiện tấn công SQL Injection

ORACLE

Trên Oracle 11g, câu lệnh SQL Injection phía trên sẽ bị lỗi, vì JDBC driver không nhận ra dấu ;

Query:["UPDATE post_comment SET review = ''; DROP TABLE post_comment; -- '' WHERE id = 1"], Params:[]
WARN  [Alice]: o.h.e.j.s.SqlExceptionHelper - SQL Error: 911, SQLState: 22019
ERROR [Alice]: o.h.e.j.s.SqlExceptionHelper - ORA-00911: invalid character
 
Query:["select p.id as id1_1_0_, p.post_id as post_id3_1_0_, p.review as review2_1_0_ from post_comment p where p.id=?"], Params:[(1)]

SQL Server

Trên SQL Server 2014, câu lệnh SQL Injection sẽ được thực thi thành công và bảng post_comment đã bị xóa.

Query:["UPDATE post_comment SET review = ''; DROP TABLE post_comment; -- '' WHERE id = 1"], Params:[]
 
Query:["select p.id as id1_1_0_, p.post_id as post_id3_1_0_, p.review as review2_1_0_ from post_comment p where p.id=?"], Params:[(1)]
 
WARN  [Alice]: o.h.e.j.s.SqlExceptionHelper - SQL Error: 208, SQLState: S0002
ERROR [Alice]: o.h.e.j.s.SqlExceptionHelper - Invalid object name 'post_comment'.
INFO  [Alice]: o.h.e.i.DefaultLoadEventListener - HHH000327: Error performing load command : org.hibernate.exception.SQLGrammarException: could not extract ResultSet

PostgreSQL

Trên PostgreSQL 9.5, câu lệnh SQL Injection sẽ được thực thi thành công, vì mặc định PreparedStatement chỉ mô phỏng câu lệnh trong giai đoạn chuẩn bị để chỉ phải thực thi câu truy vấn một lần.

Query:["UPDATE post_comment SET review = ''; DROP TABLE post_comment; -- '' WHERE id = 1"], Params:[]
 
Query:["select p.id as id1_1_0_, p.post_id as post_id3_1_0_, p.review as review2_1_0_ from post_comment p where p.id=?"], Params:[(1)]
WARN  [Alice]: o.h.e.j.s.SqlExceptionHelper - SQL Error: 0, SQLState: 42P01
ERROR [Alice]: o.h.e.j.s.SqlExceptionHelper - ERROR: relation "post_comment" does not exist

MySQL

Trên MySQL 5.7, câu lệnh SQL Injection sẽ bị lỗi vì JDBC driver không biên dịch đúng nhiều câu lệnh DML

Query:["UPDATE post_comment SET review = ''; DROP TABLE post_comment; -- '' WHERE id = 1"], Params:[]
WARN  [Alice]: o.h.e.j.s.SqlExceptionHelper - SQL Error: 1064, SQLState: 42000
ERROR [Alice]: o.h.e.j.s.SqlExceptionHelper - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'DROP TABLE post_comment; -- '' WHERE id = 1' at line 1
 
Query:["select p.id as id1_1_0_, p.post_id as post_id3_1_0_, p.review as review2_1_0_ from post_comment p where p.id=?"], Params:[(1)]

PreparedStatement không bảo vệ bạn khỏi một cuộc tấn công SQL Injection nếu bạn không sử dụng các tham số ràng buộc.

4. Ngăn chặn tấn công SQL Injection như thế nào?

Giải pháp rất đơn giản, bạn chỉ cần luôn chắc chắn sử dụng các tham số ràng buộc:

public PostComment getPostCommentByReview(String review) {
    return doInJPA(entityManager -> {
        return entityManager.createQuery(
            "select p " +
            "from PostComment p " +
            "where p.review = :review", PostComment.class)
        .setParameter("review", review)
        .getSingleResult();
    });
}

Bây giờ, khi cố thực hiện hack vào câu truy vấn trên:

getPostCommentByReview("1 AND 1 >= ALL ( SELECT 1 FROM pg_locks, pg_sleep(10) )");

tấn công SQL Injection sẽ bị ngăn chặn:

Time:1, Query:["select postcommen0_.id as id1_1_, postcommen0_.post_id as post_id3_1_, postcommen0_.review as review2_1_ from post_comment postcommen0_ where postcommen0_.review=?"], Params:[(1 AND 1 >= ALL ( SELECT 1 FROM pg_locks, pg_sleep(10) ))]
 
javax.persistence.NoResultException: No entity found for query

26 tháng 6, 2017

Đánh chỉ mục hiệu quả khi sử dụng PostgreSQL

Hệ quản trị cơ sở dữ liệu PostgreSQL có nhiều loại chỉ mục - index khác nhau, cũng như các cách sử dụng chỉ mục khác nhau. Qua bài viết này, tôi muốn đưa ra một góc nhìn tổng quan về các loại chỉ mục hiện tại đang được PostgreSQL hỗ trợ, và giải thích cách sử dụng khác nhau của loại chỉ mục phổ biến nhất - B-Tree.

Sử dụng chỉ mục là một cách hiệu quả để truy xuất số lượng tương đối nhỏ các bản ghi từ bảng dữ liệu. Nó sẽ chỉ hữu ích khi số lượng các bản ghi được truy vấn ra từ một bảng không quá lớn (ví dụ, truy vấn bản ghi theo điều kiện - mệnh đề WHERE). Chỉ mục B-Tree sẽ rất hữu ích khi ta muốn tránh phải sắp xếp lại các bản ghi đã được lựa chọn.

1. Các loại chỉ mục:

Postgres hỗ trợ các loại chỉ mục sau:

  • B-Tree: Hầu như, tất cả các cơ sở dữ liệu hiện nay đều hỗ trợ loại chỉ mục B-Tree, và trong hệ quản trị cơ sở dữ liệu PostgreSQL thì B-Tree là chỉ mục mặc định nếu như thực hiện câu lệnh truy vấn tạo chỉ mục mặc định CREATE INDEX. Chữ B trong B-Tree là viết tắt của từ Balanced - nghĩa là cân bằng, và ý tưởng của thuật toán là số lượng dữ liệu trên cả nhánh của cây dữ liệu sẽ tương đối bằng nhau. Do đó, số lần phải thực hiện tìm bản ghi ở hai bên luôn luôn giống nhau. Các câu truy vấn bằng hay truy vấn phạm vi sẽ rất hiệu quả nếu kết hợp sử dụng cùng với chỉ mục B-Tree, chúng hoạt động với tất cả các kiểu dữ liệu và cả giá trị NULL. Loại chỉ mục B-Tree được thiết kế để có thể làm việc tốt với cơ chế cache, thậm chí ngay cả khi chỉ cache một phần của dữ liệu.

  • Hash: Chỉ mục Hash chỉ hữu ích khi truy vấn so sánh bằng, nhưng có thể bạn sẽ không bao giờ sử dụng đến loại chỉ mục này vì nó không an toàn, bạn sẽ cần phải tự đánh lại chỉ mục nếu như có lỗi xảy ra nhưng các đối tượng liên quan sẽ không được thông báo về sự thay đổi này, vì vậy ưu điểm của loại chỉ mục Hash so với loại chỉ mục B-Tree là không đáng kể.

28 tháng 5, 2017

PostgreSQL Concurrency và MVCC

Một trong những điểm hấp dẫn nhất của hệ quản trị cơ sở dữ liệu Postgres đó là cách mà nó thực hiện điều khiển tương tranh giữa các transaction, nghĩa là read sẽ không bao giờ chặn write và ngược lại. Nói một cách dễ hình dung hơn, nếu hai transaction thực thi cùng một lúc thì nguyên tắc thực thi là thực thi độc lập, Postgres thực hiện được điều này là nhờ một cơ chế gọi là Multi Version Concurrency Control (MVCC). Kĩ thuật này không phải chỉ riêng Postgres mới có, mà đã có nhiều hệ quản trị cơ sở dữ liệu khác cũng đang thực hiện các cách tương tự với MVCC để điều khiển tương tranh giữa các transaction bao gồm: Oracle, Berkeley DB, CouchDB và các hệ quản trị cơ sở dữ liệu khác nữa. Việc hiểu cách MVCC được triển khai như thế nào trong PostgreSQL rất quan trọng, vì điều này giúp các nhà phát triển phần mềm có thể thiết kế các ứng dụng có tính đồng thời cao mà có sử dụng đến PostgreSQL, hoặc phần nào giúp giải quyết được rất nhiều vấn đề khó có thể gặp phải trong tương lai.

MVCC hoạt động như thế nào?

Mỗi một transaction trong PostgreSQL đều có một transaction id - id này là một số nguyên 32-bit, gọi là XID. Các transaction này bao gồm các câu lệnh đơn như INSERT, UPDATE hoặc DELETE, và một nhóm các câu lệnh tạo thành một khối lệnh được đặt bên trong hai từ khóa BEGIN - COMMIT. Khi bắt đầu một transaction, Postgres tạo ra một XID và gán nó cho transaction hiện tại. Ta có thể nhìn thấy được XID của transaction hiện tại bằng cách gọi hàm txid_current() có sẵn trong PostgreSQL.

SELECT CAST(txid_current() AS TEXT);
Postgres lưu tất cả thông tin của một transaction vào bên trong table data của hệ thống, những thông tin này sẽ được Postgres dùng để xác định một bản ghi sẽ có trạng thái ẩn hay hiện đối với một transaction.

Một điều thú vị nữa trong cơ chế MVCC này của Postgres đó là ngoài các column đã được khai báo trong quá trình tạo bảng thì mỗi bản ghi của một bảng sẽ có thêm hai cột bổ sung:

  • xmin - xác định XID của transaction đã insert bản ghi này
  • xmax - xác định XID của transaction đã delete bản ghi này

Nếu ta không truy vấn tới hai cột này, mặc định chúng sẽ bị ẩn đi. Ta có thể truy vấn giá trị của hai cột này như các cột bình thường khác có trong một bảng.

22 tháng 11, 2016

Tạo column kiểu số có giá trị tự động tăng trong Oracle Database

Để tạo được một column có kiểu là NUMBER mà giá trị của column đó có thể tự động tăng khi thực hiện câu lệnh INSERT trong hệ quản trị cơ sở dữ liệu Oracle ta sẽ phải sử dụng đến SEQUENCE kết hợp với TRIGGER.

Muốn thực hiện được điều này, đầu tiên phải tạo một SEQUENCE:

CREATE SEQUENCE name_of_sequence
  START WITH 1
  INCREMENT BY 1
  MAX VALUE 999999
  CACHE 100;

Đến đây, mỗi khi thực hiện câu lệnh INSERT, chỉ cần thực hiện như sau là đã có thể lấy được giá trị tăng kế tiếp để insert vào database:

INSERT INTO table_name(column_increment, other_column) 
  VALUES (name_of_sequence.NEXTVAL, other_column_value);

Ngoài ra, để thực hiện insert giá trị tự động tăng này vào column mà không cần gọi trực tiếp đến column đó trong câu lệnh, ta sẽ phải tạo ra một TRIGGER để thực hiện được điều này.

CREATE OR REPLACE TRIGGER trigger_name
  BEFORE INSERT ON table_name
  FOR EACH ROW
BEGIN
  SELECT name_of_sequence.NEXTVAL
    INTO :new.primary_key_column
  FROM dual;
END;

Từ phiên bản Oracle 11.1 về sau, ta có thể đơn giản khối lệnh tạo TRIGGER bên trên như sau:

CREATE OR REPLACE TRIGGER trigger_name
  BEFORE INSERT ON table_name
  FOR EACH ROW
BEGIN
  :new.primary_key_column := name_of_sequence.NEXTVAL;
END;

1 tháng 4, 2016

Cách tạo mã tự động tăng trong PostgreSQL

Ví dụ tạo cột book_id tự động tăng trong bảng tbl_books

  1. Tạo sequences tự động tăng giá trị cho cột book_id:

    CREATE SEQUENCE public.book_id_seq
    INCREMENT 1
    MINVALUE 1
    MAXVALUE 9223372036854775807
    START 10000
    CACHE 1;
    ALTER TABLE public.book_id_seq
    OWNER TO postgres;
    view raw book_id_seq.sql hosted with ❤ by GitHub
  2. Tạo bảng tbl_books sử dụng sequences đã tạo trước đó:

    CREATE TABLE public.tbl_books
    (
    book_id bigint NOT NULL DEFAULT nextval('book_id_seq'::regclass),
    CONSTRAINT pk_book_id PRIMARY KEY (book_id)
    )
    WITH (
    OIDS=FALSE
    );
    ALTER TABLE public.tbl_books
    OWNER TO postgres;
    view raw tbl_books.sql hosted with ❤ by GitHub