当前位置:WooYun >> 漏洞信息

漏洞概要 关注数(24) 关注此漏洞

缺陷编号:wooyun-2015-0154719

漏洞标题:宏景e-HR系统GETSHELL漏洞

相关厂商:北京宏景世纪软件股份有限公司

漏洞作者: applychen

提交时间:2015-11-23 16:46

修复时间:2016-01-11 15:32

公开时间:2016-01-11 15:32

漏洞类型:文件上传导致任意代码执行

危害等级:高

自评Rank:20

漏洞状态:未联系到厂商或者厂商积极忽略

漏洞来源: http://www.wooyun.org,如有疑问或需要帮助请联系 [email protected]

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2015-11-23: 积极联系厂商并且等待厂商认领中,细节不对外公开
2016-01-11: 厂商已经主动忽略漏洞,细节向公众公开

简要描述:

宏景e-HR系统GETSHELL漏洞。

详细说明:

在web.xml中配置了这么个servlet:

<servlet-name>uploadmediafileservlet</servlet-name>
<servlet-class>com.hjsj.hrms.servlet.train.media.UploadMediaFileServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>uploadmediafileservlet</servlet-name>
<url-pattern>/train/media/upload</url-pattern>
</servlet-mapping>


直接跟入com/hjsj/hrms/servlet/train/media/UploadMediaFileServlet.java文件doPost()方法:

public void doPost(HttpServletRequest paramHttpServletRequest, HttpServletResponse paramHttpServletResponse)
throws ServletException, IOException
{
……
ServletFileUpload localServletFileUpload = new ServletFileUpload(new DiskFileItemFactory());
……
try
{
List localList = localServletFileUpload.parseRequest(paramHttpServletRequest);
for (int i = 0; i < localList.size(); i++)
{
localFileItem = (FileItem)localList.get(i);
if (!localFileItem.isFormField())
{
str2 = localFileItem.getName();
str1 = str2.substring(str2.lastIndexOf("."));
localInputStream = localFileItem.getInputStream();
l = localFileItem.getSize();
}
else
{
this.paraMap.put(localFileItem.getFieldName(), localFileItem.getString("utf-8"));
}
}
String str4 = (String)this.paraMap.get("keyCode");
if ("61".equalsIgnoreCase(str4))
{
dataInit();
String str5 = (String)this.paraMap.get("acode");
this.fileType = ((String)this.paraMap.get("fileType"));
str5 = str5 == null ? "" : str5;
str2 = (String)this.paraMap.get("fileName");
if ((str2 == null) || (str2.length() == 0))
str2 = (String)this.paraMap.get("Filename");
str2 = SafeCode.decode(str2);
str3 = uploadMediaFile(str2, str1, localInputStream, str5, paramHttpServletRequest);
this.paraMap = new HashMap();
}
localPrintWriter.write("successed:" + System.currentTimeMillis() + ",id:" + SafeCode.encode(str3));
}


上面代码载入了上传数据,并从表单中读取各个参数的值(keyCode、acode、fileType、fileName)写入paraMap,当keyCode=61时调用uploadMediaFile(str2, str1, localInputStream, str5, paramHttpServletRequest);写入数据到服务器,注意这里的str1为文件名后缀由以下代码得到,可以看到在输入时是没有过滤的:

str2 = localFileItem.getName();
str1 = str2.substring(str2.lastIndexOf("."));


uploadMediaFile()定义如下:

private String uploadMediaFile(String paramString1, String paramString2, InputStream paramInputStream, String paramString3, HttpServletRequest paramHttpServletRequest)
throws Exception
{
String str1 = "";
String str2 = "";
FileOutputStream localFileOutputStream = null;
int i = 0;
String str3 = System.getProperty("file.separator");
if ((paramString3 != null) && (paramString3.length() > 0))
for (int j = 0; j < paramString3.length() / 2; j++)
str1 = str1 + paramString3.substring(0, 2 * (j + 1)) + str3;
if ((this.ftpMediaPath != null) && (this.ftpMediaPath.endsWith("/")))
this.ftpMediaPath = this.ftpMediaPath.substring(0, this.ftpMediaPath.length() - 1);
try
{
str2 = paramHttpServletRequest.getSession().getServletContext().getRealPath("/coureware");
if (SystemConfig.getPropertyValue("webserver").equals("weblogic"))
{
str2 = paramHttpServletRequest.getSession().getServletContext().getResource("/").getPath();
localFile = new File(str2 + "/coureware");
if (!localFile.exists())
localFile.mkdir();
if (str2.indexOf(':') != -1)
str2 = str2.substring(1);
str2 = URLDecoder.decode(str2 + "coureware/");
}
else
{
str2 = URLDecoder.decode(str2) + str3;
}
File localFile = new File(str2 + str1);
if (!localFile.exists())
localFile.mkdirs();
paramString1 = getFileName(paramString1);
byte[] arrayOfByte = new byte[1024];
int k = 0;
localFileOutputStream = new FileOutputStream(str2 + str1 + paramString1 + paramString2);
while ((k = paramInputStream.read(arrayOfByte)) != -1)
localFileOutputStream.write(arrayOfByte, 0, k);


上面代码由

str2 = paramHttpServletRequest.getSession().getServletContext().getRealPath("/coureware");
……
else
{
str2 = URLDecoder.decode(str2) + str3;
}
File localFile = new File(str2 + str1);
if (!localFile.exists())
localFile.mkdirs();
paramString1 = getFileName(paramString1);


获得web服务器中上传文件保存目录$webroot/coureware/,这里还调用了一个getFileName(paramString1);定义如下:

private String getFileName(String paramString)
{
String str1 = paramString;
Connection localConnection = null;
String str2 = "select count(1) n from r51 where r5103='" + paramString + "'";
RowSet localRowSet = null;
try
{
localConnection = AdminDb.getConnection();
ContentDAO localContentDAO = new ContentDAO(localConnection);
localRowSet = localContentDAO.search(str2);


可以看出这里是一个明显的SQL注入,下面再回到上传最主要的代码:

localFileOutputStream = new FileOutputStream(str2 + str1 + paramString1 + paramString2);
while ((k = paramInputStream.read(arrayOfByte)) != -1)
localFileOutputStream.write(arrayOfByte, 0, k);
if (localFileOutputStream != null)
localFileOutputStream.close();


str2 + str1 + paramString1 + paramString2是最终的文件名,paramString2是传入的后缀名,paramString1 为fileName传入的值,str1为acode传入的值,保持为空即可,最后str2是web跟路径下的/coureware/目录,因此最后的文件名为:

$webroot/coureware/$fileName.$paramString2


三个变量两个都是可控的,因此导致上传漏洞发生。
谷歌里面搜索关键字还是挺多的:

https://**.**.**.**/#newwindow=1&safe=off&q=inurl:templates%2Findex%2Fhrlogon.jsp


1.png


选择任意一个测试,直接构造包上传:

2.png


访问:

**.**.**.**/coureware/test.jsp


3.png


漏洞证明:

同上

修复方案:

1、添加鉴权措施
2、增加上传文件后缀白名单验证机制
3、全局过滤SQL注入字符

版权声明:转载请注明来源 applychen@乌云


漏洞回应

厂商回应:

未能联系到厂商或者厂商积极拒绝


漏洞评价:

评价