Skip to content
KitploitKITPLOIT
도구익스플로잇블로그
Log in
제출
도구익스플로잇블로그
제출

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

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

피드문의개인정보© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
StealMyBitcoin-PoC — Steal My Bitcoin 챌린지의 공개 릴리스 | Kitploit
도구/GitHubGitHub/killaboi/stealmybitcoin-poc
Memory ForensicsVulnerability AnalysisExploitationWeb Application ExploitationCTFPayload DevelopmentBinary ExploitationLabs & Practice
GitHubkillaboi/stealmybitcoin-poc

StealMyBitcoin-PoC

Steal My Bitcoin 챌린지의 공개 릴리스

저장소 보기
21641361일 전아직 검토되지 않음

인기

모두 보기 →

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

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

sponsored with ❤️ by hextree.io

Steal My Bitcoin CTF 개념 증명

Steal My Bitcoin 챌린지에 대한 라이브 PHP 콜백 하나를, 처음부터 끝까지, 표준 라이브러리만 사용하는 단일 Python 파일로.

Python Dependencies Target Lab

이 README는 stealmybitcoin_poc.py를 설명합니다. 이 파일은 제 Steal My Bitcoin CTF 실행에서 사용된 요청 시퀀스를 단일 파일 Python으로 재현한 것입니다. Python 표준 라이브러리만 사용합니다. 스크립트는 요청을 직접 구성하고, 다음 단계로 넘어가기 전에 각 유출을 검증하며, 새 실행에서의 HTTP 바이트를 기록합니다.

이것은 아마도 불필요한 디버그 잡동사니 없이 더 쉽게 할 수 있을 것이며, 제 Steam 로컬 권한 상승 관련 John Hammond 영상에서 발견한 것처럼, 이 못생긴 PoC를 정리할 수도 있을 것입니다.

[!NOTE] ⚠️ 솔직히 말해서, 제발 이걸 정리하거나 Sol에게 짓눌리지 않은 더 나은 버전을 직접 만드세요. ⚠️

아래의 모든 내용은 라이브 챌린지가 아닌 Docker 랩을 기준으로 작성되었습니다. 이 랩은 nginx 뒤에 있는 로컬 Alpine PHP 8.4.26 CLI 서버로, 오프셋이 보정된 것과 동일한 빌드 및 워커 모델입니다. 1분 만에 구동하면 전체 체인이 여러분의 머신에서 처음부터 끝까지 실행됩니다. 실제로 챌린지를 해결한 라이브 실행은 맨 아래에 역사적 기록으로 남겨 두었습니다.

  stage                 what goes out                        what comes back
  ─────────────────────────────────────────────────────────────────────────────
  0  health      GET /php-version.php                 ->  PHP 8.4.26 confirmed
  1  heap leak   249,042 B producer + 8 x   6,672 B   ->  256 B cookie
                 (OOM poisons a worker)                   └─ Zend chunk base
  2  code leak   251,557 B producer + 8 x  92,388 B   ->   16 B cookie
                 (OOM poisons a worker)                   └─ zval_ptr_dtor
  3  callback    368,812 B POST                       ->  forged HashTable runs
                 (fake HashTable + command)               └─ zif_system(cmd)
  ─────────────────────────────────────────────────────────────────────────────
  20 HTTP requests at most. No looping, no retries.

요약

  1. 작은 멀티파트 업로드 하나로 PHP가 전송 크기보다 훨씬 많은 메모리를 소비하게 만든다.
  2. 세션 업로드 진행 상황 파싱 도중 메모리가 부족해지면, PHP는 재사용될 워커에 오래된 상태를 남긴다.
  3. 후속 요청을 적절히 구성하면 세션 쿠키를 통해 바이트가 유출된다. 나는 이를 힙 주소와 PHP 코드 주소로 읽는다.
  4. 두 주소를 확보하면, 마지막 요청이 정리 과정에서 발견될 위치에 위조된 PHP 구조체를 떨어뜨리고, 정리 과정이 zif_system(PHP의 system 핸들러)을 호출한다.
  5. 해당 명령은 여전히 열려 있는 연결을 통해 새로운 증명 마커와 하나의 print-flag 결과를 돌려보낸다.

왜 주소가 두 개인가? OS는 PHP의 코드와 힙이 위치하는 곳을 독립적으로 무작위화한다. 힙 유출은 스크립트에게 위조된 구조체를 어디에 둘지 알려주고, 코드 유출은 호출하려는 함수가 실제로 어디에 있는지 알려준다. 이전 워커나 이전 실행에서 얻은 주소는 안전한 대체물이 아니며, 시도할 때마다 그 시도는 그저 실패했다.

목차

  • 실습 환경
  • 여기서 시작하기
  • 각 요청이 하는 일
    • 1. 작은 생산자가 메모리 한도에 도달하는 방법
    • 2. 힙 주소를 복구하는 방법
    • 3. PHP 코드 주소를 복구하는 방법
    • 4. 마지막 요청이 system에 도달하는 방법
  • 결과 읽기
  • 실제로 얼마나 자주 동작하는가
  • 문제를 해결한 실제 실행
  • 간단한 용어 설명
  • 감사의 말

실습 환경

이 PoC의 오프셋은 네 개의 워커로 내장 CLI 서버를 실행하는 Alpine PHP 8.4.26에 맞춰 보정되었다. 실습 환경은 루프백에서 nginx 뒤에 정확히 그 구성을 재현한다.

[!IMPORTANT] 버전 문자열보다 빌드가 더 중요하다. Debian php:8.4.26-fpm 스택은 동일한 PHP/8.4.26을 보고하지만 바이너리 레이아웃이 다르므로, 코드 포인터 지문이 절대 일치하지 않고 실행은 코드 유출 단계에서 중단된다. 아래의 Alpine 이미지를 사용하라.

작은 파일 세 개. 빈 디렉터리에 넣어라:

public/php-version.php (헬스 엔드포인트이며, PoC에 필요한 유일한 페이지)

<?php
header('Content-Type: text/plain');
echo 'PHP ', PHP_VERSION, "\n";
echo 'PID: ', getmypid(), "\n";

print-flag (챌린지 바이너리를 대신하는 실행 파일, 실행 가능해야 함)

#!/bin/sh
printf '%s\n' 'LOCAL_DUMMY_WIF_NEVER_USE'

nginx.conf

events {}
http {
    server {
        listen 80;
        server_name _;
        location / {
            proxy_pass http://ctf-cross-php:8000;
            proxy_http_version 1.1;
            proxy_set_header Host $host;
        }
    }
}

그런 다음 실행합니다:

docker network create ctf-cross-net

docker run -d --name ctf-cross-php --network ctf-cross-net \
  --read-only --tmpfs /tmp:rw,size=32m,mode=1777 --memory 1g \
  -v "$PWD/public:/app:ro" \
  -v "$PWD/print-flag:/usr/local/bin/print-flag:ro" \
  -e PHP_CLI_SERVER_WORKERS=4 \
  php:8.4.26-fpm-alpine3.23 php -S 0.0.0.0:8000 -t /app

docker run -d --name ctf-cross-nginx --network ctf-cross-net \
  -p 127.0.0.1:18166:80 \
  -v "$PWD/nginx.conf:/etc/nginx/nginx.conf:ro" \
  nginx:1.27-alpine

확인하세요:

$ curl -s http://127.0.0.1:18166/php-version.php
PHP 8.4.26
PID: 1

PID는 응답하는 워커에 따라 달라지며, 이는 문제되지 않습니다. PoC가 확인하는 부분은 버전 라인입니다.

docker rm -f ctf-cross-nginx ctf-cross-php && docker network rm ctf-cross-net 명령으로 정리하세요.

[!TIP] Windows에서는 해당 docker run 명령을 PowerShell에서 실행하거나, $PWD를 C:/lab과 같은 전체 드라이브 경로로 바꾸세요. Git Bash에서는 $PWD가 /tmp/... 같은 MSYS 경로로 확장되는데, Docker Desktop은 이를 자체 VM 내부에서 해석합니다. 그러면 바인드 마운트가 nginx.conf와 print-flag가 있어야 할 위치에 빈 디렉터리를 조용히 생성하고, 컨테이너는 망가진 상태로 뜹니다.

이 레시피에 관한 몇 가지 참고 사항입니다. 이미지는 fpm 태그이지만 컨테이너는 php -S를 실행하므로 SAPI는 cli-server이며, 이것이 캘리브레이션 측정 기준입니다. PHP_CLI_SERVER_WORKERS=4는 누출 단계에서 경쟁할 워커를 하나 이상 확보해 주는 설정입니다. 제 랩 하네스는 워커에 트레이서를 붙일 수 있도록 --cap-add SYS_PTRACE --security-opt seccomp=unconfined도 전달했지만, PoC 자체는 그렇게 하지 않으므로 위에서는 제외했습니다.


여기서 시작하세요

Python 3.11 이상, 표준 라이브러리만 사용하며 설치할 것은 없습니다:

python .\stealmybitcoin_poc.py --url http://127.0.0.1:18166/php-version.php

인수 없이 실행하면 도움말을 출력하고 네트워크에 접근하지 않은 채 종료됩니다.

정상적인 실행 결과는 다음과 같습니다:

[health] PHP 8.4.26 confirmed
[heap] strict Zend base 0x78113e800000
[code] strict zval_ptr_dtor 0x615b1f512930
{"stage": "final", "nonce": "1ef0e73d8615157a", "execution_proof_verified": true, "response_path": "..."}

해당 zval_ptr_dtor 값의 하위 21비트에 주목하세요: 0x112930으로, 이 Alpine 빌드에서 스크립트가 기대하는 지문과 정확히 일치합니다. 이것이 불일치하는 이미지를 거부하는 검사입니다.

돌아오는 응답 본문은 다음과 같습니다:

<br />
<b>Fatal error</b>:  Allowed memory size of 134217728 bytes exhausted (tried to allocate 131072 bytes) in <b>Unknown</b> on line <b>0</b><br />
1ef0e73d8615157a
uid=0(root) gid=0(root) groups=0(root),0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)
0ec066e8b91457c7c19b5612f3e2ded7f117df08f402693669c1ffc2cfc24942  /usr/local/bin/print-flag
LOCAL_DUMMY_WIF_NEVER_USE

PHP 치명적 오류, 그다음 이 실행에서 생성된 nonce, 그다음 id, 그다음 해석된 바이너리의 해시, 그다음 더미 플래그. 종료 상태는 검증된 실행에서는 0, 그 외의 경우에는 1이다.

[!WARNING] 프로듀서는 의도적으로 PHP를 메모리 한도까지 밀어붙인다. 이 동작이 허가된 곳에서만 실행하라. 스크립트는 챌린지 엔드포인트 또는 루프백 랩 URL과만 통신하며, 그 외의 대상은 패킷이 나가기 전에 중단된다.

한 번의 실행은 헬스 GET 한 번, 최대 두 번의 유출 배치, 그리고 최대 한 번의 최종 요청을 의미한다. 반복도, 재시도도 없다. 모든 멀티파트 본문은 1 MiB 미만으로 유지되지만, 각 유출 배치는 하나의 연결로 아홉 개의 개별 요청을 파이프라인 처리한다.

플래그수행하는 작업
--url랩 엔드포인트 또는 챌린지. 무언가를 하려면 필수.
--output-dir실행의 증거를 기록할 위치. 기본값은 poc-run-<UTC timestamp>.

각 요청이 하는 일

도구 다운로드