├── .github └── workflows │ ├── coverage.yml │ └── tests.yml ├── .gitignore ├── LICENSE ├── README.md ├── go.mod ├── go.sum ├── round_tripper.go └── round_tripper_test.go /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: coverage 2 | on: 3 | push: 4 | paths: 5 | - '**.go' 6 | - '**.mod' 7 | - '**.sum' 8 | pull_request: 9 | paths: 10 | - '**.go' 11 | - '**.mod' 12 | - '**.sum' 13 | 14 | env: 15 | PROXY_HOST: ${{ secrets.PROXY_HOST }} 16 | PROXY_USER: ${{ secrets.PROXY_USER }} 17 | PROXY_PASS: ${{ secrets.PROXY_PASS }} 18 | PROXY_HOST_HTTPS: ${{ secrets.PROXY_HOST_HTTPS }} 19 | PROXY_PORT_HTTPS: ${{ secrets.PROXY_PORT_HTTPS }} 20 | PROXY_HOST_SOCKS5: ${{ secrets.PROXY_HOST_SOCKS5 }} 21 | 22 | jobs: 23 | coverage: 24 | runs-on: ubuntu-latest 25 | steps: 26 | - name: Install Go 27 | if: success() 28 | uses: actions/setup-go@v1 29 | with: 30 | go-version: 1.17.x 31 | - name: Checkout code 32 | uses: actions/checkout@v1 33 | - name: Calc coverage 34 | run: | 35 | export PATH=$PATH:$(go env GOPATH)/bin 36 | go test -v -covermode=count -coverprofile=coverage.out ./... 37 | - name: Convert coverage to lcov 38 | uses: jandelgado/gcov2lcov-action@v1.0.0 39 | with: 40 | infile: coverage.out 41 | outfile: coverage.lcov 42 | - name: Coveralls 43 | uses: coverallsapp/github-action@v1.0.1 44 | with: 45 | github-token: ${{ secrets.GITHUB_TOKEN }} 46 | path-to-lcov: coverage.lcov -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | on: 3 | push: 4 | paths: 5 | - '**.go' 6 | - '**.mod' 7 | - '**.sum' 8 | pull_request: 9 | paths: 10 | - '**.go' 11 | schedule: 12 | # run tests regularly at 3 AM every days to check if CloudFlare updated their detection 13 | - cron: '0 3 * * *' 14 | 15 | env: 16 | PROXY_USER: ${{ secrets.PROXY_USER }} 17 | PROXY_PASS: ${{ secrets.PROXY_PASS }} 18 | PROXY_HOST_HTTPS: ${{ secrets.PROXY_HOST_HTTPS }} 19 | PROXY_PORT_HTTPS: ${{ secrets.PROXY_PORT_HTTPS }} 20 | PROXY_HOST_SOCKS5: ${{ secrets.PROXY_HOST_SOCKS5 }} 21 | 22 | jobs: 23 | test: 24 | strategy: 25 | matrix: 26 | go-version: [1.17.x] 27 | platform: [ubuntu-latest, macos-latest, windows-latest] 28 | runs-on: ${{ matrix.platform }} 29 | steps: 30 | - name: Install Go 31 | uses: actions/setup-go@v1 32 | with: 33 | go-version: ${{ matrix.go-version }} 34 | - name: Checkout code 35 | uses: actions/checkout@v1 36 | - name: Test 37 | run: go test ./... -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | # GoLand IDE 18 | .idea/ 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2019 Steffen Keuper 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go CloudFlare ByPass 2 | ![tests](https://github.com/DaRealFreak/cloudflare-bp-go/workflows/tests/badge.svg?branch=master) 3 | [![Coverage Status](https://coveralls.io/repos/github/DaRealFreak/cloudflare-bp-go/badge.svg?branch=master)](https://coveralls.io/github/DaRealFreak/cloudflare-bp-go?branch=master) 4 | ![GitHub](https://img.shields.io/github/license/DaRealFreak/cloudflare-bp-go) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/DaRealFreak/cloudflare-bp-go)](https://goreportcard.com/report/github.com/DaRealFreak/cloudflare-bp-go) 6 | 7 | small round tripper to avoid triggering the "attention required" status of CloudFlare for HTTP requests. 8 | It'll add required/validated headers on requests and update the client TLS configuration to pass the CloudFlare validation. 9 | 10 | This is (at least so far) **NOT** intended to solve challenges provided by CloudFlare, only to prevent CloudFlare from directly displaying you a challenge on the first request. 11 | 12 | The bypass is tested on a schedule everyday at 3 AM in case CloudFlare updated their detection, so the badge is always displaying if the bypass still works. 13 | 14 | ## Dependencies 15 | - [eddycjy/fake-useragent](https://github.com/EDDYCJY/fake-useragent) - for setting a believable request user agent. 16 | CloudFlare is relatively forgiving with the user agents anyways but it'll add some variety for the long term. 17 | 18 | ## Usage 19 | You can add the round tripper as any other round tripper: 20 | ```go 21 | client := &http.Client{} 22 | client.Transport = cloudflarebp.AddCloudFlareByPass(client.Transport) 23 | ``` 24 | 25 | small notice: 26 | Using the http.DefaultTransport will currently still fail, nil or empty &http.Transport works. 27 | Didn't have the time to check what exactly is different causing the CloudFlare validation to fail though. 28 | 29 | ## Development 30 | Want to contribute? Great! 31 | I'm always glad hearing about bugs or pull requests. 32 | 33 | ## License 34 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details 35 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/DaRealFreak/cloudflare-bp-go 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/EDDYCJY/fake-useragent v0.2.0 7 | github.com/PuerkitoBio/goquery v1.7.1 // indirect 8 | github.com/andybalholm/cascadia v1.3.1 // indirect 9 | github.com/stretchr/testify v1.4.0 10 | golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f 11 | ) 12 | 13 | require ( 14 | github.com/davecgh/go-spew v1.1.0 // indirect 15 | github.com/pmezard/go-difflib v1.0.0 // indirect 16 | gopkg.in/yaml.v2 v2.2.2 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/EDDYCJY/fake-useragent v0.2.0 h1:Jcnkk2bgXmDpX0z+ELlUErTkoLb/mxFBNd2YdcpvJBs= 2 | github.com/EDDYCJY/fake-useragent v0.2.0/go.mod h1:5wn3zzlDxhKW6NYknushqinPcAqZcAPHy8lLczCdJdc= 3 | github.com/PuerkitoBio/goquery v1.7.1 h1:oE+T06D+1T7LNrn91B4aERsRIeCLJ/oPSa6xB9FPnz4= 4 | github.com/PuerkitoBio/goquery v1.7.1/go.mod h1:XY0pP4kfraEmmV1O7Uf6XyjoslwsneBbgeDjLYuN8xY= 5 | github.com/andybalholm/cascadia v1.2.0/go.mod h1:YCyR8vOZT9aZ1CHEd8ap0gMVm2aFgxBp0T0eFw1RUQY= 6 | github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= 7 | github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= 8 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 11 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 12 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 13 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 14 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 15 | golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 16 | golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 17 | golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 18 | golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY= 19 | golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 20 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 21 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 22 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 23 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 24 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 25 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 26 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 27 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 28 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 29 | -------------------------------------------------------------------------------- /round_tripper.go: -------------------------------------------------------------------------------- 1 | // Package cloudflarebp provides a round tripper to not get detected by CloudFlare directly on the first HTTP request 2 | // The round tripper will add required/validated request headers and updates the client TLS configuration 3 | // It'll NOT solve challenges provided by CloudFlare, just prevent from being detected on the first request 4 | package cloudflarebp 5 | 6 | import ( 7 | "crypto/tls" 8 | browser "github.com/EDDYCJY/fake-useragent" 9 | "net/http" 10 | ) 11 | 12 | // cloudFlareRoundTripper is a custom round tripper add the validated request headers. 13 | type cloudFlareRoundTripper struct { 14 | inner http.RoundTripper 15 | options Options 16 | } 17 | 18 | // Options the option to set custom headers 19 | type Options struct { 20 | AddMissingHeaders bool 21 | Headers map[string]string 22 | } 23 | 24 | // AddCloudFlareByPass returns a round tripper adding the required headers for the CloudFlare checks 25 | // and updates the TLS configuration of the passed inner transport. 26 | func AddCloudFlareByPass(inner http.RoundTripper, options ...Options) http.RoundTripper { 27 | if trans, ok := inner.(*http.Transport); ok { 28 | trans.TLSClientConfig = getCloudFlareTLSConfiguration() 29 | } 30 | 31 | roundTripper := &cloudFlareRoundTripper{ 32 | inner: inner, 33 | } 34 | 35 | if options != nil { 36 | roundTripper.options = options[0] 37 | } else { 38 | roundTripper.options = GetDefaultOptions() 39 | } 40 | 41 | return roundTripper 42 | } 43 | 44 | // RoundTrip adds the required request headers to pass CloudFlare checks. 45 | func (ug *cloudFlareRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) { 46 | if ug.options.AddMissingHeaders { 47 | for header, value := range ug.options.Headers { 48 | if _, ok := r.Header[header]; !ok { 49 | r.Header.Set(header, value) 50 | } 51 | } 52 | } 53 | 54 | // in case we don't have an inner transport layer from the round tripper 55 | if ug.inner == nil { 56 | return (&http.Transport{ 57 | TLSClientConfig: getCloudFlareTLSConfiguration(), 58 | }).RoundTrip(r) 59 | } 60 | 61 | return ug.inner.RoundTrip(r) 62 | } 63 | 64 | // getCloudFlareTLSConfiguration returns an accepted client TLS configuration to not get detected by CloudFlare directly 65 | // in case the configuration needs to be updated later on: https://wiki.mozilla.org/Security/Server_Side_TLS . 66 | func getCloudFlareTLSConfiguration() *tls.Config { 67 | return &tls.Config{ 68 | CurvePreferences: []tls.CurveID{tls.CurveP256, tls.CurveP384, tls.CurveP521, tls.X25519}, 69 | } 70 | } 71 | 72 | // GetDefaultOptions returns the options set by default 73 | func GetDefaultOptions() Options { 74 | return Options{ 75 | AddMissingHeaders: true, 76 | Headers: map[string]string{ 77 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", 78 | "Accept-Language": "en-US,en;q=0.5", 79 | "User-Agent": browser.Firefox(), 80 | }, 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /round_tripper_test.go: -------------------------------------------------------------------------------- 1 | package cloudflarebp_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | "net/http" 8 | "net/url" 9 | "os" 10 | "testing" 11 | 12 | cloudflarebp "github.com/DaRealFreak/cloudflare-bp-go" 13 | 14 | "github.com/stretchr/testify/assert" 15 | "golang.org/x/net/proxy" 16 | ) 17 | 18 | func TestApplyCloudFlareByPassDefaultClient(t *testing.T) { 19 | client := http.DefaultClient 20 | 21 | res, err := client.Get("https://www.patreon.com/login") 22 | assert.New(t).NoError(err) 23 | assert.New(t).Equal(403, res.StatusCode) 24 | 25 | // apply our bypass for request headers and client TLS configurations 26 | http.DefaultClient.Transport = cloudflarebp.AddCloudFlareByPass(http.DefaultClient.Transport) 27 | 28 | res, err = client.Get("https://www.patreon.com/login") 29 | assert.New(t).NoError(err) 30 | assert.New(t).Equal(200, res.StatusCode) 31 | } 32 | 33 | func TestApplyCloudFlareByPassDefinedTransport(t *testing.T) { 34 | client := &http.Client{ 35 | Transport: &http.Transport{}, 36 | } 37 | 38 | // if the client requests something before applying the fix some configurations are applied already 39 | // and our ByPass won't work anymore, so we have to apply our ByPass as the first thing 40 | client.Transport = cloudflarebp.AddCloudFlareByPass(client.Transport) 41 | 42 | res, err := client.Get("https://www.patreon.com/login") 43 | assert.New(t).NoError(err) 44 | assert.New(t).Equal(200, res.StatusCode) 45 | } 46 | 47 | // TestAddCloudFlareByPassSocksProxy tests the CloudFlare bypass while we're using a SOCK5 proxy transport layer. 48 | func TestAddCloudFlareByPassSocksProxy(t *testing.T) { 49 | auth := proxy.Auth{ 50 | User: os.Getenv("PROXY_USER"), 51 | Password: os.Getenv("PROXY_PASS"), 52 | } 53 | 54 | dialer, err := proxy.SOCKS5( 55 | "tcp", 56 | fmt.Sprintf("%s:1080", os.Getenv("PROXY_HOST_SOCKS5")), 57 | &auth, 58 | proxy.Direct, 59 | ) 60 | assert.New(t).NoError(err) 61 | 62 | dc := dialer.(interface { 63 | DialContext(ctx context.Context, network, addr string) (net.Conn, error) 64 | }) 65 | 66 | client := &http.Client{ 67 | Transport: &http.Transport{DialContext: dc.DialContext}, 68 | } 69 | 70 | // if the client requests something before applying the fix some configurations are applied already 71 | // and our ByPass won't work anymore, so we have to apply our ByPass as the first thing 72 | client.Transport = cloudflarebp.AddCloudFlareByPass(client.Transport) 73 | 74 | res, err := client.Get("https://www.patreon.com/login") 75 | assert.New(t).NoError(err) 76 | assert.New(t).Equal(200, res.StatusCode) 77 | } 78 | 79 | // TestAddCloudFlareByPassHTTPProxy tests the CloudFlare bypass while we're using a HTTP proxy transport layer. 80 | func TestAddCloudFlareByPassHTTPProxy(t *testing.T) { 81 | proxyURL, _ := url.Parse( 82 | fmt.Sprintf( 83 | "https://%s:%s@%s:%s", 84 | url.QueryEscape(os.Getenv("PROXY_USER")), url.QueryEscape(os.Getenv("PROXY_PASS")), 85 | url.QueryEscape(os.Getenv("PROXY_HOST_HTTPS")), url.QueryEscape(os.Getenv("PROXY_PORT_HTTPS")), 86 | ), 87 | ) 88 | 89 | client := &http.Client{ 90 | Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)}, 91 | } 92 | 93 | res, err := client.Get("https://www.patreon.com/login") 94 | assert.New(t).NoError(err) 95 | assert.New(t).Equal(403, res.StatusCode) 96 | 97 | // if the client requests something before applying the fix some configurations are applied already 98 | // and our ByPass won't work anymore, so we have to apply our ByPass as the first thing 99 | client.Transport = cloudflarebp.AddCloudFlareByPass(client.Transport) 100 | 101 | res, err = client.Get("https://www.patreon.com/login") 102 | assert.New(t).NoError(err) 103 | assert.New(t).Equal(200, res.StatusCode) 104 | } 105 | --------------------------------------------------------------------------------