GeoServer 任意文件上传漏洞分析研究 CVE-2023-51444

目录

前言

漏洞信息

代码审计

漏洞复现


前言

        时隔半月,我又再一次地审起了这个漏洞。第一次看到这个漏洞信息时,尝试复现了一下,结果却很不近人意。从官方公布的漏洞信息来看细节还是太少,poc不是一次就能利用成功的。也是当时心比较急没有静下心来,好好审计一下,胡乱一顿测结果什么都没测出来。这次我又带着代码审计进军来了。

        考虑贴代码不如图片真实,贴图片呢又只是能显示局部.... 所以我决定这样做,第一次调用的时候我会将完整的方法函数贴进来,后面再分析的时候,我就局部截图了。

漏洞信息

参考链接

Arbitrary file upload vulnerability in GeoServer's REST Coverage Store API · CVE-2023-51444 · GitHub Advisory Database · GitHub

[GEOS-11176] Add validation to file wrapper resource paths by sikeoka · Pull Request #7222 · geoserver/geoserver · GitHub

官网漏洞信息说明

Summary

An arbitrary file upload vulnerability exists that enables an authenticated administrator with permissions to modify coverage stores through the REST Coverage Store API to upload arbitrary file contents to arbitrary file locations which can lead to remote code execution.

Details

Coverage stores that are configured using relative paths use a GeoServer Resource implementation that has validation to prevent path traversal but coverage stores that are configured using absolute paths use a different Resource implementation that does not prevent path traversal.

PoC

Step 1 (create sample coverage store): curl -vXPUT -H"Content-type:application/zip" -u"admin:geoserver" --data-binary @polyphemus.zip "http://localhost:8080/geoserver/rest/workspaces/sf/coveragestores/filewrite/file.imagemosaic" Step 2 (switch store to absolute URL): curl -vXPUT -H"Content-Type:application/xml" -u"admin:geoserver" -d"file:///{absolute path to data directory}/data/sf/filewrite" "http://localhost:8080/geoserver/rest/workspaces/sf/coveragestores/filewrite" Step 3 (upload arbitrary files): curl -vH"Content-Type:" -u"admin:geoserver" --data-binary @file/to/upload "http://localhost:8080/geoserver/rest/workspaces/sf/coveragestores/filewrite/file.a?filename=../../../../../../../../../../file/to/write" Steps 1 & 2 can be combined into a single POST REST call if local write access to anywhere on the the file system that GeoServer can read is possible (e.g., the /tmp directory).

官方修补措施,像是修补了../穿越目录的可能 需要留意一下....

 

根据poc信息,定位相关代码

非常有可能是这个处理函数

接下来好好分析一番........

代码审计
@RestController
@ControllerAdvice
@RequestMapping(path =RestBaseController.ROOT_PATH+ "/workspaces/{workspaceName}/coveragestores/{storeName}/{method}.{format}")
public class CoverageStoreFileController extends AbstractStoreUploadController {
​/** Keys every known coverage format by lowercase name */protected static final HashMap<String, String> FORMAT_LOOKUP = new HashMap<>();
​static {for (Format format : CoverageStoreUtils.formats) {FORMAT_LOOKUP.put(format.getName().toLowerCase(), format.getName());}}
​@Autowiredpublic CoverageStoreFileController(@Qualifier("catalog") Catalog catalog) {super(catalog);}
​@PostMapping@ResponseStatus(code = HttpStatus.ACCEPTED)public void coverageStorePost(@PathVariable String workspaceName,@PathVariable String storeName,@PathVariable UploadMethod method,@PathVariable String format,@RequestParam(required = false) String filename,@RequestParam(name = "updateBBox", required = false) Boolean updateBBox,HttpServletRequest request)throws IOException {
​if (updateBBox == null) updateBBox = false;// check the coverage store existsCoverageStoreInfo info = catalog.getCoverageStoreByName(workspaceName, storeName);if (info == null) {throw new ResourceNotFoundException("No such coverage store: " + workspaceName + "," + storeName);}
​GridCoverageReader reader = info.getGridCoverageReader(null, null);if (reader instanceof StructuredGridCoverage2DReader) {StructuredGridCoverage2DReader sr = (StructuredGridCoverage2DReader) reader;if (sr.isReadOnly()) {throw new RestException("Coverage store found, but it cannot harvest extra resources",HttpStatus.METHOD_NOT_ALLOWED);}} else {throw new RestException("Coverage store found, but it does not support resource harvesting",HttpStatus.METHOD_NOT_ALLOWED);}
​StructuredGridCoverage2DReader sr = (StructuredGridCoverage2DReader) reader;// This method returns a List of the harvested sources.final List<Object> harvestedResources = new ArrayList<>();if (method == UploadMethod.remote) {harvestedResources.add(handleRemoteUrl(request));
​} else {for (Resource res :doFileUpload(method, workspaceName, storeName, filename, format, request)) {harvestedResources.add(Resources.find(res));}}// File Harvestingsr.harvest(null, harvestedResources, GeoTools.getDefaultHints());if (updateBBox) new MosaicInfoBBoxHandler(catalog).updateNativeBBox(info, sr);}

跟入doFileUpload方法

/*** Does the file upload based on the specified method.** @param method The method, one of 'file.' (inline), 'url.' (via url), or 'external.' (already*     on server)* @param storeName The name of the store being added* @param format The store format.*/
protected List<Resource> doFileUpload(UploadMethod method,String workspaceName,String storeName,String filename,String format,HttpServletRequest request)throws IOException {Resource directory = null;boolean postRequest =request != null && HttpMethod.POST.name().equalsIgnoreCase(request.getMethod());// Prepare the directory only in case this is not an external uploadif (method.isInline()) {// Mapping of the input directoryif (method == UploadMethod.url) {// For URL upload method, workspace and StoreName are not considereddirectory = RESTUtils.createUploadRoot(catalog, null, null, postRequest);} else {directory =RESTUtils.createUploadRoot(catalog, workspaceName, storeName, postRequest);}}return handleFileUpload(storeName, workspaceName, filename, method, format, directory, request);
}

跟入handleFileUpload

protected List<Resource> handleFileUpload(String store,String workspace,String filename,UploadMethod method,String format,Resource directory,HttpServletRequest request) {List<Resource> files = new ArrayList<>();Resource uploadedFile;boolean external = false;try {if (method == UploadMethod.file) {// we want to delete the previous dir contents only in case of PUT, not// in case of POST (harvest, available only for raster data)boolean cleanPreviousContents = HttpMethod.PUT.name().equals(request.getMethod());if (filename == null) {filename = buildUploadedFilename(store, format);}uploadedFile =RESTUtils.handleBinUpload(filename, directory, cleanPreviousContents, request, workspace);} else if (method == UploadMethod.url) {...
 

跟入 RESTUtils.handleBinUpload

/*** Reads content from the body of a request and writes it to a file.** @param fileName The name of the file to write out.* @param directory The directory to write the file to.* @param deleteDirectoryContent Delete directory content if the file already exists.* @param request The request.* @return The file object representing the newly written file.* @throws IOException Any I/O errors that occur.*     <p>TODO: move this to IOUtils.*/
public static org.geoserver.platform.resource.Resource handleBinUpload(String fileName,org.geoserver.platform.resource.Resource directory,boolean deleteDirectoryContent,HttpServletRequest request,String workSpace)throws IOException {// Creation of a StringBuilder for the selected fileStringBuilder itemPath = new StringBuilder(fileName);// Mediatype associated to the input fileMediaType mediaType =request.getContentType() == null? null: MediaType.valueOf(request.getContentType());// Only zip files are not remappedif (mediaType == null || !isZipMediaType(mediaType)) {String baseName = FilenameUtils.getBaseName(fileName);String itemName = FilenameUtils.getName(fileName);// Store parameters used for mapping the file pathMap<String, String> storeParams = new HashMap<>();// Mapping item pathremapping(workSpace, baseName, itemPath, itemName, storeParams);}final org.geoserver.platform.resource.Resource newFile = directory.get(itemPath.toString());if (Resources.exists(newFile)) {if (deleteDirectoryContent) {for (Resource file : directory.list()) {file.delete();}} else {// delete the file, otherwise replacing it with a smaller one will leave bytes at// the endnewFile.delete();}}try (OutputStream os = newFile.out()) {IOUtils.copy(request.getInputStream(), os);}return newFile;
}
 

根据注释信息,首先是可以向系统写入文件的,

重点关注下参数的传递,重点关注下这两个参数,文件写入嘛!我们重点关注的就是写入路径与写入名称

  • @param fileName The name of the file to write out.

  • @param directory The directory to write the file to.

一,directory 参数由方法doFileUpload 中446行与454行得来

workspaceName与storeName 这个俩为我们控的变量,那么由此得到的directory 将会是什么目录呢?绝对路径?相对路径?再此之前我们可以定义这个路径吗?

这几个问题先保留着...

二,fileName 这个变量是我们直接可控的, 那么这个文件是否能以../../的形式穿越路径呢?

带着疑问我们继续往下分析

分析最关键的地方

en... 我们需要跟进get方法中去看看

Resource是一个接口,所以这里跟进的get方法 是一个实现了接口Resource的类 的get 方法

看到get我们要敏感一下啊,因为修补代码的get就属于ResourceAdaptor类,它是实现了接口Resource了的,所以逻辑上这里是可以跳到ResourceAdaptor类的get方法的。

可以看到resourcePath中间是没有经过任何过滤的,那么这就非常可能进行../../穿越目录的文件写入。

接下来我们重点分析directory的传参,要让它是ResourceAdaptor对象,这样的我们才能调用那个存在漏洞的get方法啊。

还是来到这里

跟入RESTUtils.createUploadRoot

/** Creates a file upload root for the given workspace and store */
public static Resource createUploadRoot(Catalog catalog, String workspaceName, String storeName, boolean isPost)throws IOException {// Check if the Request is a POST request, in order to search for an existing coverageResource directory = null;if (isPost && storeName != null) {// Check if the coverage already existsCoverageStoreInfo coverage = catalog.getCoverageStoreByName(storeName);if (coverage != null) {if (workspaceName == null|| coverage.getWorkspace().getName().equalsIgnoreCase(workspaceName)) {// If the coverage exists then the associated directory is defined by its URLString url = coverage.getURL();String path;if (url.startsWith("file:")) {path = URLs.urlToFile(new URL(url)).getPath();} else {path = url;}directory = Resources.fromPath(path, catalog.getResourceLoader().get(""));}}}// If the directory has not been found then it is created directlyif (directory == null) {directory =catalog.getResourceLoader().get(Paths.path("data", workspaceName, storeName));}// Selection of the original ROOT directory pathStringBuilder root = new StringBuilder(directory.path());// StoreParams to use for the mapping.Map<String, String> storeParams = new HashMap<>();// Listing of the available pathMappersList<RESTUploadPathMapper> mappers =GeoServerExtensions.extensions(RESTUploadPathMapper.class);// Mapping of the root directoryfor (RESTUploadPathMapper mapper : mappers) {mapper.mapStorePath(root, workspaceName, storeName, storeParams);}directory = Resources.fromPath(root.toString());return directory;
}

共有两处生成directory的地方

Resources.fromPath比较可疑,进入Resources.fromPath分析一下,不行的话再看462行的生成逻辑

/*** Creates resource from a path, if the path is relative it will return a resource relative to* the provided directory otherwise it will return a file based resource** @param path relative or absolute path* @param relativeDir directory to which relative paths are relative* @return resource*/
public static org.geoserver.platform.resource.Resource fromPath(String path, org.geoserver.platform.resource.Resource relativeDir) {File file = new File(path);if (file.isAbsolute()) {return Files.asResource(file);} else {return relativeDir.get(path.replace(File.separatorChar, '/'));}
}

如果是绝对路径则返回 return Files.asResource(file);

跟进去

/*** Adapter allowing a File reference to be quickly used a Resource.** <p>This is used as a placeholder when updating code to use resource, while still maintaining* deprecated File methods:** <pre><code>* //deprecated* public FileWatcher( File file ){*    this.resource = Files.asResource( file );* }* //deprecated* public FileWatcher( Resource resource ){*    this.resource = resource;* }* </code></pre>** Note this only an adapter for single files (not directories).** @param file File to adapt as a Resource* @return resource adaptor for provided file*/
public static Resource asResource(final File file) {if (file == null) {throw new IllegalArgumentException("File required");}return new ResourceAdaptor(file);
}

干的漂亮,就是我们想要的ResourceAdaptor对象

自此 就有了一条上传链,接下来就是调试细节的问题。

要怎么调用方法?,要注入什么参数?,以什么形式注入?,要符合那些要求?包括前面的问题如何修改为绝对目录?

这些都需要解决 都需要慢慢地调试,分析,最终慢慢地形成poc...

漏洞复现

更多详情可以关注我的github. 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/323322.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AXI4读时序在AXI Block RAM (BRAM) IP核中的应用

在本文中将展示描述了AXI从设备&#xff08;slave&#xff09;AXI BRAM Controller IP核与Xilinx AXI Interconnect之间的读时序关系。 1 Single Read 图1展示了一个从32位BRAM&#xff08;Block RAM&#xff09;进行AXI单次读取操作的时序示例。 图1 AXI 单次读时序图 在该…

书生浦语训练营第三次课笔记:XTuner 微调 LLM:1.8B、多模态、Agent

Finetune 简介 两种Finetune范式&#xff1a;增量预训练微调、指令跟随微调 微调数据集 上述是我们所期待模型回答的内容&#xff0c;在训练时损失的计算也是基于这个。 训练数据集看起来是这样&#xff0c;但是真正喂给模型的&#xff0c;是经过对话模板组装后的 下图中&…

信息系统项目管理师0097:价值交付系统(6项目管理概论—6.4价值驱动的项目管理知识体系—6.4.6价值交付系统)

点击查看专栏目录 文章目录 6.4.6价值交付系统1.创造价值2.价值交付组件3.信息流6.4.6价值交付系统 价值交付系统描述了项目如何在系统内运作,为组织及其干系人创造价值。价值交付系统包括项目如何创造价值、价值交付组件和信息流。 1.创造价值 项目存在于组织中,包括政府机构…

ICode国际青少年编程竞赛- Python-2级训练场-数独

ICode国际青少年编程竞赛- Python-2级训练场-数独 1、 Spaceship.step(3)2、 Spaceship.step(3)3、 Spaceship.step(1) Spaceship.turnLeft() Spaceship.step(1)4、 Spaceship.step(3) Spaceship.turnRight() Spaceship.step(1)5、 Spaceship.step(4) for i in range(3):Spaces…

(二十一)springboot实战——Spring AI劲爆来袭

前言 本节内容是关于Spring生态新发布的Spring AI的介绍&#xff0c;Spring AI 是一个面向人工智能工程的应用框架。其目标是将 Spring 生态系统的设计原则&#xff0c;如可移植性和模块化设计&#xff0c;应用到人工智能领域&#xff0c;并推广使用普通的Java对象&#xff08…

【Linux】项目自动化构建工具make/makefile

&#x1f389;博主首页&#xff1a; 有趣的中国人 &#x1f389;专栏首页&#xff1a; Linux &#x1f389;其它专栏&#xff1a; C初阶 | C进阶 | 初阶数据结构 小伙伴们大家好&#xff0c;本片文章将会讲解Linux中项目自动化构建工具make/makefile的相关内容。 如果看到最后…

2024年自动驾驶、车辆工程与智能交通国际会议(ICADVEIT2024)

2024年自动驾驶、车辆工程与智能交通国际会议&#xff08;ICADVEIT2024&#xff09; 会议简介 2024年自动驾驶、车辆工程和智能交通国际会议&#xff08;ICADVEIT 2024&#xff09;将在中国深圳举行。会议主要聚焦自动驾驶、车辆工程和智能交通等研究领域&#xff0c;旨在为从…

盲盒一番赏小程序:探索未知,开启神秘宝藏之旅

开启神秘之门&#xff0c;探索未知的乐趣 在繁忙的生活中&#xff0c;我们渴望一丝丝未知带来的惊喜与乐趣。盲盒一番赏小程序&#xff0c;正是为了满足您这种探索未知的欲望而诞生。它不仅仅是一个购物平台&#xff0c;更是一个充满神秘与惊喜的宝藏世界。 精选好物&#xf…

Electron学习笔记(一)

文章目录 相关笔记笔记说明 一、轻松入门 1、搭建开发环境2、创建窗口界面3、调试主进程 二、主进程和渲染进程1、进程互访2、渲染进程访问主进程类型3、渲染进程访问主进程自定义内容4、渲染进程向主进程发送消息5、主进程向渲染进程发送消息6、多个窗口的渲染进程接收主进程发…

Linux:进程信号

生活角度的信号 a.信号在生活中&#xff0c;随时可以产生(信号的产生和我是异步的) b.你能认识这个信号 c.我们知道信号产生了&#xff0c;我能识别这个信号&#xff0c;信号该怎么处理 d.我们可能正在做着更重要的事情&#xff0c;把到来的信号暂不处理(1.我记得这个事 2.…

YOLOv9中模块总结补充|RepNCSPELAN4详图

专栏地址&#xff1a;目前售价售价69.9&#xff0c;改进点70 专栏介绍&#xff1a;YOLOv9改进系列 | 包含深度学习最新创新&#xff0c;助力高效涨点&#xff01;&#xff01;&#xff01; 1. RepNCSPELAN4详图 RepNCSPELAN4是YOLOv9中的特征提取-融合模块&#xff0c;类似前几…

phpstudy(MySQL启动又立马停止)问题的解决办法

方法一&#xff1a;查看本地安装的MySQL有没有启动 1.鼠标右击开始按钮选择计算机管理 2.点击服务和应用程序 3.找到服务双击 4.找到MySQL服务 5.双击查看是否启动&#xff0c;如启动则停止他&#xff0c;然后确定&#xff0c;重新打开phpstudy,启动Mysql. 方法二&#xff…

Python从0到POC编写--实用小脚本02

爆破脚本&#xff1a; 爆破脚本也是我们经常使用的东西 这里就简单讲讲后台爆破脚本的编写吧 在编写之前&#xff0c;我们先通过访问网站去看看情况 首先我们可以先登录看看 输入账号 admin &#xff0c;密码 12345 后 登录失败&#xff0c;提示 用户名或密码错误 在输入…

苹果新品发布会速览:iPad革新遇市场挑战 | 百能云芯

北京时间5月7日晚&#xff0c;苹果以“放飞吧”为主题&#xff0c;举办了一场不到40分钟的线上新品发布会。在这场发布会上&#xff0c;iPad产品线成为了焦点&#xff0c;M4芯片和OLED技术的融入更是引起了广泛关注。 iPad新篇章&#xff1a;技术革新与市场竞争 时隔18个月&…

WebSocket 多屏同显和异显

介绍 多屏同显:通过在一个应用上进行操作之后,另一个应用也能跟着一起发生改变,例如app1播放了晴天这首音乐,那么app2也要同步播放这首音乐,确保所有屏幕显示的内容完全相同。多屏异显:每个屏幕可以显示不同的内容,或者在内容更新时存在一定的延迟,而不需要严格保持同步…

Linux下的SPI通信

SPI通信 一. 1.SPI简介: SPI 是一种高速,全双工,同步串行总线。 SPI 有主从俩种模式通常由一个主设备和一个或者多个从设备组从。SPI不支持多主机。 SPI通信至少需要四根线,分别是 MISO(主设备数据输入,从设备输出),MOSI (主设数据输出从设备输入),SCLK(时钟信号),CS/SS…

物理层——计算机网络学习笔记二

目录 物理层的基本概念 数据通信的基础知识 物理层下面的传输媒体 信道复用技术 图片大部分来源于谢希仁《计算机网络》教材配套的ppt。 这一样都是介绍一下概念性的东西&#xff0c;了解一下就行&#xff0c;就重要性而言不如后面的内容。 物理层的作用&#xff1a;考虑如何才…

android基础-服务

同样使用intent来传递服务 oncreate是服务第一次启动调用&#xff0c;onStartCommand是服务每次启动的时候调用&#xff0c;也就是说服务只要启动后就不会调用oncreate方法了。可以在myservice中的任何位置调用stopself方法让服务停止下来。 服务生命周期 前台服务类似于通知会…

在Linux操作系统中扩建swap容量

在Linux操作系统上创建硬盘分区不仅可以储存数据&#xff0c;还可以使用创建的硬盘分区去扩展机器上swap分区的大小——去扩展交换工具的容量。 有些软件对于swap分区的大小是有要求的&#xff0c;swap分区的大小小于多少就安装不上软件。 要扩展swap容量要么重装系统&#x…

初学者理解Transformer,本文is all you need

要问现在AI领域哪个概念最热&#xff0c;必然是openAI推出chatGPT之后引发的大模型。然而这项技术的起源&#xff0c;都来自一篇google公司员工的神作“Attention Is All You Need”——本文标题也是一种致敬^_^&#xff0c;目前已有近12万的引用(还在增长)。 在“Attention Is…