Skip to content

Commit

Permalink
Merge pull request #163 from camathieu/1.2
Browse files Browse the repository at this point in the history
1.2 security patch
  • Loading branch information
bodji authored Jul 18, 2016
2 parents daeb202 + c87fdf6 commit e9ab642
Show file tree
Hide file tree
Showing 8 changed files with 92 additions and 34 deletions.
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
language: go

go:
- 1.5.2
- 1.6.2

before_install:
- npm install -g bower
Expand All @@ -10,4 +10,4 @@ before_script:
- go get -u github.com/golang/lint/golint

script:
- make test && make
- make test && make
54 changes: 30 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,30 +127,6 @@ curl -s 'https://127.0.0.1:8080/file/0KfNj6eMb93ilCrl/q73tEBEqM04b22GP/mydirecto

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

### Authentication

Plik can authenticate users using Google and/or OVH API.
Once authenticated the only call Plik will ever make to those API is get the user ID, name and email.
Plik will never forward any upload data or metadata to any third party.
If source IP address restriction is enabled, user accounts can only be created from trusted IPs. But then
authenticated users can upload files without source IP restriction.

- **Google** :
- You'll need to create a new application in the [Google Developper Console](https://console.developers.google.com)
- You'll be handed a Google API ClientID and a Google API ClientSecret that you'll need to put in the plikd.cfg file.
- Do not forget to whitelist valid origin and redirect url ( https://yourdomain/auth/google/callback ) for your domain.

- **OVH** :
- You'll need to create a new application in the OVH API : https://eu.api.ovh.com/createApp/
- 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.

Once authenticated a user can generate upload tokens that can be specified in the ~/.plikrc file to authenticate
the command line client.

```
Token = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
```

### Available data backends

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

Suitable for distributed / High Availability deployment.

### Authentication

Plik can authenticate users using Google and/or OVH API.
Once authenticated the only call Plik will ever make to those API is get the user ID, name and email.
Plik will never forward any upload data or metadata to any third party.
If source IP address restriction is enabled, user accounts can only be created from trusted IPs. But then
authenticated users can upload files without source IP restriction.

- **Google** :
- You'll need to create a new application in the [Google Developper Console](https://console.developers.google.com)
- You'll be handed a Google API ClientID and a Google API ClientSecret that you'll need to put in the plikd.cfg file.
- Do not forget to whitelist valid origin and redirect url ( https://yourdomain/auth/google/callback ) for your domain.

- **OVH** :
- You'll need to create a new application in the OVH API : https://eu.api.ovh.com/createApp/
- 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.

Once authenticated a user can generate upload tokens that can be specified in the ~/.plikrc file to authenticate
the command line client.

```
Token = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
```

### Security
Plik allow users to upload and serve any content as-is, but hosting untrusted HTML raises some well known security concerns.
Plik will try to avoid HTML rendering by overriding Content-Type to "text-plain" instead of "text/html".
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,...
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.

### API
Plik server expose a HTTP API to manage uploads and get files :

Expand Down
9 changes: 8 additions & 1 deletion client/plik.go
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,14 @@ func getFileURL(upload *common.Upload, file *common.File) (fileURL string) {
mode = "stream"
}

fileURL += fmt.Sprintf("%s/%s/%s/%s/%s", config.Config.URL, mode, upload.ID, file.ID, file.Name)
var domain string
if upload.DownloadDomain != "" {
domain = upload.DownloadDomain
} else {
domain = config.Config.URL
}

fileURL += fmt.Sprintf("%s/%s/%s/%s/%s", domain, mode, upload.ID, file.ID, file.Name)

// Parse to get a nice escaped url
u, err := url.Parse(fileURL)
Expand Down
12 changes: 12 additions & 0 deletions server/common/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ package common

import (
"net"
"net/url"
"strings"

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

DownloadDomain string `json:"downloadDomain"`
DownloadDomainURL *url.URL `json:"-"`

YubikeyEnabled bool `json:"yubikeyEnabled"`
YubikeyAPIKey string `json:"-"`
YubikeyAPISecret string `json:"-"`
Expand Down Expand Up @@ -168,5 +172,13 @@ func LoadConfiguration(file string) {
Config.Authentication = false
}

if Config.DownloadDomain != "" {
strings.Trim(Config.DownloadDomain, "/ ")
var err error
if Config.DownloadDomainURL, err = url.Parse(Config.DownloadDomain); err != nil {
Logger().Fatalf("Invalid download domain URL %s : %s", Config.DownloadDomain, err)
}
}

Logger().Dump(logger.DEBUG, Config)
}
14 changes: 8 additions & 6 deletions server/common/upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,13 @@ var (

// Upload object
type Upload struct {
ID string `json:"id" bson:"id"`
Creation int64 `json:"uploadDate" bson:"uploadDate"`
TTL int `json:"ttl" bson:"ttl"`
ShortURL string `json:"shortUrl" bson:"shortUrl"`
RemoteIP string `json:"uploadIp,omitempty" bson:"uploadIp"`
Comments string `json:"comments" bson:"comments"`
ID string `json:"id" bson:"id"`
Creation int64 `json:"uploadDate" bson:"uploadDate"`
TTL int `json:"ttl" bson:"ttl"`
ShortURL string `json:"shortUrl" bson:"shortUrl"`
DownloadDomain string `json:"downloadDomain" bson:"-"`
RemoteIP string `json:"uploadIp,omitempty" bson:"uploadIp"`
Comments string `json:"comments" bson:"comments"`

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

Expand Down Expand Up @@ -98,6 +99,7 @@ func (upload *Upload) Sanitize() {
for _, file := range upload.Files {
file.Sanitize()
}
upload.DownloadDomain = Config.DownloadDomain
}

// GenerateRandomID generates a random string with specified length.
Expand Down
29 changes: 29 additions & 0 deletions server/handlers/getFile.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"io"
"net/http"
"strconv"
"strings"

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

// If a download domain is specified verify that the request comes from this specific domain
if common.Config.DownloadDomainURL != nil {
if req.Host != common.Config.DownloadDomainURL.Host {
downloadURL := fmt.Sprintf("%s://%s/%s",
common.Config.DownloadDomainURL.Scheme,
common.Config.DownloadDomainURL.Host,
req.RequestURI)
http.Redirect(resp, req, downloadURL, 301)
return
}
}

// Get upload from context
upload := common.GetUpload(ctx)
if upload == nil {
Expand Down Expand Up @@ -119,8 +132,24 @@ func GetFile(ctx *juliet.Context, resp http.ResponseWriter, req *http.Request) {
}
}

// Avoid rendering HTML in browser
if strings.Contains(file.Type, "html") {
file.Type = "text/plain"
}

if file.Type == "" || strings.Contains(file.Type, "flash") {
file.Type = "application/octet-stream"
}

// Set content type and print file
resp.Header().Set("Content-Type", file.Type)

/* Additional security headers for possibly unsafe content */
resp.Header().Set("X-Content-Type-Options", "nosniff")
resp.Header().Set("X-XSS-Protection", "1; mode=block")
resp.Header().Set("X-Frame-Options", "DENY")
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 ''")

if file.CurrentSize > 0 {
resp.Header().Set("Content-Length", strconv.Itoa(int(file.CurrentSize)))
}
Expand Down
1 change: 1 addition & 0 deletions server/plikd.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ MaxTTL = 2592000 # -1 => No limit
SslEnabled = false
SslCert = "" # Path to your certificate file
SslKey = "" # Path to your certificate private key file
DownloadDomain = "" # Enforce download domain ( ex: https://dl.plik.root.gg )

YubikeyEnabled = false # Enable Yubikey Functionnality
YubikeyAPIKey = "" # Yubikey API Key (get one on https://upgrade.yubico.com/getapikey/)
Expand Down
3 changes: 2 additions & 1 deletion server/public/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -662,7 +662,8 @@ function MainCtrl($scope, $api, $config, $route, $location, $dialog) {
$scope.getFileUrl = function (file, dl) {
if (!file || !file.metadata) return;
var mode = $scope.upload.stream ? "stream" : "file";
var url = location.origin + '/' + mode + '/' + $scope.upload.id + '/' + file.metadata.id + '/' + file.metadata.fileName;
var domain = $scope.config.downloadDomain ? $scope.config.downloadDomain : location.origin;
var url = domain + '/' + mode + '/' + $scope.upload.id + '/' + file.metadata.id + '/' + file.metadata.fileName;
if (dl) {
// Force file download
url += "?dl=1";
Expand Down

0 comments on commit e9ab642

Please sign in to comment.