frontend: filter output to prevent XSS

This commit is contained in:
Lan Tian 2021-01-17 01:14:49 +08:00
parent 90e5012840
commit 72946e1113
No known key found for this signature in database
GPG Key ID: 3D2E9DC81E5791C7
6 changed files with 24 additions and 19 deletions

View File

@ -2,6 +2,7 @@ package main
import ( import (
"fmt" "fmt"
"html"
"net" "net"
"strings" "strings"
) )
@ -24,7 +25,7 @@ func birdRouteToGraphviz(servers []string, responses []string, target string) st
graph := make(map[string]string) graph := make(map[string]string)
// Helper to add an edge // Helper to add an edge
addEdge := func(src string, dest string, attr string) { addEdge := func(src string, dest string, attr string) {
key := "\"" + src + "\" -> \"" + dest + "\"" key := "\"" + html.EscapeString(src) + "\" -> \"" + html.EscapeString(dest) + "\""
_, present := graph[key] _, present := graph[key]
// Do not remove edge's attributes if it's already present // Do not remove edge's attributes if it's already present
if present && len(attr) == 0 { if present && len(attr) == 0 {
@ -34,7 +35,7 @@ func birdRouteToGraphviz(servers []string, responses []string, target string) st
} }
// Helper to set attribute for a point in graph // Helper to set attribute for a point in graph
addPoint := func(name string, attr string) { addPoint := func(name string, attr string) {
key := "\"" + name + "\"" key := "\"" + html.EscapeString(name) + "\""
_, present := graph[key] _, present := graph[key]
// Do not remove point's attributes if it's already present // Do not remove point's attributes if it's already present
if present && len(attr) == 0 { if present && len(attr) == 0 {

View File

@ -1,4 +1,6 @@
<h2>BGPmap: {{ html .Target }}</h2> <h2>BGPmap: {{ html .Target }}</h2>
<div id="bgpmap">
</div>
<script src="https://cdn.jsdelivr.net/npm/viz.js@2.1.2/viz.min.js" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/viz.js@2.1.2/viz.min.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/viz.js@2.1.2/lite.render.js" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/viz.js@2.1.2/lite.render.js" crossorigin="anonymous"></script>
@ -6,9 +8,9 @@
var viz = new Viz(); var viz = new Viz();
viz.renderSVGElement(`{{ .Result }}`) viz.renderSVGElement(`{{ .Result }}`)
.then(element => { .then(element => {
document.body.appendChild(element); document.getElementById("bgpmap").appendChild(element);
}) })
.catch(error => { .catch(error => {
document.body.innerHTML = "<pre>"+error+"</pre>" document.getElementById("bgpmap").innerHTML = "<pre>"+error+"</pre>"
}); });
</script> </script>

View File

@ -5,7 +5,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<meta name="renderer" content="webkit"> <meta name="renderer" content="webkit">
<title>{{ .Title }}</title> <title>{{ html .Title }}</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.1/dist/css/bootstrap.min.css" integrity="sha256-VoFZSlmyTXsegReQCNmbXrS4hBBUl/cexZvPmPWoJsY=" crossorigin="anonymous"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.1/dist/css/bootstrap.min.css" integrity="sha256-VoFZSlmyTXsegReQCNmbXrS4hBBUl/cexZvPmPWoJsY=" crossorigin="anonymous">
<meta name="robots" content="noindex, nofollow"> <meta name="robots" content="noindex, nofollow">
</head> </head>
@ -29,12 +29,12 @@
<ul class="navbar-nav mr-auto"> <ul class="navbar-nav mr-auto">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link{{ if .AllServersLinkActive }} active{{ end }}" <a class="nav-link{{ if .AllServersLinkActive }} active{{ end }}"
href="/{{ $option }}/{{ .AllServersURL }}/{{ $target }}"> All Servers </a> href="/{{ urlquery $option }}/{{ urlquery .AllServersURL }}/{{ urlquery $target }}"> All Servers </a>
</li> </li>
{{ range $k, $v := .Servers }} {{ range $k, $v := .Servers }}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link{{ if eq $server $v }} active{{ end }}" <a class="nav-link{{ if eq $server $v }} active{{ end }}"
href="/{{ $option }}/{{ $v }}/{{ $target }}">{{ $v }}</a> href="/{{ urlquery $option }}/{{ urlquery $v }}/{{ urlquery $target }}">{{ html $v }}</a>
</li> </li>
{{ end }} {{ end }}
</ul> </ul>
@ -45,11 +45,11 @@
<div class="input-group"> <div class="input-group">
<select name="action" class="form-control"> <select name="action" class="form-control">
{{ range $k, $v := .Options }} {{ range $k, $v := .Options }}
<option value="{{ $k }}"{{ if eq $k $.URLOption }} selected{{end}}>{{ $v }}</option> <option value="{{ html $k }}"{{ if eq $k $.URLOption }} selected{{end}}>{{ html $v }}</option>
{{ end }} {{ end }}
</select> </select>
<input name="server" class="d-none" value="{{ $server }}"> <input name="server" class="d-none" value="{{ html $server }}">
<input name="target" class="form-control" placeholder="Target" aria-label="Target" value="{{ $target }}"> <input name="target" class="form-control" placeholder="Target" aria-label="Target" value="{{ html $target }}">
<div class="input-group-append"> <div class="input-group-append">
<button class="btn btn-outline-success" type="submit">&raquo;</button> <button class="btn btn-outline-success" type="submit">&raquo;</button>
</div> </div>

View File

@ -9,12 +9,12 @@
<tbody> <tbody>
{{ range .Rows }} {{ range .Rows }}
<tr class="table-{{ .MappedState }}"> <tr class="table-{{ .MappedState }}">
<td><a href="/detail/{{ $ServerName }}/{{ urlquery .Name }}">{{ html .Name }}</a></td> <td><a href="/detail/{{ urlquery $ServerName }}/{{ urlquery .Name }}">{{ html .Name }}</a></td>
<td>{{ .Proto }}</td> <td>{{ html .Proto }}</td>
<td>{{ .Table }}</td> <td>{{ html .Table }}</td>
<td>{{ .State }}</td> <td>{{ html .State }}</td>
<td>{{ .Since }}</td> <td>{{ html .Since }}</td>
<td>{{ .Info }}</td> <td>{{ html .Info }}</td>
</tr> </tr>
{{ end }} {{ end }}
</tbody> </tbody>

View File

@ -7,6 +7,7 @@ import (
"regexp" "regexp"
"sort" "sort"
"strings" "strings"
"text/template"
) )
// static options map // static options map
@ -81,6 +82,7 @@ func renderPageTemplate(w http.ResponseWriter, r *http.Request, title string, co
func smartFormatter(s string) string { func smartFormatter(s string) string {
var result string var result string
result += "<pre>" result += "<pre>"
s = template.HTMLEscapeString(s)
for _, line := range strings.Split(s, "\n") { for _, line := range strings.Split(s, "\n") {
var lineFormatted string var lineFormatted string
if strings.HasPrefix(strings.TrimSpace(line), "BGP.as_path:") || strings.HasPrefix(strings.TrimSpace(line), "Neighbor AS:") || strings.HasPrefix(strings.TrimSpace(line), "Local AS:") { if strings.HasPrefix(strings.TrimSpace(line), "BGP.as_path:") || strings.HasPrefix(strings.TrimSpace(line), "Neighbor AS:") || strings.HasPrefix(strings.TrimSpace(line), "Local AS:") {
@ -103,7 +105,7 @@ func summaryTable(data string, serverName string) string {
lines := strings.Split(strings.TrimSpace(data), "\n") lines := strings.Split(strings.TrimSpace(data), "\n")
if len(lines) <= 1 { if len(lines) <= 1 {
// Likely backend returned an error message // Likely backend returned an error message
return "<pre>" + strings.TrimSpace(data) + "</pre>" return "<pre>" + template.HTMLEscapeString(strings.TrimSpace(data)) + "</pre>"
} }
args := TemplateSummary{ args := TemplateSummary{

View File

@ -9,7 +9,7 @@ import (
"os" "os"
"strings" "strings"
"github.com/elazarl/go-bindata-assetfs" assetfs "github.com/elazarl/go-bindata-assetfs"
"github.com/gorilla/handlers" "github.com/gorilla/handlers"
) )
@ -124,7 +124,7 @@ func webBackendCommunicator(endpoint string, command string) func(w http.Respons
renderPageTemplate( renderPageTemplate(
w, r, w, r,
" - "+html.EscapeString(endpoint+" "+backendCommand), " - "+endpoint+" "+backendCommand,
content, content,
) )
} }