CVE-2022-37109: Insufficiently Protected Credentials (CWE-522)
value: patrickfuller camp up to and including commit bbd53a256ed70e79bd8758080936afbf6d738767 is vulnerable to Incorrect Access Control. Access to the password.txt file is not properly restricted as it is in the root directory served by StaticFileHandler and the Tornado rule to throw a 403 error when password.txt is accessed can be bypassed. Furthermore, it is not necessary to crack the password hash to authenticate with the application because the password hash is also used as the cookie secret, so an attacker can generate his own authentication cookie.
CWE description: The product transmits or stores authentication credentials, but it uses an insecure method that is susceptible to unauthorized interception and/or retrieval.
CVSS v3 base score: 9.8
Published: 2022-11-14T21:15Z
Repository: https://github.com/patrickfuller/camp
Language: Python
Files changed: README.md, server.py
Commit date: 2022-07-21 09:03:39 -0500
Commit message: Fix vulnerability and add warning

Diff: diff --git a/README.md b/README.md
index 288adcf..53c3338 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,13 @@
 camp
 ====
 
 Another Raspberry Pi camera webserver.
 
-![](img/example.png)
+**USE AT YOUR OWN RISK. SEE [#11](https://github.com/patrickfuller/camp/issues/11) FOR MORE.**
+
+![](static/img/example.png)
 
 What it does
 ============
 
 Hosts a website where you can view your webcam in real time.
@@ -50,15 +52,15 @@ git clone https://github.com/patrickfuller/camp.git
 python camp/server.py
 ```
 
 Navigate to http://your.r.pi.ip:8000 and check out your webcam.
 
-#### USB Camera
+### USB Camera
 
 Use with `python server.py --use-usb`.
 
-#### Password
+### Password
 
 ![](img/login.png)
 
 With the `--require-login` flag, camp will open a login page before allowing
 webcam access.
@@ -71,22 +73,21 @@ python -c "import hashlib; import getpass; print(hashlib.sha512(getpass.getpass(
 ```
 
 This will prompt you for a password, encrypt it, and save the result in
 `password.txt`.
 
-Note that this level of password protection is basic - it's fine for keeping the
-occasional stranger out, but won't stand up to targeted hacking.
+**Note that this level of password protection is basic.** It's fine for keeping the occasional stranger out but won't stand up to targeted hacking.
 
-#### Run on startup
+### Run on startup
 
 It's nice to have your pi start camp whenever it turns on. Let's make that
 happen. Type `sudo nano /etc/rc.local` to open this file for editing, and add
 the line `nohup python /home/pi/camp/server.py &` before the last line. Note
 that you may need to change the path (`/home/pi/camp/server.py`) to point to
 the right file.
 
-#### Customization
+### Customization
 
 The website consists of `index.html`, `login.html`, and `style.css`. These can be
 edited to change the look of camp.
 
 If you want to add in extra functionality, edit `client.js` and `server.py`.
diff --git a/server.py b/server.py
index 331883f..89dc290 100644
--- a/server.py
+++ b/server.py
@@ -5,11 +5,10 @@ Creates an HTTP server with basic auth and websocket communication.
 import argparse
 import base64
 import hashlib
 import os
 import time
-import threading
 import webbrowser
 
 try:
     import cStringIO as io
 except ImportError:
@@ -40,23 +39,18 @@ class LoginHandler(tornado.web.RequestHandler):
     def get(self):
         self.render("login.html")
 
     def post(self):
         password = self.get_argument("password", "")
-        if hashlib.sha512(password).hexdigest() == PASSWORD:
+        if hashlib.sha512(password.encode()).hexdigest() == PASSWORD:
             self.set_secure_cookie(COOKIE_NAME, str(time.time()))
             self.redirect("/")
         else:
             time.sleep(1)
             self.redirect(u"/login?error")
 
 
-class ErrorHandler(tornado.web.RequestHandler):
-    def get(self):
-        self.send_error(status_code=403)
-
-
 class WebSocket(tornado.websocket.WebSocketHandler):
 
     def on_message(self, message):
         """Evaluates the function pointed to by json-rpc."""
 
@@ -126,13 +120,14 @@ if args.resolution in resolutions:
 else:
     raise Exception("%s not in resolution options." % args.resolution)
 
 handlers = [(r"/", IndexHandler), (r"/login", LoginHandler),
             (r"/websocket", WebSocket),
-            (r"/static/password.txt", ErrorHandler),
-            (r'/static/(.*)', tornado.web.StaticFileHandler, {'path': ROOT})]
-application = tornado.web.Application(handlers, cookie_secret=PASSWORD)
+            (r'/static/(.*)', tornado.web.StaticFileHandler, {'path': os.path.join(ROOT, 'static')})]
+
+secret = base64.b64encode(os.urandom(50)).decode('ascii')
+application = tornado.web.Application(handlers, cookie_secret=secret)
 application.listen(args.port)
 
 webbrowser.open("http://localhost:%d/" % args.port, new=2)
 
 tornado.ioloop.IOLoop.instance().start()
diff --git a/client.js b/static/client.js
similarity index 100%
rename from client.js
rename to static/client.js
diff --git a/favicon.ico b/static/favicon.ico
similarity index 100%
rename from favicon.ico
rename to static/favicon.ico
diff --git a/img/example.png b/static/img/example.png
similarity index 100%
rename from img/example.png
rename to static/img/example.png
diff --git a/img/login.png b/static/img/login.png
similarity index 100%
rename from img/login.png
rename to static/img/login.png
diff --git a/img/logo.png b/static/img/logo.png
similarity index 100%
rename from img/logo.png
rename to static/img/logo.png
diff --git a/style.css b/static/style.css
similarity index 100%
rename from style.css
rename to static/style.css

Vulnerable code: ![](img/example.png)
#### USB Camera
#### Password
Note that this level of password protection is basic - it's fine for keeping the
occasional stranger out, but won't stand up to targeted hacking.
#### Run on startup
#### Customization
import threading
        if hashlib.sha512(password).hexdigest() == PASSWORD:
class ErrorHandler(tornado.web.RequestHandler):
    def get(self):
        self.send_error(status_code=403)


            (r"/static/password.txt", ErrorHandler),
            (r'/static/(.*)', tornado.web.StaticFileHandler, {'path': ROOT})]
application = tornado.web.Application(handlers, cookie_secret=PASSWORD)
Fixed code: **USE AT YOUR OWN RISK. SEE [#11](https://github.com/patrickfuller/camp/issues/11) FOR MORE.**

![](static/img/example.png)
### USB Camera
### Password
**Note that this level of password protection is basic.** It's fine for keeping the occasional stranger out but won't stand up to targeted hacking.
### Run on startup
### Customization
        if hashlib.sha512(password.encode()).hexdigest() == PASSWORD:
            (r'/static/(.*)', tornado.web.StaticFileHandler, {'path': os.path.join(ROOT, 'static')})]

secret = base64.b64encode(os.urandom(50)).decode('ascii')
application = tornado.web.Application(handlers, cookie_secret=secret)