
파일 공유에 대한 자격 증명 및 민감 데이터 노출 트리아지
파일 공유에서의 자격 증명 및 민감 데이터 노출 트리아지.
공개 공유(open share)가 발견되면, 질문은 절대 "이 저장소에 유출된 키가 있나요"가 아니다. 진짜 질문은 "방금 무엇이 노출되었고, 오늘 업무 종료 전에 무엇을 교체해야 하나?" 이다. sift는 바로 그 질문을 위해 만들어졌다: 높은 재현율, 빠른 검토 대기열, 그리고 눈으로 발견한 모든 것이 나머지 200개 복사본을 찾아내는 규칙이 되는 피드백 루프.

Python 3.11+, 표준 라이브러리만 사용. pip 설치, 인터넷, 빌드 단계 없음. 잠금된 IR 노트북에서 실행되며, 바로 그런 환경이 필요한 곳입니다.```bash sift survey \fileserver\openshare # how big is this thing sift copy \fileserver\openshare C:\IR\case-4471 # take a throttled copy sift scan C:\IR\case-4471 # scan it, opens the triage UI
제자리 스캔도 작동합니다. 먼저 가짜 자격 증명 공유에서 시도해 보세요:```bash
sift demo C:\temp\demoshare
sift.cmd는 어떤 디렉터리에서도 작동하는 런처입니다. 전체 경로 대신 sift를
입력하려면 PATH에 C:\Dev\sift를 추가하세요:```bash
setx PATH "%PATH%;C:\Dev\sift"
Python이 전혀 없는 머신의 경우, `python build_portable.py`는 `dist/sift-secrets-<version>-portable-win64.zip`을 빌드합니다. 공식 python.org 임베디드 런타임과 이 소스 트리를 포함하며, 번들된 `sift.cmd`를 통해 압축 해제 후 실행됩니다. 약 11MB, 설치 불필요, 관리자 권한 불필요, 그리고 그 안에 컴파일되거나 다시 패킹된 것은 없습니다 — 잠긴 IR 노트북에서 frozen .exe보다 이것이 더 나은 이유는 `build_portable.py`의 docstring을 참조하세요.
---
## gitleaks나 trufflehog만 쓰지 않는 이유
둘 다 다른 문제를 해결하는 좋은 도구입니다.
이들은 CI를 위해 만들어진 **정밀** 도구로, 오탐(false positive)이 개발자의 오후를 낭비하게 만들 수 있기 때문에 주로 알려진 벤더 API 키 모양을 한 것들에만 반응합니다. trufflehog는 한 걸음 더 나아가 벤더 API를 호출해 *검증*할 수 있는 시크릿을 선호하는데, 이는 정규식으로는 재현할 수 없는 정말 훌륭한 신호입니다.
공유 폴더 트리아지는 경제성이 반전됩니다. 사람이 이미 모든 히트를 읽고 있으므로 오탐은 3초를 소비할 뿐입니다. 실제로 비용이 드는 것은 **놓침**입니다.
벤더 API 키도 공유 폴더에서 분명히 유출됩니다. 웹 루트 백업, 배포 스크립트, 누군가의 프로젝트 폴더를 부서 드라이브에 복사한 경우, 그 안에 실제 Stripe 키가 들어 있는 `.env` 파일이 있을 수 있습니다. 그런 것들은 잡아낼 가치가 있으며 sift도 잡아냅니다. 하지만 그것들은 gitleaks와 trufflehog가 이미 잘 처리하는 부분이기도 합니다. 그 틈새는 그 외의 모든 것이며, 파일 공유에서는 대부분이 그것입니다:
| CI 스캐너가 놓치는 것 | 그것을 지나치는 이유 |
|---|---|
| `web.config` with a SQL connection string | 알려진 키 형식이 아니며, 검증할 벤더도 없음 |
| `Map-Drives.ps1` with `net use ... /user:` | 뒤에 단어가 붙은 셸 명령일 뿐임 |
| `New Hire Setup Guide.docx` | Office 파일, 바이너리로 읽혀서 완전히 건너뜀 |
| `unattend.xml`, GPP `Groups.xml` | 아무도 탐지기를 작성하지 않은 Windows 배포 아티팩트 |
| `confCons.xml`, `.rdg`, WinSCP.ini | 되돌릴 수 있는 저장된 비밀번호지만, "시크릿 형식"은 아님 |
| `passwords.xlsx` | ZIP 파일입니다. 일반 텍스트 스캐너는 바이너리로 보고 지나칩니다. |
| `.kdbx`, `.pfx`, `id_rsa` | 불투명한 바이트 - *파일 이름*이 발견 사실입니다. |
| A `.bak` with a connection string inside | 바이너리라서 결코 읽히지 않음 |
sift는 이러한 것들을 다루고, 자체 벤더 키 규칙을 포함하며, **다른 도구의 규칙 팩과 발견 결과를 가져옵니다** - gitleaks TOML, Kingfisher/Titus YAML, trufflehog JSON - 따라서 도구 중 하나를 선택할 필요가 없습니다.
가장 유사한 선행 기술은 [Snaffler](https://github.com/SnaffCon/Snaffler)입니다. 파일 이름 및 분류 부분에서 탁월하며 파일 이름 규칙의 직접적인 영감을 주었습니다. 하지만 이 도구에는 없는 것 - 400개의 히트가 발생하면 실제 병목으로 밝혀지는 것 - 은 검토 루프입니다.
---
## 루프
1. **공유 폴더를 스캔합니다.**
2. **대기열을 처리합니다.** 각 발견 항목은 일치 부분이 강조된 주변 줄을 표시합니다.
화살표 키로 컨텍스트를 넓히고, 한 번 클릭하면 전체 파일이 해당 줄의 VS Code
또는 메모장에서 열립니다.
3. **놓친 항목을 찾습니다.** 분명히 발견하게 될 것입니다. 미리보기에서 강조 표시하고 `r`을 누르세요.
4. **sift가 패턴을 제안하고** 이미 읽은 모든 항목에서 각 패턴이 몇 번 일치하는지 실시간으로 알려줍니다.
5. **저장합니다.** 캐시된 재스캔은 약 1초가 걸리며, 기존 트리아지 결정은 그대로 둔 채 새 히트가 대기열에 나타납니다.
5단계가 나머지를 할 가치가 있게 만드는 부분입니다. 발견 항목은 `(path, rule, line, value-hash)`를 키로 하므로 재스캔 시 동일한 행이 다시 삽입되고 상태, 메모, 담당자가 함께 유지됩니다. 이것이 없다면 매 반복마다 같은 300개의 히트를 다시 검토해야 하고 세 번째쯤 포기하게 될 것입니다.
---
## 명령어```bash
# size it up first: file count, total bytes, biggest folders, transfer estimates
sift survey \\fileserver\share
# take a rate-limited local copy (resumable; gentle = 5 MB/s by default)
sift copy \\fileserver\share C:\IR\case-4471 --speed gentle
# scan a share and open the triage UI
sift scan \\fileserver\share
# maximum recall: more noise, but a human is reading anyway
sift scan D:\dfs\dept --tier 3
# re-open the UI over the most recent scan
sift ui
# build a share of fabricated credentials, scan it, open the UI
sift demo C:\temp\demoshare
# inherit other tools' vendor-key rules, then use them in the live rescan loop
sift import-rules gitleaks.toml # gitleaks TOML
sift import-rules path/to/kingfisher/data/rules # a directory of YAML
# pull in what the other scanners found, into the same queue
trufflehog filesystem \\fileserver\share --json > th.json
sift import-findings th.json
# hand off to the incident record (redacted unless you say otherwise)
sift export --fmt pdf --status confirmed --out ir-4471.pdf
sift export --fmt csv --out ir-4471.csv
sift export --fmt pdf --no-redact # plaintext; handle as evidence
sift rules # what is loaded
sift selftest # detection tests against a synthetic share
# ask vendors whether confirmed findings are still live. NETWORK. Opt-in.
sift validate --status confirmed
sift가 나타나는 곳마다 C:\Dev\sift 디렉터리에서 python -m sift를 대신 사용할 수 있습니다.
발견 항목은 %LOCALAPPDATA%\sift\ 아래의 대상별 폴더에 저장되며, 작업 디렉터리에는 절대 저장되지 않습니다. 데이터베이스에는 평문 자격 증명이 들어 있으므로, 홈 디렉터리에서 도구를 실행한다고 해서 그곳에 조용히 떨어뜨려서는 안 됩니다. 각 공유는 고유한 저장소를 가지므로 두 건의 업무가 트리아지 큐를 공유하지 않습니다. 인자 없이 sift ui를 실행하면 가장 최근 항목을 다시 열고, --data DIR로 재정의할 수 있습니다.
사용자 정의 규칙은 전역이며 %LOCALAPPDATA%\sift\user-rules.json에 있습니다. 따라서 한 업무 중에 작성한 패턴은 다음에 살펴볼 공유에서도 도움이 됩니다.
둘 다 UI 헤더의 경로 상자 옆과 CLI에 있습니다.
크기 확인(Check size) 은 stat 전용 탐색입니다. 아무것도 열지 않으므로 SMB를 통해서도 저렴하며, 파일 수, 총 바이트, 가장 큰 폴더, 확장자별 분포, sift가 실제로 읽을 양, 각 속도로 복사하는 데 걸리는 시간을 알려줍니다. 알 수 없는 DFS 루트에 스캐너를 돌려놓고 기다리는 것은 오후를 날려버리는 방법입니다.
로컬 복사(Copy locally) 는 먼저 공유를 로컬 폴더로 가져옵니다. 이렇게 하는 이유는:
전송은 속도 제한이 있으며 기본값은 5MB/s입니다. 오후 2시에 운영 중인 파일 서버로의 링크를 포화시키면 조사가 두 번째 인시던트가 됩니다. 경로가 유휴 상태임을 알면 속도를 올리십시오.
| 속도 | 비율 |
|---|---|
| gentle(기본값) | 5 MB/s |
| normal | 25 MB/s |
| fast | 100 MB/s |
| unlimited | 링크가 주는 대로 |
전송은 이어서 진행됩니다. 대상 파일의 크기와 mtime이 같으면 건너뛰므로, 80%에서 중단된 수집은 중단된 지점부터 이어집니다. 잠겨 있거나 거부된 파일은 기록되고 건너뛰며 실행이 중단되지 않습니다.
UI 헤더 또는 sift export --fmt에서 PDF, CSV, MD, JSON을 생성합니다.
PDF는 인시던트 기록에 넘길 문서입니다. 대상이 표시된 표지 페이지, 심각도 및 트리아지 상태별 합계, 가장 빈번한 규칙, 파일 간에 재사용된 값, 그리고 심각도별로 그룹화된 발견 항목이 포함됩니다. PDF 라이브러리 없이 직접 생성되므로 pip를 한 번도 실행한 적 없는 머신에서도 작동합니다.
내보내기는 기본적으로 수정(redact)됩니다. 값은 마스킹되고 모든 페이지에 배너 표시가 붙으며, 엔드포인트는 명시적인 redact=0에서만 마스킹을 해제합니다. 잘리거나 잘못된 요청으로는 유출이 불가능합니다. UI에서 수정을 끄려면 경고 대화상자를 확인해야 하며, 결과 파일의 모든 페이지에는 UNREDACTED - CONTAINS PLAINTEXT CREDENTIALS 배너가 표시됩니다.
발견 항목 데이터베이스에는 실제 값이 여전히 남아 있습니다. 분석가는 어떤 비밀번호가 유출되었는지 알아야 무엇을 교체할지 알 수 있기 때문입니다. 경계는 도구 밖으로 나가는 것입니다.
모든 것이 클릭 가능합니다. 결과 목록 상단의 정렬 컨트롤은 심각도, 파일 경로, 규칙, 트리아지 상태 또는 최근 순으로 정렬하며, 순서를 뒤집는 버튼이 있습니다. 사이드바 패싯은 심각도, 범주, 규칙 및 재사용된 값으로 필터링합니다. 트리아지, 컨텍스트 확장, 파일 열기, 규칙 생성은 모두 버튼입니다.
아래의 키보드 단축키는 긴 큐를 위한 가속기이며, 유일한 조작 방법은 아닙니다.
스냅샷 버튼은 강조 표시된 스니펫을 PNG로 렌더링해 인시던트 티켓에 붙여넣을 수 있게 하며, 스니펫 복사는 마크다운으로 동일한 작업을 수행합니다.
UI는 브라우저 창에서 평문 자격 증명을 실시간으로 렌더링하므로:
127.0.0.1에만 바인딩되며, --unsafe-bind 없이는 다른 주소를 거부합니다;SameSite=Strict 쿠키에 보관됩니다;Host 헤더를 검증하므로 적대적인 페이지가 DNS 리바인딩할 수 없습니다;/api/context는 임의 파일 읽기가 아니고 /api/open은 임의 프로세스 실행이 아닙니다.발견 항목 데이터베이스는 설계상 평문 자격 증명을 포함합니다. IR 분석가는 어떤 비밀번호가 유출되었는지 알아야 무엇을 교체할지 알 수 있습니다. %LOCALAPPDATA%\sift\<target>\findings.db를 증거로 취급하십시오. 공유 자체와 동일하게 다루고, 업무가 종료되면 삭제하십시오. 인시던트 경계를 벗어날 경우 --redact를 사용하십시오.
그리고 당연한 것이지만, sift는 접근 권한이 부여된 시스템에 대해서만 실행하십시오.
sift/rules_builtin.py - 콘텐츠 규칙. sift/rules_filename.py - 파일 이름 규칙. 둘 다 raw-string 패턴을 가진 일반 Python이므로 읽기 쉽고 diff도 가능합니다. 사용자 규칙은 .sift/user-rules.json의 JSON에 있습니다.
세 가지 티어로 정밀도와 재현율을 맞바꿀 수 있습니다:
cpassword, NTLM 덤프, LDAP 바인드 비밀번호. 형태만으로 증명됩니다.규칙은 min_digits, min_lowercase, min_uppercase, min_special을 추가로 가질 수 있어, 시끄러운 규칙 하나를 나머지에 영향 없이 조일 수 있습니다. 그리고 examples - 여전히 일치해야 하는 문자열도 포함합니다.
examples는 유용한 절반입니다. sift selftest는 작성된 문자열에 대해 모든 규칙을 전체 경로(일치, 추출, 억제 필터)로 실행합니다. 마지막 단계가 중요합니다. 실제로 발생하는 회귀는 일치를 멈추는 패턴이 아니라, 다른 곳에서 좋은 이유로 조여진 노이즈 필터가 실제 발견 항목을 조용히 삼키는 것입니다.
이 기능은 즉시 효과를 발휘합니다. 기존 규칙에 examples를 추가하자 실제 공백이 드러났습니다. 밑줄은 단어 문자이므로, 일반 할당 규칙의 선행 \b가 DB_PASSWORD, MYSQL_PASSWORD, REDIS_PASSWORD 내부에서 일치하지 않았습니다. 세상에서 가장 흔한 자격 증명 변수 이름 세 가지가 조용히 놓친 것입니다. 예제는 분명히 올바른 것처럼 보였지만 일치하지 않았고, 이것이 바로 예제의 목적입니다.
UI에서 작성한 규칙은 이 기능을 자동으로 얻습니다. 선택한 줄이 규칙의 예제로 저장되므로, 6개월 후 편집하는 규칙은 그 규칙을 작성하게 만든 대상을 더 이상 일치하지 않을 때 알려줍니다.
sift import-rules는 gitleaks .toml, Kingfisher/Titus 스타일 .yml, 또는 해당 파일들의 디렉터리를 사용합니다. Kingfisher의 팩 기준 1,082개 규칙 중 1,073개가 가져와지며, 엔트로피 하한, 숫자 및 대소문자 요구 사항, 예제도 함께 가져옵니다. YAML은 sift/yamlmini.py로 읽습니다. 이 팩들이 사용하는 부분집합용 리더이며, 의존성이 없고 앵커와 태그에 대해 이해하는 척하지 않고 예외를 발생시킵니다.
가져오는 과정에서 의도적으로 두 가지를 버립니다:
validation: 블록 - 규칙별 URL을 지정합니다. sift는 validate.py에 하드코딩된 호스트에만 접속합니다. 엔드포인트를 지정할 수 있는 규칙 팩은 발견 항목이 전송될 위치를 정하는 셈이며, 규칙 파일은 데이터이지 결정이 아닙니다.[[:alnum:]]이 POSIX 클래스입니다. Python은 이를 리터럴 문자 집합으로 읽고 컴파일은 성공하지만 잘못된 대상을 일치시킵니다. 번역은 각 규칙의 예제와 대조하여 검사되므로, 컴파일은 되지만 의미가 바뀐 패턴은 조용히 발화되지 않고 거부됩니다. Kingfisher의 규칙 12개가 이 검사를 통과하지 못해 가져오지 않습니다.sift 자체의 억제가 특정 예제를 버릴지는 규칙을 탈락시키지 않습니다. 해당 팩은 의도적으로 가짜 샘플(keyXXXXXXXX, ...EXAMPLE)을 제공하므로, 플레이스홀더 필터는 예제에 대해서는 옳지만 패턴에 대해서는 아무것도 말하지 않습니다. 이 구분이 생기기 전에 그 엄격함이 121개의 작동하는 규칙을 버렸습니다.
일반 비밀번호 규칙은 노이즈 때문에 버려지므로, 노이즈 필터는 패턴만큼 세심하게 조정됩니다. 실제 6,000개 파일 트리를 기준으로 측정했을 때, 아래의 억제 규칙은 테스트 코퍼스의 재현율 손실 없이 발견 항목을 1,088건에서 257건으로 줄였습니다:
password: process.env.DB_PASS는 값이 아니라 변수입니다. 이 한 가지 억제만으로 소스 트리에서 대부분의 일반 규칙 노이즈를 제거합니다.def login(user: str, password: str)은 시그니처입니다..wrangler, .next, site-packages, node_modules, …) 및 축소된 번들, 소스 맵은 건너뜁니다. 기계 생성 텍스트는 기계 생성 오탐만 만듭니다.느슨한 꼬리를 가진 https://user:pass@host 콘텐츠 규칙은 의도적으로 없습니다. 당연해 보이는 버전은 한 개발 트리에서 476번 발화했는데, 축소된 JSON에는 공백이 없고 패턴이 하나의 URL에서 따옴표와 쉼표를 넘어 수백 문자 뒤의 관련 없는 @를 찾을 때까지 달렸기 때문입니다.
python tests/run_all.py
12개 스위트: 합성 공유에 대한 탐지("CI 스캐너가 이미 잡아내는 것"과
"이 도구가 목표로 하는 갭", 그리고 "조용히 유지되어야 하는 미끼"로
나뉨), 명령줄, 규칙 제안 순위, 조사 및 조절된 수집(벽시계 대비
속도 제한 측정 포함), PDF 작성기(읽는 사람이 읽는 방식으로 다시
파싱하여 레드랙션이 페이지 콘텐츠에 도달했는지 증명),
gitleaks/trufflehog 임포터, 모든 보안 가드를 포함한 HTTP API,
UI의 정적 분석, 그리고 대용량 파일용 블록 리더(마지막 블록의
비밀도 전체 파일에서 실제 줄 번호를
보고하는지 확인).
테스트해 볼 공유를 얻으려면:```bash
sift demo C:\temp\demoshare
해당 말뭉치의 모든 자격 증명은 가짜입니다.
두 가지 단계가 있으며, 위험도가 매우 다르기 때문입니다.
체크섬은 무료이며 항상 켜져 있습니다. ghp_, npm_, Atlassian ATATT,
Bitbucket ATCTT 및 GitLab의 라우팅 가능한 glpat- 토큰은 모두 자신의 본문에 CRC32를 포함합니다.
이를 다시 계산하면 인터넷이 필요했던 일을 오프라인으로 해결할 수 있습니다:
이것이 실제 토큰입니까, 아니면 누군가 README에 붙여넣은 예시입니까? 결과는
대기열에서 checksum ok 또는 malformed로 태그됩니다. 체크섬은 형태를 증명하지,
수명을 증명하지 않습니다 - 형식이 올바른 토큰이 1년 전에 폐기되었을 수도 있습니다.
실시간 검증은 실행하기 전까지 꺼져 있습니다. sift validate는 공급업체에게
자격 증명이 여전히 작동하는지 묻습니다. 이것은 scan의 플래그가 아니라 별도의 명령어이며,
먼저 접촉할 모든 엔드포인트를 이름으로 나열하는 프롬프트에 validate를 입력하게 합니다.
안전 규칙:
sift/validate.py에 하드코딩되어 있습니다. 어떤 규칙도 - 기본 제공,
사용자 작성, 다른 사람의 팩에서 가져온 규칙 - URL을 제공할 수 없습니다.
이것이 없으면 규칙 팩을 가져오는 것만으로도 공유 폴더의 모든 자격 증명을
팩 작성자가 선택한 주소로 보낼 수 있습니다.--redact 저장소에 대해 실행을 거부합니다: 데이터베이스가 인시던트 경계를 벗어나
값이 마스킹된 경우, 평문을 전송하는 것은
정확히 방지하려는 행위입니다.validation-log.json에 기록됩니다.
검증 시도는 그 순간 자격 증명 소유자의 감사 로그에 귀하의 주소로 기록됩니다. 그것은 때로 정확히 원하는 것이고 때로는 지켜보는 적에게 눈치를 줍니다. 실행하기 전에 결정하세요; 그래서 물어보는 것입니다.
현재 GitHub, npm, Slack, Stripe를 지원합니다. trufflehog는 훨씬 더 많은 것을 검증합니다 -
그것을 실행하고 import-findings를 사용하면 둘 다 얻을 수 있습니다.
import-findings를 사용하세요. 검증된 항목이 위로 정렬됩니다..kdbx 또는 .pfx는
이름으로 보고되며, sift는 파일을 열려고 시도하지 않습니다.name=value로 재결합됩니다. 그렇지 않으면 비밀 단어와 그 값이 별도의
열에 있어 어떤 규칙도 그 쌍을 볼 수 없습니다..7z, .rar, 중첩 아카이브는 플래그만 표시되고 추출되지 않습니다. ZIP
기반 형식과 .eml 메시지만 내부를 읽습니다.--max-size를 초과하는 파일은 블록 단위로 읽히고 캐시되지 않습니다. 스캔됩니다
(4GB .bak 파일의 연결 문자열이 실제 줄 번호와 함께 발견됩니다). 하지만
텍스트 캐시는 크기에 맞는 만큼만 보관하므로 1초 캐시 재스캔은
이들을 다루지 못합니다 - 새 규칙은 다음 전체 스캔에서 큰 파일에 도달합니다..doc/.xls/.pdf(2007 이전 및 PDF)는 실제 파서가 아닌 문자열 통과를 거치므로
OOXML 형식보다 검출 재현율이 낮습니다.Apache-2.0. LICENSE를 참조하세요.
탐지 테스트 말뭉치(sift/selftest.py, tests/)에는
형식은 올바르지만 의도적으로 가짜인 자격 증명이 포함되어 있습니다. 해당 경로의 비밀 스캐닝
알림은 .github/secret_scanning.yml을 통해 억제됩니다.
| 플래그 | 효과 |
|---|
--tier 1|2|3 | 재현율 다이얼. 1 = 높은 신호, 2 = 기본값, 3 = 놓치는 것 없음 |
--redact | 저장소 및 내보내기에서 값을 마스킹. DB가 인시던트 경계를 벗어나는 경우 사용 |
--no-ui | 저장소를 채우고 종료. 스크립트 실행용 |
--no-browser | UI 서버는 시작하되 브라우저는 열지 않음(RDP에서 유용) |
--include/--exclude GLOB | 탐색 범위 좁히기 |
--no-archives | docx/xlsx/zip 컨테이너를 열지 않음 |
--no-strings | 바이너리에서 strings 패스를 실행하지 않음 |
--no-large | --max-size 초과 파일을 블록 단위로 읽지 않고 건너뜀 |
--jobs N | 작업자 프로세스 수(기본값: 자동) |
--max-size MB | 이 크기 이상의 파일은 건너뜀(기본 25) |
--port N | UI 포트(기본 8973) |
| 키 | 동작 |
|---|
j / k | 다음 / 이전 발견 항목 |
↑ / ↓ | 컨텍스트 위 / 아래로 확장 |
c / f | 확인 / 오탐(false positive)으로 표시 |
x | 대량 트리아지를 위해 선택 토글 |
o / n | 해당 줄의 VS Code에서 열기 / Notepad에서 파일 열기 |
r | 강조 표시된 텍스트에서 규칙 생성 |
y | 값 복사 |
/ | 검색 |