Added all files

This commit is contained in:
jayateertha043 2022-05-17 22:06:56 +05:30
parent 07699d6e37
commit fa53e7b7a0
11 changed files with 815 additions and 0 deletions

21
.gitignore vendored Normal file
View file

@ -0,0 +1,21 @@
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
*.txt
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work

BIN
GoShod.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

161
GoShod.go Normal file
View file

@ -0,0 +1,161 @@
package main
import (
"bytes"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"mime"
"net"
"net/http"
"path/filepath"
"strconv"
"strings"
"sync"
"github.com/jayateertha043/goshod/pkg/shodan_utils"
"github.com/pkg/browser"
)
var ApiKey string = ""
func main() {
printbanner()
apiKey := flag.String("api", "", "Your Shodan API Key, One time Configure.")
flag.Parse()
if strings.TrimSpace(*apiKey) != "" {
ApiKey = strings.TrimSpace(*apiKey)
b_api := []byte(ApiKey)
err := ioutil.WriteFile("./shodan_config.txt", b_api, 0666)
if err != nil {
log.Fatalln("[*] Error writing api to configuration file at ./shodan_config.txt")
}
log.Println("[*] Api Key Configured at ./shodan_config.txt")
}
var server_wg = new(sync.WaitGroup)
log.Println("[*] Looking for shodan_config.txt at current directory")
b, err := ioutil.ReadFile("./shodan_config.txt")
if err != nil {
log.Fatalln("[*] Shodan_config.txt not found, Kindly configure Shodan api key.")
}
ApiKey = strings.TrimSpace(string(b))
uri := startServer(server_wg)
log.Println("[*] Server started at: " + uri)
browser.OpenURL(uri)
server_wg.Wait()
}
func startServer(wg *sync.WaitGroup) string {
wg.Add(1)
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
log.Fatal(err)
}
go func() {
defer ln.Close()
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
w.Header().Add("Access-Control-Allow-Origin", "*")
if path == "" || path == "/" {
path = "./html/index.html"
w.Header().Add("Content-Type", mime.TypeByExtension(filepath.Ext(path)))
bs, err := ioutil.ReadFile(path)
if err != nil {
log.Println(err)
}
io.Copy(w, bytes.NewBuffer(bs))
} else if path == "/shodanpagecount" && r.Method == "POST" {
err := r.ParseForm()
if err != nil {
log.Println(err)
}
apikey := ApiKey
//apikey := r.FormValue("apikey")
search := r.FormValue("search")
if strings.TrimSpace(apikey) == "" || strings.TrimSpace(search) == "" {
w.WriteHeader(401)
w.Write([]byte("missing necessary params !"))
} else {
params := make(map[string]string, 0)
params["key"] = apikey
params["query"] = search
status_code, page_count, response_body := shodan_utils.ShodanSearchHostCount(params)
if status_code == 200 && page_count > 0 && response_body != "" {
w.WriteHeader(status_code)
p_str := strconv.Itoa(page_count)
w.Write([]byte(p_str))
} else if page_count == -101 {
w.WriteHeader(status_code)
w.Write([]byte(response_body))
} else {
w.WriteHeader(status_code)
w.Write([]byte(response_body))
}
}
} else if path == "/shodansearch" && r.Method == "POST" {
err := r.ParseForm()
if err != nil {
log.Println(err)
}
apikey := ApiKey
//apikey := r.FormValue("apikey")
search := r.FormValue("search")
page := r.FormValue("page")
_, err = strconv.Atoi(page)
if err != nil || page == "" {
page = "1"
}
if strings.TrimSpace(apikey) == "" || strings.TrimSpace(search) == "" || strings.TrimSpace(page) == "" {
w.WriteHeader(401)
w.Write([]byte("missing necessary params !"))
} else {
params := make(map[string]string, 0)
params["key"] = apikey
params["query"] = search
params["page"] = page
if params["page"] < "0" {
params["page"] = "1"
}
status_code, response_body := shodan_utils.GetShodanResultForPage(params)
w.WriteHeader(status_code)
w.Write([]byte(response_body))
}
} else {
path = "./html/" + path[1:]
w.Header().Add("Content-Type", mime.TypeByExtension(filepath.Ext(path)))
bs, err := ioutil.ReadFile(path)
if err != nil {
fmt.Println(err)
}
io.Copy(w, bytes.NewBuffer(bs))
}
})
log.Fatal(http.Serve(ln, nil))
}()
return "http://" + ln.Addr().String()
}
func printbanner() {
banner :=
`
`
fmt.Println(banner)
}

8
go.mod Normal file
View file

@ -0,0 +1,8 @@
module github.com/jayateertha043/goshod
go 1.16
require (
github.com/Jeffail/gabs v1.4.0
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
)

6
go.sum Normal file
View file

@ -0,0 +1,6 @@
github.com/Jeffail/gabs v1.4.0 h1://5fYRRTq1edjfIrQGvdkcd22pkYUrHZ5YC/H2GJVAo=
github.com/Jeffail/gabs v1.4.0/go.mod h1:6xMvQMK4k33lb7GUUpaAPh6nKMmemQeg5d4gn7/bOXc=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71 h1:X/2sJAybVknnUnV7AD2HdT6rm2p5BP6eH2j+igduWgk=
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

3
html/css/style.css Normal file
View file

@ -0,0 +1,3 @@
#app {
background-color: var(--v-background-base);
}

106
html/index.html Normal file
View file

@ -0,0 +1,106 @@
<!DOCTYPE html>
<html>
<link>
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet" />
<link href="https://cdn.jsdelivr.net/npm/@mdi/font@6.x/css/materialdesignicons.min.css" rel="stylesheet" />
<link href="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.css" rel="stylesheet" />
<link href="./css/style.css" rel="stylesheet">
</link>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui" />
</head>
<body>
<div id="app">
<v-app>
<v-main>
<template>
<v-container full-width fluid>
<v-card>
<v-card-title primary-title>
<p class="text-h3 font-weight-bold mx-auto px-auto">GoShoD</p>
</v-card-title>
<v-divider></v-divider>
<v-card-text class="pa-3">
<v-container d-flex>
<v-text-field label="Shodan Search" name="search" type="text" dense solo v-model="search">
</v-text-field>
<v-btn raised color="primary" class="ml-3" @click="shodanHostSearch(search,-1,$event)">Search</v-btn>
</v-container>
</v-card-text>
</v-card>
<div class="my-3 d-flex flex-row justify-center" v-if="showLoading">
<v-progress-circular class="justify-center" indeterminate color="primary"></v-progress-circular>
</div>
<v-card id="card_list" class="my-3" v-if="host_result.matches.length>0">
<v-card-text>
<v-simple-table>
<template v-slot:default>
<thead>
<tr>
<th>IP</th>
<th>Product</th>
<th>Version</th>
<th>Port</th>
<th>Organisation</th>
<th>Hostnames</th>
<th>Country</th>
<th>City</th>
<th>Timestamp</th>
</tr>
</thead>
<tbody>
<tr v-for="result in host_result.matches">
<td><a :href='"https://shodan.io/host/"+result.ip_str' target="_blank" rel="noreferrer noopener">{{result.ip_str}}</a></td>
<td>{{result.product}}</td>
<td>{{result.version}}</td>
<td>{{result.port}}</td>
<td>{{result.org}}</td>
<td>{{result.hostnames.join(", ")}}</td>
<td>{{result.location.country_name}}</td>
<td>{{result.location.city}}</td>
<td>{{result.timestamp.split(".")[0]}}</td>
</tr>
</tbody>
</template>
</v-simple-table>
</v-card-text>
</v-card>
</template>
<template>
<div class="my-3 d-flex flex-row justify-center" class="my-3" v-if="host_result.matches.length>0">
<v-btn icon x-large @click="prevPage">
<v-icon>mdi-chevron-left</v-icon>
</v-btn>
<v-btn icon x-large>
{{curPage}}
</v-btn>
<v-btn icon x-large @click="nextPage">
<v-icon>mdi-chevron-right</v-icon>
</v-btn>
</div>
</template>
</v-container>
</v-main>
</v-app>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="./js/app.js"></script>
<script>
</script>
</body>
</html>

185
html/js/app.js Normal file
View file

@ -0,0 +1,185 @@
const vuetify = new Vuetify({
theme: {
themes: {
dark: {
primary: '#620ee',
secondary: '#03dac5',
accent: '#82B1FF',
error: '#FF5252',
info: '#2196F3',
success: '#4CAF50',
warning: '#FFC107',
background: '#121212',
},
},
options: {
customProperties: true
},
},
});
const app = new Vue({
el: "#app",
vuetify: vuetify,
data: () => ({
search:"",
host_result:null,
maxPage:1,
minPage:1,
curPage:1,
showLoading:false,
}),
created(){
this.$vuetify.theme.dark = true;
},
mounted () {
this.host_result={matches:[]}
},
methods: {
shodanHostSearch(search,page,event){
event.preventDefault();
this.host_result={matches:[]}
this.showLoading=true;
var params = new URLSearchParams();
params.append("search",search);
console.log(params.get("search"));
console.log("Search param submitted: "+params.get("search"))
if(page!=-1&&page<0){
page=1;
}
params.append("page",page);
console.log("Page:",page);
//If first time clicked on search, need page count
if(page=="-1"){
this.showLoading=true;
axios({
method: "post",
url: "/shodanpagecount",
data: params,
headers: { "Content-Type": "application/x-www-form-urlencoded" },
}).then( (response)=>{
var tmp = parseInt(response.data);
console.log("Pages:",tmp);
if(tmp<=0){
alert("No hosts found")
}
if(tmp!=NaN&&tmp>0){
this.maxPage = tmp;
this.minPage = 1;
this.curPage = 1;
params.set("page",this.curPage);
this.showLoading=true;
axios({
method: "post",
url: "/shodansearch",
data: params,
headers: { "Content-Type": "application/x-www-form-urlencoded" },
}).then( (response)=>{
this.host_result = response.data
}).catch((e)=>{
this.showLoading=false;
console.log(e);
if(e.response){
console.log(e.response.data);
var err = e.response.data.error;
alert(err);
}
}).then(()=>{
this.showLoading=false;
});
}
}).catch((e)=>{
this.showLoading=false;
console.log(e)
alert("Error, Try again later !!!");
}).then(()=>{
});
}else{
axios({
method: "post",
url: "/shodansearch",
data: params,
headers: { "Content-Type": "application/x-www-form-urlencoded" },
}).then( (response)=>{
this.host_result = response.data
console.log(response.data)
}).catch((e)=>{
this.showLoading=false;
console.log(e)
if(e.response){
console.log(e.response.data);
var err = e.response.data.error;
alert(err);
}
}).then(()=>{
this.showLoading=false;
});
}
},
nextPage(){
if(this.curPage<this.maxPage){
this.curPage++;
var params = new URLSearchParams();
params.append("search",this.search);
params.append("page",this.curPage)
axios({
method: "post",
url: "/shodansearch",
data: params,
headers: { "Content-Type": "application/x-www-form-urlencoded" },
}).then( (response)=>{
this.host_result = response.data
}).catch((e)=>{
this.curPage--;
this.showLoading=false;
console.log(e);
if(e.response){
console.log(e.response.data);
var err = e.response.data.error;
alert(err);
}
}).then(()=>{
this.showLoading=false;
});
}else{
alert("Max Pages Reached !!!");
}
},
prevPage(){
if(this.curPage>this.minPage){
this.curPage--;
var params = new URLSearchParams();
params.append("search",this.search);
params.append("page",this.curPage)
axios({
method: "post",
url: "/shodansearch",
data: params,
headers: { "Content-Type": "application/x-www-form-urlencoded" },
}).then( (response)=>{
this.host_result = response.data
}).catch((e)=>{
this.curPage++;
this.showLoading=false;
console.log(e);
if(e.response){
console.log(e.response.data);
var err = e.response.data.error;
alert(err);
}
}).then(()=>{
this.showLoading=false;
});
}else{
alert("Min Page Reached !!!");
}
}
},
});

25
html/js/shodan_utils.js Normal file
View file

@ -0,0 +1,25 @@
function shodanSearch(){
searchbar = document.getElementById("search").value;
search = document.getElementById("search").value;
console.log("Executing shodanSearch ...")
console.log(searchbar)
var xhttp = new XMLHttpRequest();
var url = '/shodansearch';
xhttp.open("POST", url, true);
xhttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
var response_body="";
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
// Typical action to be performed when the document is ready:
response_body = xhttp.responseText
console.log(response_body);
}
};
xhttp.send("search="+search);
}

View file

@ -0,0 +1,248 @@
package httpclient
import (
"bytes"
"crypto/tls"
"encoding/json"
"fmt"
"log"
"math/rand"
"net/http"
"net/url"
"strings"
"sync"
"time"
)
var userAgents = []string{
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Safari/604.1.38",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Safari/604.1.38",
}
var mu = new(sync.Mutex)
//Build Default Headers sent with every request
func BuildDefaultHeaders() map[string]string {
return map[string]string{
"User-Agent": "goshod by @jayateerthag", //RandomUA(),
"Accept": "text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
// "Accept-Encoding": "gzip,deflate",
"DNT": "1",
//"Connection": "close",
}
}
//Get request with params
func GetRequestP(url string, params map[string]string, headers map[string]string, timeoutP int) (*http.Response, error) {
timeout := timeoutP
if timeout <= 0 {
timeout = 5
}
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
}
transport := &http.Transport{
TLSClientConfig: tlsConfig,
}
var client = new(http.Client)
client = &http.Client{Transport: transport, Timeout: time.Duration(timeout) * time.Second}
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
req.Close = true
//params adding and encoding
q := req.URL.Query()
for name, value := range params {
q.Add(name, value)
}
req.URL.RawQuery = q.Encode()
//Set Default Headers with Ramdom UA
tmpHeaders := BuildDefaultHeaders()
for key, value := range tmpHeaders {
req.Header.Set(key, value)
}
mu.Lock()
if headers != nil {
for key, value := range headers {
req.Header.Set(key, value)
}
}
mu.Unlock()
resp, err := client.Do(req)
if err != nil {
return nil, err
}
return resp, nil
}
func GetRequest(uri string, headers map[string]string, timeoutP int, proxy string) (*http.Response, error) {
h := CloneHeaders(headers)
pu, err := url.Parse(proxy)
if proxy != "" {
if err != nil {
fmt.Println("Invalid proxy...")
return nil, nil
}
}
timeout := timeoutP
if timeout <= 0 {
timeout = 5
}
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
}
transport := &http.Transport{
TLSClientConfig: tlsConfig,
}
var client = new(http.Client)
if proxy != "" {
transport = &http.Transport{
TLSClientConfig: tlsConfig,
Proxy: http.ProxyURL(pu),
}
}
client = &http.Client{Transport: transport, Timeout: time.Duration(timeout) * time.Second}
req, err := http.NewRequest("GET", uri, nil)
if err != nil {
return nil, err
}
req.Close = true
//Set Default Headers with Ramdom UA
tmpHeaders := BuildDefaultHeaders()
for key, value := range tmpHeaders {
req.Header.Set(key, value)
}
mu.Lock()
if h != nil {
for key, value := range h {
req.Header.Set(key, value)
}
}
mu.Unlock()
resp, err := client.Do(req)
if err != nil {
return nil, err
}
return resp, nil
}
//Post Request supports normal and json body too
func PostRequest(uri string, data string, headers map[string]string, timeoutP int, proxy string) (*http.Response, error) {
h := CloneHeaders(headers)
pu, err := url.Parse(proxy)
if proxy != "" {
if err != nil {
fmt.Println("Invalid proxy...")
return nil, nil
}
}
var req *http.Request
var resp *http.Response
timeout := timeoutP
if timeout <= 0 {
timeout = 5
}
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
}
transport := &http.Transport{
TLSClientConfig: tlsConfig,
}
var client = new(http.Client)
if proxy != "" {
transport = &http.Transport{
TLSClientConfig: tlsConfig,
Proxy: http.ProxyURL(pu),
}
}
client = &http.Client{Transport: transport, Timeout: time.Duration(timeout) * time.Second}
req, err = http.NewRequest("POST", uri, strings.NewReader(data))
if err != nil {
return nil, err
}
req.Close = true
req.Header.Set("Content-Type", "application/x-www-form-uriencoded")
//Set Default Headers with Ramdom UA
tmpHeaders := BuildDefaultHeaders()
for key, value := range tmpHeaders {
req.Header.Set(key, value)
}
mu.Lock()
if h != nil {
for key, value := range h {
req.Header.Set(key, value)
}
}
mu.Unlock()
resp, err = client.Do(req)
if err != nil {
return nil, err
}
return resp, nil
}
func RandomUA() string {
rand.Seed(time.Now().Unix())
choice := rand.Intn(len(userAgents))
return userAgents[choice]
}
//default json escapes html characters such as <> so custom marshal needed
func JsonMarshal(params interface{}) ([]byte, error) {
buffer := new(bytes.Buffer)
enc := json.NewEncoder(buffer)
enc.SetEscapeHTML(false)
err := enc.Encode(&params)
if err != nil {
log.Println(err)
}
return buffer.Bytes(), err
}
//default json escapes html characters such as <> so custom marshal needed
func JsonMarshalIndent(params interface{}, prefix string, indent string) ([]byte, error) {
buffer := new(bytes.Buffer)
enc := json.NewEncoder(buffer)
enc.SetEscapeHTML(false)
enc.SetIndent(prefix, indent)
err := enc.Encode(&params)
if err != nil {
log.Println(err)
}
return buffer.Bytes(), err
}
func CloneHeaders(m map[string]string) map[string]string {
cp := make(map[string]string)
if m == nil {
return nil
}
for k, v := range m {
cp[k] = v
}
return cp
}

View file

@ -0,0 +1,52 @@
package shodan_utils
import (
"io/ioutil"
"github.com/Jeffail/gabs"
"github.com/jayateertha043/goshod/pkg/httpclient"
)
func ShodanSearchHostCount(params map[string]string) (statuscode int, hostcount int, responsebody string) {
sres, err := httpclient.GetRequestP("https://api.shodan.io/shodan/host/count", params, nil, 25)
if err != nil {
return 500, -100, "Error: Unable to make request to shodan"
}
defer sres.Body.Close()
status_code := sres.StatusCode
sresbytes, _ := ioutil.ReadAll(sres.Body)
response_body := string(sresbytes)
if status_code == 200 {
res, err := gabs.ParseJSON([]byte(sresbytes))
if err != nil {
response_body = "Error: Unable to parse result from shodan"
return 500, -101, response_body
}
total_exist := res.ExistsP("total")
if total_exist {
total := res.Path("total").Data().(float64)
pages := int(((total - 1) / 100)) + 1
return 200, pages, response_body
}
} else {
return 500, -100, "Error: Unknown"
}
return 500, -100, "Error: Unknown"
}
func GetShodanResultForPage(params map[string]string) (status_code int, response_body string) {
sres, err := httpclient.GetRequestP("https://api.shodan.io/shodan/host/search", params, nil, 25)
if err != nil {
return 500, `{"error": "Internal connection error with shodan"}`
}
defer sres.Body.Close()
statuscode := sres.StatusCode
sresbytes, _ := ioutil.ReadAll(sres.Body)
responsebody := string(sresbytes)
return statuscode, responsebody
}