summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ychat/Dockerfile6
-rw-r--r--ychat/src/chat/chat.cpp9
2 files changed, 13 insertions, 2 deletions
diff --git a/ychat/Dockerfile b/ychat/Dockerfile
index 1804d54..43e9d7f 100644
--- a/ychat/Dockerfile
+++ b/ychat/Dockerfile
@@ -54,6 +54,12 @@ COPY --from=builder /build/ychat/bin/ychat /app/bin/ychat
# Read-only resources
COPY --from=builder /build/ychat/html/ /app/html/
COPY --from=builder /build/ychat/mods/ /app/mods/
+# Defense-in-depth: the /exec command module does popen() on an attacker-
+# controlled shell string (host RCE for any operator). It is gated to
+# operators by permissions and the defaultop grant now requires a
+# registered user, but we also physically omit it from the revival image
+# so the RCE primitive is absent, not merely unreachable.
+RUN rm -f /app/mods/commands/yc_exec.so
COPY docker-entrypoint.sh /app/docker-entrypoint.sh
COPY etc/ychat.conf /app/etc/ychat.conf
diff --git a/ychat/src/chat/chat.cpp b/ychat/src/chat/chat.cpp
index cea9ab6..8910a80 100644
--- a/ychat/src/chat/chat.cpp
+++ b/ychat/src/chat/chat.cpp
@@ -257,8 +257,13 @@ chat::login( map<string,string> &map_params )
p_user->set_status( tool::string2int(map_params["status"]));
}
- // Prove if user is the default operator.
- if ( tool::to_lower(wrap::CONF->get_elem("chat.defaultop")) == tool::to_lower(s_user) )
+ // Grant operator status only to an AUTHENTICATED (registered) user whose
+ // nick matches chat.defaultop. In guest/no-DB mode (Mode A) is_reg is always
+ // false, so nobody can claim operator by simply logging in as the defaultop
+ // nick — this closes unauth -> op -> /exec (popen RCE) and /set (re-enable)
+ // escalation. Registered ops still work once database auth (Mode B) is on.
+ if ( p_user->get_is_reg() &&
+ tool::to_lower(wrap::CONF->get_elem("chat.defaultop")) == tool::to_lower(s_user) )
{
wrap::system_message(CHATDOP);
p_user->set_status(0);