第52批: 修复\/JSON转义脏值四层防护
- 解析根修: addParsedEntry/parseJsonArrayItem清洗链补\/还原(置于\\还原之前); extractJsonString扫描跳过转义序列并按JSON规则反转义 - 写库兜底: createUser/updatePassword入参b64Clean; applyPulledRow credentials的hash/salt/temp写库前白名单清洗 - 存量迁移: init启动时REPLACE char92清洗users表password_hash/password_salt/temp_password(仿一次性自愈范式,WHERE instr条件幂等) - 读取容错: checkPassword/checkPasswordWithFallback/checkPasswordForChange比对前双侧b64Clean - 根因: PHP json_encode把base64中的/转义成\/,历史解析层漏还原写进库,hash脏值致游戏端+网页端密码恒错
This commit is contained in:
@@ -150,6 +150,20 @@ public class DatabaseManager {
|
||||
} catch (SQLException ignored) {
|
||||
}
|
||||
|
||||
// ★ 第52批:存量脏凭证一次性清洗——PHP json_encode 把 base64 中的
|
||||
// / 转义成 \/,历史解析层漏还原曾把反斜杠写进库(hash 脏 → 密码恒错)。
|
||||
// base64 合法字符永不含反斜杠(char92),删除即还原正确值;WHERE 带
|
||||
// instr 条件,无脏值时为近似 no-op,可每次启动安全重跑。
|
||||
try {
|
||||
st.executeUpdate("UPDATE users SET password_hash=REPLACE(password_hash, char(92), '') "
|
||||
+ "WHERE instr(password_hash, char(92))>0");
|
||||
st.executeUpdate("UPDATE users SET password_salt=REPLACE(password_salt, char(92), '') "
|
||||
+ "WHERE instr(password_salt, char(92))>0");
|
||||
st.executeUpdate("UPDATE users SET temp_password=REPLACE(temp_password, char(92), '') "
|
||||
+ "WHERE instr(temp_password, char(92))>0");
|
||||
} catch (SQLException ignored) {
|
||||
}
|
||||
|
||||
// ===== 背包备份表:安全迁移 =====
|
||||
// 检查旧表是否存在(用 inventory_data 列判断)
|
||||
boolean hasOldTable = false;
|
||||
@@ -748,8 +762,22 @@ public class DatabaseManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ★ 第52批:base64 凭证白名单清洗。
|
||||
* PHP json_encode 默认把 base64 中的 / 转义成 \/,历史解析层漏还原
|
||||
* 会把反斜杠写进库(hash 脏值 → 游戏/网页两端密码恒错)。
|
||||
* base64 合法字符永不含反斜杠,先还原 \/ 再按白名单清洗即得正确值;
|
||||
* 写库与比对两侧同款清洗,保证对称。
|
||||
*/
|
||||
public static String b64Clean(String v) {
|
||||
if (v == null) return null;
|
||||
return v.replace("\\/", "/").replaceAll("[^A-Za-z0-9+/=]", "");
|
||||
}
|
||||
|
||||
public void createUser(String name, String hash,
|
||||
String salt) {
|
||||
hash = b64Clean(hash);
|
||||
salt = b64Clean(salt);
|
||||
try {
|
||||
PreparedStatement ps = db.prepareStatement(
|
||||
"INSERT INTO users "
|
||||
@@ -774,6 +802,8 @@ public class DatabaseManager {
|
||||
* 更新玩家密码凭证(salt + hash),用于Web重新注册时同步
|
||||
*/
|
||||
public void updatePassword(String name, String hash, String salt) {
|
||||
hash = b64Clean(hash);
|
||||
salt = b64Clean(salt);
|
||||
try {
|
||||
PreparedStatement ps = db.prepareStatement(
|
||||
"UPDATE users SET password_hash = ?, password_salt = ? WHERE player_name = ?");
|
||||
@@ -835,7 +865,8 @@ public class DatabaseManager {
|
||||
rs.getString("password_hash");
|
||||
rs.close();
|
||||
ps.close();
|
||||
return hash.equals(mainHash);
|
||||
// ★ 第52批:比对前双侧清洗(存量脏 hash 容错,见 b64Clean)
|
||||
return b64Clean(hash).equals(b64Clean(mainHash));
|
||||
} else {
|
||||
rs.close();
|
||||
ps.close();
|
||||
@@ -874,7 +905,7 @@ public class DatabaseManager {
|
||||
ps.close();
|
||||
if (tempHash != null
|
||||
&& !tempHash.isEmpty()
|
||||
&& mainHash.equals(tempHash)
|
||||
&& b64Clean(mainHash).equals(b64Clean(tempHash))
|
||||
&& System.currentTimeMillis() < expire
|
||||
&& used != 1) {
|
||||
return "temp";
|
||||
@@ -923,7 +954,7 @@ public class DatabaseManager {
|
||||
ps.close();
|
||||
if (tempHash != null
|
||||
&& !tempHash.isEmpty()
|
||||
&& hash.equals(tempHash)
|
||||
&& b64Clean(hash).equals(b64Clean(tempHash))
|
||||
&& used != 1) {
|
||||
return "temp";
|
||||
}
|
||||
|
||||
@@ -4222,7 +4222,10 @@ public class WebManager {
|
||||
}
|
||||
|
||||
// 字符串(去掉引号)
|
||||
// ★ 第52批:\/ 必须先于 \\ 还原——PHP json_encode 默认把 base64 中的
|
||||
// / 转义成 \/,历史解析层漏还原会把反斜杠写进 login.db(hash 脏值 → 密码恒错)。
|
||||
String cleaned = value.replaceAll("^\"|\"$", "")
|
||||
.replace("\\/", "/")
|
||||
.replace("\\\"", "\"")
|
||||
.replace("\\n", "\n")
|
||||
.replace("\\\\", "\\");
|
||||
@@ -4290,7 +4293,9 @@ public class WebManager {
|
||||
return parseJson(item);
|
||||
}
|
||||
if (item.startsWith("\"") && item.endsWith("\"")) {
|
||||
// ★ 第52批:\/ 先于 \\ 还原(PHP json_encode 的 base64 斜杠转义)
|
||||
return item.substring(1, item.length() - 1)
|
||||
.replace("\\/", "/")
|
||||
.replace("\\\"", "\"")
|
||||
.replace("\\n", "\n")
|
||||
.replace("\\\\", "\\");
|
||||
@@ -4827,8 +4832,9 @@ public class WebManager {
|
||||
DatabaseManager dbMgr = plugin.getDb();
|
||||
if (dbMgr == null) return false;
|
||||
String name = strOf(row.get("player_name"));
|
||||
String hash = strOf(row.get("password_hash"));
|
||||
String salt = strOf(row.get("password_salt"));
|
||||
// ★ 第52批:写库前白名单清洗(PHP json_encode 的 \/ 转义残留兜底)
|
||||
String hash = DatabaseManager.b64Clean(strOf(row.get("password_hash")));
|
||||
String salt = DatabaseManager.b64Clean(strOf(row.get("password_salt")));
|
||||
if (name.isEmpty() || hash.isEmpty() || salt.isEmpty()) return false;
|
||||
// [凭证诊断49] 收回写库前抓本地旧值,写库后回读,完整新旧值入日志抓 ping-pong 现行
|
||||
boolean ex49 = dbMgr.userExists(name);
|
||||
@@ -4851,7 +4857,7 @@ public class WebManager {
|
||||
plugin.getLogger().info("[凭证诊断49] 收回写库(无变化) name=" + name
|
||||
+ " hash=" + newH49 + " salt=" + newS49);
|
||||
}
|
||||
String tmpHash = strOf(row.get("temp_password"));
|
||||
String tmpHash = DatabaseManager.b64Clean(strOf(row.get("temp_password")));
|
||||
long tmpExpire = longOf(row.get("temp_pw_expire"));
|
||||
if (!tmpHash.isEmpty() && tmpExpire > 0) {
|
||||
dbMgr.setField(name, "temp_password", tmpHash);
|
||||
@@ -11469,9 +11475,24 @@ public class WebManager {
|
||||
int start = json.indexOf(search);
|
||||
if (start == -1) return null;
|
||||
start += search.length();
|
||||
int end = json.indexOf("\"", start);
|
||||
// ★ 第52批:扫描跳过转义序列(\" \/ \\ 等),避免值内转义引号提前截断;
|
||||
// 取到原文后按 JSON 规则反转义,\/ 先于 \\ 还原(PHP json_encode 的
|
||||
// base64 斜杠转义),否则 password_hash/salt 会带反斜杠直接入库。
|
||||
int i = start;
|
||||
int end = -1;
|
||||
while (i < json.length()) {
|
||||
char c = json.charAt(i);
|
||||
if (c == '\\' && i + 1 < json.length()) { i += 2; continue; }
|
||||
if (c == '"') { end = i; break; }
|
||||
i++;
|
||||
}
|
||||
if (end == -1) return null;
|
||||
return json.substring(start, end);
|
||||
String raw = json.substring(start, end);
|
||||
return raw.replace("\\/", "/")
|
||||
.replace("\\\"", "\"")
|
||||
.replace("\\n", "\n")
|
||||
.replace("\\r", "\r")
|
||||
.replace("\\\\", "\\");
|
||||
}
|
||||
|
||||
// ==================== Web注册请求轮询 ====================
|
||||
|
||||
Reference in New Issue
Block a user