Skip to content

Commit a7ad4f5

Browse files
committed
Merge pull request #24 from dmitris/bindata
Standalone executable, binpacking of templates
2 parents 09aaf73 + b6bf1ff commit a7ad4f5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+2325
-407
lines changed

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,9 @@ In all tests, excepts where specially mentioned, the attack input is assumed to
6565

6666
* xss/dom/yuinode_hash_unencoded?#in=xyz - passing the unencoded hash value to YUI's setHTML function. PoE (Firefox / Chrome): /xss/dom/yuinode_hash?#in=xyz">/xss/dom/yuinode_hash?#in=xyz</A> - DOM XSS using YUI (decoded location.hash)
6767

68-
### Adding New Tests
68+
### Modifying Tests
69+
70+
When modifying, adding or deleting any tests, you need to rerun ```go generate```.
6971

7072
For most of the tests, you need to add a template that contains the "moustache" with {{.In}}.
7173

@@ -80,4 +82,4 @@ to the map in the FilterMap function in custom.go. For example:
8082

8183
To add a new fully custom testcase, add a template (if needed),
8284
add a mapping of the entrypoint to the handling function to CustomMap in custom.go and implement the custom function with the signature: func(http.ResponseWriter, *http.Request). For example, for a test case with XSS injection through the Morse code, you could add:
83-
```mp["/xss/reflect/morse"] = XssUnsafeMorse```
85+
```mp["/xss/reflect/morse"] = XssUnsafeMorse```. See entrypoints and implementing functions in CustomMap in custom.go.

webseclab/main.go renamed to cmd/webseclab/main.go

Lines changed: 12 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,9 @@ package main
99
import (
1010
"flag"
1111
"fmt"
12-
"log"
1312
"net"
1413
"net/http"
1514
"os"
16-
"path"
1715
"runtime"
1816

1917
"github.com/yahoo/webseclab"
@@ -26,46 +24,33 @@ func (fn indexHandler) ServerHTTP(w http.ResponseWriter, r *http.Request) {
2624
return
2725
}
2826

27+
const notice = `Attention: Webseclab is purposedly INSECURE software intended for testing and education. Use it at your own risk and be careful! Hit Ctrl-C now if you don't understand the risks invovled.
28+
29+
Webseclab executable includes the Go runtime which is covered by the Go license that can be found in http://golang.org/LICENSE. See https://github.com/yahoo/webseclab for the Webseclab source code, license, and additional information.`
30+
2931
func main() {
3032
// use up to 2 CPUs, not more
3133
cpus := runtime.NumCPU()
3234
if cpus >= 2 {
3335
cpus = 2
3436
}
3537
runtime.GOMAXPROCS(cpus)
36-
defaultBase, err := webseclab.TemplateBaseDefault()
37-
if err != nil {
38-
panic(err)
39-
}
40-
base := flag.String("base", defaultBase, "base path for webseclab templates")
38+
4139
port := flag.String("http", ":8080", "port to run the webserver on")
4240
noindex := flag.Bool("noindex", false, "do not serve the top index page (/ and /index.html)")
4341
cleanup := flag.Bool("cleanup", false, "cleanup only (terminate existing instance and exit)")
42+
version := flag.Bool("version", false, "display version number and exit")
4443
flag.Parse()
45-
fmt.Printf("Using %s as the template base, change it with -base command-line option (run 'webseclab -help' for usage help)\n", *base)
46-
var abspath string
47-
if path.IsAbs(*base) {
48-
abspath = *base
49-
} else {
50-
pwd, err := os.Getwd()
51-
if err != nil {
52-
panic(err)
53-
}
54-
abspath = path.Join(pwd, *base)
55-
}
56-
if err := webseclab.CheckPath(abspath); err != nil {
57-
log.Printf("Unable to open base %s: %s\n", abspath, err)
58-
os.Exit(1)
44+
if *version {
45+
fmt.Println(webseclab.WEBSECLAB_VERSION)
46+
os.Exit(0)
5947
}
48+
fmt.Println(notice, "\n")
6049
// if there is another webseclab server running, tell it to quit and make way for us
6150
webseclab.KillPredecessor(*port)
6251
if *cleanup {
6352
return
6453
}
65-
err = webseclab.ParseTemplates(abspath)
66-
if err != nil {
67-
panic(err)
68-
}
6954
ln, err := net.Listen("tcp", *port)
7055
if err != nil {
7156
panic(err)
@@ -74,12 +59,12 @@ func main() {
7459
fmt.Printf("Webseclab starts to listen on %s\n", *port)
7560

7661
if !*noindex {
77-
http.HandleFunc("/index.html", webseclab.MakeIndexFunc(*base, "/index.html"))
62+
http.HandleFunc("/index.html", webseclab.MakeIndexFunc("/index.html"))
7863
}
7964
http.HandleFunc("favicon.ico", func(w http.ResponseWriter, r *http.Request) {
8065
return
8166
})
82-
http.Handle("/", webseclab.MakeMainHandler(*base, *noindex))
67+
http.Handle("/", webseclab.MakeMainHandler(*noindex))
8368
http.HandleFunc("/exit", webseclab.MakeExitFunc(ln))
8469
http.HandleFunc("/ruok", webseclab.Ruok)
8570

genstatic.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Copyright 2015, Yahoo Inc. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// based on https://github.com/golang/tools/blob/master/godoc/static/makestatic.go
6+
// which carries the following copyright notice:
7+
8+
// Copyright 2013 The Go Authors. All rights reserved.
9+
// Use of this source code is governed by a BSD-style
10+
// license that can be found in the LICENSE file.
11+
12+
// The LICENSE file for the golang/tools repository can be found in
13+
// https://github.com/golang/tools/blob/master/LICENSE
14+
15+
// +build ignore
16+
17+
// Command genstatic reads a set of files (Webseclab templates)
18+
// and writes a Go source file to "static.go"
19+
// that declares a map of string constants containing contents of the input files.
20+
// It is intended to be invoked via "go generate" (directive in "templates.go").
21+
22+
package main
23+
24+
import (
25+
"bufio"
26+
"bytes"
27+
"errors"
28+
"fmt"
29+
"io/ioutil"
30+
"os"
31+
"path/filepath"
32+
"strings"
33+
"unicode/utf8"
34+
)
35+
36+
func main() {
37+
if err := makestatic(); err != nil {
38+
fmt.Fprintln(os.Stderr, err)
39+
os.Exit(1)
40+
}
41+
}
42+
43+
func makestatic() error {
44+
f, err := os.Create("static.go")
45+
if err != nil {
46+
return err
47+
}
48+
defer f.Close()
49+
w := bufio.NewWriter(f)
50+
fmt.Fprintf(w, "%v\n\npackage webseclab\n\n", warning)
51+
fmt.Fprintf(w, "var Templates = map[string]string{\n")
52+
base := "templates/"
53+
files, err := getTemplateFiles(base)
54+
for _, fn := range files {
55+
b, err := ioutil.ReadFile(base + fn)
56+
if err != nil {
57+
return err
58+
}
59+
fmt.Fprintf(w, "\t%q: ", fn)
60+
if utf8.Valid(b) {
61+
fmt.Fprintf(w, "`%s`", sanitize(b))
62+
} else {
63+
fmt.Fprintf(w, "%q", b)
64+
}
65+
fmt.Fprintln(w, ",")
66+
}
67+
68+
fmt.Fprintln(w, "}")
69+
if err := w.Flush(); err != nil {
70+
return err
71+
}
72+
return f.Close()
73+
}
74+
75+
// sanitize prepares a valid UTF-8 string as a raw string constant.
76+
func sanitize(b []byte) []byte {
77+
// Replace ` with `+"`"+`
78+
b = bytes.Replace(b, []byte("`"), []byte("`+\"`\"+`"), -1)
79+
80+
// Replace BOM with `+"\xEF\xBB\xBF"+`
81+
// (A BOM is valid UTF-8 but not permitted in Go source files.
82+
// I wouldn't bother handling this, but for some insane reason
83+
// jquery.js has a BOM somewhere in the middle.)
84+
return bytes.Replace(b, []byte("\xEF\xBB\xBF"), []byte("`+\"\\xEF\\xBB\\xBF\"+`"), -1)
85+
}
86+
87+
// copied from templates.go to prevent making it exported
88+
// getTemplateFiles collects all files under the base dir
89+
func getTemplateFiles(base string) (files []string, err error) {
90+
files = make([]string, 0, 50)
91+
visit := func(path string, f os.FileInfo, err error) error {
92+
if f == nil {
93+
return errors.New("File " + path + " does not exist or not readable")
94+
}
95+
// don't add common shared files
96+
if strings.HasSuffix(path, "common/footer") ||
97+
strings.HasSuffix(path, "common/header") {
98+
return nil
99+
}
100+
if f.IsDir() == false {
101+
files = append(files, strings.TrimPrefix(path, base))
102+
}
103+
return err
104+
}
105+
err = filepath.Walk(base, visit)
106+
return files, err
107+
}
108+
109+
const warning = "// DO NOT EDIT ** This file was generated by \"go generate\" (gen directive in templates.go) ** DO NOT EDIT //"

handler.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,15 @@
55
package webseclab
66

77
import (
8+
"errors"
9+
"fmt"
810
"html/template"
11+
"io"
912
"log"
13+
"net"
1014
"net/http"
1115
"strconv"
16+
"strings"
1217
)
1318

1419
// LabResp wraps the data related to processing results
@@ -48,3 +53,115 @@ func (fn LabHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
4853
return
4954
}
5055
}
56+
57+
// MakeMainHandler performs routing between 'standard' (template based with standard parameters) cases
58+
// and those requiring custom processing. For standard processing, it unescapes input and prepars an instance of Indata
59+
// which is then passed to the template execution. For URLs found in the map of custom processing,
60+
// the corresponding function is called.
61+
func MakeMainHandler(noindex bool) LabHandler {
62+
return func(w http.ResponseWriter, r *http.Request) *LabResp {
63+
// routing - handle a special case, path = "/" (=> index.html)
64+
// dump, _ := httputil.DumpRequest(r, true)
65+
// fmt.Printf("DEBUG request dump: %q\n", dump)
66+
indexFn := MakeIndexFunc("index.html")
67+
if r.URL.Path == "/" || r.URL.Path == "/index.html" {
68+
if noindex {
69+
return &LabResp{
70+
Err: errors.New("index page is prevented with -noindex option"),
71+
Code: http.StatusForbidden,
72+
}
73+
74+
}
75+
indexFn(w, r)
76+
return &LabResp{Err: nil, Code: http.StatusOK}
77+
}
78+
// be paranoid about the non-IP domains to protect cookies against XSS
79+
safe := IsSafeHost(r.Host)
80+
if safe == false {
81+
ipurl, err := GetIpUrl(r.Host, r.URL)
82+
if err != nil {
83+
return &LabResp{Err: errors.New("ERROR in GetIpUrl(" + r.URL.String() + "): " + err.Error()),
84+
Code: http.StatusInternalServerError}
85+
}
86+
return &LabResp{Err: errors.New(r.Host + " - not an IP quad pair"),
87+
Code: http.StatusFound,
88+
Redirect: ipurl.String()}
89+
}
90+
91+
// check if custom handling is needed
92+
funcmap := CustomMap()
93+
handler, ok := funcmap[r.URL.Path]
94+
if ok {
95+
return handler(w, r)
96+
}
97+
filtermap := FilterMap()
98+
filters, ok := filtermap[r.URL.Path]
99+
if ok {
100+
return HandleFilterBased(w, r, filters)
101+
}
102+
return DoLabTestStandard(w, r)
103+
}
104+
}
105+
106+
// ack to a ping: "ruok" => "imok\n" (for /ruok monitoring entrypoint)
107+
func Ruok(w http.ResponseWriter, r *http.Request) {
108+
w.Header().Set("Content-type", "text/plain; charset=utf-8")
109+
fmt.Fprintf(w, "imok\n")
110+
}
111+
112+
// MakeExitFunc creates an /exit handler
113+
func MakeExitFunc(ln net.Listener) func(http.ResponseWriter, *http.Request) {
114+
return func(w http.ResponseWriter, r *http.Request) {
115+
parts := strings.Split(r.RemoteAddr, ":")
116+
if len(parts) > 0 {
117+
if parts[0] != "127.0.0.1" && parts[0] != "localhost" {
118+
w.WriteHeader(http.StatusForbidden)
119+
io.WriteString(w, "Access denied. (/exit called from non-local IP: "+parts[0]+")\n")
120+
return
121+
}
122+
}
123+
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
124+
io.WriteString(w, `bye`)
125+
log.Println("Received exit request, exiting")
126+
ln.Close()
127+
}
128+
}
129+
130+
// MakeIndexFunc creates a function to display the index file (ToC)
131+
func MakeIndexFunc(page string) func(http.ResponseWriter, *http.Request) {
132+
return func(w http.ResponseWriter, r *http.Request) {
133+
// log.Printf("Request for %s from %s\n", r.URL.String(), r.RemoteAddr)
134+
var data = &InData{}
135+
// for UI, find out an Ip quad-pair link if we are not on a such already
136+
// this is done to protect cookies of our domain against XSS (see ip.go)
137+
if IsIp(r.Host) == false {
138+
iplink, err := GetIpUrl(r.Host, r.URL)
139+
if err != nil {
140+
w.WriteHeader(http.StatusInternalServerError)
141+
w.Write([]byte(`Internal Server Error`))
142+
log.Printf("ERROR - unable to find out my own IP address: r.Host = %s, r.URL = %s\n", r.Host, r.URL.String())
143+
return
144+
}
145+
data.In = iplink.Host
146+
}
147+
err := DoTemplate(w, "/index.html", data)
148+
if err != nil {
149+
w.WriteHeader(http.StatusInternalServerError)
150+
w.Write([]byte(`Internal Server Error`))
151+
return
152+
}
153+
return
154+
}
155+
}
156+
157+
// MakeStaticFunc
158+
func MakeStaticFunc() LabHandler {
159+
return func(w http.ResponseWriter, r *http.Request) *LabResp {
160+
err := DoTemplate(w, r.URL.Path, &InData{})
161+
if err != nil {
162+
log.Printf("Error in MakeStaticFunc, DoTemplate returned Err: %s\n", err)
163+
return &LabResp{Err: err, Code: http.StatusInternalServerError}
164+
}
165+
return &LabResp{Err: nil, Code: http.StatusOK}
166+
}
167+
}

process.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,13 @@ func DoTemplate(w http.ResponseWriter, path string, input *InData) (err error) {
8686

8787
func doHtmlTemplate(w http.ResponseWriter, fpath string, input *InData) (err error) {
8888
// html/template - context-sensitive escaping
89+
fmt.Printf("DMDEBUG - fpath=%s\n", fpath)
8990
tmpl, ok := LookupHtmlTemplate(fpath)
9091
if ok == false {
9192
return errors.New("Error in DoTemplate - html template " + fpath + " not found.")
9293
}
9394
w.Header().Set("Content-type", "text/html; charset=utf-8")
94-
parts := strings.Split(fpath, "/")
95-
err = tmpl.ExecuteTemplate(w, parts[len(parts)-1], *input)
95+
err = tmpl.ExecuteTemplate(w, fpath, *input)
9696
if err != nil {
9797
log.Printf("Error in DoTemplate (html) - tmpl.Execute: %s\n", err)
9898
return err
@@ -107,8 +107,7 @@ func doTextTemplate(w http.ResponseWriter, fpath string, input *InData) (err err
107107
return err
108108
}
109109
w.Header().Set("Content-type", "text/html; charset=utf-8")
110-
parts := strings.Split(fpath, "/")
111-
err = tmpl.ExecuteTemplate(w, parts[len(parts)-1], *input)
110+
err = tmpl.ExecuteTemplate(w, fpath, *input)
112111
if err != nil {
113112
log.Printf("Error in DoTemplate (text) - tmpl.Execute's err: %s\n", err)
114113
return err

process_test.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ package webseclab
22

33
import (
44
"net/http"
5-
"os"
6-
"path"
75
"testing"
86
)
97

@@ -14,11 +12,7 @@ type dohandler func(w http.ResponseWriter, fpath string, input *InData) (err err
1412

1513
// read the templates
1614
func init() {
17-
pwd, err := os.Getwd()
18-
if err != nil {
19-
panic(err)
20-
}
21-
err = ParseTemplates(path.Join(pwd, "templates"))
15+
err := parseTemplates()
2216
if err != nil {
2317
panic(err)
2418
}

0 commit comments

Comments
 (0)