HTTP(S) Servers for File Transfer in Linux

I cannot tell you how many times I needed to have a quick and dirty way to transfer files in an engagement, so here are some that I have used before.


Local –, remote –

Python #

Python can help out in almost any situation, and our case is no exception.

Everyone knows these great commands for starting HTTP servers for the second version of python:

local@server:~$ python -m SimpleHTTPServer [port]

Also this:

And its analog for Python 3:

local@server:~$ python3 -m http.server [-h] [--cgi] [--bind ADDRESS] [port]

In this way, you can only pull files from where the server was raised, since the only methods that it understands out of the box are HEAD and GET. However, no one forbids us to slightly modify the default behavior by adding, for example, handling POST (displaying the contents to the console for example) and PUT requests.

Simple script:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# Usage: python3 [-h] [--bind ADDRESS] [port]

import http.server
import os

from argparse import ArgumentParser

class HTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
	def _set_headers(self):
		self.send_header('Content-type', 'text/html')

	def do_POST(self):
		content_length = int(self.headers['Content-Length'])
		post_data =


	def do_PUT(self):
		path = self.translate_path(self.path)
		if path.endswith('/'):
			self.send_response(405, 'Method Not Allowed')
			self.wfile.write(b'PUT not allowed on a directory\n')
			except FileExistsError: pass
			length = int(self.headers['Content-Length'])
			with open(path, 'wb') as f:
			self.send_response(201, 'Created')

def cli_options():
	parser = ArgumentParser()

		help='Specify alternate bind address [default: all interfaces]'

		help='Specify alternate port [default: 8000]'

	return parser.parse_args()

if __name__ == '__main__':
	args = cli_options()
	http.server.test(HandlerClass=HTTPRequestHandler, port=args.port, bind=args.bind)

Allows to successfully both upload and download files:

local@server:~$ wget
--2018-10-11 10:51:35--
Connecting to connected.
HTTP request sent, awaiting response... 200 OK
Length: 10 [application/octet-stream]
Saving to: ‘message’

message              100%[===================>]      10  --.-KB/s    in 0s

2018-10-11 10:51:35 (2.40 MB/s) - ‘message’ saved [10/10]
local@server:~$ cat message
Hi there!
remote@server:~$ python3 8881
Serving HTTP on port 8881 ( ... - - [11/Oct/2018 11:04:37] "GET /message HTTP/1.1" 200 -

So to upload to a Linux machine:

local@server:~$ curl --upload-file message
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    10    0     0  100    10      0      9  0:00:01  0:00:01 --:--:--     9

local@server:~$ curl -d @message -X POST
remote@server:~$ python3 8881
Serving HTTP on port 8881 ( ... - - [11/Oct/2018 10:52:10] "PUT /message HTTP/1.1" 201 - - - [11/Oct/2018 10:52:18] "POST / HTTP/1.1" 200 -
Hi there!
remote@server:~$ cat message
Hi there!

Available methods: GET, POST, PUT

Python 3 SSL Server #

# taken from
# generate server.xml with the following command:
#    openssl req -new -x509 -keyout /tmp/key.pem -out /tmp/cert.pem -days 365 -nodes
# run as follows:
#    python
# then in your browser, visit:
#    https://localhost:443

from http.server import HTTPServer, SimpleHTTPRequestHandler
import ssl

httpd = HTTPServer(('<your-ip>', 443), SimpleHTTPRequestHandler)

httpd.socket = ssl.wrap_socket(httpd.socket, keyfile='/tmp/key.pem', certfile='/tmp/cert.pem', server_side=True)

  • You can use a higher port e.g.:4443 so you can run as a normal user


Unsurprisingly, a two-line PHP script can solve all our problems - the “hypertext preprocessor” after all :sunglasses:

So, for a trivial PHP server, we need code like this:

$fname = basename($_REQUEST['filename']);
file_put_contents('uploads/' . $fname, file_get_contents('php://input'));

In the screenshot below (clickable) you can see the entire procedure for starting the server: preliminary settings on the panel on the left, tests on the right.


A few words about what’s going on here:

  1. Create the necessary directories and a script with the content above.
  2. Create a user from which the server will spin. The new user is needed so that enemies cannot execute the code that they themselves upload. Therefore, with the command umask 555, we set the setting for access rights issued to all new files that our user will create. 555 is 777 XOR 222, so the default bits will be set as if we manually set chmod 222 to each new file (only write allowed).
  3. We start the server and test it.
  4. ???????

Available methods: GET, POST, PUT

Nginx #

So where can we go without the High-Performance Web Server and Reverse Proxy? Fortunately, on most Linux distributions Nginx is preinstalled, so it can be configured and deployed in a matter of minutes.

Again, in the screenshot below, you can see the entire launch procedure: preliminary settings on the panel at the top, tests at the bottom.


What’s going on here:

  1. Create the necessary directories and server configuration based on the sample from `default ‘(the contents of the config are below).
  2. Make the config active (symlink in /etc/nginx/sites-enabled/)
  3. Restart the nginx service, check its activity and test the server.
  4. ???????

Config file:

root@kali:~# cat /etc/nginx/sites-available/file_upload
server {
	listen 8881 default_server;
	location / {
		root                  /var/www/uploads;
		dav_methods           PUT;
		create_full_put_path  on;
    		dav_access            group:rw all:r;

How to use it, do not forget to stop the server:

root@kali:~# systemctl stop nginx

Available methods: GET, PUT.

SPECIAL THANKS TO For this post but slightly modified #