Thứ Năm, 7 tháng 9, 2017

Các lỗi nguy hiểm nhất trong ứng dụng web - Dành cho các coder và tester trong quá trình phát triển phần mềm


  1. Tương tác với cơ sở dữ liệu tránh lỗ hổng SQL Injection:
  • Dữ liệu input từ người dùng phải được truyền dưới dạng tham số không được sử dụng cách cộng xâu trong các truy vấn tới cơ sở dữ liệu.
2.       Xử lý dữ liệu đầu vào tránh lỗ hổng XSS:
  • Mã hóa dưới dạng HTML các ký tự đặc biệt do người dùng gửi lên máy chủ và các ký tự đặc biệt trong cơ sở dữ liệu trước khi gửi tới người dùng.
3.      Sử dụng Token trong các phương thức GET và POST tránh lỗ hổng CSRF:
  • Trong các tương tác của người dùng với cơ sở dữ liệu thông qua các form, liên kết, sử dụng thêm biến token (được tạo ra mỗi đầu phiên truy cập của người dùng) như một tham số trong phương thức GET hoặc POST và kiểm tra giá trị token này tại máy chủ để xác nhận hành vi của người dùng.
4.      Kiểm soát các thao tác với file:
  • Giới hạn chỉ cho phép các định dạng file theo yêu cầu của ứng dụng được phép upload lên máy chủ. Kiểm soát file upload ở phía máy chủ. Lưu trữ các file upload tại một thư mục riêng nằm ngoài thư mục web hoặc không cho phép truy cập, thực thi trên các thư mục đó.
  • Chặn các kí tự \, /, null và kiểm tra phần mở rộng của file khi xử lý với tên file trên máy chủ.
5.      Mã hóa dữ liệu nhạy cảm
  • Những dữ liệu nhạy cảm trong cơ sở dữ liệu cần được mã hóa.
6.      Kiểm tra quyền truy cập của người dùng
  • Kiểm soát quyền của người dùng trong mỗi request lên máy chủ.
7.      User enumeration
  • Sử dụng chung thông báo lỗi cho cả 2 trường hợp nhập sai tên đăng nhập và mật khẩu trên trang đăng nhập vào hệ thống. Nhằm tránh trường hợp thông báo lỗi trên trang đăng nhập phân biệt giữa nhập sai tên đăng nhập và sai mật khẩu à Dựa vào đó hacker có thể thử và tìm ra các user có trên hệ thống.
8.      Session fixation
  • Renew session sau khi đăng nhập và xóa session cũ (trên Server) sau khi log out. Tránh trường hợp Session của ứng dụng trước và sau khi đăng nhập không thay đổi. Hacker có thể sử dụng lỗ hổng này để đăng nhập mà không cần biết username/password.
9.      Session Hijacking
  • Không cho phép 2 phiên truy cập đồng thời, session được xây dựng từ các thông tin hướng người dùng (IP, User-Agent hoặc địa chỉ MAC).
10. HTTP Only cookie
  • Yêu cầu thiết lập thuộc tính ”HTTP Only” cho session cookie. Vì nếu Session cookie không được set thuộc tính ”HTTP Only”. Hacker có thể sử dụng mã javascript để ăn cắp cookie của người dùng.
  • Tránh người dùng cuối có thể can thiệp vào quá trình redirect từ ứng dụng này sang ứng dụng khác. Nếu cần sử dụng thì URI phải được validate, đảm bảo URI được redirect đến nằm trong whitelist của ứng dụng. Nếu không dược validate, kẻ tấn công có thể redirect đến URI có nhiễm mã độc.
12. Để lộ dữ liệu của hệ thống
  • Yêu cầu tất cả dữ liệu, tài nguyên hệ thống(báo cáo, file lưu trữ) không được lưu trong thư mục chia sẻ(share). Các dữ liệu này lưu trong thu mục bên ngoài thư mục cài đặt web server, việc thực hiện download các dữ liệu này phải qua bước xác thực và tham số phải mã hóa
13. Thất thoát thông tin và xử lý lỗi không dúng tốt
  • Yêu cầu không hiển thị chi tiết lỗi cho người dùng cuối, hạn chế thông tin hiển thị nhất có thể. Đồng thời các thông tin lỗi này phải được log lại bên server để phục vụ bảo trì

1.   Kiểm soát truy vấn cơ sở dữ liệu để tránh lỗ hổng SQL Injection
-   Nguy cơ: Khi truy vấn tới cơ sử dữ liệu lập trình viên thường sử dụng cách cộng xâu Input từ người dùng, các câu truy vấn này có thể bị mắc lỗi SQL Injection hoặc HQL Injection (nếu sử dụng Hibernate). Bằng việc lợi dụng các lỗi này, kẻ tấn công có thể xem, thêm, sửa, xóa dữ liệu trong database từ đó chiếm được tài khoản admin, lấy cắp thông tin người dùng...
-   Phòng chống:
o  Truy vấn SQL phải dùng PrepareStatement, tất cả tham số phải được add bằng hàm(  setParam..), không được xử dụng cách cộng xâu trong truy vấn.
o  Truy vấn HQL tất cả tham số phải được add bằng hàm( setParam..), không được xử dụng cách cộng xâu trong truy vấn.
-   Ví dụ: Đoạn code kiểm tra đăng nhập với username/password do người dùng nhập vào
String sql = "select * from users where user_name = '" + userName
        + "' and password = '" + encrypt(password) + "'";
Statement statement = connection.createStatement();
ResultSet rs = statement.executeQuery(sql);
if (!rs.next()) {
            bResult = false;
} else {
            bResult = true;
}
Nhập vào username là test’ or ‘1’=‘1 thì câu query sẽ là: select * from users where user_name=‘test’ or ‘1’=‘1’ and password=‘...’. Mệnh đề where sẽ tương đương với user_name = ‘test’. Như vậy dù không có password vẫn đăng nhập được vào hệ thống.
Đoạn code bên dưới, username,password được tham số hóa khi đưa vào câu truy vấn nên tránh được lỗi SQL Injection:
String sql = "select * from users where user_name = ? and password = ?";
PreparedStatement statement = connection.prepareStatement(sql);
statement.setString(0, userName);
statement.setString(1, encrypt(password));
ResultSet rs = statement.executeQuery(sql);
if (!rs.next()) {
     bResult = false;
} else {
     bResult = true;
}
2.   Xử lý dữ liệu đầu vào để tránh lỗ hổng XSS
-         Nguy cơ: Kết quả server trả về cho người dùng chủ yếu là dưới dạng HTML. Nội dung trả về thường bao gồm cả những giá trị mà người dùng nhập vào hệ thống có thể bị mắc lỗi XSS nếu không kiểm soát dữ liệu đầu vào. XSS (Cross-Site Scripting) là một kĩ thuật tấn công bằng cách chèn vào các website động (ASP, PHP, CGI, JSP ...) những thẻ HTML hay những đoạn mã script nguy hiểm có thể gây nguy hại cho những người sử dụng khác. Trong đó, những đoạn mã nguy hiểm đựơc chèn vào hầu hết được viết bằng các Client-Site Script như JavaScript, JScript, DHTML và cũng có thể là cả các thẻ HTML.
-   Phòng chống: Mã hóa dưới dạng HTML các ký tự đặc biệt do client gửi đến bao gồm: <,>,&,’,”,/ trong các trường hợp
o   Dữ liệu client gửi lên máy chủ
o   Dữ liệu lấy ra từ database khi trả về cho client
Bản chất của việc mã hóa là thay thế các kí tự trên bằng chuỗi tương ứng trong bảng bên dưới:
STT
Ký tự
HTML
1
"
&quot;
2
&
 &amp;
3
'
&#x27;
4
/
 &#x2F;
5
<
 &lt;
6
>
&gt;
-   Ví dụ: Trang jsp bên dưới hiển thị lên lời chào với tên người dùng được lấy từ client
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>JSP Page</title>
    </head>
    <body>
        <%
            String user = request.getParameter("user");
            request.setAttribute("user", user);
        %>
      <h1>Hello ${user} !</h1>
    </body>
</html>
Khi nhập vào địa chỉ trình duyệt http://localhost/example?user=abc thì trên trình duyệt sẽ hiện thì dòng Hello abc !
Khi nhập vào địa chỉ trình duyệt
http://localhost/example?user=abc</h1><script>alert('XSS')</script> thì trên trên trình duyệt sẽ thực hiện đoạn java script thông báo XSS.
Để khắc phục lỗi này ta có thể dùng thư viện JSTL để mã hóa HTML biến user
<%@taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>JSP Page</title>
    </head>
    <body>
        <%
            String user = request.getParameter("user");
            request.setAttribute("user", user);
        %>
      <h1>Hello ${fn:escapeXml(user)} !</h1>
    </body>
</html>

3.   Sử dụng token để tránh lỗ hổng CSRF
-   Nguy cơ: CSRF (Cross-site request forgery) là phương pháp mượn quyền của người dùng khác để thực hiện một hành động không cho phép. Ví dụ: Để có thể xóa một bài viết trên diễn đàn một member có thể mượn tay của một admin để làm việc đó vì member không đủ chủ quyền nhưng admin lại đủ chủ quyền để thực hiện hành động này. Kẻ tấn công lừa admin truy cập vào trang web có chứa đoạn mã xóa bài viết trên diễn đàn (Admin đang đăng nhập vào diễn đàn) như vậy admin đã gửi yêu cầu xóa bài viết trên diễn đàn mà không hề biết.
-   Phòng chống: Đối với các yêu cầu quan trọng, sử dụng thêm biến token. Trên server sẽ kiểm tra token trong yêu cầu gửi lên từ client, nếu token không hợp lệ thì yêu cầu sẽ không được thực hiện
-   Ví dụ: Ứng dụng struts cho phép hiển thị lời chào với tên người dùng nhập từ form
index.jsp
<%@taglib prefix="s" uri="/struts-tags" %><br />
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    </head>
    <body>
        <h2>Hello World!</h2>
        Struts 2 Message: <s:property value="message" default="Guest." />
        <s:form method="GET" action="/HelloStruts2World.action">
            Enter your name:<s:textfield name="userName" />
            <s:submit value="Submit" />            
        </s:form>
    </body>
</html>
struts.xml
<struts>
    <package name="/" extends="struts-default">       
        <action name="HelloStruts2World" class="hello.HelloStruts2World">           
            <result name="success">/index.jsp</result>           
        </action>
    </package>
</struts>
helloStruts2World.java
package hello;
import com.opensymphony.xwork2.ActionSupport;
public class HelloStruts2World extends ActionSupport {
    private String userName;
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    private String message;
    public String getMessage() {
        return message;
    }
    @Override
    public String execute() {
        message = "Hello, " + userName + ".";
        return SUCCESS;
    }
}
Tuy nhiên, yêu cầu trên có thể được thực hiện mà không cần phải nhập username từ form bằng cách đưa trực tiếp vào URL:
Để khắc phục lỗi trên, ta có thể sử dụng token intercepter đã có sẵn của struts.
index.jsp
<%@taglib prefix="s" uri="/struts-tags" %><br />
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    </head>
    <body>
        <h2>Hello World!</h2>
        Struts 2 Message: <s:property value="message" default="Guest." />
        <s:form method="GET" action="/HelloStruts2World.action">
            <s:token/>
            Enter your name:<s:textfield name="userName" />
            <s:submit value="Submit" />           
        </s:form>
    </body>
</html>
struts.xml
<struts>
    <package name="/" extends="struts-default">
        <interceptors>
            <interceptor-stack name="defaultSecurityStack">
                <interceptor-ref name="defaultStack" />
                <interceptor-ref name="tokenSession">
                    <param name="excludeMethods">*</param>
                </interceptor-ref>
            </interceptor-stack>
        </interceptors>
        <default-interceptor-ref name="defaultSecurityStack" />
        <global-results>
            <result name="invalid.token">/error.jsp</result>
        </global-results>
        <action name="HelloStruts2World" class="hello.HelloStruts2World">
            <interceptor-ref name="defaultSecurityStack">
                <param name="tokenSession.includeMethods">*</param>
            </interceptor-ref>
            <result name="success">/index.jsp</result>           
        </action>
    </package>
</struts>
4.   Kiểm soát file upload lên hệ thống
-   Nguy cơ: Các thao tác với file thường sử dụng tên file, đường dẫn file được gửi lên từ client, nếu ứng dụng không kiểm soát tốt các giá trị này (việc kiểm soát phải được thực hiện phía server) có thể dẫn đến việc download hoặc upload các file không hợp lệ.
-   Phòng chống: Kiểm soát phía server tên file, đường dẫn file được gửi lên từ client
o  Kiểm soát phần mở rộng của file, chỉ cho phép thực hiện với các file có định dạng theo yêu cầu.
o  Các hàm liên quan đọc ghi file, biến đường dẫn file phải được lọc /, \ và kí tự null.
-   Ví dụ 1: Đoạn code bên dưới in ra đường dẫn file với đầu vào là tên file
String fileName = "temp.txt";
File file1 = new File(fileName);
System.out.println("File 1 path: " + file1.getCanonicalPath());
fileName = "./../../../../../../../boot.ini";
File file2 = new File(fileName);
System.out.println("File 2 path: " + file2.getCanonicalPath());
fileName = "boot.ini" + String.valueOf((char) 0) + ".txt";
File file3 = new File(fileName);
System.out.println("File 3 path: " + file3.getCanonicalPath());
Với đường dẫn thư mục hiện tại là
C:\Documents and Settings\Website\upload\test\”.Ta có kết quả thực hiện:
File 1 path: C:\Documents and Settings\Website\upload\test\temp.txt
File 2 path: C:\boot.ini
File 3 path: C:\Documents and Settings\Website\upload\test\boot.ini
Trong trường hợp 1, kết quả in ra đường dẫn file temp.txt nằm trong thư mục hiện tại 
Trong 2 trường hợp còn lại, tên file được thay đổi để truy cập đến các file không được phép.
o  Trường hợp 2: Trong tên file có chứa các chuỗi ./../, các chuỗi này có tác dụng chuyển đến thư mục hiện tại (./) và thư mục cha của thư mục hiện tại (../). Vì thế với tên file là “./../../boot.ini”, kết quả in ra là file boot.ini nằm trong thư mục gốc ổ C.
Chú ý: Kỹ thuật này thường được dùng để truy cập đến các thư mục nằm ngoài thư mục hiện tại. Các chuỗi .\..\ cũng có tác dụng tương tự như ./../
o  Trường hợp 3: Các hàm xử lý file của hệ điều hành khi gặp kí tự NULL (mã ASCII là 0) trong tên file sẽ hiểu rằng đây là kí tự kết thúc xâu chứa tên file và bỏ qua tất cả các kí tự phía sau (đặc điểm của các hàm xử lý xâu bằng ngôn ngữ C – ngôn ngữ của hầu hết các hệ điều hành). Vì thế kết quả trong trường hợp này sẽ là file boot.ini trong thư mục hiện tại mặc dù tên file nhập vào có phần mở rộng là “.txt”.
Chú ý: Kỹ thuật này thường được dùng để vượt qua việc chặn phần mở rộng của file.
 Để sửa lỗi trên ta có thể sử dụng hàm lọc các kí tự /,\,null trong tên file
public static String getSafeFileName(String input) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < input.length(); i++) {
            char c = input.charAt(i);
            if (c != '/' && c != '\\' && c != 0) {
                sb.append(c);
            }
        }
        return sb.toString();
    }
-   Ví dụ 2: Đoạn mã bên dưới có tác dụng upload file từ client và save vào thư mục upload
private File clientFile;
private String clientFileFileName;
@Override
public String execute() throws Exception {
    try {
        if (clientFileFileName != null) {
            clientFileFileName = getSafeFileName(clientFileFileName);
            if (!clientFileFileName.endsWith(".txt")) {
                message = "Not .txt file!";
            } else {
                String uploadDir = "C:\\upload\\";
                File uploadFile = new File(uploadDir + clientFileFileName);
                FileUtils.copyFile(clientFile, uploadFile);
                message = "Upload file success!";
            }
        }
    } catch (Exception e) {
        message = "Upload file error!";
    }
    return SUCCESS;
}
Sử dụng hàm  getSafeFileName trong ví dụ 1 kết hợp với việc kiểm tra phần mở rộng đảm bảo upload file an toàn, chỉ các file được phép mới được upload lên server và các file này chỉ có thể được phép save vào thư mục chỉ định.
5.   Mã hóa dữ liệu nhạy cảm
-   Nguy cơ: Khi hệ thống bị tấn công và kẻ tấn công lấy được thông tin trong cơ sở dữ liệu, các dữ liệu nhạy cảm sẽ bị lộ nếu không được mã hóa hoặc mã hóa không an toàn.
-   Phòng chống:
o   Mã hóa các dự liệu nhạy cảm trong cơ sở dữ liệu.
o   Các hàm mã hóa 1 chiều phải có thêm salt.
-   Ví dụ: Mật khẩu của người dùng được mã hóa trong cơ sở dữ liệu bằng hàm mã hóa 1 chiều. Kẻ tấn công lấy được mật khẩu đã mã hóa là QL0AFWMIX8NRZTKeof9cXsvbvu8= và có thể tìm được mật khẩu chưa mã hóa là 123, thuật toán mã hóa là hash =  base64(SHA1(pass)) bằng cách tìm kiếm trên Internet. Các mật khẩu phổ biến dễ bị dò ngược lại bằng phương pháp này.
-   Để sửa lỗi này ta cần thêm salt vào hàm mã hóa. Thay vì hash = encrypt(pass), hàm mã hóa sẽ chuyển thành hash = encrypt(salt + pass)và khi bị lộ hash cũng không thể tìm kiếm được trên Internet để tìm ra mật khẩu chưa mã hóa
6.   Kiểm tra quyền truy cập của người dùng
-   Nguy cơ: Trong các hệ thống có phân quyền, mỗi người dùng chỉ được phép truy cập các dữ liệu mà mình được phép. Tuy nhiên, nếu việc kiểm tra quyền không được kiểm soát tốt thì người dùng có thể truy cập được các dữ liệu không được quyền.
-   Phòng chống: Kiểm tra quyền trong từng request gửi lên server.
-   Ví dụ: Hàm bên dưới cho phép quản trị khóa tài khoản người dùng với đầu vào là ID của người dùng được gửi từ client
public String lockUsers() {
        String strUserId = getRequest().getParameter("userId");
        Long userId = Long.parseLong(strUserId);
        doLock(userId);
        return SUCCESS;
    }
Như vậy, bằng cách thay đổi ID của người dùng từ client, người quản trị này có thể khóa tài khoản của các người dùng mà mình không được phép quản lý.
Để sửa lỗi này, ta cần kiểm tra quyền khóa của quản trị đối với người dùng này trước khi thực hiện khóa
public String lockUsers() {
        String strUserId = getRequest().getParameter("userId");
        Long userId = Long.parseLong(strUserId);
        if (checkLockPermission(userId)) {
            doLock(userId);
            return SUCCESS;
        } else {
            return ERROR;
        }
    }

7.   Để lộ dữ liệu của hệ thống
-         Nguy cơ: Trên hệ thống tồn tại các chức năng cho phép kết xuất dữ liệu truy vấn, kết quả làm việc ra dưới dạng các file excel tuy nhiên chức năng này có các lỗ hổng sau có thể làm lộ dữ liệu của hệ thống:
·        File exel được lưu trữ trong thư mục con của thư mục webapp.
·        File exel sau khi được người dùng tải về không được xóa.
·        Cho phép truy cập trực tiếp vào các file exel này mà không qua xác thực.
Ví dụ như đường link sau sẽ cho phép tải một file exel mà hệ thống đã xuất ra trước đó về mà không cần qua các bước xác thực:
-         Phòng chống: Các dữ liệu này lưu trong thu mục bên ngoài thư mục cài đặt web server, việc thực hiện download các dữ liệu này phải qua bước xác thực và tham số phải mã hóa

0 nhận xét:

Đăng nhận xét