Curl, unquoted URLs, and LANGSEC

April 1st, 2017

The other day I had an unpleasant realization about curl, and how I use it. I'm going to guess most programmers have had this experience:

tim@puter:~$ curl -sS
[1] 638
bash: baz: command not found
tim@puter:~$ <!doctype html>
    <title>Example Domain</title>

...and immediately have the reaction "oh dammit I forgot to quote the URL", because that innocuous little ampersand is getting interpreted in bash as "run the preceding as a command in the background".

This has happened to me from time to time for years, but it was only this week that I realized how *dangerous* it is.

Read full entry »

Securing my Clojure photo gallery: Let’s Encrypt certs on NFSN

January 10th, 2017

I have a photo gallery site written in Clojure, hosted at NearlyFreeSpeech.NET. I just spent less than an hour learning about ACME and successfully hooked up my app with TLS! Here's what I did.

In my case, the Clojure application is packaged up as a .jar file, and is run as a daemon behind NFSN's reverse proxy, which is capable of terminating TLS. They also provide a script,, that can do the ACME challenge-response. However, it does this by dropping the response files in the Apache-served public directory, which isn't exposed in my configuration.

I chose to modify my application to proxy the acme-challenge dir. It has risks; if you screw up filesystem proxying, you risk path traversal attacks that allow an attacker to read anything on your filesystem! Anyway, it takes very little code, and it worked on the first try, so I would encourage people to use this method.

Below is the meat of the commit that implements this. It's not very much!

(ns org.timmc.pellucida.res.acme
  "Pass through ACME requests to filesystem. (ACME: Automated
Certificate Management Environment)"
   [compojure.core :refer [defroutes GET]]
   [org.timmc.pellucida.settings :as cnf]))

(defn is-token-shaped?
  "Does this string look like an ACME http-01 token? In particular,
does it *not* look like a path traversal attack?"
  [^String s]
  (boolean (re-matches #"[a-zA-Z0-9\-_=]+" s)))

(defn token-response
  "Answer with the contents of the ACME challenge file."
  ;; The ACME http-01 token value is used as the filename as well
  ;; as the value of a field within the JSON file.
  (when (is-token-shaped? magic-token)
    (let [resp-path (str (:acme-challenge-dir @cnf/config) "/" magic-token)]
      {:status 200
       :headers {"Content-Type" "text/plain"} ;; Spec: This or none
       :body ( resp-path)})))

(defroutes acme-routes
  (GET ["/.well-known/acme-challenge/:magic-token"] [magic-token :as r]
       (when (:acme-challenge-dir @cnf/config)
         (token-response magic-token))))

Incidentally, there were a few other options I thought of, but did not pursue or fully evaluate:

  • Hook: uses dehydrated to implement ACME, and you can hook into it for various events, including challenge generation. I could use that to notify the app to do something.
  • Proxy: I already configure the NFSN site to proxy the root path to my app's port 8080. Could I add another proxy that just mirrors /home/public/.well-known/acme-challenge? Not sure. From the forums, it sounds like if I change the site type to "Apache2.4 generic" instead of "Custom", I could possibly do this. Ah well, the code is already written!

LastPass’s local-only decryption only technically so

December 17th, 2015

LastPass is a password manager and service that promises local-only password decryption: "Your key never leaves your device, and is never shared with LastPass." (, 2015-06-15.) That claim sounds good, and it's technically true under normal usage, but they completely fail to ensure that it *can't* happen -- that is, even under an attack scenario.

Read full entry »

How to move personal publishing to the desktop

May 11th, 2011

You can do everything "in the cloud" these days, from blogging to posting photos to running servers. Most impressively, you can now also lose control of your files and personal information with unprecedented ease, or simply lose it, period. This is exactly the worst possible feature for the personal publishing use-cases of cloud computing. Possibly the most distressing aspect of cloud-based publishing is that it firmly designates the intangible network as the primary resting place of one's data. (I will note here that this aspect is itself what I am using to define "cloud computing" for the purposes of this blog post.) If the first place you put your creations is some hosted service on the great wide interwebs, you're playing with fire.

Read full entry »

Force SSL for Wikipedia (for advanced users)

May 22nd, 2010

I like using HTTPS whenever possible. Usually this is as simple as adding a single letter to a URL, but some sites have separate domains for SSL. The Wikimedia sites are a great example of this; they share the domain and use the first elements of the path to specify the site.

(Edit 2016-11-25: Nowadays, you can just use the HTTPS Everywhere browser extension for Firefox, Opera, or Chrome! And you don't need to use a separate domains anymore.)

Now, I could have set up a Greasemonkey script to redirect me once I hit an unsecure Wikipedia page, but then it's too late. (I'm usually going directly to the article via web search results.) I could also use Greasemonkey to rewrite URLs in web pages, but that's a mess. Instead, I wanted to intercept any requests to unsecure Wikipedia and redirect them on the fly, before they even left my machine. Here's how I set up my browser to always use SSL for Wikimedia sites:

  1. Have Apache with virtual hosts and Mozilla Firefox with FoxyProxy
  2. In my default virtual host:
    <Directory /var/www/>
    	RewriteEngine On
    	RewriteBase /
    	RewriteCond ${HTTP_HOST} !.*mycomputername.*
    	RewriteRule . rewriter.php [L]
  3. And this file at /var/www/rewriter.php:
    $host = $_SERVER['HTTP_HOST'];
    $path = $_SERVER['REQUEST_URI'];
    // ensure path is not of form http://...
    if(strpos($path, '/') !== 0) {
    	$start = "http://$host/";
    	if(strpos($path, $start) === 0) {
    		$path = substr($path, strlen($start) - 1); // include slash
    	} else {
    if(preg_match('/([a-z0-9]+)\.wikipedia\.org/', $host, $m_domain)) {
  4. Then set up a proxy in FoxyProxy, early in the chain, called "rewriter". Set it to a SOCKS 5 proxy at localhost:80, using the whitelist regex http://[a-z]+\.wikipedia\.org/.*

Obviously, the setup as written here only gets Wikipedia, but it could easily be expanded to Wiktionary, Wikibooks, Wikimedia Commons, and other sister sites.

I'll delete any tech-support questions in the comments area, so don't ask them. This guide is for advanced users only. Discussions of potential improvements are welcome.