U1
Storage AWS

Uguu 1.9.9 on AWS User Guide

| Product: Uguu 1.9.9 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using the Uguu 1.9.9 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg File Browser and File Sharing listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.

Uguu is a simple, lightweight temporary file host (the maintained successor to Pomf). You upload a file by drag and drop, paste or a small HTTP API and get back a short link to share. Files expire automatically after a retention window (8 hours by default) and are then deleted from disk and from the database. It runs on PHP 8.3 behind nginx, with a local SQLite database for file metadata.

Upstream Uguu lets anyone upload, which makes an open instance easy to abuse for hosting malware or pirated content. The cloudimg image adds:

  • Uploads need a key unique to your instance. The uploader page and the upload endpoint require HTTP Basic authentication (user uguu) with a key generated on your instance's first boot and written to /root/uguu-credentials.txt. An upload without it is refused by nginx before it reaches PHP. Downloads stay public, so a recipient only needs the link. A browser upload posted from another web site is refused even when your browser has the key cached.
  • Uploaded files are never run or rendered as active content. They are served as static files from their own directory, which PHP never executes. Every download is sent with X-Content-Type-Options: nosniff and a sandboxing Content Security Policy, and XML, XHTML and script types are sent as downloads rather than shown in the browser. HTML, SVG, PHP and executable uploads are refused outright.
  • HTTPS from the first boot. nginx serves Uguu on port 443 with a certificate generated on your instance. Port 80 redirects to HTTPS. Requests for a host name the instance does not serve get no answer.
  • Fail closed. No key, password hash or certificate is baked into the image, and nginx refuses to start until first boot has created them.
  • Hardened defaults. nginx, PHP-FPM and the expiry job are blocked from the instance metadata service, and the AMI requires IMDSv2. The client address Uguu sees (it rate limits uploads per client) comes from the connection, never from a header a client can forge. Uploads are capped at 128 MB, and no client IP addresses are stored.
  • Your files on their own volume. Uploaded files live on a separate 32 GiB EBS volume mounted at /data, which you can resize and snapshot on its own.
  • Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.

What is on the image: Uguu 1.9.9 (/var/www/uguu/dist, built from the upstream release with its checksum verified), nginx, PHP 8.3 (php8.3-fpm), SQLite, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.

Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large

Prerequisites

  • An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet (or a private route you use for management).
  • A security group that allows TCP 443 (uploads and downloads) and TCP 80 (redirects to HTTPS) from the people who will use the service, and TCP 22 (SSH) from your own networks only.

Step 1: Launch the instance

In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick Uguu 1.9.9 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large (Uguu is light; a smaller type works for a small team), your key pair and a security group as above. Keep the second EBS volume: it is where uploaded files are stored.

With the AWS CLI (replace the values in angle brackets):

aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
  --instance-type m5.large --key-name <your-key-pair> \
  --security-group-ids <your-security-group> --subnet-id <your-subnet> \
  --metadata-options HttpTokens=required,HttpEndpoint=enabled \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=uguu}]'

Step 2: Connect to your instance

Version SSH user
Uguu 1.9.9 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>

Step 3: Check the services and the data volume

First boot takes a few seconds. Then nginx, PHP-FPM and the first boot unit are active:

systemctl is-active nginx php8.3-fpm uguu-firstboot
active
active
active

nginx is the only web listener (PHP-FPM uses a local socket, not a port):

sudo ss -tlnp | awk 'NR==1 || /nginx|sshd|php/' | awk '{print $1, $4, $6}' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g'
State Local Peer
LISTEN 0.0.0.0:443 users:(("nginx"),("nginx"),("nginx"))
LISTEN 0.0.0.0:22 users:(("sshd"),("systemd"))
LISTEN 0.0.0.0:80 users:(("nginx"),("nginx"),("nginx"))
LISTEN [::]:443 users:(("nginx"),("nginx"),("nginx"))
LISTEN [::]:22 users:(("sshd"),("systemd"))
LISTEN [::]:80 users:(("nginx"),("nginx"),("nginx"))

Uploaded files are stored on the separate data volume:

findmnt -no SOURCE,TARGET,FSTYPE /data
df -h --output=size,avail,target /data
/dev/nvme1n1 /data  ext4
 Size Avail Mounted on
  32G   30G /data

Step 4: Get your URL and upload key

sudo cat /root/uguu-credentials.txt

The file holds the address Uguu is served as (your instance's public IP address by default) and the upload key generated on this instance:

# Uguu - generated on first boot by uguu-firstboot.service. Unique to this instance; keep it safe.

uguu.url=https://<public-ip>/
uguu.upload.endpoint=https://<public-ip>/upload.php
uguu.upload.user=uguu
uguu.upload.key=<32 letters and digits>

The configuration keeps file metadata in SQLite, stores files on /data, expires them after 8 hours, caps uploads at 128 MB, keeps the type filter and rate limiting on and stores no client IP addresses:

jq '{DOMAIN,FILE_DOMAIN,DB_MODE,FILES_ROOT,expireTime,expireTimeUnit,max_upload_size,FILTER_MODE,RATE_LIMIT,LOG_IP}' /var/www/uguu/dist/config.json
{
  "DOMAIN": "<public-ip>",
  "FILE_DOMAIN": "<public-ip>",
  "DB_MODE": "sqlite",
  "FILES_ROOT": "/data/uguu/files/",
  "expireTime": 8,
  "expireTimeUnit": "hours",
  "max_upload_size": 128,
  "FILTER_MODE": true,
  "RATE_LIMIT": true,
  "LOG_IP": false
}

Step 5: Upload from your browser

Open uguu.url in your browser. The certificate is self signed (generated on your instance), so accept the browser warning once, or use your own name and certificate (Step 10). The browser then asks for a user name and password: enter uguu and the upload key from Step 4. The uploader shows the size limit and the expiry window:

The Uguu uploader page with the Drop or paste files button and the note that the maximum upload size is 128 MiB and files expire after 8 hours

Drag a file onto Drop or paste file(s), paste from the clipboard, or click to choose a file. Uguu uploads it and shows its share link with a copy button:

The Uguu uploader after an upload, showing the file name cloudimg-logo.png on the left and its share link with a copy button on the right

Step 6: Share a link

Anyone with the link can open the file; no key is needed to download. The link stops working when the file expires:

The shared image opened directly in the browser from its share link

Step 7: Upload from the API or a screenshot tool

Uguu has a small HTTP API for scripts and screenshot tools. POST one or more files as files[] to /upload (or /upload.php); you get JSON back by default, or csv, text, html or gyazo with ?output=. Uploads need the key with HTTP Basic authentication (user uguu). The API page on your instance shows its own endpoint:

The Uguu API page showing the upload endpoint for this instance, the supported response formats and a curl example

On the instance, this uploads a file and downloads it back through its share link. The key is read from the credentials file and handed to curl on standard input (-K -), so it never appears on a command line or in the process list:

H=$(sudo sed -nE 's#^uguu.url=https://([^/]+)/.*#\1#p' /root/uguu-credentials.txt)
echo "hello from cloudimg" > /tmp/uguu-demo.txt
URL=$(sudo sed -nE 's/^uguu.upload.key=(.*)$/user = "uguu:\1"/p' /root/uguu-credentials.txt \
  | curl -sk -K - --resolve "$H:443:127.0.0.1" -F "files[]=@/tmp/uguu-demo.txt" "https://$H/upload?output=json" | jq -r '.files[0].url')
echo "share link: $URL"
curl -sk --resolve "$H:443:127.0.0.1" "$URL"
rm -f /tmp/uguu-demo.txt
share link: https://<public-ip>/<8 random letters>.txt
hello from cloudimg

From your own computer, let curl prompt for the key instead of typing it on the command line:

curl -k -u uguu -F "files[]=@photo.jpg" "https://<public-ip>/upload?output=text"

For a screenshot tool such as ShareX, set the request URL to https://<public-ip>/upload, the method to POST, the file form name to files[], and add HTTP Basic authentication with user uguu and your key.

Step 8: Check the upload gate

The image ships a self test that proves the security model end to end over HTTPS: an upload without the key is refused, an upload with it returns a share link on your configured host, the file lands on the /data volume, and it downloads back without a key, with the safe download headers:

sudo /opt/uguu/uguu-selftest.sh
OK: upload without key refused (401); keyed upload -> https://<public-ip>/<8 random letters>.txt; blob on /data; public download matches with nosniff + CSP sandbox

Step 9: Security checks

Plain HTTP redirects to HTTPS, the uploader and the upload endpoint refuse you without the key, an upload posted from another web site is refused even with the key, and a request for a host name the instance does not serve gets no answer:

H=$(sudo sed -nE 's#^uguu.url=https://([^/]+)/.*#\1#p' /root/uguu-credentials.txt)
echo test > /tmp/uguu-check.txt
curl -s -o /dev/null -w 'plain HTTP: %{http_code} -> %{redirect_url}\n' --resolve "$H:80:127.0.0.1" "http://$H/"
curl -sk -o /dev/null -w 'uploader page without the key: %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/"
curl -sk -o /dev/null -w 'upload without the key: %{http_code}\n' --resolve "$H:443:127.0.0.1" -F "files[]=@/tmp/uguu-check.txt" "https://$H/upload"
sudo sed -nE 's/^uguu.upload.key=(.*)$/user = "uguu:\1"/p' /root/uguu-credentials.txt \
  | curl -sk -K - -o /dev/null -w 'upload posted from another web site: %{http_code}\n' --resolve "$H:443:127.0.0.1" \
      -H 'Origin: https://evil.example' -F "files[]=@/tmp/uguu-check.txt" "https://$H/upload"
curl -s -o /dev/null -w 'unknown host name: %{http_code}\n' -H 'Host: evil.example' http://127.0.0.1/ || true
rm -f /tmp/uguu-check.txt
plain HTTP: 301 -> https://<public-ip>/
uploader page without the key: 401
upload without the key: 401
upload posted from another web site: 403
unknown host name: 000

An HTML page is refused at upload, and an XML file is sent as a download, with nosniff and a sandboxing Content Security Policy, so nothing an uploader sends can run in your browser:

H=$(sudo sed -nE 's#^uguu.url=https://([^/]+)/.*#\1#p' /root/uguu-credentials.txt)
printf '<html><script>alert(1)</script></html>\n' > /tmp/page.html
printf '<?xml version="1.0"?><note>hi</note>\n' > /tmp/note.xml
auth() { sudo sed -nE 's/^uguu.upload.key=(.*)$/user = "uguu:\1"/p' /root/uguu-credentials.txt; }
auth | curl -sk -K - --resolve "$H:443:127.0.0.1" -F "files[]=@/tmp/page.html" "https://$H/upload" | jq -c '{success, description}'
URL=$(auth | curl -sk -K - --resolve "$H:443:127.0.0.1" -F "files[]=@/tmp/note.xml" "https://$H/upload" | jq -r '.files[0].url')
curl -sk --resolve "$H:443:127.0.0.1" -o /dev/null -D - "$URL" | grep -iE '^(content-type|content-disposition|x-content-type-options|content-security-policy):'
rm -f /tmp/page.html /tmp/note.xml
{"success":false,"description":"Filetype not allowed"}
content-type: text/xml; charset=utf-8
x-content-type-options: nosniff
content-security-policy: default-src 'none'; img-src 'self'; media-src 'self'; style-src 'unsafe-inline'; frame-ancestors 'none'; sandbox
content-disposition: attachment

nginx, PHP-FPM and the expiry job cannot reach the instance metadata service; nginx hands PHP the client address from the connection and discards forwarding headers a client could forge; and the secrets are readable by root (and, for the key hash, nginx) only:

systemctl show nginx php8.3-fpm uguu-expire -p Id -p IPAddressDeny
grep -E 'fastcgi_param HTTP_(CF_CONNECTING_IP|X_REAL_IP|X_FORWARDED_FOR)' /etc/nginx/uguu-fastcgi.conf
sudo stat -c '%a %U:%G %n' /root/uguu-credentials.txt /etc/nginx/uguu.htpasswd /etc/nginx/ssl/uguu.key
IPAddressDeny=fd00:ec2::254/128 168.63.129.16/32 169.254.169.254/32
Id=nginx.service

IPAddressDeny=fd00:ec2::254/128 168.63.129.16/32 169.254.169.254/32
Id=php8.3-fpm.service

IPAddressDeny=fd00:ec2::254/128 168.63.129.16/32 169.254.169.254/32
Id=uguu-expire.service
fastcgi_param HTTP_CF_CONNECTING_IP $remote_addr;
fastcgi_param HTTP_X_REAL_IP $remote_addr;
fastcgi_param HTTP_X_FORWARDED_FOR "";
600 root:root /root/uguu-credentials.txt
640 root:www-data /etc/nginx/uguu.htpasswd
600 root:root /etc/nginx/ssl/uguu.key

The certificate was generated on this instance and names its own addresses:

H=$(sudo sed -nE 's#^uguu.url=https://([^/]+)/.*#\1#p' /root/uguu-credentials.txt)
echo | openssl s_client -connect 127.0.0.1:443 -servername "$H" 2>/dev/null | openssl x509 -noout -subject -ext subjectAltName
subject=CN = <public-ip>, O = cloudimg
X509v3 Subject Alternative Name:
    IP Address:<public-ip>, DNS:ec2-<public-ip-dashed>.compute-1.amazonaws.com, IP Address:<private-ip>, IP Address:127.0.0.1, DNS:localhost

Step 10: Your own DNS name and certificate

Point a DNS record at the instance, then set the name Uguu is served as. This mints a new self signed certificate for the name, updates nginx, the host in every new share link, the API and FAQ pages and the URL in /root/uguu-credentials.txt, and keeps the name across reboots:

sudo uguu-set-host <your-domain>

To use a certificate from your own certificate authority instead, copy it over the generated pair and reload nginx (running uguu-set-host again replaces it with a self signed one):

sudo install -m 0644 <your-domain>.crt /etc/nginx/ssl/uguu.crt
sudo install -m 0600 <your-domain>.key /etc/nginx/ssl/uguu.key
sudo nginx -t && sudo systemctl reload nginx

Step 11: Rotate the upload key

The key is the only thing between your instance and an open file drop. To change it, set a new one for the uguu user (htpasswd prompts for it, so it never appears on a command line), then update uguu.upload.key in /root/uguu-credentials.txt and in your API clients. nginx uses the new key at once; share links are unaffected:

sudo htpasswd -B /etc/nginx/uguu.htpasswd uguu

Step 12: Expiry, limits and data

Files expire automatically. uguu-expire.timer runs checkExpire.php every 15 minutes and deletes files past their retention window from disk and from the database:

systemctl is-enabled uguu-expire.timer
enabled

The retention window is expireTime and expireTimeUnit in /var/www/uguu/dist/config.json. Other settings in the same file: max_upload_size (MB; also raise client_max_body_size in /etc/nginx/sites-available/cloudimg-uguu and upload_max_filesize/post_max_size in /etc/php/8.3/fpm/conf.d/99-uguu.ini to match), the FILTER_EXTENSIONS and FILTER_MIME block lists, and the RATE_LIMIT settings. PHP caches the configuration, so after a change run sudo systemctl restart php8.3-fpm.

Uploaded files are under /data/uguu/files/ on the separate EBS volume; the metadata database is /var/lib/uguu/db/uguu.sq3. To back up, snapshot the /data volume. To grow it, resize the volume in EC2, then run sudo resize2fs on its device.

Step 13: Server components

Component Path
Uguu application (web root dist/public) /var/www/uguu/dist
Configuration /var/www/uguu/dist/config.json
Uploaded files /data/uguu/files/ (on the /data volume)
Metadata database /var/lib/uguu/db/uguu.sq3
nginx site /etc/nginx/sites-available/cloudimg-uguu
Upload key hash /etc/nginx/uguu.htpasswd (root:www-data, 0640)
Certificate and key /etc/nginx/ssl/
First boot script /usr/local/sbin/uguu-firstboot.sh
Host name helper /usr/local/sbin/uguu-set-host
Self test /opt/uguu/uguu-selftest.sh
Expiry job uguu-expire.service / uguu-expire.timer
Credentials /root/uguu-credentials.txt (mode 0600)
Licence /usr/share/doc/uguu/LICENSE

Step 14: Security recommendations

  • Allow port 22 in the security group from your own networks only; open 80 and 443 only to the people who should upload or download.
  • Keep the upload key private and rotate it if it may have leaked (Step 11).
  • Use your own DNS name and a certificate your clients trust (Step 10), so nobody learns to click through certificate warnings.
  • Keep the retention window as short as your workflow allows.
  • Keep the operating system patched; unattended security upgrades are on, and sudo apt-get update && sudo apt-get upgrade applies everything else.

Support and licensing

Uguu is free software under the GNU General Public License, version 3 or (at your option) any later version; a copy is at /usr/share/doc/uguu/LICENSE. There is no per user or per CPU licence fee. The AMI price covers cloudimg's hardening, first boot setup and support.

  • Email: support@cloudimg.co.uk
  • Website: https://www.cloudimg.co.uk
  • Support: 24/7, with a 24 hour response SLA