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方法

JAVA代码审计

定位到代码段进行审计,检查是否存在文件上传漏洞

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,这明显是不会和黑名单中的后缀相等的,实现了后缀绕过。

JAVA代码审计

在代码中发现程序使用了Contenttype,可以使用BP对其进行抓包并修改Contenttype值,修改filename为1.jpg.jsp进行绕过上传恶意文件到服务器

JAVA代码审计

JAVA代码审计

四、文件上传漏洞修复

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代码审计 任意文件上传篇的主要内容,如果未能解决你的问题,请参考以下文章

[代码审计] beecms 4.0 漏洞总结

JAVA代码审计 SQL注入篇

[PHP代码审计]emlog6.0.0整站审计

[PHP代码审计]emlog6.0.0整站审计

当前市面上的代码审计工具哪个比较好?

[网络安全提高篇] 一〇九.津门杯CTF的Web Write-Up万字详解(SSRF文件上传SQL注入代码审计中国蚁剑)