[
  {
    "path": "README.md",
    "content": "# plution\n<h2>Prototype pollution scanner using headless chrome</h2>\n\n![alt text](https://i.imgur.com/xumApSF.png)\n\n# What this is\nPlution is a convenient way to scan at scale for pages that are vulnerable to client side prototype pollution via a URL payload. In the default configuration, it will use a hardcoded payload that can detect 11 of the cases documented here: https://github.com/BlackFan/client-side-prototype-pollution/tree/master/pp\n\n# What this is not\nThis is not a one stop shop. Prototype pollution is a complicated beast. This tool does nothing you couldn't do manually. This is not a polished bug-free super tool. It is functional but poorly coded and to be considered alpha at best.\n\n# How it works\nPlution appends a payload to supplied URLs, naviguates to each URL with headless chrome and runs javascript on the page to verify if a prototype was successfully polluted.\n\n# how it is used\n* Basic scan, output only to screen:<br />\n `cat URLs.txt | plution`\n\n* Scan with a supplied payload rather than hardcoded one:<br />\n`cat URLs.txt|plution -p '__proto__.zzzc=example'`<br />\n**Note on custom payloads: The variable you are hoping to inject must be called or render to \"zzzc\". This is because 'window.zzzc' will be run on each page to verify pollution.**\n\n* Output:<br />\n`Passing '-o' followed by a location will output only URLs of pages that were successfully polluted.`\n\n* Concurrency:<br />\n* `Pass the '-c' option to specify how many concurrent jobs are run (default is 5)`\n\n# questions and answers\n* How do I install it?<br />\n`go get -u github.com/raverrr/plution`\n\n* why specifically limit it to checking if window.zzzc is defined?<br />\n`zzzc is a short pattern that is unlikely to already be in a prototype. If you want more freedom in regards to the javascript use https://github.com/detectify/page-fetch instead`\n\n* Got a more specific question?<br />\n`Ask me on twitter @divadbate.`\n\n\n"
  },
  {
    "path": "plution.go",
    "content": "package main\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"regexp\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/chromedp/chromedp\"\n\n\t\"github.com/fatih/color\"\n)\n\nfunc init() {\n\tfmt.Println(color.YellowString(\"==================================================\"))\n\tfmt.Println(color.CyanString(\"       ▛▀▖▜    ▐  ▗             \"))\n\tfmt.Println(color.CyanString(\"       ▙▄▘▐ ▌ ▌▜▀ ▄ ▞▀▖▛▀▖      \"))\n\tfmt.Println(color.CyanString(\"       ▌  ▐ ▌ ▌▐ ▖▐ ▌ ▌▌ ▌      \"))\n\tfmt.Println(color.CyanString(\"▀▀▀▀▀▀ ▘   ▘▝▀▘ ▀ ▀▘▝▀ ▘ ▘▀▀▀▀▀▀\") + \"v0.1 By @divadbate\")\n\n\tfmt.Println(color.BlueString(\"Scans URLs for Prototype Pollution via query parameter.\"))\n\tfmt.Println(color.YellowString(\"==================================================\"))\n\tfmt.Println(color.CyanString(\"Credits:\"))\n\tfmt.Println(\"-@tomnomnom for inspiring me with Page-fetch\")\n\tfmt.Println(\"-Blackfan (github.com/BlackFan/client-side-prototype-pollution)\")\n\tfmt.Println(color.YellowString(\"==================================================\\n\"))\n\n}\n\nvar output string\nvar concurrency int\nvar customPayload string\nvar URLpayload string\n\nfunc main() {\n\tlog.SetFlags(0) //supress date and time on each line\n\n\tflag.StringVar(&customPayload, \"p\", \"\", \"--> Set custom URL payload (The varable RENDERED must be called 'zzzc')\"+\"\\n\") //do this with hasempty\n\tflag.StringVar(&output, \"o\", \"/dev/null\", \"--> Output (Will only output vulnerable URLs)\"+\"\\n\")\n\tflag.IntVar(&concurrency, \"c\", 5, \"--> Number of concurrent threads (default 5)\"+\"\\n\")\n\tflag.Parse()\n\n\t//create the output file\n\tfile, err := os.OpenFile(output, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)\n\tif err != nil {\n\t\tlog.Fatalf(\"failed creating file: %s\", err)\n\n\t}\n\tdatawriter := bufio.NewWriter(file)\n\n\tcopts := append(chromedp.DefaultExecAllocatorOptions[:],\n\t\tchromedp.Flag(\"ignore-certificate-errors\", true),\n\t)\n\n\tectx, ecancel := chromedp.NewExecAllocator(context.Background(), copts...)\n\tdefer ecancel()\n\n\tpctx, pcancel := chromedp.NewContext(ectx)\n\tdefer pcancel()\n\n\t// start the browser to ensure we end up making new tabs in an\n\t// existing browser instead of making a new browser each time.\n\t// see: https://godoc.org/github.com/chromedp/chromedp#NewContext\n\tif err := chromedp.Run(pctx); err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"error starting browser: %s\\n\", err)\n\t\treturn\n\t}\n\n\tsc := bufio.NewScanner(os.Stdin)\n\n\tvar wg sync.WaitGroup\n\tjobs := make(chan string)\n\n\tfor i := 0; i < concurrency; i++ {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tfor requestURL := range jobs {\n\n\t\t\t\tctx, cancel := context.WithTimeout(pctx, time.Second*10)\n\t\t\t\tctx, _ = chromedp.NewContext(ctx)\n\t\t\t\tvar res string\n\n\t\t\t\terr := chromedp.Run(ctx,\n\t\t\t\t\tchromedp.Navigate(requestURL+hasQuery(requestURL)+URLpayload),\n\t\t\t\t\tchromedp.Evaluate(\"window.zzzc\", &res),\n\t\t\t\t)\n\t\t\t\t//fmt.Println(requestURL + hasQuery(requestURL) + URLpayload)\n\n\t\t\t\tif res != \"\" || err.Error() == \"json: cannot unmarshal array into Go value of type string\" { //fix this hack\n\t\t\t\t\tlog.Printf(\"%s: %v\", color.GreenString(\"[+]\")+requestURL, color.GreenString(\"Vulnerable!\"))\n\t\t\t\t\tdatawriter.WriteString(requestURL + \"\\n\")\n\t\t\t\t\tdatawriter.Flush()\n\t\t\t\t}\n\n\t\t\t\tif err != nil && err.Error() != \"json: cannot unmarshal array into Go value of type string\" { //fix this hack\n\t\t\t\t\tfmt.Println(color.RedString(\"[-]\"), requestURL, color.RedString(err.Error()))\n\t\t\t\t}\n\n\t\t\t\tcancel()\n\t\t\t}\n\t\t\twg.Done()\n\t\t}()\n\t}\n\tfor sc.Scan() {\n\t\tjobs <- sc.Text()\n\t}\n\tclose(jobs)\n\twg.Wait()\n}\n\n//Does the URL contain a query already?\nfunc hasQuery(url string) string {\n\tvar Qmark = regexp.MustCompile(`\\?`)\n\tvar p = \"\"\n\turlPayload()\n\tif Qmark.MatchString(url) {\n\t\tp = \"&\"\n\n\t} else {\n\t\tp = \"?\"\n\t}\n\treturn p\n}\n\n//todo add chuncking\nfunc urlPayload() {\n\n\tif !containsEmpty(customPayload) {\n\t\tURLpayload = customPayload\n\t} else {\n\t\tURLpayload = \"constructor.prototype.zzzc=cccz&__proto__[zzzc]=cccz&constructor[prototype][zzzc]=cccz&__proto__.zzzc=cccz#__proto__[zzzc]=cccz\"\n\n\t}\n}\n\n//check if header flags are empty\nfunc containsEmpty(ss ...string) bool {\n\tfor _, s := range ss {\n\t\tif s == \"\" {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n"
  }
]