JAVA代码审计 任意文件上传篇
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JAVA代码审计 任意文件上传篇相关的知识,希望对你有一定的参考价值。
一、文件上传方式(常见)
通过文件流方式上传;通过ServletFileUpload方式上传;通过MultipartFile方式上传
1、通过文件流方式进行上传
public String fileUpload(@RequestParam("file") CommonsMultipartFile file) throws IOException
long startTime=System.currentTimeMillis();
System.out.println("fileName:"+file.getOriginalFilename());
try
OutputStream os=new FileOutputStream("/tmp"+newDate().getTime()+file.getOriginalFilename());
InputStream is=file.getInputStream();
int temp;
while((temp=is.read())!=(-1))
os.write(temp);
os.flush();
os.close();
is.close();
catch (FileNotFoundException e)
e.printStackTrace();
return "/success";
2、通过ServletFileUpload方式上传
String realPath = this.getServletContext().getRealPath("/upload");
String tempPath = "/tmp";
File f = new File(realPath);
if(!f.exists()&&!f.isDirectory())
f.mkdir();
File f1 = new File(tempPath);
if(!f1.isDirectory())
f1.mkdir();
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setRepository(f1);
ServletFileUpload upload = new ServletFileUpload(factory);
upload.setHeaderEncoding("UTF-8");
if(!ServletFileUpload.isMultipartContent(req)) return;
List<FileItem> items =upload.parseRequest(req);
for(FileItem item:items)
if(item.isFormField())
String filedName = item.getFieldName();
String filedValue = item.getString("UTF-8");
else
String fileName = item.getName();
if(fileName==null||"".equals(fileName.trim())) continue;
fileName = fileName.substring(fileName.lastIndexOf("/")+1);
String filePath = realPath+"/"+fileName;
InputStream in = item.getInputStream();
OutputStream out = new FileOutputStream(filePath);
byte b[] = new byte[1024];
int len = -1;
while((len=in.read(b))!=-1)
out.write(b, 0, len);
out.close();
in.close();
try
Thread.sleep(3000);
catch (InterruptedException e)
e.printStackTrace();
item.delete();
3、通过MultipartFile方式进行上传
public String handleFileUpload(@RequestParam("file") MultipartFile file)
if (file.isEmpty())
return "请上传文件";
// 获取文件名
String fileName = file.getOriginalFilename();
String suffixName = fileName.substring(fileName.indexOf("."));
String filePath = "/tmp";
File dest = new File(filePath + fileName);
if (!dest.getParentFile().exists())
dest.getParentFile().mkdirs();
try
file.transferTo(dest);
return "上传成功";
catch (IllegalStateException e)
e.printStackTrace();
catch (IOException e)
e.printStackTrace();
return "上传失败";
在这个代码中,存在一个经典文件上传绕过问题,程序运行时通过如下代码
String suffixName = fileName.substring(fileName.indexOf("."));
获取了上传文件的后缀对其进行判断是否存在于黑名单中。该代码写法看似安全其实很容易犯错误,当文件名为 abc.jpg.jsp时suffixName将等于.jpg.jsp,这明显是不会和黑名单中的后缀相等的,实现了后缀绕过。
二、文件上传代码审计关键词
org.apache.commons.fileupload
java.io.File
MultipartFile
RequestMethod
MultipartHttpServletRequest
CommonsMutipartResolver
三、漏洞审计
通过搜索关键词定位到相应代码段,发现程序使用了MultipartFile方法
定位到代码段进行审计,检查是否存在文件上传漏洞
String rootPath = "../webapps/ROOT/upload";
File dir = new File(rootPath + File.separator + "img");
if (!dir.exists())
dir.mkdirs();
String fileName = file.getOriginalFilename();
String contentType = file.getContentType();
String Suffix = fileName.substring(fileName.indexOf("."));
String picBlack[] = ".jsp", ".jspx", ".bat", ".exe", ".vbs";
String white_type[] = "image/gif", "image/jpeg", "image/jpg", "image/png";
Boolean BlackFlag = false;
Boolean whiteFlag = false;
for (String black_suffix : picBlack)
if (Suffix.toLowerCase().equals(black_suffix))
BlackFlag = true;
break;
for (String type : white_type)
if (contentType.toLowerCase().equals(type))
whiteFlag = true;
break;
if (!whiteFlag)
return "File type not allowed1";
if (BlackFlag)
return "File type not allowed";
File serverFile = new File(dir.getAbsolutePath() + File.separator + file.getOriginalFilename());
file.transferTo(serverFile);
return "upload file successfully=" + file.getOriginalFilename();
程序通过如下方法进行对上传文件的判断,通过判断上传文件后缀名是否合法,使用黑白名单的方式进行对照
String Suffix = fileName.substring(fileName.indexOf("."));
该方法看似安全,当文件名为 abc.jpg.jsp时suffixName将等于.jpg.jsp,这明显是不会和黑名单中的后缀相等的,实现了后缀绕过。
在代码中发现程序使用了Contenttype,可以使用BP对其进行抓包并修改Contenttype值,修改filename为1.jpg.jsp进行绕过上传恶意文件到服务器
四、文件上传漏洞修复
1、设置白名单进行绕过而不是通过黑名单认证的方式
String picwhite[] = ".jpg", ".jpeg", ".png", ".gif", ".bmp"
2、通过String Suffix = fileName.substring(fileName.lastIndexOf("."))来获取上传文件的真实后缀判断上传文件是否合法而不是通过fileName.substring(fileName.indexOf("."))获取文件后缀来判断上传文件是否合法,避免以黑白名单写法进行绕过。
fileName.substring(fileName.lastIndexOf(".")
fileName.substring(fileName.indexOf("."))
3、通过String whitetype[]="image/gif","image/jpeg","image/jpg","image/png" 对文件类型进行判断,虽然可以通过BP抓包修改MIME类型但是有一定的限制,通过验证后在文件存储时又对文件进行随机重命名。
4、全局过滤关键字
[PHP代码审计]emlog6.0.0整站审计
写在前面
---------------------------------不小心十点了,该送女朋友回寝室了,剩下的太简单了,自己分析,还有一堆洞------------------------------
随便花了点时间读完了代码,看了很多页面,至少前台也是比较安全的,主要是测试了sql注入xss之类的,那么就去后台看看,作者还是比较有水平的,蛮细心的;
节约时间,不带师傅们从起点分析过滤啥的,有用到再提,本篇算是一次随意的审计练习吧!也算是带师傅们踏入一次简单的审计之旅!
当然可能也有遗漏的漏洞,毕竟是自己审计出来的,也没翻别人的,见谅;
Emlog
SQL注入
注入点1
很明显在一个地方没过滤admin/comment.php
if ($action== 'delbyip') {
LoginAuth::checkToken();
if (ROLE != ROLE_ADMIN) {
emMsg('权限不足!', './');
}
$ip = isset($_GET['ip']) ? $_GET['ip'] : '';
$Comment_Model->delCommentByIp($ip);
$CACHE->updateCache(array('sta','comment'));
emDirect("./comment.php?active_del=1");
}
这里很明显ip没有过滤吧,至于你说LoginAuth::checkToken();
怎么拿到token,到处都是反正这个插件
注入点2
比较常见的一种注入方式,去数据库备份页面
之后上传导入恢复数据,发现成功实现注入select version()
注入点3
在admin/navbar.php
文件上传漏洞
利用点1
在后台,插件上传处,在admin/plugin.php
逻辑很简单,自己看看。但是返回结果是安装失败,插件安装包不符合标准
尴尬,瞧瞧view
视图,找到了结果
<?php if(isset($_GET['error_e'])):?><span class="alert alert-danger">安装失败,插件安装包不符合标准</span><?php endif;?>
再返回得到分支,只有这里传了error_e
case -1:
emDirect("./plugin.php?error_e=1");
break;
因此得到,返回结果-1,而这个返回结果来于
$ret = emUnZip($zipfile['tmp_name'], '../content/plugins/', 'plugin');
我跳过不重要的
$r = explode('/', $zip->getNameIndex(0), 2);
$dir = isset($r[0]) ? $r[0] . '/' : '';
这里是得到压缩包下目录路径,比如说我有个y4tacker.php
,压缩文件有一个y4tacker
文件夹,这个dir最后就会返回/y4tacker
,很简单不多说,看看之前报错返回-1的地方,是由于$zip->getFromName
带来的报错
case 'plugin':
$plugin_name = substr($dir, 0, -1);
$re = $zip->getFromName($dir . $plugin_name . '.php');
if (false === $re)
return -1;
break;
substr($dir, 0, -1);
截取全名不多说,耐心Y4还告诉你了,之后便执行解压
if (true === @$zip->extractTo($path)) {
$zip->close();
return 0;
} else {
return 1;//文件权限问题
}
因此我们制作一个php文件,内容为<?php phpinfo();
,放在y4tacker文件夹下面后压缩上传即可
任意文件删除漏洞
任意文件删除1
admin/data.php
下139行,有点傻的洞
if ($action == 'dell_all_bak') {
if (!isset($_POST['bak'])) {
emDirect('./data.php?error_a=1');
} else{
foreach ($_POST['bak'] as $val) {
unlink($val);
}
emDirect('./data.php?active_del=1');
}
}
任意文件删除2(一不小心发现个洞交CNVD了)
漏洞点在后台插件删除处,点击删除抓包
当然从location处的值发现已经删除成功
检查下网站根目录是否如此,确实robots.txt被删除了
在admin/plugin.php
,能看见如果成功删除则重定向到./plugin.php?activate_del=1
//删除插件
if ($action == 'del') {
LoginAuth::checkToken();
$Plugin_Model = new Plugin_Model();
$Plugin_Model->inactivePlugin($plugin);
$pludir = preg_replace("/^([^\\/]+)\\/.*/", "$1", $plugin);
if (true === emDeleteFile('../content/plugins/' . $pludir)) {
$CACHE->updateCache('options');
emDirect("./plugin.php?activate_del=1");
} else {
emDirect("./plugin.php?error_a=1");
}
}
漏洞存在点在于这个preg_replace
如果我们能绕过这个正则匹配那么他就可以返回我们原始字符串
这个正则表达式的意思是,输入的字符满足
1.开头不能为/
2.将匹配到的值替换为$1
也就是第一个括号中的内容
因此我们如果传入../../
则会返回..
那如果第一位是\\
也就成功绕过了这个替换,也就是可以实现任意目录穿越了,
因此不难理解我为什么上面写的plugin=/../../robots.txt
了,拼接起来也就是../content/plugins//../../robots.txt
,根据linux特性两个//
其实也就是一个/
漏洞太多了就这样了,躺平了
以上是关于JAVA代码审计 任意文件上传篇的主要内容,如果未能解决你的问题,请参考以下文章
[网络安全提高篇] 一〇九.津门杯CTF的Web Write-Up万字详解(SSRF文件上传SQL注入代码审计中国蚁剑)