本帖最后由 小七 于 2025-4-18 10:35 编辑
代码审计-任意文件写入RCE+SQL注入挖掘技巧
前言
最近在代码审计的时候,发现了一个任意文件写入漏洞,利用方式有点意思,不是直接覆盖文件的常规操作,而是通过一些逻辑上的小技巧实现写入。虽然漏洞本身不算复杂,但利用过程还挺好玩的,所以想分享一下我的思路。 另外,还挖到一个SQL注入,不过这个注入点比较普通,就简单提一下,重点是介绍一下代码审计挖掘SQL注入时的辅助工具MySQLMonitor(Mysql监控)。 (1) 任意文件写入这套源码不是基于ThinkPHP、Laravel编写的。对于这种非主流框架编写的源代码,我一般会先在本地部署好环境之后再开始审计(部署好之后,直接打开BP抓包,照着功能点一顿乱点,观察请求的URL,了解路由规则,方便快速定位控制器以及对应的方法)。 打开编辑器全局搜索upload关键词 上传点A(核心方法): - <span style="background-color: silver;">private function saveImage($file, $type)
- {
- $tmpName = $file['tmp_name'];
- $tmpFile = file_get_contents($tmpName);
- if($type == FileType::FileDocument) {
- $name = $file['name'];
- $ext = array_pop(explode(".", $name));
- $fileName = $this->ctx->File->saveDocument($tmpFile, $ext);
- } else{
- $fileName = $this->ctx->File->saveFile($tmpFile);
- }
- return $fileName;
- }</span>
复制代码- public function saveFile($content, $dateDir = false)
- {
- if (!$dateDir) {
- $dateDir = date("Ymd");
- }
- $fileName = sha1(uniqid());
- $path = $this->getPath($dateDir, $fileName);
- file_put_contents($path, $content);
- $mime = mime_content_type($path);
- if (!in_array($mime, $this->defaultType)) {
- thrownewException("file type error");
- }
- $ext = isset($this->mimeMapping[$mime]) ? $this->mimeMapping[$mime] : "";
- if (false == empty($ext)) {
- $fileName = $fileName . "." . $ext;
- rename($path, $this->getPath($dateDir, $fileName));
- }
- return $dateDir . "-" . $fileName;
- }
复制代码 核心逻辑是通过mime_content_type函数获取上传文件(临时文件)的mime类型,并判断是否存在于defaultType(白名单)数组中,如果不存在则直接抛出异常。存在则通过文件的mime类型在mimeMapping数组中取出该mime对应的值(对应的文件类型),并对最终保存的文件名进行拼接。 因为文件后缀名是固定的,所以这个上传点行不通,限制死了。 - private $defaultType = [
- "image/jpeg",
- "image/jpg",
- "image/png",
- "image/gif",
- "audio/mp4",
- "audio/x-m4a",
- "video/mp4",
- ];
复制代码- private $mimeMapping = array(
- "image/jpeg" => "jpeg",
- "image/jpg" => "jpg",
- "image/png" => "png",
- "image/gif" => "gif",
- 'application/zip' => "zip",
- 'application/msword' => "word",
- 'application/xml' => "xml",
- 'application/vnd.ms-powerpoint' => "ppt"
- );
复制代码 由于通篇只有这一个上传点,所以开始全局搜索危险函数,首先搜索的file_put_contents ,文件写入函数。 定位到一个高危的功能点,moveImage方法,因为这个方法在工具类中,所以还需要向上查找调用处(编辑器光标悬停在方法名处,点击右键,Alt+F7可查找方法在项目中的哪里被引用/调用,如果没有输出结果的话还是老方法全局搜索)。 - public function moveImage($fileId, $newDir)
- {
- $fileContent = $this->readFile($fileId);
- if (!is_dir($newDir)) {
- mkdir($newDir, 0755, true);
- }
- $lastStr = substr($newDir, -1);
- $path = $newDir . "/" . $fileId;
- if ($lastStr == "/") {
- $path = $newDir . $fileId;
- }
- file_put_contents($path, $fileContent);
- return $path;
- }
复制代码 在B控制器中找到一处调用,且$fileId参数可控。- $fileId = $_POST["value"]
- $imageDir = LIB_DIR . "../public/web/image/";
- $this->ctx->File_Manager->moveImage($fileId, $imageDir);
复制代码 moveImage的核心逻辑是将$fileId作为文件路径读取文件内容,这里会对字符串(fileId)文件路径处理,按"-"符号将字符串为分割为数组。- public function readFile($fileId)
- {
- if (strlen($fileId) < 1) {
- return "";
- }
- $fileName = explode("-", $fileId);
- $dirName = $fileName[0];
- $fileId = $fileName[1];
- $path = $this->getPath($dirName, $fileId, false);
- return file_get_contents($path);
- }
复制代码例: 123456-1234567.jpg会被分割为 - $arr = array("123456", "1234567.jpg");
复制代码如果是123456-1234567.jpg-.php则会被分割为 - $arr = array("123456", "1234567.jpg","-.php");
复制代码因为readFile方法直接将切割后数组的第一个值作为目录,第二个值作为文件名,忽略了第三个值,并且直接传入了getPath方法拼接为绝对路径。所以这里可以直接读取到上传点A中上传的正常文件的内容。 - public function getPath($dateDir, $fileId, $isCreateFolder = true)
- {
- $fileId = str_replace("../", "", $fileId);
- $dateDir = str_replace("../", "", $dateDir);
- $dirName = WEB_DIR . "/../{$this->imgDir}/$dateDir";
- if (!is_dir($dirName) && $isCreateFolder) {
- mkdir($dirName, 0755, true);
- }
- return $dirName . "/" . $fileId;
- }
复制代码回到moveImage方法,判断可控参数的最后一个字符串是否为/符号,这里不是,故跳过。函数最后直接将readFile读取到的合法图片文件的内容写入到了123456-1234567.jpg-.php文件中。 - if (!is_dir($newDir)) {
- mkdir($newDir, 0755, true);
- }
- $lastStr = substr($newDir, -1);
- $path = $newDir . "/" . $fileId;
- if ($lastStr == "/") {
- $path = $newDir . $fileId;
- }
- file_put_contents($path, $fileContent);
- return $path;
复制代码利用: 通过上传点A上传图片,再通过B控制器将上传的合法文件复制到任意后缀的文件中。 (2) SQL注入注入点: $inSql变量可控,直接拼接到SQL语句中进行执行。 使用MySQLMonitor,下断点,再使用Burp发包即可捕获执行的完整SQL语句,便于挖掘SQL注入漏洞时进行调试。
|