XD安全交流

 找回密码
 注册交流

QQ登录

只需一步,快速开始

快捷发帖
查看: 20|回复: 0

代码审计-任意文件写入RCE+SQL注入挖掘技巧

[复制链接]
  • TA的每日心情

    2025-4-18 08:21
  • 签到天数: 1 天

    [LV.1]初来乍到

    9

    主题

    12

    帖子

    136

    积分

    认证安全响应员

    Rank: 2

    积分
    136

    XD社区嘉宾QQ群管理勋章赞助会员

    发表于 2025-4-18 08:28:01 | 显示全部楼层 |阅读模式
    XD论坛免责声明
    本论坛为网络技术研究交流平台,所有内容仅限于学术探讨、防御技术提升及合法授权测试场景。禁止任何主体将本站技术信息用于非法渗透、网络攻击、数据窃取等违法犯罪活动。用户发布内容须符合《网络安全法》《数据安全法》等中国法律法规及所在司法辖区法律要求,禁止传播恶意代码、漏洞利用工具等非法资源。所有技术文档需明确标注适用场景(如:竞赛环境、授权渗透测试),提供实验环境搭建指南及风险控制方案。用户需承诺不利用本平台技术进行未授权测试,因技术滥用导致的法律后果由行为人独立承担。免责条款‌:用户因技术误用导致的系统瘫痪、数据丢失等后果,本平台不承担任何连带责任,技术交流产生的观点分歧及学术争议,不代表本论坛的官方立场,论坛不担保技术文档的绝对安全性,使用者应自行搭建隔离测试环境。
    本帖最后由 小七 于 2025-4-18 10:35 编辑

    代码审计-任意文件写入RCE+SQL注入挖掘技巧




    前言
    最近在代码审计的时候,发现了一个任意文件写入漏洞,利用方式有点意思,不是直接覆盖文件的常规操作,而是通过一些逻辑上的小技巧实现写入。虽然漏洞本身不算复杂,但利用过程还挺好玩的,所以想分享一下我的思路。
    另外,还挖到一个SQL注入,不过这个注入点比较普通,就简单提一下,重点是介绍一下代码审计挖掘SQL注入时的辅助工具MySQLMonitor(Mysql监控)
    (1) 任意文件写入
    这套源码不是基于ThinkPHP、Laravel编写的。对于这种非主流框架编写的源代码,我一般会先在本地部署好环境之后再开始审计(部署好之后,直接打开BP抓包,照着功能点一顿乱点,观察请求的URL,了解路由规则,方便快速定位控制器以及对应的方法)。
    打开编辑器全局搜索upload关键词
    上传点A(核心方法):
    1. <span style="background-color: silver;">private function saveImage($file, $type)
    2.     {
    3.         $tmpName  = $file['tmp_name'];
    4.         $tmpFile = file_get_contents($tmpName);

    5.         if($type == FileType::FileDocument) {
    6.             $name = $file['name'];
    7.             $ext = array_pop(explode(".", $name));
    8.             $fileName = $this->ctx->File->saveDocument($tmpFile, $ext);
    9.         } else{
    10.             $fileName = $this->ctx->File->saveFile($tmpFile);
    11.         }
    12.         return $fileName;
    13. }</span>
    复制代码
    1. public function saveFile($content, $dateDir = false)
    2.     {
    3.         if (!$dateDir) {
    4.             $dateDir = date("Ymd");
    5.         }

    6.         $fileName = sha1(uniqid());

    7.         $path = $this->getPath($dateDir, $fileName);
    8.         file_put_contents($path, $content);

    9.         $mime = mime_content_type($path);

    10.         if (!in_array($mime, $this->defaultType)) {
    11.             thrownewException("file type error");
    12.         }

    13.         $ext = isset($this->mimeMapping[$mime]) ? $this->mimeMapping[$mime] : "";
    14.         if (false == empty($ext)) {
    15.             $fileName = $fileName . "." . $ext;
    16.             rename($path, $this->getPath($dateDir, $fileName));
    17.         }

    18.         return $dateDir . "-" . $fileName;
    19. }
    复制代码
    核心逻辑是通过mime_content_type函数获取上传文件(临时文件)的mime类型,并判断是否存在于defaultType(白名单)数组中,如果不存在则直接抛出异常。
    存在则通过文件的mime类型在mimeMapping数组中取出该mime对应的值(对应的文件类型),并对最终保存的文件名进行拼接。
    因为文件后缀名是固定的,所以这个上传点行不通,限制死了。
    1. private $defaultType = [
    2.         "image/jpeg",
    3.         "image/jpg",
    4.         "image/png",
    5.         "image/gif",
    6.         "audio/mp4",
    7.         "audio/x-m4a",
    8.         "video/mp4",
    9.     ];
    复制代码
    1. private $mimeMapping = array(
    2.         "image/jpeg" => "jpeg",
    3.         "image/jpg" => "jpg",
    4.         "image/png" => "png",
    5.         "image/gif" => "gif",
    6.         'application/zip' => "zip",
    7.         'application/msword' => "word",
    8.         'application/xml' => "xml",
    9.         'application/vnd.ms-powerpoint' => "ppt"
    10.     );
    复制代码
    由于通篇只有这一个上传点,所以开始全局搜索危险函数,首先搜索的file_put_contents,文件写入函数。
    定位到一个高危的功能点,moveImage方法,因为这个方法在工具类中,所以还需要向上查找调用处(编辑器光标悬停在方法名处,点击右键,Alt+F7可查找方法在项目中的哪里被引用/调用,如果没有输出结果的话还是老方法全局搜索)。
    1. public function moveImage($fileId, $newDir)
    2.     {

    3.         $fileContent = $this->readFile($fileId);


    4.         if (!is_dir($newDir)) {
    5.             mkdir($newDir, 0755, true);
    6.         }

    7.         $lastStr = substr($newDir, -1);
    8.         $path = $newDir . "/" . $fileId;
    9.         if ($lastStr == "/") {
    10.             $path = $newDir . $fileId;
    11.         }

    12.         file_put_contents($path, $fileContent);

    13.         return $path;
    14. }
    复制代码
    在B控制器中找到一处调用,且$fileId参数可控。
    1. $fileId = $_POST["value"]
    2. $imageDir = LIB_DIR . "../public/web/image/";
    3. $this->ctx->File_Manager->moveImage($fileId, $imageDir);
    复制代码
    moveImage的核心逻辑是将$fileId作为文件路径读取文件内容,这里会对字符串(fileId)文件路径处理,按"-"符号将字符串为分割为数组。
    1. public function readFile($fileId)
    2.     {
    3.         if (strlen($fileId) < 1) {
    4.             return "";
    5.         }

    6.         $fileName = explode("-", $fileId);
    7.         $dirName = $fileName[0];
    8.         $fileId = $fileName[1];

    9.         $path = $this->getPath($dirName, $fileId, false);
    10.         return file_get_contents($path);
    11. }
    复制代码
    例:
    123456-1234567.jpg会被分割为
    1. $arr = array("123456", "1234567.jpg");
    复制代码
    如果是123456-1234567.jpg-.php则会被分割为
    1. $arr = array("123456", "1234567.jpg","-.php");
    复制代码
    因为readFile方法直接将切割后数组的第一个值作为目录,第二个值作为文件名,忽略了第三个值,并且直接传入了getPath方法拼接为绝对路径。所以这里可以直接读取到上传点A中上传的正常文件的内容。
    1. public function getPath($dateDir, $fileId, $isCreateFolder = true)
    2.     {
    3.         $fileId = str_replace("../", "", $fileId);
    4.         $dateDir = str_replace("../", "", $dateDir);
    5.         $dirName = WEB_DIR . "/../{$this->imgDir}/$dateDir";
    6.         if (!is_dir($dirName) && $isCreateFolder) {
    7.             mkdir($dirName, 0755, true);
    8.         }
    9.         return $dirName . "/" . $fileId;
    10. }
    复制代码
    回到moveImage方法,判断可控参数的最后一个字符串是否为/符号,这里不是,故跳过。函数最后直接将readFile读取到的合法图片文件的内容写入到了123456-1234567.jpg-.php文件中。
    1. if (!is_dir($newDir)) {
    2.             mkdir($newDir, 0755, true);
    3.         }

    4.         $lastStr = substr($newDir, -1);
    5.         $path = $newDir . "/" . $fileId;
    6.         if ($lastStr == "/") {
    7.             $path = $newDir . $fileId;
    8.         }

    9.         file_put_contents($path, $fileContent);

    10.         return $path;
    复制代码
    利用:
        通过上传点A上传图片,再通过B控制器将上传的合法文件复制到任意后缀的文件中。
    (2) SQL注入
    注入点:
        $inSql变量可控,直接拼接到SQL语句中进行执行。
    使用MySQLMonitor,下断点,再使用Burp发包即可捕获执行的完整SQL语句,便于挖掘SQL注入漏洞时进行调试。









    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有账号?注册交流

    x
    XD论坛免责声明
    本论坛为网络技术研究交流平台,所有内容仅限于学术探讨、防御技术提升及合法授权测试场景。禁止任何主体将本站技术信息用于非法渗透、网络攻击、数据窃取等违法犯罪活动。用户发布内容须符合《网络安全法》《数据安全法》等中国法律法规及所在司法辖区法律要求,禁止传播恶意代码、漏洞利用工具等非法资源。所有技术文档需明确标注适用场景(如:竞赛环境、授权渗透测试),提供实验环境搭建指南及风险控制方案。用户需承诺不利用本平台技术进行未授权测试,因技术滥用导致的法律后果由行为人独立承担。免责条款‌:用户因技术误用导致的系统瘫痪、数据丢失等后果,本平台不承担任何连带责任,技术交流产生的观点分歧及学术争议,不代表本论坛的官方立场,论坛不担保技术文档的绝对安全性,使用者应自行搭建隔离测试环境。
    回复

    使用道具 举报

    懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
    您需要登录后才可以回帖 登录 | 注册交流

    本版积分规则

    QQ|Archiver|手机版|小黑屋|小段安全交流论坛 ( 冀ICP备2024093090号-1 )

    GMT+8, 2025-4-30 19:11 , Processed in 0.117864 second(s), 32 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2020, Tencent Cloud.

    快速回复 返回顶部 返回列表