Some vulnerabilities in JEECMSV9

Reprinted: https: //blog.csdn.net/weixin_44063566/article/details/88897406

 

JEECMS before encountered a bit of looking at a test version JEECMSV9.3

SSRF
/src/main/java/com/jeecms/cms/action/member/UeditorAct.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RequestMapping(value = "/ueditor/getRemoteImage.jspx")
public void getRemoteImage(HttpServletRequest request,
HttpServletResponse response) throws Exception {
String url = request.getParameter("upfile");
CmsSite site=CmsUtils.getSite(request);
JSONObject json = new JSONObject();
String[] arr = url.split(UE_SEPARATE_UE);
String[] outSrc = new String[arr.length];
for (int i = 0; i < arr.length; i++) {
outSrc[i]=saveRemoteImage(arr[i], site.getContextPath(), site.getUploadPath());
}
String outstr = "";
for (int i = 0; i < outSrc.length; i++) {
outstr += outSrc[i] + UE_SEPARATE_UE;
}
outstr = outstr.substring(0, outstr.lastIndexOf(UE_SEPARATE_UE));
json.put(URL, outstr);
json.put(SRC_URL, url);
json.put(TIP, LocalizedMessages.getRemoteImageSuccessSpecified(request));
ResponseUtils.renderJson(response, json.toString());
}
在接受了用户传递过来的url之后, 带入saveRemoteImage方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private String saveRemoteImage(String imgUrl,String contextPath,String uploadPath) {
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
CloseableHttpClient client = httpClientBuilder.build();
String outFileName="";
try{
if(endWithImg(imgUrl)){
HttpGet httpget = new HttpGet(new URI(imgUrl));
HttpResponse response = client.execute(httpget);
InputStream is = null;
OutputStream os = null;
HttpEntity entity = null;
entity = response.getEntity();
entity.getContent = IS ();
outfilename = UploadUtils.generateFilename (uploadPath, FileNameUtils.getFileSufix (for imgUrl));
OS = new new a FileOutputStream (realPathResolver.get (outfilename));
IOUtils.copy (IS, OS);
}
In the method saveRemoteImage which, if the detection by a method endWithImg, direct initiation request, and outputs the result to the file to request them.

. 1
2
. 3
. 4
. 5
. 6
. 7
. 8
. 9
Private Boolean endWithImg (String for imgUrl) {
IF (StringUtils.isNotBlank (for imgUrl) && (imgUrl.endsWith (. "BMP") || imgUrl.endsWith (. "GIF")
|| for imgUrl .endsWith (. "JPEG") || imgUrl.endsWith (. "JPG")
|| imgUrl.endsWith (. "PNG"))) {
return to true;
} the else {
return to false;
}
}
endWithImg detection is relatively simple, bypass is relatively simple to add? .jpg will be bypassed.

But when local test, the results of this visit jpg file is 404.
First, let's take a look at the file name to save the file access method to generate the results, is the month that contains a directory.

. 1
2
. 3
. 4
public static String generateFilename (path String, String EXT) {
return path MONTH_FORMAT.format + (new new a Date ())
+ RandomStringUtils.random (. 4, Num62.N36_CHARS) + + EXT ".";
}
With similar results as /u/cms/www/201902/15002619t400.jpg
in the default source jeecms among 201,902 does not exist in this directory.


Among saveRemoteImage and methods, and there is no "Analyzing the directory exists or not, if it does not exist, create the directory" this logic.
When FileOutputStream, if the directory does not exist, it would be abnormal, so the file is not saved on here.
To save this file, above all, you have to create this directory.
in

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@RequestMapping(value = "/ueditor/upload.jspx",method = RequestMethod.POST)
public void upload(
@RequestParam(value = "Type", required = false) String typeStr,
Boolean mark,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
responseInit(response);
if (Utils.isEmpty(typeStr)) {
typeStr = "File";
}
if(mark==null){
mark=false;
}
= New new JSON the JSONObject the JSONObject ();
the JSONObject validateUpload OB = (Request, typeStr);
IF (OB == null) {
JSON = doUpload (Request, typeStr, Mark);
} the else {
JSON = OB;
}
ResponseUtils.renderJson (Response , json.toString ());
}
directly access doUpload method call,

1
2
3
4
5
6
private JSONObject doUpload(HttpServletRequest request, String typeStr,Boolean mark) throws Exception {
.......
else {
fileUrl = fileRepository.storeByExt(site.getUploadPath(),
ext, uplFile);
}
继续查看storeByExt方法

. 1
2
. 3
. 4
. 5
. 6
. 7
. 8
. 9
10
. 11
public String storeByExt (path String, String EXT, a MultipartFile File)
throws IOException {
// String filename = UploadUtils.generateFilename (path, EXT);
// File dest = new new File (the getRealPath (filename));
String fileName = UploadUtils.generateRamdonFilename (EXT);
String fileURL + fileName = path;
File dest = new new File (the getRealPath (path), fileName);
dest = UploadUtils.getUniqueFile (dest);
Store (File, dest );
return fileURL;
}
using the same method and the method of generating saveRemoteImage file name and directory, and then calls the store method.

. 1
2
. 3
. 4
. 5
. 6
. 7
. 8
. 9
Private void Store (a MultipartFile File, File dest) throws IOException {
the try {
UploadUtils.checkDirAndCreate (dest.getParentFile ());
file.transferTo (dest);
} the catch (IOException E) {
log .error ( "File Transfer File Upload When error", E);
the throw E;
}
}

. 1
2
. 3
. 4
public static void checkDirAndCreate (File the dir) {
(! dir.exists ()) IF
dir.mkdirs ();
}
can Although seen in the picture to download a remote function, there is no "if this directory does not exist to date to create the directory" this logic, but there is logic in this time of upload. After the first pass so we can upload, create the directory, and then continue to use SSRF.
Upload this function, then you need to sign in order to work properly.
Because before doupload method,

. 1
2
. 3
. 4
. 5
. 6
the JSONObject validateUpload OB = (Request, typeStr);
IF (OB == null) {
JSON = doUpload (Request, typeStr, Mark);
} the else {
JSON = OB;
}
elapsed validateUpload process in which among methods

. 1
2
. 3
. 4
. 5
. 6
. 7
CmsUser User CmsUtils.getUser = (Request);
// non-permissive suffix
IF {(user.isAllowSuffix (EXT)!)
Result.put (the STATE, LocalizedMessages
.getInvalidFileSuffixSpecified (Request));
return Result ;
}
if not logged in, user is null null pointer exception occurs next.


Once uploaded, successfully creates the directory.


Then SSRF

 

But httpClientBuilder initiated the request, only supports HTTP / HTTPS protocol.


SSTI
some can upload any file JEECMS point, only for example a
/src/main/java/com/jeecms/cms/action/member/SwfUploadAct.java

1
2
3
4
5
6
7
8
9
@RequestMapping(value = "/member/o_swfAttachsUpload.jspx", method = RequestMethod.POST)
public void swfAttachsUpload(
String root,
Integer uploadNum,
@RequestParam(value = "Filedata", required = false) MultipartFile file,
HttpServletRequest request, HttpServletResponse response,
ModelMap model) throws Exception{
super.swfAttachsUpload(root, uploadNum, file, request, response, model);
}
调用了父类的swfAttachsUpload方法,

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
protected void swfAttachsUpload(
String root,
Integer uploadNum,
@RequestParam(value = "Filedata", required = false) MultipartFile file,
HttpServletRequest request, HttpServletResponse response,
ModelMap model) throws Exception {
JSONObject data=new JSONObject();
WebCoreErrors errors = validateUpload( file, request);
if (errors.hasErrors()) {
data.put("error", errors.getErrors().get(0));
ResponseUtils.renderJson(response, data.toString());
}else{
CmsSite site = CmsUtils.getSite(request);
String ctx = request.getContextPath();
String origName = file.getOriginalFilename();
String ext = FilenameUtils.getExtension(origName).toLowerCase(
Locale.ENGLISH);
// check the TODO allow uploading suffix
String fileURL = "";
the try {
. IF (site.getConfig () getUploadToDb ()) {
String dbFilePath = site.getConfig () getDbFileUri ();.
FileURL = dbFileMng .storeByExt (site.getUploadPath (), EXT, File
.getInputStream ());
// add the access address
fileURL request.getContextPath = () + + dbFilePath fileURL;
} the else IF (! site.getUploadFtp () null =) {
FTP = site.getUploadFtp FTP ();
String = ftp.getUrl FTP uRL ();
fileURL = ftp.storeByExt (site.getUploadPath (), EXT, File
.getInputStream ());
// url prefix plus
fileUrl = ftpUrl + fileUrl ;
} the else IF (! site.getUploadOss () = null) {
CmsOss site.getUploadOss OSS = ();
fileUrl = oss.storeByExt(site.getUploadPath(), ext, file.getInputStream());
} else {
fileUrl = fileRepository.storeByExt(site.getUploadPath(), ext,
file);
// 加上部署路径
fileUrl = ctx + fileUrl;
}
cmsUserMng.updateUploadSize(CmsUtils.getUserId(request), Integer.parseInt(String.valueOf(file.getSize()/1024)));
fileMng.saveFileByPath(fileUrl, origName, false);
model.addAttribute("attachmentPath", fileUrl);
} catch (IllegalStateException e) {
model.addAttribute("error", e.getMessage());
} catch (IOException e) {
model.addAttribute("error", e.getMessage());
}
data.put("attachUrl", fileUrl);
data.put("attachName", origName);
ResponseUtils.renderJson (Response, data.toString ());
}
}
In this process, no check the file upload suffix,


From the TODO comment can see it, check this function allow suffix uploaded yet been realized directly on the line.

However uploaded jeecms in jsp, jspx file can not be accessed.

1
2
3
4
5
6
7
8
<servlet-mapping>
<servlet-name>JeeCmsFront</servlet-name>
<url-pattern>*.jspx</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>JeeCmsFront</servlet-name>
<url-pattern>*.jsp</url-pattern>
</servlet-mapping>
jsp和jspx文件都经过了JeeCmsFront,



























/src/main/java/com/jeecms/cms/action/front/CsiCustomAct.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RequestMapping(value = "/csi_custom*.jspx")
public String custom(String tpl, HttpServletRequest request,
HttpServletResponse response, ModelMap model) {
log.debug("visit csi custom template: {}", tpl);
CmsSite site = CmsUtils.getSite(request);
if(StringUtils.isNotBlank(tpl)){
// 将request中所有参数保存至model中。
model.putAll(RequestUtils.getQueryParams(request));
FrontUtils.frontData(request, model, site);
FrontUtils.frontPageData(request, model);
FrontUtils.getTplPath return (site.getSolutionPath (), TPLDIR_CSI_CUSTOM,
tpl);
} the else {
return FrontUtils.pageNotFound (Request, Response, Model);
}
}
may be seen to pass over the user directly into the variable tpl getTplPath method,

. 1
2
. 3
public static String getTplPath (String Solution, the dir String, String name) {
return Solution + "/" + the dir + "/" + name + TPL_SUFFIX;
}
controllable variable tpl directly into the splice template which path,

1
public static String TPL_SUFFIX Final = ".html";
the default template .html suffix, high jdk version which is no longer able to cut, so here is a .html file to upload any file by just then control the template file path Upload your own template files SSTI.

Because jeecms template engine using freemarker, first thought of SSTI will be able to directly freemarker rce, but when the test failed.

1
<#assign ex="freemarker.template.utility.Execute"?new()> ${ ex("id") }

 

In the new version freemarker in more than a TemplateClassResolver.SAFER_RESOLVER configuration.

TemplateClassResolver.SAFER_RESOLVER now disallows creating freemarker.template.utility.JythonRuntime and freemarker.template.utility.Execute. This change affects the behavior of the new built-in if FreeMarker was configured to use SAFER_RESOLVER, which is not the default until 2.4 and is hence improbable.

1
2
3
4
5
6
7
8
9
10
11
12
13
TemplateClassResolver SAFER_RESOLVER = new TemplateClassResolver() {
public Class resolve(String className, Environment env, Template template) throws TemplateException {
if (!className.equals(ObjectConstructor.class.getName()) && !className.equals(Execute.class.getName()) && !className.equals("freemarker.template.utility.JythonRuntime")) {
try {
return ClassUtil.forName(className);
} catch (ClassNotFoundException var5) {
throw new _MiscTemplateException(var5, env);
}
} else {
MessageUtil.newInstantiatingClassNotAllowedException the throw (className, env);
}
}
}
If a TemplateClassResolver.SAFER_RESOLVER, it is not allowed to call freemarker.template.utility.Execute, freemarker.template.utility.ObjectConstructor and freemarker.template.utility.JythonRuntime.

. 1
2
. 3
. 4
. 5
. 6
public constructorFunction (String className, the env Environment, Template Template) throws TemplateException {
this.env the env =;
. This.cl = env.getNewBuiltinClassResolver () Resolve (className, the env, Template);
IF (TemplateModel! .class.isAssignableFrom (this.cl)) {
the throw new new _MiscTemplateException (NewBI.this, the env, new new Object [] { "Class", this.cl.getName (), "Not Implement does freemarker.template.TemplateModel"}) ;
}
and allows the caller to class only allows for the realization of a class freemarker.template.TemplateModel interface, probably looked under the class that implements this interface, in addition to the three classes that are not allowed, no other class can use to find it RCE only to give up.

As can be seen from the document, freemarker From version 2.4 was turned on by default TemplateClassResolver.SAFER_RESOLVER, jeecms used version

1
<freemarker.version> 2.3.25-incubating </freemarker.version>
Although this configuration is not turned on by default, but JEECMS in freemarker manually open the TemplateClassResolver.SAFER_RESOLVER, so SSTI no way the RCE.

. 1
2
. 3
. 4
. 5
. 6
. 7
. 8
. 9
10
. 11
protected void initApplicationContext () throws BeansException {
super.initApplicationContext ();
IF (the getConfiguration () == null) {
FreeMarkerConfig autodetectConfiguration config = ();
the Configuration Configuration = config.getConfiguration () ;
configuration.setNewBuiltinClassResolver (TemplateClassResolver.SAFER_RESOLVER);
the setConfiguration (Configuration);
}
checkTemplate ();
}
at the limit TemplateClassResolver.SAFER_RESOLVER, SSTI it can only read the document, you can only read files and directories under the WEB.

 


Anti sequence
JEECMS used in shiro, version

1
<shiro.version> 1.4.0 </shiro.version>
older version shiro (1.2.4) has been an anti-burst sequence,
looked shiro download package maven 1.4.0, there is still anti-sequence of points


. 1
2
. 3
. 4
. 5
. 6
. 7
protected PrincipalCollection convertBytesToPrincipals (byte [] bytes, SubjectContext subjectContext) {
IF (! This.getCipherService () = null) {
bytes = this.decrypt (bytes);
}
return this.deserialize (bytes);
}
after decrypt, decryption aes began after the anti-sequence.

1
2
3
protected PrincipalCollection deserialize(byte[] serializedIdentity) {
return (PrincipalCollection)this.getSerializer().deserialize(serializedIdentity);
}

1
2
3
4
5
6
7
8
9
10
11
12
public T deserialize(byte[] serialized) throws SerializationException {
if (serialized == null) {
String msg = "argument cannot be null.";
throw new IllegalArgumentException(msg);
} else {
ByteArrayInputStream bais = new ByteArrayInputStream(serialized);
BufferedInputStream bis = new BufferedInputStream(bais);
try {
OIS = ClassResolvingObjectInputStream new new ObjectInputStream (BIS);
T = deserialized ois.readObject ();
ois.close ();
high version shiro just not hard-coded key AES in AbstractRememberMeManager in, but JEECMS which, again hard-coded AES the Key
/src/main/webapp/WEB-INF/config/shiro-context.xml

. 1
2
. 3
. 4
. 5
<-! The rememberMe Manager ->
<the bean ID = "rememberMeManager" class = "org.apache.shiro.web.mgt.CookieRememberMeManager">
<Property name = "cipherKey" value = "# { T (org.apache.shiro.codec.Base64) .decode ( '4AvVhmFLUs0KTA3Kprsdag ==')} "/>
<Property name =" Cookie "REF =" rememberMeCookie "/>
</ the bean>
directly use the AES key will be able to playing the anti-sequence.
I looked under JEECMS jar package, playing the anti-sequence version suitable for C3P0 jar package.
JEECMS the same package version and C3P0 C3P0 ysoserial own package version.

. 1
<c3p0.version> 0.9.5.2 </c3p0.version>
not initially know how C3P0 This gadget is used in the end, the next code is read.
/com/mchange/c3p0/0.9.5.2/c3p0-0.9.5.2.jar!/com/mchange/v2/c3p0/impl/PoolBackedDataSourceBase.class

1
2
3
4
5
6
7
8
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
short version = ois.readShort();
switch(version) {
case 1:
Object o = ois.readObject();
if (o instanceof IndirectlySerialized) {
o = ((IndirectlySerialized)o).getObject();
}
继续调用getObject方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public Object getObject() throws ClassNotFoundException, IOException {
try {
InitialContext var1;
if (this.env == null) {
var1 = new InitialContext();
} else {
var1 = new InitialContext(this.env);
}
Context var2 = null;
if (this.contextName != null) {
var2 = (Context)var1.lookup(this.contextName);
}
return ReferenceableUtils.referenceToObject(this.reference, this.name, var2, this.env);
调用referenceToObject方法,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static Object referenceToObject(Reference var0, Name var1, Context var2, Hashtable var3) throws NamingException {
try {
String var4 = var0.getFactoryClassName();
String var11 = var0.getFactoryClassLocation();
ClassLoader var6 = Thread.currentThread().getContextClassLoader();
if (var6 == null) {
var6 = ReferenceableUtils.class.getClassLoader();
}
Object var7;
if (var11 == null) {
var7 = var6;
} else {
= New new var8 the URL the URL (var11);
Var7 the URLClassLoader new new = (the URL new new [] {} var8, var6);
}
Class = var12 the Class.forName (var4, to true, (ClassLoader) Var7);
the ObjectFactory var9 = (the ObjectFactory) var12 .newInstance ();
return var9.getObjectInstance (varO, var1, var2, var3);
get remote jar through the URLClassLoader included in the category, then the classforname, newInstance class instantiation, the constructor call.

 

But playing the anti-sequence when the error occurred suid


Obviously yso the C3P0 and jeecms version of the same, but still prompted suid error.

Because jeecms dependent of quartz-scheduler package, which in turn depends on the c3p0 0.9.1.1 of the Anti sequence when calling an older version of C3P0 package. (Here I do not understand why I call the old local version of the package, reasonably maven priority Shortest Path First resolving conflicts dependent, you should call is 0.9.5.2 package. C3P0 and the high dependence of the previous version, there is Big Brother know why call the old version of a jar in one hand and taught me the trouble.)

 

This time C3P0 version ysoserial and jeecms version is not the same as the suid is different, directly modify here C3P0 version of ysoserial,

Ysoserial string text variable is generated C3P0 payload base64 encoding,

 

 


The References
1.https: //freemarker.apache.org/docs/versions_2_3_19.html
2.https: //portswigger.net/blog/server-side-template-injection
------------- --------
author:; console.log (document.cookie); //
source: CSDN
original: https: //blog.csdn.net/weixin_44063566/article/details/88897406
copyright: This article is Bo main original article, reproduced, please attach Bowen link!

Guess you like

Origin www.cnblogs.com/Jeely/p/11223950.html