diff --git a/README.md b/README.md index 42888ea..7a0d369 100644 --- a/README.md +++ b/README.md @@ -26,14 +26,14 @@ go install -v github.com/riza/linx/cmd/linx@latest # Usage ```sh -linx --target=https://rizasabuncu.com/assets/admin_acces.js +linx --target=https://rizasabuncu.com/assets/admin_acces.js --output=admin_access_result.html ``` # TODOs -* [x] HTML output support +* [x] HTML output support * [ ] JSON output support * [ ] Custom cookie support -* [ ] Rule improvement & blacklist support +* [x] Rule improvement & blacklist support * [ ] Support parallel scan multiple files * [ ] ... diff --git a/cmd/linx/linx.go b/cmd/linx/linx.go index c77986d..63bc272 100644 --- a/cmd/linx/linx.go +++ b/cmd/linx/linx.go @@ -7,7 +7,7 @@ import ( "github.com/riza/linx/pkg/logger" ) -const Version = "v0.0.2" +const Version = "v0.0.4" func main() { banner.Show(Version) @@ -17,7 +17,7 @@ func main() { logger.Get().Fatal(err) } - scanner := scanner.NewScanner(opts.Target) + scanner := scanner.NewScanner(opts) err = scanner.Run() if err != nil { logger.Get().Error(err) diff --git a/internal/options/options.go b/internal/options/options.go index 0c716fe..7e82357 100644 --- a/internal/options/options.go +++ b/internal/options/options.go @@ -10,6 +10,7 @@ import ( type Options struct { Target string + Output string Debug bool } @@ -28,6 +29,7 @@ func Get() *Options { func (o *Options) Parse() (*Options, error) { flag.StringVar(&o.Target, "target", "", "can be *.js file path or url") flag.BoolVar(&o.Debug, "debug", false, "do you want to know what's inside the engine?") + flag.StringVar(&o.Output, "output", "", "output file name (currently support html)") flag.Parse() if o.Debug { diff --git a/internal/output/output.go b/internal/output/output.go index 00fbdd6..f6bdb2f 100644 --- a/internal/output/output.go +++ b/internal/output/output.go @@ -1,5 +1,9 @@ package output +type Output interface { + RenderAndSave(data OutputData) error +} + type OutputData struct { Target string Filename string diff --git a/internal/output/output_html.go b/internal/output/output_html.go index 7df1950..fab24f2 100644 --- a/internal/output/output_html.go +++ b/internal/output/output_html.go @@ -1,12 +1,13 @@ package output import ( + "github.com/riza/linx/pkg/logger" "html/template" "os" ) -const templateFile = "./internal/output/output_html_template.html" -const templateRaw = ` +const htmlExtension = ".html" +const htmlTemplate = ` @@ -17,13 +18,13 @@ const templateRaw = ` -
+
{{ .Target }}
- +
@@ -55,29 +56,27 @@ const templateRaw = `` type OutputHTML struct { - output OutputData } -func NewOutputHTML(output OutputData) OutputHTML { - return OutputHTML{output: output} -} +func (oh OutputHTML) RenderAndSave(data OutputData) error { + fileName := data.Filename + htmlExtension -func (oh OutputHTML) RenderAndSave() error { - f, err := os.Create(oh.output.Filename) + f, err := os.Create(fileName) if err != nil { return err } defer f.Close() - t, err := template.New("output").Parse(templateRaw) + t, err := template.New("output").Parse(htmlTemplate) if err != nil { return err } - err = t.Execute(f, oh.output) + err = t.Execute(f, data) if err != nil { return err } + logger.Get().Infof("results saved: %s", fileName) return nil } diff --git a/internal/output/output_html_template.html b/internal/output/output_html_template.html deleted file mode 100644 index 7924d1f..0000000 --- a/internal/output/output_html_template.html +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - {{ .Target }} - linx report - - - - -
-
- {{ .Target }} -
- -
-
URL
- - - - - - - - {{range .Results}} - - - - - {{end}} - -
URLLocation in file
{{ .URL }} -
{{ .Location }}
-
-
- -
- created with linx -
-
- - - - \ No newline at end of file diff --git a/internal/output/output_noop.go b/internal/output/output_noop.go new file mode 100644 index 0000000..bee55a4 --- /dev/null +++ b/internal/output/output_noop.go @@ -0,0 +1,8 @@ +package output + +type OutputNoop struct { +} + +func (o OutputNoop) RenderAndSave(data OutputData) error { + return nil +} diff --git a/internal/scanner/scanner.go b/internal/scanner/scanner.go index 1b4c8f2..a2ef539 100644 --- a/internal/scanner/scanner.go +++ b/internal/scanner/scanner.go @@ -2,21 +2,33 @@ package scanner import ( "fmt" + "github.com/riza/linx/internal/options" "github.com/riza/linx/internal/output" "github.com/riza/linx/internal/scanner/strategies" "github.com/riza/linx/pkg/logger" - "os/exec" + "path/filepath" "regexp" - "runtime" "strings" "unsafe" ) // rule from LinkFinder https://github.com/GerbenJavado/LinkFinder/blob/master/linkfinder.py#L29 ty @GerbenJavado -const rule = `(?:"|')(((?:[a-zA-Z]{1,10}://|//)[^"'/]{1,}\.[a-zA-Z]{2,}[^"']{0,})|((?:/|\.\./|\./)[^"'><,;| *()(%%$^/\\\[\]][^"'><,;|()]{1,})|([a-zA-Z0-9_\-/]{1,}/[a-zA-Z0-9_\-/]{1,}\.(?:[a-zA-Z]{1,4}|action)(?:[\?|#][^"|']{0,}|))|([a-zA-Z0-9_\-/]{1,}/[a-zA-Z0-9_\-/]{3,}(?:[\?|#][^"|']{0,}|))|([a-zA-Z0-9_\-]{1,}\.(?:php|asp|aspx|jsp|json|action|html|js|txt|xml)(?:[\?|#][^"|']{0,}|)))(?:"|')` +const ( + rule = `(?:"|')(((?:[a-zA-Z]{1,10}://|//)[^"'/]{1,}\.[a-zA-Z]{2,}[^"']{0,})|((?:/|\.\./|\./)[^"'><,;| *()(%%$^/\\\[\]][^"'><,;|()]{1,})|([a-zA-Z0-9_\-/]{1,}/[a-zA-Z0-9_\-/]{1,}\.(?:[a-zA-Z]{1,4}|action)(?:[\?|#][^"|']{0,}|))|([a-zA-Z0-9_\-/]{1,}/[a-zA-Z0-9_\-/]{3,}(?:[\?|#][^"|']{0,}|))|([a-zA-Z0-9_\-]{1,}\.(?:php|asp|aspx|jsp|json|action|html|js|txt|xml)(?:[\?|#][^"|']{0,}|)))(?:"|')` + excludeFileTypeRule = `.css|.jpg|.jpeg|.png|.svg|.img|.gif|.mp4|.flv|.ogv|.webm|.webp|.mov|.mp3|.m4a|.m4p|.scss|.tif|.tiff|.ttf|.otf|.woff|.woff2|.bmp|.ico|.eot|.htc|.rtf|.swf|.image|w3.org|doubleclick.net|youtube.com|.vue|jquery|bootstrap|font|jsdelivr.net|vimeo.com|pinterest.com|facebook|linkedin|twitter|instagram|google|mozilla.org|jibe.com|schema.org|schemas.microsoft.com|wordpress.org|w.org|wix.com|parastorage.com|whatwg.org|polyfill.io|typekit.net|schemas.openxmlformats.org|openweathermap.org|openoffice.org|reactjs.org|angularjs.org|java.com|purl.org|/image|/img|/css|/wp-json|/wp-content|/wp-includes|/theme|/audio|/captcha|/font|robots.txt|node_modules|.wav|.gltf|.js` + excludeMimeTypeRule = `text/css|image/jpeg|image/jpg|image/png|image/svg+xml|image/gif|image/tiff|image/webp|image/bmp|image/x-icon|image/vnd.microsoft.icon|font/ttf|font/woff|font/woff2|font/x-woff2|font/x-woff|font/otf|audio/mpeg|audio/wav|audio/webm|audio/aac|audio/ogg|audio/wav|audio/webm|video/mp4|video/mpeg|video/webm|video/ogg|video/mp2t|video/webm|video/x-msvideo|application/font-woff|application/font-woff2|application/vnd.android.package-archive|binary/octet-stream|application/octet-stream|application/pdf|application/x-font-ttf|application/x-font-otf|application/json|text/javascript|text/plain|text/x-yaml|text/html|text/babel|text/markdown|text/tsx|application/typescript|application/javascript|text/x-handlebars-template|application/x-typescript|text/x-gfm|text/jsx` +) + +var ( + outputEngines = map[string]output.Output{ + "": output.OutputNoop{}, + ".html": output.OutputHTML{}, + } +) type task struct { target string + output string strategy strategies.ScanStrategy } @@ -24,17 +36,20 @@ type scanner struct { task task } -func NewScanner(target string) scanner { +func NewScanner(opts *options.Options) scanner { return scanner{ task{ - target: target, - strategy: defineStrategyForTarget(target), + target: opts.Target, + output: opts.Output, + strategy: defineStrategyForTarget(opts.Target), }, } } func (s scanner) Run() error { r, _ := regexp.Compile(rule) + rFt, _ := regexp.Compile(excludeFileTypeRule) + rMt, _ := regexp.Compile(excludeMimeTypeRule) strategy := s.task.strategy content, err := strategy.GetContent() @@ -44,14 +59,17 @@ func (s scanner) Run() error { out := output.OutputData{ Target: s.task.target, - Filename: strategy.GetFileName() + "_result.html", + Filename: strategy.GetFileName(), Results: []output.Result{}, } for _, s := range r.FindAllStringSubmatchIndex(*(*string)(unsafe.Pointer(&content)), -1) { url := content[s[0]:s[1]] - closeLines := content[s[0]-100 : s[1]+100] + if rFt.MatchString(string(url)) || rMt.MatchString(string(url)) { + continue + } + closeLines := content[s[0]-100 : s[1]+100] out.Results = append(out.Results, output.Result{ URL: string(url), Location: string(closeLines), @@ -61,42 +79,26 @@ func (s scanner) Run() error { } logger.Get().Infof("%d possible url found", len(out.Results)) - - err = output.NewOutputHTML(out).RenderAndSave() - if err != nil { - return fmt.Errorf(errOutputFailed, err) + oE, ok := outputEngines[s.getOutputEngineKey()] + if !ok { + return fmt.Errorf(errOutputEngineNotFound, s.getOutputEngineKey()) } - err = openResults(out.Filename) + err = oE.RenderAndSave(out) if err != nil { - return fmt.Errorf(errOutputOpenFailed, err) + return fmt.Errorf(errOutputFailed, err) } return nil } +func (s scanner) getOutputEngineKey() string { + return filepath.Ext(s.task.output) +} + func defineStrategyForTarget(target string) strategies.ScanStrategy { if strings.Contains(target, "http://") || strings.Contains(target, "https://") { return strategies.URLStrategy{target} } return strategies.FileStrategy{target} } - -func openResults(fileName string) error { - var err error - switch runtime.GOOS { - case "linux": - err = exec.Command("xdg-open", fileName).Start() - case "windows": - err = exec.Command("rundll32", "url.dll,FileProtocolHandler", fileName).Start() - case "darwin": - err = exec.Command("open", fileName).Start() - default: - err = fmt.Errorf("unsupported platform") - } - if err != nil { - return err - } - - return nil -} diff --git a/internal/scanner/scanner_errors.go b/internal/scanner/scanner_errors.go index 1db607e..ccca7cc 100644 --- a/internal/scanner/scanner_errors.go +++ b/internal/scanner/scanner_errors.go @@ -1,7 +1,7 @@ package scanner var ( - errGetFileContent = "error getting content err: %w" - errOutputFailed = "output fail render and save err: %w" - errOutputOpenFailed = "output fail opening err: %w" + errGetFileContent = "error getting content err: %w" + errOutputFailed = "output fail render and save err: %w" + errOutputEngineNotFound = "output engine not fount ext: %s" ) diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index 86014c9..3120ca5 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -21,47 +21,47 @@ func Get() *logger { } func (l *logger) Print(args ...interface{}) { - l.l.Print(args) + l.l.Print(args...) } func (l *logger) Printf(format string, args ...interface{}) { - l.l.Printf(format, args) + l.l.Printf(format, args...) } func (l *logger) Info(args ...interface{}) { - l.l.Info(args) + l.l.Info(args...) } func (l *logger) Infof(format string, args ...interface{}) { - l.l.Infof(format, args) + l.l.Infof(format, args...) } func (l *logger) Warn(args ...interface{}) { - l.l.Warn(args) + l.l.Warn(args...) } func (l *logger) Warnf(format string, args ...interface{}) { - l.l.Warnf(format, args) + l.l.Warnf(format, args...) } func (l *logger) Error(args ...interface{}) { - l.l.Error(args) + l.l.Error(args...) } func (l *logger) Errorf(format string, args ...interface{}) { - l.l.Errorf(format, args) + l.l.Errorf(format, args...) } func (l *logger) Fatal(args ...interface{}) { - l.l.Fatal(args) + l.l.Fatal(args...) } func (l *logger) Debug(args ...interface{}) { - l.l.Debug(args) + l.l.Debug(args...) } func (l *logger) Debugf(format string, args ...interface{}) { - l.l.Debugf(format, args) + l.l.Debugf(format, args...) } func (l *logger) SetLevelDebug() {