Repository: CTFd/CTFd

Issue:
Files not Downloadable
**Environment**:

  - CTFd Version/Commit: 2.3.3
  - Operating System: Windows
  - Web Browser and Version: Chrome 80.0.3987.132

**What happened?**
When building a challenge that has a file, the file is not downloadable.

**What did you expect to happen?**
A file should be downloadable from the frontend.

**How to reproduce your issue**
Create a fresh CTFd instance. Run serve.py. Create a challenge and upload a file. Navigate to the challenge on the front end. The file link is 404.

**Any associated stack traces or error logs**
```
CTFd-Base> python .\serve.py
 * Loaded module, <module 'CTFd.plugins.challenges' from 'C:\Users\StormCTF\Downloads\CTFd-Base\CTFd\plugins\challenges\__init__.pyc'>
 * Loaded module, <module 'CTFd.plugins.dynamic_challenges' from 'C:\Users\StormCTF\Downloads\CTFd-Base\CTFd\plugins\dynamic_challenges\__init__.pyc'>
 * Loaded module, <module 'CTFd.plugins.flags' from 'C:\Users\StormCTF\Downloads\CTFd-Base\CTFd\plugins\flags\__init__.pyc'>
 * Serving Flask app "CTFd" (lazy loading)
 * Environment: development
 * Debug mode: on
 * Restarting with stat
 * Loaded module, <module 'CTFd.plugins.challenges' from 'C:\Users\StormCTF\Downloads\CTFd-Base\CTFd\plugins\challenges\__init__.pyc'>
 * Loaded module, <module 'CTFd.plugins.dynamic_challenges' from 'C:\Users\StormCTF\Downloads\CTFd-Base\CTFd\plugins\dynamic_challenges\__init__.pyc'>
 * Loaded module, <module 'CTFd.plugins.flags' from 'C:\Users\StormCTF\Downloads\CTFd-Base\CTFd\plugins\flags\__init__.pyc'>
 * Debugger is active!
 * Debugger PIN: 310-671-378
 * Running on http://127.0.0.1:4000/ (Press CTRL+C to quit)
127.0.0.1 - - [19/Apr/2020 12:40:42] "GET /challenges HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2020 12:40:43] "GET /themes/core/static/css/challenge-board.dev.css?d=a5654bce HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2020 12:40:43] "GET /themes/core/static/css/main.dev.css?d=a5654bce HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2020 12:40:43] "GET /themes/core/static/css/core.dev.css?d=a5654bce HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2020 12:40:43] "GET /themes/core/static/js/vendor.bundle.dev.js?d=a5654bce HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2020 12:40:43] "GET /themes/core/static/css/fonts.dev.css?d=a5654bce HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2020 12:40:43] "GET /themes/core/static/js/core.dev.js?d=a5654bce HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2020 12:40:43] "GET /themes/core/static/js/helpers.dev.js?d=a5654bce HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2020 12:40:43] "GET /themes/core/static/js/pages/challenges.dev.js?d=a5654bce HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2020 12:40:43] "GET /themes/core/static/img/favicon.ico?d=a5654bce HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2020 12:40:43] "GET /api/v1/teams/me/solves HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2020 12:40:43] "GET /api/v1/challenges HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2020 12:40:43] "GET /api/v1/teams/me/solves HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2020 12:40:46] "GET /plugins/challenges/assets/view.js?_=1587314443544 HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2020 12:40:46] "GET /api/v1/challenges/1/solves HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2020 12:40:46] "GET /api/v1/challenges/1 HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2020 12:40:50] "GET /files/e57e22cdfa601592e5239c2d358994d6%5COrange-Icon-Black-BG.png?token=.eJyrVipJTcyNz0xRsjLSUSotTi2CsdMyc1LBbMNaAObrC-o.Xpx_Dg.vcuDUI8hJiIgPLExAPan7WVg4FU HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2020 12:40:50] "GET /themes/core/static/js/pages/main.dev.js?d=a5654bce HTTP/1.1" 200 -```



Discussion:
This is probably because you're using Windows to run CTFd. In the past Windows was supported as a deployment OS but it doesn't get much usage and it's difficult to test for. It's far more reliable to deploy on Linux via Docker. 

This might be fixed at some point but no idea when. 
Got it. I'll dev with Linux. If the issue persists I'll leave the ticket up. Standby
Confirmed it's working in Ubuntu. Thanks @ColdHeat . 
I investigated this a little and it's probably an issue in the FileSystemUploader where os.path.join is being used which uses the OS's seperater. 

https://github.com/CTFd/CTFd/blob/master/CTFd/utils/uploads/uploaders.py#L57

This should probably be `posixpath.join` because the resulting location is used in a URL. I don't know if this will influence something else though. 

Resolving patch:
diff --git a/CTFd/utils/uploads/uploaders.py b/CTFd/utils/uploads/uploaders.py
--- a/CTFd/utils/uploads/uploaders.py
+++ b/CTFd/utils/uploads/uploaders.py
@@ -1,4 +1,5 @@
 import os
+import posixpath
 import string
 from shutil import copyfileobj
 
@@ -54,7 +55,7 @@ def upload(self, file_obj, filename):
 
         filename = secure_filename(filename)
         md5hash = hexencode(os.urandom(16))
-        file_path = os.path.join(md5hash, filename)
+        file_path = posixpath.join(md5hash, filename)
 
         return self.store(file_obj, file_path)
 


Test patch: