Skip to content

Commit e9ab642

Browse files
authored
Merge pull request src-d#163 from camathieu/1.2
1.2 security patch
2 parents daeb202 + c87fdf6 commit e9ab642

File tree

8 files changed

+92
-34
lines changed

8 files changed

+92
-34
lines changed

.travis.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
language: go
22

33
go:
4-
- 1.5.2
4+
- 1.6.2
55

66
before_install:
77
- npm install -g bower
@@ -10,4 +10,4 @@ before_script:
1010
- go get -u github.com/golang/lint/golint
1111

1212
script:
13-
- make test && make
13+
- make test && make

README.md

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -127,30 +127,6 @@ curl -s 'https://127.0.0.1:8080/file/0KfNj6eMb93ilCrl/q73tEBEqM04b22GP/mydirecto
127127

128128
Client configuration and preferences are stored at ~/.plikrc ( overridable with PLIKRC environement variable )
129129

130-
### Authentication
131-
132-
Plik can authenticate users using Google and/or OVH API.
133-
Once authenticated the only call Plik will ever make to those API is get the user ID, name and email.
134-
Plik will never forward any upload data or metadata to any third party.
135-
If source IP address restriction is enabled, user accounts can only be created from trusted IPs. But then
136-
authenticated users can upload files without source IP restriction.
137-
138-
- **Google** :
139-
- You'll need to create a new application in the [Google Developper Console](https://console.developers.google.com)
140-
- You'll be handed a Google API ClientID and a Google API ClientSecret that you'll need to put in the plikd.cfg file.
141-
- Do not forget to whitelist valid origin and redirect url ( https://yourdomain/auth/google/callback ) for your domain.
142-
143-
- **OVH** :
144-
- You'll need to create a new application in the OVH API : https://eu.api.ovh.com/createApp/
145-
- You'll be handed an OVH application key and an OVH application secret key that you'll need to put in the plikd.cfg file.
146-
147-
Once authenticated a user can generate upload tokens that can be specified in the ~/.plikrc file to authenticate
148-
the command line client.
149-
150-
```
151-
Token = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
152-
```
153-
154130
### Available data backends
155131

156132
Plik is shipped with multiple data backend for uploaded files and metadata backend for the upload metadata.
@@ -195,6 +171,36 @@ Only suitable for a single instance deployment as the Bolt database can only be
195171

196172
Suitable for distributed / High Availability deployment.
197173

174+
### Authentication
175+
176+
Plik can authenticate users using Google and/or OVH API.
177+
Once authenticated the only call Plik will ever make to those API is get the user ID, name and email.
178+
Plik will never forward any upload data or metadata to any third party.
179+
If source IP address restriction is enabled, user accounts can only be created from trusted IPs. But then
180+
authenticated users can upload files without source IP restriction.
181+
182+
- **Google** :
183+
- You'll need to create a new application in the [Google Developper Console](https://console.developers.google.com)
184+
- You'll be handed a Google API ClientID and a Google API ClientSecret that you'll need to put in the plikd.cfg file.
185+
- Do not forget to whitelist valid origin and redirect url ( https://yourdomain/auth/google/callback ) for your domain.
186+
187+
- **OVH** :
188+
- You'll need to create a new application in the OVH API : https://eu.api.ovh.com/createApp/
189+
- You'll be handed an OVH application key and an OVH application secret key that you'll need to put in the plikd.cfg file.
190+
191+
Once authenticated a user can generate upload tokens that can be specified in the ~/.plikrc file to authenticate
192+
the command line client.
193+
194+
```
195+
Token = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
196+
```
197+
198+
### Security
199+
Plik allow users to upload and serve any content as-is, but hosting untrusted HTML raises some well known security concerns.
200+
Plik will try to avoid HTML rendering by overriding Content-Type to "text-plain" instead of "text/html".
201+
Also the [Content-Security-Policy](https://content-security-policy.com/) HTTP header should disable sensible features of most recent browsers like resource loading, xhr requests, iframes,...
202+
Along with that it is still strongly advised to serve uploaded files on a separate (sub-)domain to fight against phishing links and to protect Plik's session cookie with the DownloadDomain configuration parameter.
203+
198204
### API
199205
Plik server expose a HTTP API to manage uploads and get files :
200206

client/plik.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,14 @@ func getFileURL(upload *common.Upload, file *common.File) (fileURL string) {
426426
mode = "stream"
427427
}
428428

429-
fileURL += fmt.Sprintf("%s/%s/%s/%s/%s", config.Config.URL, mode, upload.ID, file.ID, file.Name)
429+
var domain string
430+
if upload.DownloadDomain != "" {
431+
domain = upload.DownloadDomain
432+
} else {
433+
domain = config.Config.URL
434+
}
435+
436+
fileURL += fmt.Sprintf("%s/%s/%s/%s/%s", domain, mode, upload.ID, file.ID, file.Name)
430437

431438
// Parse to get a nice escaped url
432439
u, err := url.Parse(fileURL)

server/common/config.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ package common
3131

3232
import (
3333
"net"
34+
"net/url"
3435
"strings"
3536

3637
"github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/BurntSushi/toml"
@@ -52,6 +53,9 @@ type Configuration struct {
5253
SslCert string `json:"-"`
5354
SslKey string `json:"-"`
5455

56+
DownloadDomain string `json:"downloadDomain"`
57+
DownloadDomainURL *url.URL `json:"-"`
58+
5559
YubikeyEnabled bool `json:"yubikeyEnabled"`
5660
YubikeyAPIKey string `json:"-"`
5761
YubikeyAPISecret string `json:"-"`
@@ -168,5 +172,13 @@ func LoadConfiguration(file string) {
168172
Config.Authentication = false
169173
}
170174

175+
if Config.DownloadDomain != "" {
176+
strings.Trim(Config.DownloadDomain, "/ ")
177+
var err error
178+
if Config.DownloadDomainURL, err = url.Parse(Config.DownloadDomain); err != nil {
179+
Logger().Fatalf("Invalid download domain URL %s : %s", Config.DownloadDomain, err)
180+
}
181+
}
182+
171183
Logger().Dump(logger.DEBUG, Config)
172184
}

server/common/upload.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,13 @@ var (
4141

4242
// Upload object
4343
type Upload struct {
44-
ID string `json:"id" bson:"id"`
45-
Creation int64 `json:"uploadDate" bson:"uploadDate"`
46-
TTL int `json:"ttl" bson:"ttl"`
47-
ShortURL string `json:"shortUrl" bson:"shortUrl"`
48-
RemoteIP string `json:"uploadIp,omitempty" bson:"uploadIp"`
49-
Comments string `json:"comments" bson:"comments"`
44+
ID string `json:"id" bson:"id"`
45+
Creation int64 `json:"uploadDate" bson:"uploadDate"`
46+
TTL int `json:"ttl" bson:"ttl"`
47+
ShortURL string `json:"shortUrl" bson:"shortUrl"`
48+
DownloadDomain string `json:"downloadDomain" bson:"-"`
49+
RemoteIP string `json:"uploadIp,omitempty" bson:"uploadIp"`
50+
Comments string `json:"comments" bson:"comments"`
5051

5152
Files map[string]*File `json:"files" bson:"files"`
5253

@@ -98,6 +99,7 @@ func (upload *Upload) Sanitize() {
9899
for _, file := range upload.Files {
99100
file.Sanitize()
100101
}
102+
upload.DownloadDomain = Config.DownloadDomain
101103
}
102104

103105
// GenerateRandomID generates a random string with specified length.

server/handlers/getFile.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
"io"
3535
"net/http"
3636
"strconv"
37+
"strings"
3738

3839
"github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/gorilla/mux"
3940
"github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/root-gg/juliet"
@@ -46,6 +47,18 @@ import (
4647
func GetFile(ctx *juliet.Context, resp http.ResponseWriter, req *http.Request) {
4748
log := common.GetLogger(ctx)
4849

50+
// If a download domain is specified verify that the request comes from this specific domain
51+
if common.Config.DownloadDomainURL != nil {
52+
if req.Host != common.Config.DownloadDomainURL.Host {
53+
downloadURL := fmt.Sprintf("%s://%s/%s",
54+
common.Config.DownloadDomainURL.Scheme,
55+
common.Config.DownloadDomainURL.Host,
56+
req.RequestURI)
57+
http.Redirect(resp, req, downloadURL, 301)
58+
return
59+
}
60+
}
61+
4962
// Get upload from context
5063
upload := common.GetUpload(ctx)
5164
if upload == nil {
@@ -119,8 +132,24 @@ func GetFile(ctx *juliet.Context, resp http.ResponseWriter, req *http.Request) {
119132
}
120133
}
121134

135+
// Avoid rendering HTML in browser
136+
if strings.Contains(file.Type, "html") {
137+
file.Type = "text/plain"
138+
}
139+
140+
if file.Type == "" || strings.Contains(file.Type, "flash") {
141+
file.Type = "application/octet-stream"
142+
}
143+
122144
// Set content type and print file
123145
resp.Header().Set("Content-Type", file.Type)
146+
147+
/* Additional security headers for possibly unsafe content */
148+
resp.Header().Set("X-Content-Type-Options", "nosniff")
149+
resp.Header().Set("X-XSS-Protection", "1; mode=block")
150+
resp.Header().Set("X-Frame-Options", "DENY")
151+
resp.Header().Set("Content-Security-Policy", "default-src 'none'; script-src 'none'; style-src 'none'; img-src 'none'; connect-src 'none'; font-src 'none'; object-src 'none'; media-src 'none'; child-src 'none'; form-action 'none'; frame-ancestors 'none'; plugin-types ''; sandbox ''")
152+
124153
if file.CurrentSize > 0 {
125154
resp.Header().Set("Content-Length", strconv.Itoa(int(file.CurrentSize)))
126155
}

server/plikd.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ MaxTTL = 2592000 # -1 => No limit
1818
SslEnabled = false
1919
SslCert = "" # Path to your certificate file
2020
SslKey = "" # Path to your certificate private key file
21+
DownloadDomain = "" # Enforce download domain ( ex: https://dl.plik.root.gg )
2122

2223
YubikeyEnabled = false # Enable Yubikey Functionnality
2324
YubikeyAPIKey = "" # Yubikey API Key (get one on https://upgrade.yubico.com/getapikey/)

server/public/js/app.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -662,7 +662,8 @@ function MainCtrl($scope, $api, $config, $route, $location, $dialog) {
662662
$scope.getFileUrl = function (file, dl) {
663663
if (!file || !file.metadata) return;
664664
var mode = $scope.upload.stream ? "stream" : "file";
665-
var url = location.origin + '/' + mode + '/' + $scope.upload.id + '/' + file.metadata.id + '/' + file.metadata.fileName;
665+
var domain = $scope.config.downloadDomain ? $scope.config.downloadDomain : location.origin;
666+
var url = domain + '/' + mode + '/' + $scope.upload.id + '/' + file.metadata.id + '/' + file.metadata.fileName;
666667
if (dl) {
667668
// Force file download
668669
url += "?dl=1";

0 commit comments

Comments
 (0)