Skip to content

Commit

Permalink
Merge pull request #89 from sleeyax/v2
Browse files Browse the repository at this point in the history
  • Loading branch information
sleeyax authored Feb 26, 2025
2 parents 86b0643 + 2d21248 commit 552a369
Show file tree
Hide file tree
Showing 26 changed files with 517 additions and 903 deletions.
73 changes: 52 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
# Awesome TLS
This extension hijacks Burp's HTTP and TLS stack, allowing you to spoof any browser TLS fingerprint ([JA3](https://github.com/salesforce/ja3)).
It boosts the power of Burp Suite while reducing the likelihood of fingerprinting by various WAFs like CloudFlare, PerimeterX, Akamai, DataDome, etc.

It does this without resorting to hacks, reflection or forked Burp Suite Community code. All code in this repository only leverages Burp's official Extender API.
This extension hijacks Burp's HTTP and TLS stack, allowing you to spoof any browser TLS
fingerprint ([JA3](https://github.com/salesforce/ja3)).
It boosts the power of Burp Suite while reducing the likelihood of fingerprinting by various WAFs like CloudFlare,
PerimeterX, Akamai, DataDome, etc.

This extension works without resorting to ugly hacks, reflection or forked Burp Suite Community code.

![screenshot](./docs/settings.png)

Expand All @@ -11,47 +14,65 @@ It does this without resorting to hacks, reflection or forked Burp Suite Communi
## Sponsors

> Maintenance of this project is made possible by all the lovely contributors and sponsors.
If you'd like to sponsor this project and have your avatar or company logo appear in this section, click [here](https://github.com/sponsors/sleeyax). 💖
> If you'd like to sponsor this project and have your avatar or company logo appear in this section,
> click [here](https://github.com/sponsors/sleeyax). 💖
---

## Showcase

[CloudFlare bot score](https://cloudflare.manfredi.io/en/tools/connection):

![cloudflare bot score of Burp Pro](./docs/cloudflare_bot_score_burp_pro.png)
![cloudflare bot score of Awesome TLS](./docs/cloudflare_bot_score_awesome_tls.png)

This is just one example. If you tested with another dedicated bot detection site, let me know your results!
This is just one example. If you tested with another dedicated bot detection site, let me know your results!

## How it works
Unfortunately Burp's Extender API is very limited for more advanced use cases like this, so I had to play around with it to make this work.

Once a request comes in, the extension intercepts it and forwards it to a local HTTPS server that started in the background (once the extension loaded).
This server works like a proxy; it forwards the request to the destination, while persisting the original header order and applying a customizable TLS configuration.
Then, the local server forwards the response back to Burp. The response header order is also preserved.
Unfortunately Burp's API is very limited for more advanced use cases like this, so I had to play around with it
to make this work.

Once a request comes in, the extension intercepts it and forwards it to a local HTTPS server that started in the
background (once the extension loaded).
This server works like a proxy; it forwards the request to the destination, while persisting the original header order
and applying a customizable TLS configuration.
Then, the local server forwards the response back to Burp.

Configuration settings and other necessary information like the destination server address and protocol are sent to the local server per request by a magic header.
This magic header is stripped from the request before it's forwarded to the destination server, of course.
Configuration settings and other necessary information like the destination server address and protocol are sent to the
local server per request by a magic header.
This magic header is stripped from the request before it's forwarded to the destination server.

![diagram](./docs/basic_diagram.png)

> :information_source: Another option would've been to code an upstream proxy server and connect burp to it, but I personally needed an extension for customization and portability.
> :information_source: Another option would've been to code an upstream proxy server and connect burp to it, but I
> personally needed an extension for customization and portability.
## Installation
1. Download the jar file for your operating system from [releases](https://github.com/sleeyax/burp-awesome-tls/releases). You can also download a fat jar, which works on all platforms supported by Awesome TLS. This means it's also portable and could be loaded from a USB for cross-platform access.
2. Open burp (pro or community), go to Extender > Extensions and click on 'Add'. Then, select `Java` as the extension type and browse to the jar file you just downloaded. Click 'Next' at the bottom, and it should load the extension without any errors.

1. Download the jar file for your operating system
from [releases](https://github.com/sleeyax/burp-awesome-tls/releases). You can also download a fat jar, which works
on all platforms supported by Awesome TLS. This means it's also portable and could be loaded from a USB for
cross-platform access.
2. Open burp (pro or community), go to Extender > Extensions and click on 'Add'. Then, select `Java` as the extension
type and browse to the jar file you just downloaded. Click 'Next' at the bottom, and it should load the extension
without any errors.
3. Check your new 'Awesome TLS' tab in Burp for configuration settings and start hacking!

## Configuration
This extension is 'plug and play' and should speak for itself. You can hover with your mouse over each field in the 'Awesome TLS' tab for more information about each field.

To load your custom Client Hello, you can capture it in Wireshark, copy client hello record as hex stream and paste it into the field "Hex Client Hello".
This extension is 'plug and play' and should speak for itself. You can hover with your mouse over each field in the '
Awesome TLS' tab for more information about each field.

To load your custom Client Hello from WireShark, you can copy the client hello record as hex stream and paste it
into the field "Hex Client Hello".
![screenshot](./docs/wireshark_capture_client_hello.png)

<details>
<summary>Advanced usage</summary>

In the 'advanced' tab, you can enable an additional proxy listener that will automatically apply the current fingerprint from the request:

In the 'advanced' tab, you can enable an additional proxy listener that will automatically apply the current fingerprint
from the request:

![screenshot](./docs/advanced_settings.png)

Expand All @@ -62,27 +83,37 @@ When enabled, the diagram changes to this:
</details>

## Manual build Instructions
This extension was developed with JetBrains IntelliJ (and GoLand) IDE.

This extension was developed with JetBrains IntelliJ (and GoLand) IDE.
The build instructions below assume you're using the same tools to build.
See [workflows](.github/workflows) for the target programming language versions.

1. Compile the go package within `./src-go/`. Run `cd ./src-go/server && go build -o ../../src/main/resources/{OS}-{ARCH}/server.{EXT} -buildmode=c-shared ./cmd/main.go`, replacing `{OS}-{ARCH}` with your OS and CPU architecture and `{EXT}` with your platform's preferred extension for dynamic C libraries. For example: `linux-x86-64/server.so`. See the [JNA docs](https://github.com/java-native-access/jna/blob/master/www/GettingStarted.md) for more info about supported platforms.
1. Compile the go package within `./src-go/`. Run
`cd ./src-go/server && go build -o ../../src/main/resources/{OS}-{ARCH}/server.{EXT} -buildmode=c-shared ./cmd/main.go`,
replacing `{OS}-{ARCH}` with your OS and CPU architecture and `{EXT}` with your platform's preferred extension for
dynamic C libraries. For example: `linux-x86-64/server.so`. See
the [JNA docs](https://github.com/java-native-access/jna/blob/master/www/GettingStarted.md) for more info about
supported platforms.
2. Compile the GUI form `SettingsTab.form` into Java code via `Build > Build project`.
3. Build the jar with Gradle: `gradle buildJar`.

You should now have one jar file (usually located at `./build/libs`) that works with Burp on your operating system.

## Credits

Special thanks to the maintainers of the following repositories:

- [refraction-networking/utls](https://github.com/refraction-networking/utls)
- [ooni/oohttp](https://github.com/ooni/oohttp)
- [bogdanfinn/tls-client](https://github.com/bogdanfinn/tls-client)

And the creators of the following websites:

- https://tlsfingerprint.io/
- https://kawayiyi.com/tls
- https://tls.peet.ws/
- https://cloudflare.manfredi.io/en/tools/connection
- https://scrapfly.io/web-scraping-tools/http2-fingerprint

## License

[GPL V3](./LICENSE)
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ repositories {
}

dependencies {
implementation 'net.portswigger.burp.extender:burp-extender-api:2.3'
compileOnly "net.portswigger.burp.extensions:montoya-api:2025.2"
implementation 'net.java.dev.jna:jna:5.10.0'
implementation 'com.google.code.gson:gson:2.8.9'
implementation 'com.intellij:forms_rt:7.0.3'
Expand Down
Binary file modified docs/settings.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
41 changes: 7 additions & 34 deletions src-go/server/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,17 @@ package main
import "C"

import (
"encoding/json"
"flag"
"fmt"
"log"

"server/internal"
"server/internal/tls"

"server"
"strings"
)

func main() {
spoofAddr := flag.String("spoof", server.DefaultSpoofProxyAddress, "Spoof proxy address to listen on ([ip:]port)")
spoofAddr := flag.String("spoof", "", "Spoof proxy address to listen on ([ip:]port)")
flag.Parse()

defaultConfig, err := json.Marshal(internal.TransportConfig{
InterceptProxyAddr: server.DefaultInterceptProxyAddress,
BurpAddr: server.DefaultBurpProxyAddress,
Fingerprint: tls.DefaultFingerprint,
HexClientHello: "",
UseInterceptedFingerprint: false,
HttpTimeout: int(internal.DefaultHttpTimeout.Seconds()),
HttpKeepAliveInterval: int(internal.DefaultHttpKeepAlive.Seconds()),
IdleConnTimeout: int(internal.DefaultIdleConnTimeout.Seconds()),
TLSHandshakeTimeout: int(internal.DefaultTLSHandshakeTimeout.Seconds()),
})
if err != nil {
log.Fatalln(err)
}

if err := server.SaveSettings(string(defaultConfig)); err != nil {
log.Fatalln(err)
}

log.Fatalln(server.StartServer(*spoofAddr))
}

Expand All @@ -56,16 +33,12 @@ func StopServer() *C.char {
return C.CString("")
}

//export SaveSettings
func SaveSettings(configJson *C.char) *C.char {
if err := server.SaveSettings(C.GoString(configJson)); err != nil {
return C.CString(err.Error())
}

return C.CString("")
}

//export SmokeTest
func SmokeTest() {
fmt.Println("smoke test success")
}

//export GetFingerprints
func GetFingerprints() *C.char {
return C.CString(strings.Join(server.GetFingerprints(), "\n"))
}
12 changes: 12 additions & 0 deletions src-go/server/fingerprints.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package server

import (
"github.com/bogdanfinn/tls-client/profiles"
"maps"
"slices"
)

func GetFingerprints() []string {
fingerprints := slices.Sorted(maps.Keys(profiles.MappedTLSClients))
return append([]string{"default"}, fingerprints...)
}
26 changes: 14 additions & 12 deletions src-go/server/go.mod
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
module server

go 1.21
go 1.22.0

toolchain go1.22.2
toolchain go1.24.0

require (
github.com/ooni/oohttp v0.7.1
github.com/open-ch/ja3 v1.0.1
github.com/refraction-networking/utls v1.6.4
github.com/bogdanfinn/fhttp v0.5.34
github.com/bogdanfinn/tls-client v1.8.0
github.com/bogdanfinn/utls v1.6.5
)

require (
github.com/andybalholm/brotli v1.0.6 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/klauspost/compress v1.17.4 // indirect
golang.org/x/crypto v0.18.0 // indirect
golang.org/x/net v0.20.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/text v0.14.0 // indirect
github.com/andybalholm/brotli v1.1.1 // indirect
github.com/cloudflare/circl v1.5.0 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/quic-go/quic-go v0.48.1 // indirect
github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5 // indirect
golang.org/x/crypto v0.29.0 // indirect
golang.org/x/net v0.31.0 // indirect
golang.org/x/sys v0.27.0 // indirect
golang.org/x/text v0.20.0 // indirect
)

replace github.com/ooni/oohttp => github.com/sleeyax/oohttp v0.0.0-20230603105812-6ac0447b1a8e
56 changes: 36 additions & 20 deletions src-go/server/go.sum
Original file line number Diff line number Diff line change
@@ -1,20 +1,36 @@
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/open-ch/ja3 v1.0.1 h1:kMqfkgS+cTasMlsQaJ627qlw7kA/qRZVTmF0BtFjOLQ=
github.com/open-ch/ja3 v1.0.1/go.mod h1:lTWgltvZDGQjIa/TjWTzfpCVa/eGP+szng2DWz9mAvk=
github.com/refraction-networking/utls v1.6.4 h1:aeynTroaYn7y+mFtqv8D0bQ4bw0y9nJHneGxJ7lvRDM=
github.com/refraction-networking/utls v1.6.4/go.mod h1:2VL2xfiqgFAZtJKeUTlf+PSYFs3Eu7km0gCtXJ3m8zs=
github.com/sleeyax/oohttp v0.0.0-20230603105812-6ac0447b1a8e h1:evx5O2TAZdPLDCqPuEI5yo4Sg3LT5cImPVbno6HKM2s=
github.com/sleeyax/oohttp v0.0.0-20230603105812-6ac0447b1a8e/go.mod h1:/7fPgmXNkMSXBpLOdARkhyn3vsNAtmZ0C3G5C/KLd6Q=
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
github.com/bogdanfinn/fhttp v0.5.34 h1:avRD2JNYqj6I6DqjSrI9tl8mP8Nk7T4CCmUsPz7afhg=
github.com/bogdanfinn/fhttp v0.5.34/go.mod h1:BlcawVfXJ4uhk5yyNGOOY2bwo8UmMi6ccMszP1KGLkU=
github.com/bogdanfinn/tls-client v1.8.0 h1:IB44SqKa0XKdx3GYXpRbkqN3+tsBtg9RJYRsl36boOA=
github.com/bogdanfinn/tls-client v1.8.0/go.mod h1:ehNITC7JBFeh6S7QNWtfD+PBKm0RsqvizAyyij2d/6g=
github.com/bogdanfinn/utls v1.6.5 h1:rVMQvhyN3zodLxKFWMRLt19INGBCZ/OM2/vBWPNIt1w=
github.com/bogdanfinn/utls v1.6.5/go.mod h1:czcHxHGsc1q9NjgWSeSinQZzn6MR76zUmGVIGanSXO0=
github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys=
github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/quic-go v0.48.1 h1:y/8xmfWI9qmGTc+lBr4jKRUWLGSlSigv847ULJ4hYXA=
github.com/quic-go/quic-go v0.48.1/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5 h1:YqAladjX7xpA6BM04leXMWAEjS0mTZ5kUU9KRBriQJc=
github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5/go.mod h1:2JjD2zLQYH5HO74y5+aE3remJQvl6q4Sn6aWA2wD1Ng=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
46 changes: 46 additions & 0 deletions src-go/server/hexclienthello.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package server

import (
"encoding/hex"
"errors"
utls "github.com/bogdanfinn/utls"
)

type HexClientHello string

func (hexClientHello HexClientHello) ToClientHelloSpec() (utls.ClientHelloSpec, error) {
if hexClientHello == "" {
return utls.ClientHelloSpec{}, errors.New("empty client hello")
}

raw, err := hex.DecodeString(string(hexClientHello))
if err != nil {
return utls.ClientHelloSpec{}, err
}

fingerprinter := &utls.Fingerprinter{
AllowBluntMimicry: true,
}
spec, err := fingerprinter.RawClientHello(raw)
if err != nil {
return utls.ClientHelloSpec{}, err
}

for i, extension := range spec.Extensions {
// Replace ECH extension with a GREASE ECH extension.
// Real ECH is not supported yet.
if genericExtension, ok := extension.(*utls.GenericExtension); ok {
if genericExtension.Id == utls.ExtensionECH {
spec.Extensions[i] = utls.BoringGREASEECH()
}
}

// If the PSK extension already contains a key, it's identified as a 'fake PSK' extensions by utls.
// So we replace it with a real PSK extension.
if _, ok := extension.(*utls.FakePreSharedKeyExtension); ok {
spec.Extensions[i] = &utls.UtlsPreSharedKeyExtension{}
}
}

return *spec, nil
}
Loading

0 comments on commit 552a369

Please sign in to comment.