mirror of
https://github.com/boringproxy/boringproxy.git
synced 2025-02-25 18:55:29 -06:00
Fix some security issues
* The CSS styles were leaking information about tunnels, even for things like the login page, which can be sent to anyone. * Tokens could be created for any user by any user.
This commit is contained in:
parent
8e8045cde7
commit
b3f1636be6
22
api.go
22
api.go
@ -160,6 +160,28 @@ func (a *Api) DeleteTunnel(tokenData TokenData, params url.Values) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Api) CreateToken(tokenData TokenData, params url.Values) (string, error) {
|
||||
|
||||
owner := params.Get("owner")
|
||||
if owner == "" {
|
||||
return "", errors.New("Invalid owner paramater")
|
||||
}
|
||||
|
||||
if tokenData.Owner != owner {
|
||||
user, _ := a.db.GetUser(tokenData.Owner)
|
||||
if !user.IsAdmin {
|
||||
return "", errors.New("Unauthorized")
|
||||
}
|
||||
}
|
||||
|
||||
token, err := a.db.AddToken(owner)
|
||||
if err != nil {
|
||||
return "", errors.New("Failed to create token")
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func (a *Api) handleTunnels(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
token, err := extractToken("access_token", r)
|
||||
|
4
todo.md
4
todo.md
@ -1,7 +1,7 @@
|
||||
# 31 Oct 2020 Launch List
|
||||
|
||||
- [ ] I think it's possible to create tokens for arbitrary user, even if you're not that user.
|
||||
- [ ] Responses to unauthorized requests are leaking information about the current tunnels through the genereated CSS.
|
||||
- [ ] QR codes for admin are broken
|
||||
- [ ] General security review.
|
||||
- [ ] Invalid database is wiping out tunnels
|
||||
- [ ] Improve SSH key download UI.
|
||||
@ -11,6 +11,8 @@
|
||||
- [ ] Demo video
|
||||
- [ ] Demo auto email signup
|
||||
- [ ] Post on /r/selfhosted
|
||||
- [x] Head can be rendered before h.headHtml is ever set, ie if login page is visited before any other page
|
||||
- [x] Responses to unauthorized requests are leaking information about the current tunnels through the genereated CSS.
|
||||
|
||||
|
||||
# Eventually
|
||||
|
104
ui_handler.go
104
ui_handler.go
@ -72,10 +72,6 @@ type HeadData struct {
|
||||
Styles template.CSS
|
||||
}
|
||||
|
||||
type StylesData struct {
|
||||
Tunnels map[string]Tunnel
|
||||
}
|
||||
|
||||
type MenuData struct {
|
||||
IsAdmin bool
|
||||
}
|
||||
@ -107,6 +103,39 @@ func (h *WebUiHandler) handleWebUiRequest(w http.ResponseWriter, r *http.Request
|
||||
|
||||
homePath := "/#/tunnel"
|
||||
|
||||
// Note: h.box and h.headHtml need to be ready before pretty much
|
||||
// everything else, including sendLoginPage
|
||||
|
||||
box, err := rice.FindBox("webui")
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
io.WriteString(w, "Error opening webui")
|
||||
return
|
||||
}
|
||||
h.box = box
|
||||
|
||||
stylesText, err := box.String("styles.css")
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
io.WriteString(w, "Error reading styles.css")
|
||||
return
|
||||
}
|
||||
headTmplStr, err := box.String("head.tmpl")
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
io.WriteString(w, "Error reading head.tmpl")
|
||||
return
|
||||
}
|
||||
headTmpl, err := template.New("head").Parse(headTmplStr)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
io.WriteString(w, "Error compiling head.tmpl")
|
||||
return
|
||||
}
|
||||
var headBuilder strings.Builder
|
||||
headTmpl.Execute(&headBuilder, HeadData{Styles: template.CSS(stylesText)})
|
||||
h.headHtml = template.HTML(headBuilder.String())
|
||||
|
||||
token, err := extractToken("access_token", r)
|
||||
if err != nil {
|
||||
h.sendLoginPage(w, r, 401)
|
||||
@ -121,29 +150,6 @@ func (h *WebUiHandler) handleWebUiRequest(w http.ResponseWriter, r *http.Request
|
||||
|
||||
user, _ := h.db.GetUser(tokenData.Owner)
|
||||
|
||||
box, err := rice.FindBox("webui")
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
io.WriteString(w, "Error opening webui")
|
||||
return
|
||||
}
|
||||
|
||||
h.box = box
|
||||
|
||||
stylesText, err := box.String("styles.css")
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
io.WriteString(w, "Error reading styles.css")
|
||||
return
|
||||
}
|
||||
|
||||
stylesTmpl, err := template.New("styles").Parse(stylesText)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
h.alertDialog(w, r, err.Error(), homePath)
|
||||
return
|
||||
}
|
||||
|
||||
tunnels := h.api.GetTunnels(tokenData)
|
||||
|
||||
for domain, tun := range tunnels {
|
||||
@ -152,28 +158,6 @@ func (h *WebUiHandler) handleWebUiRequest(w http.ResponseWriter, r *http.Request
|
||||
tunnels[domain] = tun
|
||||
}
|
||||
|
||||
var stylesBuilder strings.Builder
|
||||
stylesTmpl.Execute(&stylesBuilder, StylesData{Tunnels: tunnels})
|
||||
|
||||
headTmplStr, err := box.String("head.tmpl")
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
io.WriteString(w, "Error reading head.tmpl")
|
||||
return
|
||||
}
|
||||
|
||||
headTmpl, err := template.New("head").Parse(headTmplStr)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
io.WriteString(w, "Error compiling head.tmpl")
|
||||
return
|
||||
}
|
||||
|
||||
var headBuilder strings.Builder
|
||||
headTmpl.Execute(&headBuilder, HeadData{Styles: template.CSS(stylesBuilder.String())})
|
||||
|
||||
h.headHtml = template.HTML(headBuilder.String())
|
||||
|
||||
switch r.URL.Path {
|
||||
case "/login":
|
||||
h.handleLogin(w, r)
|
||||
@ -396,26 +380,10 @@ func (h *WebUiHandler) handleTokens(w http.ResponseWriter, r *http.Request, user
|
||||
|
||||
r.ParseForm()
|
||||
|
||||
if len(r.Form["owner"]) != 1 {
|
||||
w.WriteHeader(400)
|
||||
h.alertDialog(w, r, "Invalid owner parameter", "/#/tokens")
|
||||
return
|
||||
}
|
||||
owner := r.Form["owner"][0]
|
||||
|
||||
users := h.db.GetUsers()
|
||||
|
||||
_, exists := users[owner]
|
||||
if !exists {
|
||||
w.WriteHeader(400)
|
||||
h.alertDialog(w, r, "Owner doesn't exist", "/#/tokens")
|
||||
return
|
||||
}
|
||||
|
||||
_, err := h.db.AddToken(owner)
|
||||
_, err := h.api.CreateToken(tokenData, r.Form)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
h.alertDialog(w, r, "Failed creating token", "/#/tokens")
|
||||
h.alertDialog(w, r, err.Error(), "/#/tokens")
|
||||
return
|
||||
}
|
||||
|
||||
@ -578,7 +546,7 @@ func (h *WebUiHandler) sendLoginPage(w http.ResponseWriter, r *http.Request, cod
|
||||
return
|
||||
}
|
||||
|
||||
loginTemplate, err := template.New("test").Parse(loginTemplateStr)
|
||||
loginTemplate, err := template.New("login").Parse(loginTemplateStr)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
io.WriteString(w, "Error compiling login.tmpl")
|
||||
|
@ -2,6 +2,29 @@
|
||||
<html>
|
||||
<head>
|
||||
{{.Head}}
|
||||
|
||||
<style>
|
||||
{{range $domain, $tunnel:= .Tunnels}}
|
||||
#toggle-tunnel-delete-dialog-{{$tunnel.CssId}} {
|
||||
display: none;
|
||||
}
|
||||
#toggle-tunnel-delete-dialog-{{$tunnel.CssId}}:checked + .dialog {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#toggle-tunnel-hide-deleted-{{$tunnel.CssId}}:checked + .list-item {
|
||||
/* This is a trick to make the delete request after the delete button is
|
||||
* clicked. The background will never actually be displayed, because it's
|
||||
* moved offscreen. */
|
||||
position: absolute;
|
||||
left: -999em;
|
||||
background: url("/delete-tunnel?domain={{$domain}}");
|
||||
}
|
||||
#toggle-tunnel-hide-deleted-{{$tunnel.CssId}}:checked ~ .dialog {
|
||||
display: none;
|
||||
}
|
||||
{{end}}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -229,27 +229,6 @@ main *:target {
|
||||
z-index: 1010;
|
||||
}
|
||||
|
||||
{{range $domain, $tunnel:= .Tunnels}}
|
||||
#toggle-tunnel-delete-dialog-{{$tunnel.CssId}} {
|
||||
display: none;
|
||||
}
|
||||
#toggle-tunnel-delete-dialog-{{$tunnel.CssId}}:checked + .dialog {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#toggle-tunnel-hide-deleted-{{$tunnel.CssId}}:checked + .list-item {
|
||||
/* This is a trick to make the delete request after the delete button is
|
||||
* clicked. The background will never actually be displayed, because it's
|
||||
* moved offscreen. */
|
||||
position: absolute;
|
||||
left: -999em;
|
||||
background: url("/delete-tunnel?domain={{$domain}}");
|
||||
}
|
||||
#toggle-tunnel-hide-deleted-{{$tunnel.CssId}}:checked ~ .dialog {
|
||||
display: none;
|
||||
}
|
||||
{{end}}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
|
||||
main {
|
||||
|
Loading…
Reference in New Issue
Block a user