CVE-2019-25089 (Other, published 2022-12-27T12:15Z)
Description: value: A vulnerability has been found in Morgawr Muon 0.1.1 and classified as problematic. Affected by this vulnerability is an unknown functionality of the file src/muon/handler.clj. The manipulation leads to insufficiently random values. The attack can be launched remotely. Upgrading to version 0.2.0-indev is able to address this issue. The name of the patch is c09ed972c020f759110c707b06ca2644f0bacd7f. It is recommended to upgrade the affected component. The identifier VDB-216877 was assigned to this vulnerability.
Severity: nan (CVSS v3 base score: 7.5)
Weakness: CWE-330 \u2014 Use of Insufficiently Random Values
The product uses insufficiently random numbers or values in a security context that depends on unpredictable numbers.
Repository: https://github.com/Morgawr/Muon
Files changed:
build-wordlist.sh
create-db.sh
project.clj
src/muon/handler.clj
Commit message: Fix #4: Use randomized URLs

With this commit we fix the vulnerability attack described in #4.
Now files are stored in their own semi-unique folder locations generated
by a list of dictionary words, randomized at runtime.

Also this commit introduces 0.2.0-indev version with initial groundwork
for automated file expiration and collection.


Vulnerable code:
create table data(id integer primary key autoincrement, folder text, filename text, text text, type text, policy text, expires_at timestamp, max_visits int, visits int);
(defproject muon "0.1.1"
; webserver
  (reduce str DOMAIN_ROOT args))
  [text type opts]
        clicks (try (Integer/parseInt (:clicks opts)) (catch Exception e nil))]
       (let [res (db/insert! (db-connection) :data {:text text :type type :policy "clicks" :expires_at 0 :max_visits clicks :visits 0})]
         (build-url
          "resource/"
          (str (last (ffirst res)) "\n")))
       (let [res (db/insert! (db-connection) :data {:text text :type type :policy "timed" :expires_at (+ (System/currentTimeMillis) (* duration 1000)) :max_visits 0 :visits 0})]
         (build-url
          "resource/"
          (str (last (ffirst res)) "\n")))
  (let [filename (str "resources/" (System/currentTimeMillis))]
    (io/copy (:tempfile (:file data)) (io/as-file filename))
    (save-to-db filename (get-mime (:filename (:file data))) data)))
           (not= (:password params) PASSWORD)

Fixed code:
#!/usr/bin/env bash

function gen_dict() {
	DICT=$1

}
DICTFILE=/usr/share/dict/words
OUTFILE=db/wordlist
if test -f "$DICTFILE"; then
	# Get all words longer than 5 characters and save them in their own wordlist file
	awk 'length>5' $DICTFILE | uniq > $OUTFILE
else
	(>&2 echo "Error: The file ${DICTFILE} does not exist. Cannot generate wordlist for file indexing.")
	exit 1
fi

create table data(id integer primary key autoincrement, folder text, filename text, type text, policy text, expires_at timestamp, max_visits int, visits int);
(defproject muon "0.2.0-indev"
; webserver.
; Change this if you want to use a different file to source
; the randomized wordlists for your system.
(def WORDSFILE "db/wordlist")

  (reduce str DOMAIN_ROOT (interpose "/" args)))
(defn get-words-file []
  (clojure.string/split-lines (slurp WORDSFILE)))

(def words-file (memoize get-words-file))

(defn generate-random-folder []
  (let [words (words-file)]
    (str (rand-nth words) (rand-nth words) (rand-nth words))))

(def base-file-data {:folder ""
                     :filename ""
                     :type ""
                     :visits 0
                     :expires_at 0
                     :max_visits 0
                     :policy ""})

  [folder filename type opts]
        clicks (try (Integer/parseInt (:clicks opts)) (catch Exception e nil))
        data (merge base-file-data {:folder folder :filename filename :type type})
        get-id #(last (ffirst %))
        save-fn (fn [query expires?] 
                  (let [res (db/insert! (db-connection) :data query)]
                    (when expires?
                      ; Set up auto-expire entry in the table
                      (db/insert! (db-connection) :autoexpire {:data_id (get-id res) 
                                                               :expires_at (:expires_at query)}))
                    res))
        get-url #(build-url "resource" folder filename)]
       (let [res (save-fn (merge data {:policy "clicks" :max_visits clicks}) false)]
         (str (get-url) "\n"))
       (let [res (save-fn (merge data {:policy "timed" :expires_at (+ (System/currentTimeMillis) (* duration 1000))}) true)]
         (str (get-url) "\n"))
; TODO(morg): Make exit condition with exception if we recur too many times.
(defn randomize-file-location [filename]
  (loop []
    (let [folder (generate-random-folder)
          full-name (reduce str (interpose "/" ["resources" folder filename]))
          new-file (io/as-file full-name)]
      (do
        (io/make-parents full-name)
        (if (not (.exists new-file))
          [folder new-file]
          (recur))))))

  (let [[folder file] (randomize-file-location (:filename (:file data)))]
    (io/copy (:tempfile (:file data)) file)
    (save-to-db folder (:filename (:file data)) (get-mime (:filename (:file data))) data)))

(defn check-password [password]
  (not= password PASSWORD))

  ; TODO(morg) Remove the /resource/ path and only access $HOST/<folder>/<filename> 
           (check-password (:password params))